diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..e5196cb74 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,71 @@ +on: + pull_request: + push: + branches: + - main + +name: CI + +env: + RUSTDOCFLAGS: "-D warnings" + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: "sparse" + +jobs: + + # MSRV check. + # Blitz only guarantees "latest stable". However we have this check here to ensure that we advertise + # our MSRV. We also make an effort not to increase MSRV in patch versions of Blitz. + # + # We only run `cargo build` (not `cargo test`) so as to avoid requiring dev-dependencies to build with the MSRV + # version. Building is likely sufficient as runtime errors varying between rust versions is very unlikely. + build-msrv: + name: "MSRV Build [Rust 1.79]" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.79 + - run: sudo apt install libgtk-3-dev libxdo-dev + - run: cargo build --workspace + + test-features-default: + name: "Test [default features]" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: sudo apt install libgtk-3-dev libxdo-dev + - run: cargo build --workspace + - run: cargo test --workspace + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: rustfmt + - run: cargo fmt --all --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + components: clippy + - run: sudo apt install libgtk-3-dev libxdo-dev + - run: cargo clippy --workspace -- -D warnings + + doc: + name: Documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo doc diff --git a/Cargo.toml b/Cargo.toml index 6354e317c..1ab4812f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ edition = "2021" description = "Top level crate for Blitz" license = "MIT OR Apache-2.0" keywords = ["dom", "ui", "gui", "react", "wasm"] -rust-version = "1.60.0" +rust-version = "1.70.0" publish = false [profile.dev.package."*"] @@ -40,11 +40,6 @@ opt-level = 2 # Need to force specific versions of these dependencies [dependencies] -# webrender = "0.61.0" -euclid = { version = "0.22", features = ["serde"] } -# mozbuild = "0.1.0" - -[dev-dependencies] blitz = { path = "./packages/blitz" } blitz-dom = { path = "./packages/dom" } comrak = { version = "0.21.0", default-features = false } diff --git a/examples/dom_only.rs b/examples/dom_only.rs deleted file mode 100644 index 71265007b..000000000 --- a/examples/dom_only.rs +++ /dev/null @@ -1,5 +0,0 @@ -use blitz_dom::Document; - -fn main() { - let document = Document::new(); -} diff --git a/examples/font.rs b/examples/font.rs deleted file mode 100644 index 8b1378917..000000000 --- a/examples/font.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/examples/tailwind.rs b/examples/tailwind.rs index 73508c870..807606d9b 100644 --- a/examples/tailwind.rs +++ b/examples/tailwind.rs @@ -13,7 +13,7 @@ fn main() { fn app() -> Element { rsx! { style { {CSS} } - for row in 0..3 { + for _row in 0..3 { div { class: "flex flex-row", div { id: "cool", "h123456789asdjkahskj\nhiiiii" } p { class: "cool", "hi" } diff --git a/packages/blitz/Cargo.toml b/packages/blitz/Cargo.toml index abfab1ef5..1d01a462c 100644 --- a/packages/blitz/Cargo.toml +++ b/packages/blitz/Cargo.toml @@ -15,10 +15,6 @@ parley = { workspace = true } tokio = { workspace = true, features = ["full"] } vello = { workspace = true } wgpu = { workspace = true } -shipyard = { version = "0.6.2", features = [ - "proc", - "std", -], default-features = false } app_units = "0.7.3" atomic_refcell = { version = "0.1.13", features = ["serde"] } @@ -28,10 +24,6 @@ string_cache = "0.8.7" futures-util = "0.3.29" raw-window-handle = "0.6.0" blitz-dom = { path = "../dom" } -glyphon = "0.5.0" -cosmic-text = "0.11.2" -quadtree_rs = "0.1.3" -askama_escape = "0.10.3" image = "0.25" # futures-util = "0.3.29" diff --git a/packages/blitz/src/render.rs b/packages/blitz/src/render.rs index 37040a9a7..dc246236c 100644 --- a/packages/blitz/src/render.rs +++ b/packages/blitz/src/render.rs @@ -141,19 +141,22 @@ where .await .expect("Error creating surface"); - let default_threads = || -> Option { - #[cfg(target_arch = "macos")] + const DEFAULT_THREADS: Option = { + #[cfg(target_os = "macos")] { - Some(NonZeroUsize::new(1)?) + NonZeroUsize::new(1) + } + #[cfg(not(target_os = "macos"))] + { + None } - None }; let options = RendererOptions { surface_format: Some(surface.config.format), antialiasing_support: AaSupport::all(), use_cpu: false, - num_init_threads: default_threads(), + num_init_threads: DEFAULT_THREADS, }; let renderer = diff --git a/packages/blitz/src/viewport.rs b/packages/blitz/src/viewport.rs index 55095bcf4..7dfaab971 100644 --- a/packages/blitz/src/viewport.rs +++ b/packages/blitz/src/viewport.rs @@ -1,7 +1,5 @@ -use style::{ - media_queries::{Device, MediaType}, - servo::media_queries::FontMetricsProvider, -}; +use blitz_dom::document::DummyFontMetricsProvider; +use style::media_queries::{Device, MediaType}; #[derive(Default, Debug)] pub struct Viewport { @@ -14,22 +12,6 @@ pub struct Viewport { pub font_size: f32, } -// TODO: implement a proper font metrics provider -#[derive(Debug, Clone)] -struct DummyFontMetricsProvider; -impl FontMetricsProvider for DummyFontMetricsProvider { - fn query_font_metrics( - &self, - _vertical: bool, - _font: &style::properties::style_structs::Font, - _base_size: style::values::computed::CSSPixelLength, - _in_media_query: bool, - _retrieve_math_scales: bool, - ) -> style::font_metrics::FontMetrics { - Default::default() - } -} - impl Viewport { pub fn new(window_size: (u32, u32)) -> Self { Self { diff --git a/packages/dioxus-blitz/Cargo.toml b/packages/dioxus-blitz/Cargo.toml index ea5ab5507..9b17c2968 100644 --- a/packages/dioxus-blitz/Cargo.toml +++ b/packages/dioxus-blitz/Cargo.toml @@ -8,13 +8,12 @@ hot-reload = [] default = [] [dependencies] -tao = { version = "0.26.1", features = ["serde"] } +winit = "0.30.2" muda = { version = "0.11.5", features = ["serde"] } tokio = { workspace = true, features = ["full"] } dioxus = { workspace = true } -dioxus-ssr = { workspace = true } -dioxus-cli-config = { git = "https://github.com/dioxuslabs/dioxus", rev = "a3aa6ae771a2d0a4d8cb6055c41efc0193b817ef" } -dioxus-hot-reload = { git = "https://github.com/dioxuslabs/dioxus", rev = "a3aa6ae771a2d0a4d8cb6055c41efc0193b817ef" } +dioxus-cli-config = { git = "https://github.com/dioxuslabs/dioxus", rev = "a3aa6ae771a2d0a4d8cb6055c41efc0193b817ef"} +dioxus-hot-reload = { git = "https://github.com/dioxuslabs/dioxus", rev = "a3aa6ae771a2d0a4d8cb6055c41efc0193b817ef"} futures-util = "0.3.30" vello = { workspace = true } wgpu = { workspace = true } diff --git a/packages/dioxus-blitz/src/documents/dioxus_document.rs b/packages/dioxus-blitz/src/documents/dioxus_document.rs index e612540de..70f0edf8a 100644 --- a/packages/dioxus-blitz/src/documents/dioxus_document.rs +++ b/packages/dioxus-blitz/src/documents/dioxus_document.rs @@ -48,30 +48,29 @@ impl AsMut for DioxusDocument { &mut self.inner } } -impl Into for DioxusDocument { - fn into(self) -> Document { - self.inner +impl From for Document { + fn from(doc: DioxusDocument) -> Document { + doc.inner } } impl DocumentLike for DioxusDocument { fn poll(&mut self, mut cx: std::task::Context) -> bool { - loop { - { - let fut = self.vdom.wait_for_work(); - pin_mut!(fut); - - match fut.poll_unpin(&mut cx) { - std::task::Poll::Ready(_) => {} - std::task::Poll::Pending => return false, - } - } + { + let fut = self.vdom.wait_for_work(); + pin_mut!(fut); - self.vdom.render_immediate(&mut MutationWriter { - doc: &mut self.inner, - state: &mut self.vdom_state, - }); - return true; + match fut.poll_unpin(&mut cx) { + std::task::Poll::Ready(_) => {} + std::task::Poll::Pending => return false, + } } + + self.vdom.render_immediate(&mut MutationWriter { + doc: &mut self.inner, + state: &mut self.vdom_state, + }); + + true } fn handle_event(&mut self, event: blitz_dom::events::RendererEvent) -> bool { diff --git a/packages/dioxus-blitz/src/documents/html_document.rs b/packages/dioxus-blitz/src/documents/html_document.rs index 22922d1a9..aa520f6ff 100644 --- a/packages/dioxus-blitz/src/documents/html_document.rs +++ b/packages/dioxus-blitz/src/documents/html_document.rs @@ -19,9 +19,9 @@ impl AsMut for HtmlDocument { &mut self.inner } } -impl Into for HtmlDocument { - fn into(self) -> Document { - self.inner +impl From for Document { + fn from(doc: HtmlDocument) -> Document { + doc.inner } } impl DocumentLike for HtmlDocument {} diff --git a/packages/dioxus-blitz/src/lib.rs b/packages/dioxus-blitz/src/lib.rs index dd5728d33..dd9f9ae93 100644 --- a/packages/dioxus-blitz/src/lib.rs +++ b/packages/dioxus-blitz/src/lib.rs @@ -11,13 +11,13 @@ use dioxus::prelude::*; use documents::DioxusDocument; use muda::{MenuEvent, MenuId}; use std::collections::HashMap; -use tao::event_loop::EventLoopBuilder; -use tao::window::WindowId; -use tao::{ +use url::Url; +use winit::event_loop::{EventLoop, EventLoopBuilder}; +use winit::window::WindowId; +use winit::{ event::{Event, WindowEvent}, event_loop::ControlFlow, }; -use url::Url; #[derive(Default)] pub struct Config { @@ -93,7 +93,7 @@ fn launch_with_window(window: View<'static, Doc>) { let _guard = rt.enter(); // Build an event loop for the application - let event_loop = EventLoopBuilder::::with_user_event().build(); + let event_loop = EventLoop::::with_user_event().build().unwrap(); let proxy = event_loop.create_proxy(); // Multiwindow ftw @@ -126,134 +126,134 @@ fn launch_with_window(window: View<'static, Doc>) { }); } - event_loop.run(move |event, event_loop, control_flow| { - *control_flow = ControlFlow::Wait; + event_loop + .run(move |event, event_loop| { + event_loop.set_control_flow(ControlFlow::Wait); - let mut on_resume = || { - for (_, view) in windows.iter_mut() { - view.resume(event_loop, &proxy, &rt); - } + let mut on_resume = || { + for (_, view) in windows.iter_mut() { + view.resume(event_loop, &proxy, &rt); + } - for view in pending_windows.iter_mut() { - view.resume(event_loop, &proxy, &rt); - } + for view in pending_windows.iter_mut() { + view.resume(event_loop, &proxy, &rt); + } - for window in pending_windows.drain(..) { - let RenderState::Active(state) = &window.renderer.render_state else { - continue; - }; - windows.insert(state.window.id(), window); - } - }; + for window in pending_windows.drain(..) { + let RenderState::Active(state) = &window.renderer.render_state else { + continue; + }; + windows.insert(state.window.id(), window); + } + }; - #[cfg(not(any(target_os = "android", target_os = "ios")))] - if initial { - on_resume(); - initial = false; - } - - match event { - // Exit the app when close is request - // Not always necessary - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - - // Nothing else to do, try redrawing? - Event::MainEventsCleared => {} - - Event::UserEvent(UserEvent::Window { - data: EventData::Poll, - window_id, - }) => { - if let Some(view) = windows.get_mut(&window_id) { - if view.poll() { - view.request_redraw(); - } - }; + #[cfg(not(any(target_os = "android", target_os = "ios")))] + if initial { + on_resume(); + initial = false; } - #[cfg(all( - feature = "hot-reload", - debug_assertions, - not(target_os = "android"), - not(target_os = "ios") - ))] - Event::UserEvent(UserEvent::HotReloadEvent(msg)) => match msg { - dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { - dbg!("Update template {:?}", template); - - for window in windows.values_mut() { - if let Some(dx_doc) = window - .renderer - .dom - .as_any_mut() - .downcast_mut::() - { - dx_doc.vdom.replace_template(template); + match event { + // Exit the app when close is request + // Not always necessary + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => event_loop.exit(), + + Event::WindowEvent { + window_id, + event: winit::event::WindowEvent::RedrawRequested, + } => { + if let Some(window) = windows.get_mut(&window_id) { + window.renderer.dom.as_mut().resolve(); + window.renderer.render(&mut window.scene); + }; + } + + Event::UserEvent(UserEvent::Window { + data: EventData::Poll, + window_id: id, + }) => { + if let Some(view) = windows.get_mut(&id) { + if view.poll() { + view.request_redraw(); } + }; + } - if window.poll() { - window.request_redraw(); + #[cfg(all( + feature = "hot-reload", + debug_assertions, + not(target_os = "android"), + not(target_os = "ios") + ))] + Event::UserEvent(UserEvent::HotReloadEvent(msg)) => match msg { + dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { + dbg!("Update template {:?}", template); + + for window in windows.values_mut() { + if let Some(dx_doc) = window + .renderer + .dom + .as_any_mut() + .downcast_mut::() + { + dx_doc.vdom.replace_template(template); + } + + if window.poll() { + window.request_redraw(); + } } } + dioxus_hot_reload::HotReloadMsg::Shutdown => event_loop.exit(), + dioxus_hot_reload::HotReloadMsg::UpdateAsset(asset) => { + dbg!("Update asset {:?}", asset); + } + }, + + // Event::UserEvent(_redraw) => { + // for (_, view) in windows.iter() { + // view.request_redraw(); + // } + // } + Event::NewEvents(_) => { + for window_id in windows.keys().copied() { + _ = proxy.send_event(UserEvent::Window { + data: EventData::Poll, + window_id, + }); + } } - dioxus_hot_reload::HotReloadMsg::Shutdown => { - *control_flow = ControlFlow::Exit; - } - dioxus_hot_reload::HotReloadMsg::UpdateAsset(asset) => { - dbg!("Update asset {:?}", asset); - } - }, - - // Event::UserEvent(_redraw) => { - // for (_, view) in windows.iter() { - // view.request_redraw(); - // } - // } - Event::NewEvents(_) => { - for id in windows.keys() { - _ = proxy.send_event(UserEvent::Window { - data: EventData::Poll, - window_id: *id, - }); + + Event::Suspended => { + for (_, view) in windows.iter_mut() { + view.suspend(); + } } - } - Event::RedrawRequested(window_id) => { - if let Some(window) = windows.get_mut(&window_id) { - window.renderer.dom.as_mut().resolve(); - window.renderer.render(&mut window.scene); - }; - } + Event::Resumed => on_resume(), - Event::Suspended => { - for (_, view) in windows.iter_mut() { - view.suspend(); + Event::WindowEvent { + window_id, event, .. + } => { + if let Some(window) = windows.get_mut(&window_id) { + window.handle_window_event(event); + }; } - } - - Event::Resumed => on_resume(), - Event::WindowEvent { - window_id, event, .. - } => { - if let Some(window) = windows.get_mut(&window_id) { - window.handle_window_event(event); - }; + _ => (), } - _ => (), - } - - if let Ok(event) = menu_channel.try_recv() { - if event.id == MenuId::new("dev.show_layout") { - for (_, view) in windows.iter_mut() { - view.renderer.devtools.show_layout = !view.renderer.devtools.show_layout; - view.request_redraw(); + if let Ok(event) = menu_channel.try_recv() { + if event.id == MenuId::new("dev.show_layout") { + for (_, view) in windows.iter_mut() { + view.renderer.devtools.show_layout = !view.renderer.devtools.show_layout; + view.request_redraw(); + } } } - } - }); + }) + .unwrap(); } diff --git a/packages/dioxus-blitz/src/waker.rs b/packages/dioxus-blitz/src/waker.rs index b3f830a01..2c20983e7 100644 --- a/packages/dioxus-blitz/src/waker.rs +++ b/packages/dioxus-blitz/src/waker.rs @@ -1,6 +1,6 @@ use futures_util::task::ArcWake; use std::sync::Arc; -use tao::{event_loop::EventLoopProxy, window::WindowId}; +use winit::{event_loop::EventLoopProxy, window::WindowId}; #[derive(Debug, Clone)] pub enum UserEvent { diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index fae38a471..b0599100f 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -1,12 +1,16 @@ use crate::waker::UserEvent; use blitz::{RenderState, Renderer, Viewport}; use blitz_dom::DocumentLike; +use wgpu::rwh::HasWindowHandle; +use winit::keyboard::PhysicalKey; +use winit::window::WindowAttributes; use std::sync::Arc; use std::task::Waker; -use tao::dpi::LogicalSize; -use tao::event::{ElementState, MouseButton}; -use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget}; +use vello::Scene; +use winit::dpi::LogicalSize; +use winit::event::{ElementState, MouseButton}; +use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; #[cfg(any( target_os = "linux", target_os = "dragonfly", @@ -14,16 +18,10 @@ use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget}; target_os = "netbsd", target_os = "openbsd" ))] -use tao::platform::unix::WindowExtUnix; +use winit::platform::unix::WindowExtUnix; #[cfg(target_os = "windows")] -use tao::platform::windows::WindowExtWindows; -use tao::{ - event::WindowEvent, - keyboard::KeyCode, - keyboard::ModifiersState, - window::{Window, WindowBuilder}, -}; -use vello::Scene; +use winit::platform::windows::WindowExtWindows; +use winit::{event::WindowEvent, keyboard::KeyCode, keyboard::ModifiersState, window::Window}; #[cfg(not(target_os = "macos"))] use muda::{AboutMetadata, Menu, MenuId, MenuItem, PredefinedMenuItem, Submenu}; @@ -91,7 +89,7 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { // Store new keyboard modifier (ctrl, shift, etc) state for later use WindowEvent::ModifiersChanged(new_state) => { - self.keyboard_modifiers = new_state; + self.keyboard_modifiers = new_state.state(); } // todo: if there's an active text input, we want to direct input towards it and translate system emi text @@ -99,53 +97,58 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { dbg!(&event); match event.physical_key { - KeyCode::Equal => { - if self.keyboard_modifiers.control_key() - || self.keyboard_modifiers.super_key() - { - self.renderer.zoom(0.1); - self.request_redraw(); - } - } - KeyCode::Minus => { - if self.keyboard_modifiers.control_key() - || self.keyboard_modifiers.super_key() - { - self.renderer.zoom(-0.1); - self.request_redraw(); - } - } - KeyCode::Digit0 => { - if self.keyboard_modifiers.control_key() - || self.keyboard_modifiers.super_key() - { - self.renderer.reset_zoom(); - self.request_redraw(); - } - } - KeyCode::KeyD => { - if event.state == ElementState::Pressed && self.keyboard_modifiers.alt_key() - { - self.renderer.devtools.show_layout = - !self.renderer.devtools.show_layout; - self.request_redraw(); - } - } - KeyCode::KeyH => { - if event.state == ElementState::Pressed && self.keyboard_modifiers.alt_key() - { - self.renderer.devtools.highlight_hover = - !self.renderer.devtools.highlight_hover; - self.request_redraw(); + PhysicalKey::Code(key_code) => { + match key_code { + KeyCode::Equal => { + if self.keyboard_modifiers.control_key() + || self.keyboard_modifiers.super_key() + { + self.renderer.zoom(0.1); + self.request_redraw(); + } + } + KeyCode::Minus => { + if self.keyboard_modifiers.control_key() + || self.keyboard_modifiers.super_key() + { + self.renderer.zoom(-0.1); + self.request_redraw(); + } + } + KeyCode::Digit0 => { + if self.keyboard_modifiers.control_key() + || self.keyboard_modifiers.super_key() + { + self.renderer.reset_zoom(); + self.request_redraw(); + } + } + KeyCode::KeyD => { + if event.state == ElementState::Pressed && self.keyboard_modifiers.alt_key() + { + self.renderer.devtools.show_layout = + !self.renderer.devtools.show_layout; + self.request_redraw(); + } + } + KeyCode::KeyH => { + if event.state == ElementState::Pressed && self.keyboard_modifiers.alt_key() + { + self.renderer.devtools.highlight_hover = + !self.renderer.devtools.highlight_hover; + self.request_redraw(); + } + } + KeyCode::KeyT => { + if event.state == ElementState::Pressed && self.keyboard_modifiers.alt_key() + { + self.renderer.print_taffy_tree(); + } + } + _ => {} } - } - KeyCode::KeyT => { - if event.state == ElementState::Pressed && self.keyboard_modifiers.alt_key() - { - self.renderer.print_taffy_tree(); - } - } - _ => {} + }, + PhysicalKey::Unidentified(_) => {} } } WindowEvent::Moved(_) => {} @@ -154,7 +157,6 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { WindowEvent::DroppedFile(_) => {} WindowEvent::HoveredFile(_) => {} WindowEvent::HoveredFileCancelled => {} - WindowEvent::ReceivedImeText(_) => {} WindowEvent::Focused(_) => {} WindowEvent::CursorMoved { // device_id, @@ -163,7 +165,7 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { .. } => { let changed = if let RenderState::Active(state) = &self.renderer.render_state { - let tao::dpi::LogicalPosition:: { x, y } = position.to_logical(state.window.scale_factor()); + let winit::dpi::LogicalPosition:: { x, y } = position.to_logical(state.window.scale_factor()); self.renderer.mouse_move(x, y) } else { @@ -175,11 +177,11 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { if let Some(cursor) = cursor { use style::values::computed::ui::CursorKind; - use tao::window::CursorIcon as TaoCursor; + use winit::window::CursorIcon as TaoCursor; let tao_cursor = match cursor { CursorKind::None => todo!("set the cursor to none"), CursorKind::Default => TaoCursor::Default, - CursorKind::Pointer => TaoCursor::Hand, + CursorKind::Pointer => TaoCursor::Pointer, CursorKind::ContextMenu => TaoCursor::ContextMenu, CursorKind::Help => TaoCursor::Help, CursorKind::Progress => TaoCursor::Progress, @@ -240,10 +242,10 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { .. } => { match delta { - tao::event::MouseScrollDelta::LineDelta(_, y) => { + winit::event::MouseScrollDelta::LineDelta(_, y) => { self.renderer.scroll_by(y as f64 * 20.0) } - tao::event::MouseScrollDelta::PixelDelta(offsets) => { + winit::event::MouseScrollDelta::PixelDelta(offsets) => { self.renderer.scroll_by(offsets.y) } _ => {} @@ -270,33 +272,36 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { .. } => {} WindowEvent::ThemeChanged(_) => {} - WindowEvent::DecorationsClick => {} _ => {} } } pub fn resume( &mut self, - event_loop: &EventLoopWindowTarget, + event_loop: &ActiveEventLoop, proxy: &EventLoopProxy, rt: &tokio::runtime::Runtime, ) { let window_builder = || { - let window = WindowBuilder::new() - .with_inner_size(LogicalSize { + let window = event_loop + .create_window(Window::default_attributes().with_inner_size(LogicalSize { width: 800, height: 600, - }) - .build(event_loop) + })) .unwrap(); #[cfg(target_os = "windows")] { - build_menu().init_for_hwnd(window.hwnd()); + use winit::raw_window_handle::*; + if let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() { + build_menu().init_for_hwnd(handle.hwnd.get()).unwrap(); + } } #[cfg(target_os = "linux")] { - build_menu().init_for_gtk_window(window.gtk_window(), window.default_vbox()); + build_menu() + .init_for_gtk_window(window.gtk_window(), window.default_vbox()) + .unwrap(); } // !TODO - this may not be the right way to do this, but it's a start @@ -306,7 +311,7 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { // build_menu().set_as_windows_menu_for_nsapp(); // } - let size: tao::dpi::PhysicalSize = window.inner_size(); + let size: winit::dpi::PhysicalSize = window.inner_size(); let mut viewport = Viewport::new((size.width, size.height)); viewport.set_hidpi_scale(window.scale_factor() as _); @@ -331,10 +336,10 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { #[cfg(not(target_os = "macos"))] fn build_menu() -> Menu { - let mut menu = Menu::new(); + let menu = Menu::new(); // Build the about section - let mut about = Submenu::new("About", true); + let about = Submenu::new("About", true); about .append_items(&[ diff --git a/packages/dom/Cargo.toml b/packages/dom/Cargo.toml index 56bdf07a9..6cdad1b12 100644 --- a/packages/dom/Cargo.toml +++ b/packages/dom/Cargo.toml @@ -18,14 +18,11 @@ atomic_refcell = { version = "0.1.13", features = ["serde"] } fxhash = "0.2.1" html5ever = { workspace = true } string_cache = "0.8.7" -futures-util = "0.3.30" -askama_escape = "0.10.3" html-escape = "0.2.13" url = { version = "2.5.0", features = ["serde"] } data-url = "0.3.1" ureq = "2.9" image = "0.25" -quadtree_rs = "0.1.3" # on wasm use the js feature on getrandom diff --git a/packages/dom/src/document.rs b/packages/dom/src/document.rs index 3185899d3..395c5f919 100644 --- a/packages/dom/src/document.rs +++ b/packages/dom/src/document.rs @@ -8,6 +8,7 @@ use std::any::Any; use std::collections::HashMap; use style::invalidation::element::restyle_hints::RestyleHint; use style::selector_parser::ServoElementSnapshot; +use style::servo::media_queries::FontMetricsProvider; use style::servo_arc::Arc as ServoArc; use style::{ dom::{TDocument, TNode}, @@ -21,6 +22,22 @@ use style_traits::dom::ElementState; use taffy::AvailableSpace; use url::Url; +// TODO: implement a proper font metrics provider +#[derive(Debug, Clone)] +pub struct DummyFontMetricsProvider; +impl FontMetricsProvider for DummyFontMetricsProvider { + fn query_font_metrics( + &self, + _vertical: bool, + _font: &style::properties::style_structs::Font, + _base_size: style::values::computed::CSSPixelLength, + _in_media_query: bool, + _retrieve_math_scales: bool, + ) -> style::font_metrics::FontMetrics { + Default::default() + } +} + pub trait DocumentLike: AsRef + AsMut + Into + 'static { fn poll(&mut self, _cx: std::task::Context) -> bool { // Default implementation does nothing diff --git a/packages/dom/src/htmlsink.rs b/packages/dom/src/htmlsink.rs index 98e4fba20..03397648f 100644 --- a/packages/dom/src/htmlsink.rs +++ b/packages/dom/src/htmlsink.rs @@ -270,8 +270,7 @@ impl<'b> TreeSink for DocumentHtmlParser<'b> { if has_appended { return; } else { - let id = self.create_text_node(&text); - id + self.create_text_node(&text) } } NodeOrText::AppendNode(id) => id, @@ -366,6 +365,7 @@ impl<'b> TreeSink for DocumentHtmlParser<'b> { #[test] fn parses_some_html() { + use crate::document::DummyFontMetricsProvider; use euclid::{Scale, Size2D}; use style::media_queries::{Device, MediaType}; @@ -378,6 +378,7 @@ fn parses_some_html() { selectors::matching::QuirksMode::NoQuirks, viewport_size, device_pixel_ratio, + Box::new(DummyFontMetricsProvider), ); let mut doc = Document::new(device); let sink = DocumentHtmlParser::new(&mut doc); diff --git a/packages/dom/src/layout/mod.rs b/packages/dom/src/layout/mod.rs index 4916dd5a0..94e100f3d 100644 --- a/packages/dom/src/layout/mod.rs +++ b/packages/dom/src/layout/mod.rs @@ -411,13 +411,13 @@ impl PrintTree for Document { } } -pub struct ChildIter<'a>(std::slice::Iter<'a, usize>); -impl<'a> Iterator for ChildIter<'a> { - type Item = NodeId; - fn next(&mut self) -> Option { - self.0.next().copied().map(NodeId::from) - } -} +// pub struct ChildIter<'a>(std::slice::Iter<'a, usize>); +// impl<'a> Iterator for ChildIter<'a> { +// type Item = NodeId; +// fn next(&mut self) -> Option { +// self.0.next().copied().map(NodeId::from) +// } +// } pub struct RefCellChildIter<'a> { items: Ref<'a, [usize]>, diff --git a/packages/dom/src/stylo.rs b/packages/dom/src/stylo.rs index 0334f01d1..10e99f6ef 100644 --- a/packages/dom/src/stylo.rs +++ b/packages/dom/src/stylo.rs @@ -815,8 +815,7 @@ impl<'a> TElement for BlitzNode<'a> { let root_element = TDocument::as_node(&root_node) .first_element_child() .unwrap(); - let is_child_of_root_element = root_element.children.contains(&self.id); - is_child_of_root_element + root_element.children.contains(&self.id) } fn synthesize_presentational_hints_for_legacy_attributes( diff --git a/packages/dom/src/stylo_to_parley.rs b/packages/dom/src/stylo_to_parley.rs index f5a724db4..066eb0e42 100644 --- a/packages/dom/src/stylo_to_parley.rs +++ b/packages/dom/src/stylo_to_parley.rs @@ -72,7 +72,7 @@ pub(crate) fn style(style: &stylo::ComputedValues) -> parley::TextStyle<'static, } // TODO: fix leak! - parley::FontFamily::Named(name.to_string().leak()) + break 'ret parley::FontFamily::Named(name.to_string().leak()); } } stylo::SingleFontFamily::Generic(generic) => { diff --git a/packages/dom/src/util.rs b/packages/dom/src/util.rs index 03ff0f92c..bacd07c65 100644 --- a/packages/dom/src/util.rs +++ b/packages/dom/src/util.rs @@ -6,14 +6,17 @@ use image::DynamicImage; const USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"; const FILE_SIZE_LIMIT: u64 = 1_000_000_000; // 1GB -pub(crate) fn fetch_blob(url: &str) -> Result, ureq::Error> { +pub(crate) fn fetch_blob(url: &str) -> Result, Box> { if url.starts_with("data:") { let data_url = data_url::DataUrl::process(url).unwrap(); let decoded = data_url.decode_to_vec().expect("Invalid data url"); return Ok(decoded.0); } - let resp = ureq::get(url).set("User-Agent", USER_AGENT).call()?; + let resp = ureq::get(url) + .set("User-Agent", USER_AGENT) + .call() + .map_err(Box::new)?; let len: usize = resp .header("Content-Length") @@ -23,12 +26,13 @@ pub(crate) fn fetch_blob(url: &str) -> Result, ureq::Error> { resp.into_reader() .take(FILE_SIZE_LIMIT) - .read_to_end(&mut bytes)?; + .read_to_end(&mut bytes) + .unwrap(); Ok(bytes) } -pub(crate) fn fetch_string(url: &str) -> Result { +pub(crate) fn fetch_string(url: &str) -> Result> { fetch_blob(url).map(|vec| String::from_utf8(vec).expect("Invalid UTF8")) } @@ -41,11 +45,11 @@ pub(crate) fn fetch_string(url: &str) -> Result { #[allow(unused)] pub(crate) enum ImageFetchErr { - FetchErr(ureq::Error), + FetchErr(Box), ImageError(image::error::ImageError), } -impl From for ImageFetchErr { - fn from(value: ureq::Error) -> Self { +impl From> for ImageFetchErr { + fn from(value: Box) -> Self { Self::FetchErr(value) } } diff --git a/tests/stylo_usage.rs b/tests/stylo_usage.rs index 7cbcc3f40..5cecdb614 100644 --- a/tests/stylo_usage.rs +++ b/tests/stylo_usage.rs @@ -1,158 +1,157 @@ -pub use blitz::style_impls::{BlitzNode, RealDom}; -use dioxus::prelude::*; -use style::{ - animation::DocumentAnimationSet, - context::{QuirksMode, SharedStyleContext}, - driver, - global_style_data::GLOBAL_STYLE_DATA, - media_queries::MediaType, - media_queries::{Device as StyleDevice, MediaList}, - selector_parser::SnapshotMap, - servo_arc::Arc, - shared_lock::{SharedRwLock, StylesheetGuards}, - stylesheets::{AllowImportRules, DocumentStyleSheet, Origin, Stylesheet}, - stylist::Stylist, - thread_state::ThreadState, - traversal::DomTraversal, - traversal_flags::TraversalFlags, -}; - -fn main() { - let css = r#" - h1 { - background-color: red; - } - - h2 { - background-color: green; - } - - h3 { - background-color: blue; - } - - h4 { - background-color: yellow; - } - - "#; - - let nodes = rsx! { - h1 { } - h2 { } - h3 { } - h4 { } - }; - - let styled_dom = style_lazy_nodes(css, nodes); - - // print_styles(&styled_dom); -} - -pub fn style_lazy_nodes(css: &str, markup: LazyNodes) -> RealDom { - const QUIRKS_MODE: QuirksMode = QuirksMode::NoQuirks; - - // Figured out a single-pass system from the servo repo itself: - // - // components/layout_thread_2020/lib.rs:795 - // handle_reflow - // tests/unit/style/custom_properties.rs - style::thread_state::enter(ThreadState::LAYOUT); - - // make the guards that we use to thread everything together - let guard = SharedRwLock::new(); - let guards = StylesheetGuards { - author: &guard.read(), - ua_or_user: &guard.read(), - }; - - // Make some CSS - let stylesheet = Stylesheet::from_str( - css, - servo_url::ServoUrl::from_url("data:text/css;charset=utf-8;base64,".parse().unwrap()), - Origin::UserAgent, - Arc::new(guard.wrap(MediaList::empty())), - guard.clone(), - None, - None, - QUIRKS_MODE, - 0, - AllowImportRules::Yes, - ); - - // Make the real domtree by converting dioxus vnodes - let markup = RealDom::from_dioxus(markup); - - // Now we need to do what handle_reflow does in servo - // Reflows should be fast due to caching in the Stylist object - // - // - // We can force reflows. Happens in the script/document section - // The browser keeps track of pending restyles itself when attributes are changed. - // When something like val.set_attribute() happens, the pending reflow is inserted into the list. - // Once ticks are finished, the ScriptReflow object is created and sent to the layout thread. - // The layout thread uses the ScriptReflow object to inform itself on what changes need to happen. - // Zooming and touching causes full reflows. - // For this demo we want to do complete reflows (have yet to figure it out) - // But eventually we'll want to queue up modifications to the ECS engine and then build the script-reflow type object. - // Unfortunately, this API assumes nodes are backed by pointers which adds some unsafe where we wouldn't want it. - // - // Reflow allows us to specify a dirty root node and a list of nodes to reflow. - // - // Notes: - // - https://developers.google.com/speed/docs/insights/browser-reflow - // - components/script/dom/window.rs:force_reflow - // - // Create a styling context for use throughout the following passes. - // In servo we'd also create a layout context, but since servo isn't updated with the new layout code, we're just using the styling context - // In a different world we'd use both - // Build the stylist object from our screen requirements - // Todo: pull this in from wgpu - let mut stylist = Stylist::new( - StyleDevice::new( - MediaType::screen(), - QUIRKS_MODE, - euclid::Size2D::new(800., 600.), - euclid::Scale::new(1.0), - ), - QUIRKS_MODE, - ); - - // We have no snapshots on initial render, but we will need them for future renders - let snapshots = SnapshotMap::new(); - - // Add the stylesheets to the stylist - stylist.append_stylesheet(DocumentStyleSheet(Arc::new(stylesheet)), &guard.read()); - - // We don't really need to do this, but it's worth keeping it here anyways - stylist.force_stylesheet_origins_dirty(Origin::Author.into()); - - // Note that html5ever parses the first node as the document, so we need to unwrap it and get the first child - // For the sake of this demo, it's always just a single body node, but eventually we will want to construct something like the - // BoxTree struct that servo uses. - stylist.flush(&guards, Some(markup.root_element()), Some(&snapshots)); - - // Build the style context used by the style traversal - let context = SharedStyleContext { - traversal_flags: TraversalFlags::empty(), - stylist: &stylist, - options: GLOBAL_STYLE_DATA.options.clone(), - guards, - visited_styles_enabled: false, - animations: (&DocumentAnimationSet::default()).clone(), - current_time_for_animations: 0.0, - snapshot_map: &snapshots, - registered_speculative_painters: &style_impls::RegisteredPaintersImpl, - }; - - // components/layout_2020/lib.rs:983 - println!("------Pre-traversing the DOM tree -----"); - let token = style_traverser::RecalcStyle::pre_traverse(markup.root_element(), &context); - - // Style the elements, resolving their data - println!("------ Traversing domtree ------",); - let traverser = style_traverser::RecalcStyle::new(context); - driver::traverse_dom(&traverser, token, None); - - markup -} +// pub use blitz::style_impls::{BlitzNode, RealDom}; +// use dioxus::prelude::*; +// use style::{ +// animation::DocumentAnimationSet, +// context::{QuirksMode, SharedStyleContext}, +// driver, +// global_style_data::GLOBAL_STYLE_DATA, +// media_queries::MediaType, +// media_queries::{Device as StyleDevice, MediaList}, +// selector_parser::SnapshotMap, +// servo_arc::Arc, +// shared_lock::{SharedRwLock, StylesheetGuards}, +// stylesheets::{AllowImportRules, DocumentStyleSheet, Origin, Stylesheet}, +// stylist::Stylist, +// thread_state::ThreadState, +// traversal::DomTraversal, +// traversal_flags::TraversalFlags, +// }; + +// fn main() { +// let css = r#" +// h1 { +// background-color: red; +// } + +// h2 { +// background-color: green; +// } + +// h3 { +// background-color: blue; +// } + +// h4 { +// background-color: yellow; +// } + +// "#; + +// let nodes = rsx! { +// h1 { } +// h2 { } +// h3 { } +// h4 { } +// }; + +// let styled_dom = style_lazy_nodes(css, nodes); + +// // print_styles(&styled_dom); +// } + +// // pub fn style_lazy_nodes(css: &str, markup: LazyNodes) -> RealDom { +// // const QUIRKS_MODE: QuirksMode = QuirksMode::NoQuirks; + +// // // Figured out a single-pass system from the servo repo itself: +// // // +// // // components/layout_thread_2020/lib.rs:795 +// // // handle_reflow +// // // tests/unit/style/custom_properties.rs +// // style::thread_state::enter(ThreadState::LAYOUT); + +// // // make the guards that we use to thread everything together +// // let guard = SharedRwLock::new(); +// // let guards = StylesheetGuards { +// // author: &guard.read(), +// // ua_or_user: &guard.read(), +// // }; + +// // // Make some CSS +// // let stylesheet = Stylesheet::from_str( +// // css, +// // servo_url::ServoUrl::from_url("data:text/css;charset=utf-8;base64,".parse().unwrap()), +// // Origin::UserAgent, +// // Arc::new(guard.wrap(MediaList::empty())), +// // guard.clone(), +// // None, +// // None, +// // QUIRKS_MODE, +// // AllowImportRules::Yes, +// // ); + +// // // Make the real domtree by converting dioxus vnodes +// // let markup = RealDom::from_dioxus(markup); + +// // // Now we need to do what handle_reflow does in servo +// // // Reflows should be fast due to caching in the Stylist object +// // // +// // // +// // // We can force reflows. Happens in the script/document section +// // // The browser keeps track of pending restyles itself when attributes are changed. +// // // When something like val.set_attribute() happens, the pending reflow is inserted into the list. +// // // Once ticks are finished, the ScriptReflow object is created and sent to the layout thread. +// // // The layout thread uses the ScriptReflow object to inform itself on what changes need to happen. +// // // Zooming and touching causes full reflows. +// // // For this demo we want to do complete reflows (have yet to figure it out) +// // // But eventually we'll want to queue up modifications to the ECS engine and then build the script-reflow type object. +// // // Unfortunately, this API assumes nodes are backed by pointers which adds some unsafe where we wouldn't want it. +// // // +// // // Reflow allows us to specify a dirty root node and a list of nodes to reflow. +// // // +// // // Notes: +// // // - https://developers.google.com/speed/docs/insights/browser-reflow +// // // - components/script/dom/window.rs:force_reflow +// // // +// // // Create a styling context for use throughout the following passes. +// // // In servo we'd also create a layout context, but since servo isn't updated with the new layout code, we're just using the styling context +// // // In a different world we'd use both +// // // Build the stylist object from our screen requirements +// // // Todo: pull this in from wgpu +// // let mut stylist = Stylist::new( +// // StyleDevice::new( +// // MediaType::screen(), +// // QUIRKS_MODE, +// // euclid::Size2D::new(800., 600.), +// // euclid::Scale::new(1.0), +// // ), +// // QUIRKS_MODE, +// // ); + +// // // We have no snapshots on initial render, but we will need them for future renders +// // let snapshots = SnapshotMap::new(); + +// // // Add the stylesheets to the stylist +// // stylist.append_stylesheet(DocumentStyleSheet(Arc::new(stylesheet)), &guard.read()); + +// // // We don't really need to do this, but it's worth keeping it here anyways +// // stylist.force_stylesheet_origins_dirty(Origin::Author.into()); + +// // // Note that html5ever parses the first node as the document, so we need to unwrap it and get the first child +// // // For the sake of this demo, it's always just a single body node, but eventually we will want to construct something like the +// // // BoxTree struct that servo uses. +// // stylist.flush(&guards, Some(markup.root_element()), Some(&snapshots)); + +// // // Build the style context used by the style traversal +// // let context = SharedStyleContext { +// // traversal_flags: TraversalFlags::empty(), +// // stylist: &stylist, +// // options: GLOBAL_STYLE_DATA.options.clone(), +// // guards, +// // visited_styles_enabled: false, +// // animations: (&DocumentAnimationSet::default()).clone(), +// // current_time_for_animations: 0.0, +// // snapshot_map: &snapshots, +// // registered_speculative_painters: &style_impls::RegisteredPaintersImpl, +// // }; + +// // // components/layout_2020/lib.rs:983 +// // println!("------Pre-traversing the DOM tree -----"); +// // let token = style_traverser::RecalcStyle::pre_traverse(markup.root_element(), &context); + +// // // Style the elements, resolving their data +// // println!("------ Traversing domtree ------",); +// // let traverser = style_traverser::RecalcStyle::new(context); +// // driver::traverse_dom(&traverser, token, None); + +// // markup +// // }