diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs index 618c0751..762a0a92 100644 --- a/examples/counter/src/main.rs +++ b/examples/counter/src/main.rs @@ -74,7 +74,6 @@ fn app_view() -> impl View { .flex_col() .items_center() .justify_center() - .background(Color::WHITE) }); let id = view.id(); diff --git a/examples/window-scale/src/main.rs b/examples/window-scale/src/main.rs index 453fe88d..791ca25f 100644 --- a/examples/window-scale/src/main.rs +++ b/examples/window-scale/src/main.rs @@ -1,4 +1,6 @@ use floem::{ + event::{Event, EventListener}, + keyboard::{Key, NamedKey}, peniko::Color, reactive::{create_rw_signal, create_signal}, unit::UnitExt, @@ -9,7 +11,7 @@ use floem::{ fn app_view() -> impl View { let (counter, set_counter) = create_signal(0); let window_scale = create_rw_signal(1.0); - stack(( + let view = stack(( label(move || format!("Value: {}", counter.get())).style(|s| s.padding(10.0)), stack({ ( @@ -121,6 +123,16 @@ fn app_view() -> impl View { .flex_col() .items_center() .justify_center() + }); + + let id = view.id(); + view.on_event(EventListener::KeyUp, move |e| { + if let Event::KeyUp(e) = e { + if e.key.logical_key == Key::Named(NamedKey::F11) { + id.inspect(); + } + } + true }) } diff --git a/src/inspector.rs b/src/inspector.rs index 8915f21f..45a2e61e 100644 --- a/src/inspector.rs +++ b/src/inspector.rs @@ -3,6 +3,7 @@ use crate::context::AppState; use crate::event::{Event, EventListener}; use crate::id::Id; use crate::new_window; +use crate::style::TextOverflow; use crate::view::View; use crate::views::{ dyn_container, empty, img_dynamic, list, scroll, stack, text, Decorators, Label, @@ -15,7 +16,7 @@ use peniko::Color; use std::cell::Cell; use std::fmt::Display; use std::rc::Rc; -use std::time::Instant; +use std::time::{Duration, Instant}; use taffy::style::AlignItems; use winit::keyboard::{Key, NamedKey}; use winit::window::WindowId; @@ -71,7 +72,10 @@ pub struct Capture { pub start: Instant, pub post_layout: Instant, pub end: Instant, + pub taffy_duration: Duration, pub window: Option>, + pub window_size: Size, + pub scale: f64, } impl Capture {} @@ -83,7 +87,7 @@ pub fn captured_view( highlighted: RwSignal>, ) -> Box { let offset = depth as f64 * 14.0; - let name = text(view.name.clone()); + let name = text(view.name.clone()).style(|s| s.text_overflow(TextOverflow::Ellipsis)); let height = 20.0; let id = view.id; @@ -91,6 +95,7 @@ pub fn captured_view( return Box::new( name.style(move |s| { s.width_full() + .text_overflow(TextOverflow::Ellipsis) .padding_left(20.0 + offset) .hover(move |s| { s.background(Color::rgba8(228, 237, 216, 160)) @@ -229,20 +234,28 @@ fn header(label: impl Display) -> Label { }) } +fn info(s: String) -> Label { + text(s).style(|s| s.padding(5.0)) +} + fn stats(capture: &Capture) -> impl View { let layout_time = capture.post_layout.saturating_duration_since(capture.start); let paint_time = capture.end.saturating_duration_since(capture.post_layout); - let layout_time = text(format!( + let layout_time = info(format!( "Layout time: {:.4} ms", layout_time.as_secs_f64() * 1000.0 - )) - .style(|s| s.padding(5.0)); - let paint_time = text(format!( + )); + let taffy_time = info(format!( + "Taffy time: {:.4} ms", + capture.taffy_duration.as_secs_f64() * 1000.0 + )); + let paint_time = info(format!( "Paint time: {:.4} ms", paint_time.as_secs_f64() * 1000.0 - )) - .style(|s| s.padding(5.0)); - stack((layout_time, paint_time)).style(|s| s.flex_col()) + )); + let w = info(format!("Window Width: {}", capture.window_size.width)); + let h = info(format!("Window Height: {}", capture.window_size.height)); + stack((layout_time, taffy_time, paint_time, w, h)).style(|s| s.flex_col()) } fn selected_view(capture: &Rc, selected: RwSignal>) -> impl View { @@ -250,14 +263,38 @@ fn selected_view(capture: &Rc, selected: RwSignal>) -> impl dyn_container( move || selected.get(), move |current| { - let info = |i| text(i).style(|s| s.padding(5.0)); if let Some(view) = current.and_then(|id| capture.root.find(id)) { let name = info(format!("Type: {}", view.name)); let count = info(format!("Child Count: {}", view.children.len())); - let x = info(format!("X: {}", view.layout.x0)); - let y = info(format!("Y: {}", view.layout.y0)); - let w = info(format!("Width: {}", view.layout.width())); - let h = info(format!("Height: {}", view.layout.height())); + let beyond = |view: f64, window| { + if view > window { + format!(" ({} after window edge)", view - window) + } else if view < 0.0 { + format!(" ({} before window edge)", -view) + } else { + String::new() + } + }; + let x = info(format!( + "X: {}{}", + view.layout.x0, + beyond(view.layout.x0, capture.window_size.width) + )); + let y = info(format!( + "Y: {}{}", + view.layout.y0, + beyond(view.layout.y0, capture.window_size.height) + )); + let w = info(format!( + "Width: {}{}", + view.layout.width(), + beyond(view.layout.x1, capture.window_size.width) + )); + let h = info(format!( + "Height: {}{}", + view.layout.height(), + beyond(view.layout.y1, capture.window_size.height) + )); let clear = text("Clear selection") .style(|s| { s.background(Color::WHITE_SMOKE) @@ -291,13 +328,25 @@ fn capture_view(capture: &Rc) -> impl View { let window = capture.window.clone(); let capture_ = capture.clone(); let capture__ = capture.clone(); + let (image_width, image_height) = capture + .window + .as_ref() + .map(|img| { + ( + img.width() as f64 / capture.scale, + img.height() as f64 / capture.scale, + ) + }) + .unwrap_or_default(); let image = img_dynamic(move || window.clone()) - .style(|s| { + .style(move |s| { s.margin(5.0) .border(1.0) .border_color(Color::BLACK.with_alpha_factor(0.5)) - .margin_bottom(25.0) - .margin_right(25.0) + .width(image_width + 2.0) + .height(image_height + 2.0) + .margin_bottom(21.0) + .margin_right(21.0) }) .on_event(EventListener::PointerMove, move |e| { if let Event::PointerMove(e) = e { @@ -362,13 +411,20 @@ fn capture_view(capture: &Rc) -> impl View { let image = stack((image, selected_overlay, highlighted_overlay)); + let left_scroll = scroll( + stack(( + header("Selected View"), + selected_view(capture, selected), + header("Stats"), + stats(capture), + )) + .style(|s| s.flex_col().width_full()), + ); + let left = stack(( header("Captured Window"), scroll(image).style(|s| s.max_height_pct(60.0)), - header("Selected View"), - scroll(selected_view(capture, selected)), - header("Stats"), - scroll(stats(capture)), + left_scroll, )) .style(|s| s.flex_col().height_full().max_width_pct(60.0)); @@ -414,12 +470,13 @@ fn inspector_view(capture: &Option>) -> impl View { .width_full() .height_full() .background(Color::WHITE) - .set(scroll::Thickness, 20.0) + .set(scroll::Thickness, 16.0) .set(scroll::Rounded, false) + .set(scroll::HandleRadius, 4.0) .set(scroll::HandleColor, Color::rgba8(166, 166, 166, 140)) .set(scroll::DragColor, Color::rgb8(166, 166, 166)) .set(scroll::HoverColor, Color::rgb8(184, 184, 184)) - .set(scroll::BgActiveColor, Color::rgba8(166, 166, 166, 40)) + .set(scroll::BgActiveColor, Color::rgba8(166, 166, 166, 30)) }) } diff --git a/src/view.rs b/src/view.rs index dee3786f..013874fc 100644 --- a/src/view.rs +++ b/src/view.rs @@ -1061,8 +1061,8 @@ impl View for Box { (**self).children_mut() } - fn compute_layout(&mut self, cx: &mut LayoutCx) -> Option { - (**self).compute_layout(cx) + fn debug_name(&self) -> std::borrow::Cow<'static, str> { + (**self).debug_name() } fn update(&mut self, cx: &mut UpdateCx, state: Box) -> ChangeFlags { @@ -1073,6 +1073,10 @@ impl View for Box { (**self).layout(cx) } + fn compute_layout(&mut self, cx: &mut LayoutCx) -> Option { + (**self).compute_layout(cx) + } + fn event(&mut self, cx: &mut EventCx, id_path: Option<&[Id]>, event: Event) -> bool { (**self).event(cx, id_path, event) } diff --git a/src/views/scroll.rs b/src/views/scroll.rs index 5ce2bf30..8c73bd96 100644 --- a/src/views/scroll.rs +++ b/src/views/scroll.rs @@ -46,6 +46,7 @@ prop!(pub DragColor: Color { inherited } = Color::rgba8(0, 0, 0, 160)); prop!(pub Rounded: bool { inherited } = cfg!(target_os = "macos")); prop!(pub Thickness: Px { inherited } = Px(10.0)); prop!(pub EdgeWidth: Px { inherited } = Px(0.0)); +prop!(pub HandleRadius: Px { inherited } = Px(0.0)); prop!(pub BgActiveColor: Color { inherited } = Color::rgba8(0, 0, 0, 25)); prop_extracter! { @@ -53,6 +54,7 @@ prop_extracter! { handle_color: HandleColor, hover_color: Option, drag_color: Option, + handle_radius: HandleRadius, rounded: Rounded, thickness: Thickness, edge_width: EdgeWidth, @@ -329,7 +331,7 @@ impl Scroll { (rect.y1 - rect.y0) / 2. } } else { - 0. + self.style.handle_radius().0 } }; @@ -350,7 +352,7 @@ impl Scroll { self.style.handle_color() }; if self.vbar_whole_hover || matches!(self.held, BarHeldState::Vertical(..)) { - let mut bounds = bounds; + let mut bounds = bounds - scroll_offset; bounds.y0 = self.actual_rect.y0; bounds.y1 = self.actual_rect.y1; if let Some(color) = self.style.bg_active_color() { @@ -383,7 +385,7 @@ impl Scroll { self.style.handle_color() }; if self.hbar_whole_hover || matches!(self.held, BarHeldState::Horizontal(..)) { - let mut bounds = bounds; + let mut bounds = bounds - scroll_offset; bounds.x0 = self.actual_rect.x0; bounds.x1 = self.actual_rect.x1; if let Some(color) = self.style.bg_active_color() { diff --git a/src/window_handle.rs b/src/window_handle.rs index b646bc0a..53a3e27e 100644 --- a/src/window_handle.rs +++ b/src/window_handle.rs @@ -449,11 +449,14 @@ impl WindowHandle { } } - fn layout(&mut self) { + fn layout(&mut self) -> Duration { let mut cx = LayoutCx::new(&mut self.app_state); cx.app_state_mut().root = Some(self.view.layout_main(&mut cx)); + + let start = Instant::now(); cx.app_state_mut().compute_layout(); + let taffy_duration = Instant::now().saturating_duration_since(start); cx.clear(); self.view.compute_layout_main(&mut cx); @@ -465,11 +468,13 @@ impl WindowHandle { if let Some(id) = id { if self.app_state.capture.is_none() { - exec_after(Duration::from_millis(1), move |_| { - id.request_layout(); - }); + exec_after(Duration::from_millis(1), move |_| { + id.request_layout(); + }); + } } - } + + taffy_duration } pub fn paint(&mut self) -> Option { @@ -505,9 +510,15 @@ impl WindowHandle { .renderer .begin(cx.app_state.capture.is_some()); if !self.transparent { + let scale = cx.app_state.scale; // fill window with default white background if it's not transparent cx.fill( - &self.size.get_untracked().to_rect(), + &self + .size + .get_untracked() + .to_rect() + .scale_from_origin(1.0 / scale) + .expand(), peniko::Color::WHITE, 0.0, ); @@ -515,29 +526,34 @@ impl WindowHandle { self.view.paint_main(&mut cx); if let Some(window) = self.window.as_ref() { if cx.app_state.capture.is_none() { - window.pre_present_notify(); - } + window.pre_present_notify(); + } } let image = cx.paint_state.renderer.finish(); if cx.app_state.capture.is_none() { - self.process_update(); - } + self.process_update(); + } image } pub(crate) fn capture(&mut self) -> Capture { + self.app_state.capture = Some(()); + + // Trigger painting to create a Vger renderer which can capture the output. + // This can be expensive so it could skew the paint time measurement. + self.paint(); + // Ensure we run layout again for accurate timing. self.app_state .view_states .values_mut() .for_each(|state| state.request_layout = true); - self.app_state.capture = Some(()); let start = Instant::now(); - self.layout(); + let taffy_duration = self.layout(); let post_layout = Instant::now(); let window = self.paint().map(Rc::new); let end = Instant::now(); @@ -549,7 +565,10 @@ impl WindowHandle { start, post_layout, end, + taffy_duration, window, + window_size: self.size.get_untracked(), + scale: self.scale * self.app_state.scale, root: CapturedView::capture(&self.view, &mut self.app_state, root_layout), }; // Process any updates produced by capturing @@ -838,10 +857,10 @@ impl WindowHandle { } UpdateMessage::Inspect => { inspector::capture(self.window_id); + } } } } - } flags } diff --git a/vger/src/lib.rs b/vger/src/lib.rs index e6d57009..0105612f 100644 --- a/vger/src/lib.rs +++ b/vger/src/lib.rs @@ -1,3 +1,4 @@ +use std::mem; use std::sync::mpsc::sync_channel; use std::sync::Arc; @@ -18,6 +19,7 @@ pub struct VgerRenderer { queue: Arc, surface: Surface, vger: Vger, + alt_vger: Option, config: SurfaceConfiguration, scale: f64, transform: Affine, @@ -106,6 +108,7 @@ impl VgerRenderer { queue, surface, vger, + alt_vger: None, scale, config, transform: Affine::IDENTITY, @@ -265,7 +268,19 @@ impl VgerRenderer { impl Renderer for VgerRenderer { fn begin(&mut self, capture: bool) { - self.capture = capture; + // Switch to the capture Vger if needed + if self.capture != capture { + self.capture = capture; + if self.alt_vger.is_none() { + self.alt_vger = Some(vger::Vger::new( + self.device.clone(), + self.queue.clone(), + TextureFormat::Rgba8Unorm, + )); + } + mem::swap(&mut self.vger, self.alt_vger.as_mut().unwrap()) + } + self.transform = Affine::IDENTITY; self.vger.begin( self.config.width as f32,