diff options
38 files changed, 537 insertions, 87 deletions
@@ -4,3 +4,4 @@ pkg/ Cargo.lock .cargo/ dist/ +traces/ @@ -39,6 +39,13 @@ smol = ["iced_futures/smol"] palette = ["iced_core/palette"] # Enables querying system information system = ["iced_winit/system"] +# Enables chrome traces +chrome-trace = [ + "iced_winit/chrome-trace", + "iced_glutin?/trace", + "iced_wgpu?/tracing", + "iced_glow?/tracing", +] [badges] maintenance = { status = "actively-developed" } @@ -8,7 +8,7 @@ [](https://crates.io/crates/iced) [](https://github.com/iced-rs/iced/blob/master/LICENSE) [](https://crates.io/crates/iced) -[](https://github.com/iced-rs/iced/actions) +[](https://github.com/iced-rs/iced/actions) [](https://discord.gg/3xZJ65GAhd) A cross-platform GUI library for Rust focused on simplicity and type-safety. diff --git a/glow/Cargo.toml b/glow/Cargo.toml index f586d24d..c126a511 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -42,6 +42,10 @@ version = "0.5" path = "../graphics" features = ["font-fallback", "font-icons", "opengl"] +[dependencies.tracing] +version = "0.1.6" +optional = true + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] all-features = true diff --git a/glow/src/image.rs b/glow/src/image.rs index 521a01e7..d3a25b5b 100644 --- a/glow/src/image.rs +++ b/glow/src/image.rs @@ -21,6 +21,9 @@ use glow::HasContext; use std::cell::RefCell; +#[cfg(feature = "tracing")] +use tracing::info_span; + #[derive(Debug)] pub(crate) struct Pipeline { program: <glow::Context as HasContext>::Program, @@ -148,6 +151,9 @@ impl Pipeline { images: &[layer::Image], layer_bounds: Rectangle<u32>, ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Glow::Image", "DRAW").entered(); + unsafe { gl.use_program(Some(self.program)); gl.bind_vertex_array(Some(self.vertex_array)); diff --git a/glow/src/quad.rs b/glow/src/quad.rs index d9f1c6ae..67d9a098 100644 --- a/glow/src/quad.rs +++ b/glow/src/quad.rs @@ -7,6 +7,9 @@ use glow::HasContext; use iced_graphics::layer; use iced_native::Rectangle; +#[cfg(feature = "tracing")] +use tracing::info_span; + #[derive(Debug)] pub enum Pipeline { Core(core::Pipeline), @@ -42,6 +45,9 @@ impl Pipeline { scale: f32, bounds: Rectangle<u32>, ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Glow::Quad", "DRAW").enter(); + match self { Pipeline::Core(pipeline) => { pipeline.draw( diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs index d0205e08..42c88455 100644 --- a/glow/src/triangle.rs +++ b/glow/src/triangle.rs @@ -9,6 +9,9 @@ use iced_graphics::triangle::{ColoredVertex2D, Vertex2D}; use glow::HasContext; use std::marker::PhantomData; +#[cfg(feature = "tracing")] +use tracing::info_span; + const DEFAULT_VERTICES: usize = 1_000; const DEFAULT_INDICES: usize = 1_000; @@ -58,6 +61,9 @@ impl Pipeline { transformation: Transformation, scale_factor: f32, ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Glow::Triangle", "DRAW").enter(); + unsafe { gl.enable(glow::MULTISAMPLE); gl.enable(glow::SCISSOR_TEST); diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index 022457b1..304170cd 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] +trace = ["iced_winit/trace"] debug = ["iced_winit/debug"] system = ["iced_winit/system"] @@ -35,3 +36,7 @@ features = ["application"] version = "0.5" path = "../graphics" features = ["opengl"] + +[dependencies.tracing] +version = "0.1.6" +optional = true
\ No newline at end of file diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 3e9d11f9..1464bb2d 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -17,6 +17,9 @@ use iced_winit::{Clipboard, Command, Debug, Proxy, Settings}; use glutin::window::Window; use std::mem::ManuallyDrop; +#[cfg(feature = "tracing")] +use tracing::{info_span, instrument::Instrument}; + /// Runs an [`Application`] with an executor, compositor, and the provided /// settings. pub fn run<A, E, C>( @@ -35,9 +38,15 @@ where use glutin::platform::run_return::EventLoopExtRunReturn; use glutin::ContextBuilder; + #[cfg(feature = "trace")] + let _guard = iced_winit::Profiler::init(); + let mut debug = Debug::new(); debug.startup_started(); + #[cfg(feature = "tracing")] + let _ = info_span!("Application::Glutin", "RUN").entered(); + let mut event_loop = EventLoopBuilder::with_user_event().build(); let proxy = event_loop.create_proxy(); @@ -124,18 +133,26 @@ where let (mut sender, receiver) = mpsc::unbounded(); - let mut instance = Box::pin(run_instance::<A, E, C>( - application, - compositor, - renderer, - runtime, - proxy, - debug, - receiver, - context, - init_command, - settings.exit_on_close_request, - )); + let mut instance = Box::pin({ + let run_instance = run_instance::<A, E, C>( + application, + compositor, + renderer, + runtime, + proxy, + debug, + receiver, + context, + init_command, + settings.exit_on_close_request, + ); + + #[cfg(feature = "tracing")] + let run_instance = + run_instance.instrument(info_span!("Application", "LOOP")); + + run_instance + }); let mut context = task::Context::from_waker(task::noop_waker_ref()); @@ -333,6 +350,9 @@ async fn run_instance<A, E, C>( messages.push(message); } event::Event::RedrawRequested(_) => { + #[cfg(feature = "tracing")] + let _ = info_span!("Application", "FRAME").entered(); + debug.render_started(); #[allow(unsafe_code)] diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs index 83f25238..61e919d6 100644 --- a/graphics/src/gradient.rs +++ b/graphics/src/gradient.rs @@ -64,7 +64,7 @@ impl From<(Point, Point)> for Position { } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] /// The location of a relatively-positioned gradient. pub enum Location { /// Top left. @@ -86,7 +86,7 @@ pub enum Location { } impl Location { - fn to_absolute(&self, top_left: Point, size: Size) -> Point { + fn to_absolute(self, top_left: Point, size: Size) -> Point { match self { Location::TopLeft => top_left, Location::Top => { diff --git a/graphics/src/image/storage.rs b/graphics/src/image/storage.rs index 2098c7b2..1b5b5c35 100644 --- a/graphics/src/image/storage.rs +++ b/graphics/src/image/storage.rs @@ -20,7 +20,7 @@ pub trait Storage { state: &mut Self::State<'_>, ) -> Option<Self::Entry>; - /// Romve a [`Self::Entry`] from the [`Storage`]. + /// Remove a [`Self::Entry`] from the [`Storage`]. fn remove(&mut self, entry: &Self::Entry, state: &mut Self::State<'_>); } diff --git a/lazy/src/component.rs b/lazy/src/component.rs index ad15d69d..d8f21f8a 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -46,6 +46,16 @@ pub trait Component<Message, Renderer> { /// Produces the widgets of the [`Component`], which may trigger an [`Event`](Component::Event) /// on user interaction. fn view(&self, state: &Self::State) -> Element<'_, Self::Event, Renderer>; + + /// Update the [`Component`] state based on the provided [`Operation`](widget::Operation) + /// + /// By default, it does nothing. + fn operate( + &self, + _state: &mut Self::State, + _operation: &mut dyn widget::Operation<Message>, + ) { + } } /// Turns an implementor of [`Component`] into an [`Element`] that can be @@ -106,6 +116,26 @@ where ); } + fn rebuild_element_with_operation( + &self, + state: &mut S, + operation: &mut dyn widget::Operation<Message>, + ) { + let heads = self.state.borrow_mut().take().unwrap().into_heads(); + + heads.component.operate(state, operation); + + *self.state.borrow_mut() = Some( + StateBuilder { + component: heads.component, + message: PhantomData, + state: PhantomData, + element_builder: |component| Some(component.view(state)), + } + .build(), + ); + } + fn with_element<T>( &self, f: impl FnOnce(&Element<'_, Event, Renderer>) -> T, @@ -237,6 +267,11 @@ where renderer: &Renderer, operation: &mut dyn widget::Operation<Message>, ) { + self.rebuild_element_with_operation( + tree.state.downcast_mut(), + operation, + ); + struct MapOperation<'a, B> { operation: &'a mut dyn widget::Operation<B>, } diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs index 945c935a..52badda2 100644 --- a/lazy/src/responsive.rs +++ b/lazy/src/responsive.rs @@ -280,12 +280,14 @@ where ); let Content { - element, layout, .. + element, + layout: content_layout, + .. } = content.deref_mut(); let content_layout = Layout::with_offset( layout.bounds().position() - Point::ORIGIN, - layout, + content_layout, ); element diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs index 33a452d0..4cbb970d 100644 --- a/native/src/layout/limits.rs +++ b/native/src/layout/limits.rs @@ -1,3 +1,4 @@ +#![allow(clippy::manual_clamp)] use crate::{Length, Padding, Size}; /// A set of size constraints for layouting. @@ -51,7 +52,7 @@ impl Limits { } Length::Units(units) => { let new_width = - (units as f32).clamp(self.min.width, self.max.width); + (units as f32).min(self.max.width).max(self.min.width); self.min.width = new_width; self.max.width = new_width; @@ -73,7 +74,7 @@ impl Limits { } Length::Units(units) => { let new_height = - (units as f32).clamp(self.min.height, self.max.height); + (units as f32).min(self.max.height).max(self.min.height); self.min.height = new_height; self.max.height = new_height; @@ -86,14 +87,16 @@ impl Limits { /// Applies a minimum width constraint to the current [`Limits`]. pub fn min_width(mut self, min_width: u32) -> Limits { - self.min.width = self.min.width.clamp(min_width as f32, self.max.width); + self.min.width = + self.min.width.max(min_width as f32).min(self.max.width); self } /// Applies a maximum width constraint to the current [`Limits`]. pub fn max_width(mut self, max_width: u32) -> Limits { - self.max.width = self.max.width.clamp(self.min.width, max_width as f32); + self.max.width = + self.max.width.min(max_width as f32).max(self.min.width); self } @@ -101,7 +104,7 @@ impl Limits { /// Applies a minimum height constraint to the current [`Limits`]. pub fn min_height(mut self, min_height: u32) -> Limits { self.min.height = - self.min.height.clamp(min_height as f32, self.max.height); + self.min.height.max(min_height as f32).min(self.max.height); self } @@ -109,7 +112,7 @@ impl Limits { /// Applies a maximum height constraint to the current [`Limits`]. pub fn max_height(mut self, max_height: u32) -> Limits { self.max.height = - self.max.height.clamp(self.min.height, max_height as f32); + self.max.height.min(max_height as f32).max(self.min.height); self } @@ -155,10 +158,14 @@ impl Limits { /// intrinsic size of some content. pub fn resolve(&self, intrinsic_size: Size) -> Size { Size::new( - intrinsic_size.width.clamp(self.fill.width, self.max.width), + intrinsic_size + .width + .min(self.max.width) + .max(self.fill.width), intrinsic_size .height - .clamp(self.fill.height, self.max.height), + .min(self.max.height) + .max(self.fill.height), ) } } diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index bec5c448..b46433c2 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -27,7 +27,7 @@ pub use iced_style::checkbox::{Appearance, StyleSheet}; /// /// let is_checked = true; /// -/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled); +/// Checkbox::new("Toggle me!", is_checked, Message::CheckboxToggled); /// ``` /// ///  @@ -67,7 +67,7 @@ where /// * a function that will be called when the [`Checkbox`] is toggled. It /// will receive the new state of the [`Checkbox`] and must produce a /// `Message`. - pub fn new<F>(is_checked: bool, label: impl Into<String>, f: F) -> Self + pub fn new<F>(label: impl Into<String>, is_checked: bool, f: F) -> Self where F: 'a + Fn(bool) -> Message, { diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index 8cc1ae82..dfd949f6 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -129,7 +129,7 @@ where Renderer: crate::text::Renderer, Renderer::Theme: widget::checkbox::StyleSheet + widget::text::StyleSheet, { - widget::Checkbox::new(is_checked, label, f) + widget::Checkbox::new(label, is_checked, f) } /// Creates a new [`Radio`]. @@ -162,7 +162,7 @@ where Renderer: crate::text::Renderer, Renderer::Theme: widget::toggler::StyleSheet, { - widget::Toggler::new(is_checked, label, f) + widget::Toggler::new(label, is_checked, f) } /// Creates a new [`TextInput`]. diff --git a/native/src/widget/operation/focusable.rs b/native/src/widget/operation/focusable.rs index 0067006b..312e4894 100644 --- a/native/src/widget/operation/focusable.rs +++ b/native/src/widget/operation/focusable.rs @@ -18,10 +18,10 @@ pub trait Focusable { #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Count { /// The index of the current focused widget, if any. - focused: Option<usize>, + pub focused: Option<usize>, /// The total amount of focusable widgets. - total: usize, + pub total: usize, } /// Produces an [`Operation`] that focuses the widget with the given [`Id`]. diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 52cb1ad1..c2853314 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -20,6 +20,60 @@ use std::borrow::Cow; pub use iced_style::pick_list::{Appearance, StyleSheet}; +/// The handle to the right side of the [`PickList`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Handle<Renderer> +where + Renderer: text::Renderer, +{ + /// Displays an arrow icon (▼). + /// + /// This is the default. + Arrow { + /// Font size of the content. + size: Option<u16>, + }, + /// A custom handle. + Custom { + /// Font that will be used to display the `text`, + font: Renderer::Font, + /// Text that will be shown. + text: String, + /// Font size of the content. + size: Option<u16>, + }, + /// No handle will be shown. + None, +} + +impl<Renderer> Default for Handle<Renderer> +where + Renderer: text::Renderer, +{ + fn default() -> Self { + Self::Arrow { size: None } + } +} + +impl<Renderer> Handle<Renderer> +where + Renderer: text::Renderer, +{ + fn content(&self) -> Option<(Renderer::Font, String, Option<u16>)> { + match self { + Self::Arrow { size } => Some(( + Renderer::ICON_FONT, + Renderer::ARROW_DOWN_ICON.to_string(), + *size, + )), + Self::Custom { font, text, size } => { + Some((font.clone(), text.clone(), *size)) + } + Self::None => None, + } + } +} + /// A widget for selecting a single value from a list of options. #[allow(missing_debug_implementations)] pub struct PickList<'a, T, Message, Renderer> @@ -36,6 +90,7 @@ where padding: Padding, text_size: Option<u16>, font: Renderer::Font, + handle: Handle<Renderer>, style: <Renderer::Theme as StyleSheet>::Style, } @@ -67,9 +122,10 @@ where placeholder: None, selected, width: Length::Shrink, - text_size: None, padding: Self::DEFAULT_PADDING, + text_size: None, font: Default::default(), + handle: Default::default(), style: Default::default(), } } @@ -104,6 +160,12 @@ where self } + /// Sets the [`Handle`] of the [`PickList`]. + pub fn handle(mut self, handle: Handle<Renderer>) -> Self { + self.handle = handle; + self + } + /// Sets the style of the [`PickList`]. pub fn style( mut self, @@ -214,6 +276,7 @@ where &self.font, self.placeholder.as_deref(), self.selected.as_ref(), + &self.handle, &self.style, ) } @@ -515,6 +578,7 @@ pub fn draw<T, Renderer>( font: &Renderer::Font, placeholder: Option<&str>, selected: Option<&T>, + handle: &Handle<Renderer>, style: &<Renderer::Theme as StyleSheet>::Style, ) where Renderer: text::Renderer, @@ -541,19 +605,24 @@ pub fn draw<T, Renderer>( style.background, ); - renderer.fill_text(Text { - content: &Renderer::ARROW_DOWN_ICON.to_string(), - font: Renderer::ICON_FONT, - size: bounds.height * style.icon_size, - bounds: Rectangle { - x: bounds.x + bounds.width - f32::from(padding.horizontal()), - y: bounds.center_y(), - ..bounds - }, - color: style.text_color, - horizontal_alignment: alignment::Horizontal::Right, - vertical_alignment: alignment::Vertical::Center, - }); + if let Some((font, text, size)) = handle.content() { + let size = f32::from(size.unwrap_or_else(|| renderer.default_size())); + + renderer.fill_text(Text { + content: &text, + size, + font, + color: style.handle_color, + bounds: Rectangle { + x: bounds.x + bounds.width - f32::from(padding.horizontal()), + y: bounds.center_y() - size / 2.0, + height: size, + ..bounds + }, + horizontal_alignment: alignment::Horizontal::Right, + vertical_alignment: alignment::Vertical::Top, + }); + } let label = selected.map(ToString::to_string); diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 05b47ff9..8b4514e3 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -454,9 +454,17 @@ where ) } else { None - }; + } + .unwrap_or(0); - state.cursor.move_to(position.unwrap_or(0)); + if state.keyboard_modifiers.shift() { + state.cursor.select_range( + state.cursor.start(value), + position, + ); + } else { + state.cursor.move_to(position); + } state.is_dragging = true; } click::Kind::Double => { diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 1ae65ba6..f0a944a3 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -24,9 +24,9 @@ pub use iced_style::toggler::{Appearance, StyleSheet}; /// TogglerToggled(bool), /// } /// -/// let is_active = true; +/// let is_toggled = true; /// -/// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b)); +/// Toggler::new(String::from("Toggle me!"), is_toggled, |b| Message::TogglerToggled(b)); /// ``` #[allow(missing_debug_implementations)] pub struct Toggler<'a, Message, Renderer> @@ -34,7 +34,7 @@ where Renderer: text::Renderer, Renderer::Theme: StyleSheet, { - is_active: bool, + is_toggled: bool, on_toggle: Box<dyn Fn(bool) -> Message + 'a>, label: Option<String>, width: Length, @@ -63,15 +63,15 @@ where /// will receive the new state of the [`Toggler`] and must produce a /// `Message`. pub fn new<F>( - is_active: bool, label: impl Into<Option<String>>, + is_toggled: bool, f: F, ) -> Self where F: 'a + Fn(bool) -> Message, { Toggler { - is_active, + is_toggled, on_toggle: Box::new(f), label: label.into(), width: Length::Fill, @@ -193,7 +193,7 @@ where let mouse_over = layout.bounds().contains(cursor_position); if mouse_over { - shell.publish((self.on_toggle)(!self.is_active)); + shell.publish((self.on_toggle)(!self.is_toggled)); event::Status::Captured } else { @@ -260,9 +260,9 @@ where let is_mouse_over = bounds.contains(cursor_position); let style = if is_mouse_over { - theme.hovered(&self.style, self.is_active) + theme.hovered(&self.style, self.is_toggled) } else { - theme.active(&self.style, self.is_active) + theme.active(&self.style, self.is_toggled) }; let border_radius = bounds.height / BORDER_RADIUS_RATIO; @@ -289,7 +289,7 @@ where let toggler_foreground_bounds = Rectangle { x: bounds.x - + if self.is_active { + + if self.is_toggled { bounds.width - 2.0 * space - (bounds.height - (4.0 * space)) } else { 2.0 * space diff --git a/native/src/window.rs b/native/src/window.rs index f910b8f2..1b97e655 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -2,7 +2,9 @@ mod action; mod event; mod mode; +mod user_attention; pub use action::Action; pub use event::Event; pub use mode::Mode; +pub use user_attention::UserAttention; diff --git a/native/src/window/action.rs b/native/src/window/action.rs index da307e97..37fcc273 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -1,4 +1,4 @@ -use crate::window::Mode; +use crate::window::{Mode, UserAttention}; use iced_futures::MaybeSend; use std::fmt; @@ -35,6 +35,8 @@ pub enum Action<T> { }, /// Set the [`Mode`] of the window. SetMode(Mode), + /// Fetch the current [`Mode`] of the window. + FetchMode(Box<dyn FnOnce(Mode) -> T + 'static>), /// Sets the window to maximized or back ToggleMaximize, /// Toggles whether window has decorations @@ -42,8 +44,31 @@ pub enum Action<T> { /// - **X11:** Not implemented. /// - **Web:** Unsupported. ToggleDecorations, - /// Fetch the current [`Mode`] of the window. - FetchMode(Box<dyn FnOnce(Mode) -> T + 'static>), + /// Requests user attention to the window, this has no effect if the application + /// is already focused. How requesting for user attention manifests is platform dependent, + /// see [`UserAttentionType`] for details. + /// + /// Providing `None` will unset the request for user attention. Unsetting the request for + /// user attention might not be done automatically by the WM when the window receives input. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Web:** Unsupported. + /// - **macOS:** `None` has no effect. + /// - **X11:** Requests for user attention must be manually cleared. + /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. + RequestUserAttention(Option<UserAttention>), + /// Brings the window to the front and sets input focus. Has no effect if the window is + /// already in focus, minimized, or not visible. + /// + /// This method steals input focus from other applications. Do not use this method unless + /// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive + /// user experience. + /// + /// ## Platform-specific + /// + /// - **Web / Wayland:** Unsupported. + GainFocus, } impl<T> Action<T> { @@ -63,9 +88,13 @@ impl<T> Action<T> { Self::Minimize(bool) => Action::Minimize(bool), Self::Move { x, y } => Action::Move { x, y }, Self::SetMode(mode) => Action::SetMode(mode), + Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))), Self::ToggleMaximize => Action::ToggleMaximize, Self::ToggleDecorations => Action::ToggleDecorations, - Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))), + Self::RequestUserAttention(attention_type) => { + Action::RequestUserAttention(attention_type) + } + Self::GainFocus => Action::GainFocus, } } } @@ -86,9 +115,13 @@ impl<T> fmt::Debug for Action<T> { write!(f, "Action::Move {{ x: {}, y: {} }}", x, y) } Self::SetMode(mode) => write!(f, "Action::SetMode({:?})", mode), + Self::FetchMode(_) => write!(f, "Action::FetchMode"), Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"), Self::ToggleDecorations => write!(f, "Action::ToggleDecorations"), - Self::FetchMode(_) => write!(f, "Action::FetchMode"), + Self::RequestUserAttention(_) => { + write!(f, "Action::RequestUserAttention") + } + Self::GainFocus => write!(f, "Action::GainFocus"), } } } diff --git a/native/src/window/user_attention.rs b/native/src/window/user_attention.rs new file mode 100644 index 00000000..b03dfeef --- /dev/null +++ b/native/src/window/user_attention.rs @@ -0,0 +1,21 @@ +/// The type of user attention to request. +/// +/// ## Platform-specific +/// +/// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and [`Informational`]. +/// +/// [`Critical`]: Self::Critical +/// [`Informational`]: Self::Informational +#[derive(Debug, Clone, Copy)] +pub enum UserAttention { + /// ## Platform-specific + /// + /// - **macOS:** Bounces the dock icon until the application is in focus. + /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus. + Critical, + /// ## Platform-specific + /// + /// - **macOS:** Bounces the dock icon once. + /// - **Windows:** Flashes the taskbar button until the application is in focus. + Informational, +} @@ -24,7 +24,7 @@ //! [scrollables]: https://gfycat.com/perkybaggybaboon-rust-gui //! [Debug overlay with performance metrics]: https://gfycat.com/incredibledarlingbee //! [Modular ecosystem]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md -//! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/0.4/master/native +//! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/tree/0.6/native //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.6/wgpu //! [windowing shell]: https://github.com/iced-rs/iced/tree/0.6/winit diff --git a/src/widget.rs b/src/widget.rs index f87782d0..f71bf7ff 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -80,7 +80,7 @@ pub mod pane_grid { pub mod pick_list { //! Display a dropdown list of selectable values. - pub use iced_native::widget::pick_list::{Appearance, StyleSheet}; + pub use iced_native::widget::pick_list::{Appearance, Handle, StyleSheet}; /// A widget allowing the selection of a single value from a list of options. pub type PickList<'a, T, Message, Renderer = crate::Renderer> = diff --git a/style/src/pick_list.rs b/style/src/pick_list.rs index 8d93dff2..11e13b01 100644 --- a/style/src/pick_list.rs +++ b/style/src/pick_list.rs @@ -8,6 +8,8 @@ pub struct Appearance { pub text_color: Color, /// The placeholder [`Color`] of the pick list. pub placeholder_color: Color, + /// The handle [`Color`] of the pick list. + pub handle_color: Color, /// The [`Background`] of the pick list. pub background: Background, /// The border radius of the pick list. @@ -16,8 +18,6 @@ pub struct Appearance { pub border_width: f32, /// The border color of the pick list. pub border_color: Color, - /// The size of the arrow icon of the pick list. - pub icon_size: f32, } /// A set of rules that dictate the style of a container. diff --git a/style/src/theme.rs b/style/src/theme.rs index f780f952..55bfa4ca 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -535,10 +535,10 @@ impl pick_list::StyleSheet for Theme { text_color: palette.background.weak.text, background: palette.background.weak.color.into(), placeholder_color: palette.background.strong.color, + handle_color: palette.background.weak.text, border_radius: 2.0, border_width: 1.0, border_color: palette.background.strong.color, - icon_size: 0.7, } } PickList::Custom(custom, _) => custom.active(self), @@ -554,10 +554,10 @@ impl pick_list::StyleSheet for Theme { text_color: palette.background.weak.text, background: palette.background.weak.color.into(), placeholder_color: palette.background.strong.color, + handle_color: palette.background.weak.text, border_radius: 2.0, border_width: 1.0, border_color: palette.primary.strong.color, - icon_size: 0.7, } } PickList::Custom(custom, _) => custom.hovered(self), diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index a40d9967..8dc4b990 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -50,6 +50,10 @@ version = "0.5" path = "../graphics" features = ["font-fallback", "font-icons"] +[dependencies.tracing] +version = "0.1.6" +optional = true + [dependencies.encase] version = "0.3.0" features = ["glam"] diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 946eb712..9ab12ce0 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -10,6 +10,9 @@ use iced_graphics::{Primitive, Viewport}; use iced_native::alignment; use iced_native::{Font, Size}; +#[cfg(feature = "tracing")] +use tracing::info_span; + #[cfg(any(feature = "image", feature = "svg"))] use crate::image; @@ -77,6 +80,8 @@ impl Backend { overlay_text: &[T], ) { log::debug!("Drawing"); + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Backend", "PRESENT").entered(); let target_size = viewport.physical_size(); let scale_factor = viewport.scale_factor() as f32; diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 390bad90..a5e63b17 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -23,6 +23,9 @@ use iced_native::image; #[cfg(feature = "svg")] use iced_native::svg; +#[cfg(feature = "tracing")] +use tracing::info_span; + #[derive(Debug)] pub struct Pipeline { #[cfg(feature = "image")] @@ -289,6 +292,9 @@ impl Pipeline { target: &wgpu::TextureView, _scale: f32, ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Image", "DRAW").entered(); + let instances: &mut Vec<Instance> = &mut Vec::new(); #[cfg(feature = "image")] diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 027a34be..2f5fcc6b 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -6,6 +6,9 @@ use bytemuck::{Pod, Zeroable}; use std::mem; use wgpu::util::DeviceExt; +#[cfg(feature = "tracing")] +use tracing::info_span; + #[derive(Debug)] pub struct Pipeline { pipeline: wgpu::RenderPipeline, @@ -173,6 +176,9 @@ impl Pipeline { bounds: Rectangle<u32>, target: &wgpu::TextureView, ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Quad", "DRAW").entered(); + let uniforms = Uniforms::new(transformation, scale); { @@ -207,6 +213,9 @@ impl Pipeline { instance_buffer.copy_from_slice(instance_bytes); + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Quad", "BEGIN_RENDER_PASS").enter(); + { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 061154b6..efdd214b 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -8,6 +8,8 @@ use crate::Transformation; use iced_graphics::layer::mesh::{self, Mesh}; use iced_graphics::triangle::ColoredVertex2D; use iced_graphics::Size; +#[cfg(feature = "tracing")] +use tracing::info_span; #[derive(Debug)] pub struct Pipeline { @@ -53,6 +55,9 @@ impl Pipeline { scale_factor: f32, meshes: &[Mesh<'_>], ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Triangle", "DRAW").entered(); + // Count the total amount of vertices & indices we need to handle let count = mesh::attribute_count_of(meshes); @@ -247,6 +252,9 @@ impl Pipeline { (target, None, wgpu::LoadOp::Load) }; + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Triangle", "BEGIN_RENDER_PASS").enter(); + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("iced_wgpu::triangle render pass"), diff --git a/winit/Cargo.toml b/winit/Cargo.toml index ebbadb12..94aaa2ca 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -11,6 +11,8 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] +trace = ["tracing", "tracing-core", "tracing-subscriber"] +chrome-trace = ["trace", "tracing-chrome"] debug = ["iced_native/debug"] system = ["sysinfo"] application = [] @@ -37,6 +39,24 @@ path = "../graphics" version = "0.5" path = "../futures" +[dependencies.tracing] +version = "0.1.37" +optional = true +features = ["std"] + +[dependencies.tracing-core] +version = "0.1.30" +optional = true + +[dependencies.tracing-subscriber] +version = "0.3.16" +optional = true +features = ["registry"] + +[dependencies.tracing-chrome] +version = "0.7.0" +optional = true + [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" diff --git a/winit/src/application.rs b/winit/src/application.rs index 0f9b562e..74c73815 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,4 +1,6 @@ //! Create interactive, native cross-platform applications. +#[cfg(feature = "trace")] +mod profiler; mod state; pub use state::State; @@ -24,6 +26,11 @@ pub use iced_native::application::{Appearance, StyleSheet}; use std::mem::ManuallyDrop; +#[cfg(feature = "trace")] +pub use profiler::Profiler; +#[cfg(feature = "trace")] +use tracing::{info_span, instrument::Instrument}; + /// An interactive, native cross-platform application. /// /// This trait is the main entrypoint of Iced. Once implemented, you can run @@ -111,9 +118,15 @@ where use futures::Future; use winit::event_loop::EventLoopBuilder; + #[cfg(feature = "trace")] + let _guard = Profiler::init(); + let mut debug = Debug::new(); debug.startup_started(); + #[cfg(feature = "trace")] + let _ = info_span!("Application", "RUN").entered(); + let event_loop = EventLoopBuilder::with_user_event().build(); let proxy = event_loop.create_proxy(); @@ -175,18 +188,26 @@ where let (mut sender, receiver) = mpsc::unbounded(); - let mut instance = Box::pin(run_instance::<A, E, C>( - application, - compositor, - renderer, - runtime, - proxy, - debug, - receiver, - init_command, - window, - settings.exit_on_close_request, - )); + let mut instance = Box::pin({ + let run_instance = run_instance::<A, E, C>( + application, + compositor, + renderer, + runtime, + proxy, + debug, + receiver, + init_command, + window, + settings.exit_on_close_request, + ); + + #[cfg(feature = "trace")] + let run_instance = + run_instance.instrument(info_span!("Application", "LOOP")); + + run_instance + }); let mut context = task::Context::from_waker(task::noop_waker_ref()); @@ -391,6 +412,9 @@ async fn run_instance<A, E, C>( messages.push(message); } event::Event::RedrawRequested(_) => { + #[cfg(feature = "trace")] + let _ = info_span!("Application", "FRAME").entered(); + let physical_size = state.physical_size(); if physical_size.width == 0 || physical_size.height == 0 { @@ -529,12 +553,24 @@ pub fn build_user_interface<'a, A: Application>( where <A::Renderer as crate::Renderer>::Theme: StyleSheet, { + #[cfg(feature = "trace")] + let view_span = info_span!("Application", "VIEW").entered(); + debug.view_started(); let view = application.view(); + + #[cfg(feature = "trace")] + let _ = view_span.exit(); debug.view_finished(); + #[cfg(feature = "trace")] + let layout_span = info_span!("Application", "LAYOUT").entered(); + debug.layout_started(); let user_interface = UserInterface::build(view, size, cache, renderer); + + #[cfg(feature = "trace")] + let _ = layout_span.exit(); debug.layout_finished(); user_interface @@ -559,10 +595,16 @@ pub fn update<A: Application, E: Executor>( <A::Renderer as crate::Renderer>::Theme: StyleSheet, { for message in messages.drain(..) { + #[cfg(feature = "trace")] + let update_span = info_span!("Application", "UPDATE").entered(); + debug.log_message(&message); debug.update_started(); let command = runtime.enter(|| application.update(message)); + + #[cfg(feature = "trace")] + let _ = update_span.exit(); debug.update_finished(); run_command( @@ -657,12 +699,6 @@ pub fn run_command<A, E>( mode, )); } - window::Action::ToggleMaximize => { - window.set_maximized(!window.is_maximized()) - } - window::Action::ToggleDecorations => { - window.set_decorations(!window.is_decorated()) - } window::Action::FetchMode(tag) => { let mode = if window.is_visible().unwrap_or(true) { conversion::mode(window.fullscreen()) @@ -674,6 +710,17 @@ pub fn run_command<A, E>( .send_event(tag(mode)) .expect("Send message to event loop"); } + window::Action::ToggleMaximize => { + window.set_maximized(!window.is_maximized()) + } + window::Action::ToggleDecorations => { + window.set_decorations(!window.is_decorated()) + } + window::Action::RequestUserAttention(user_attention) => window + .request_user_attention( + user_attention.map(conversion::user_attention), + ), + window::Action::GainFocus => window.focus_window(), }, command::Action::System(action) => match action { system::Action::QueryInformation(_tag) => { diff --git a/winit/src/application/profiler.rs b/winit/src/application/profiler.rs new file mode 100644 index 00000000..23eaa390 --- /dev/null +++ b/winit/src/application/profiler.rs @@ -0,0 +1,101 @@ +//! A simple profiler for Iced. +use std::ffi::OsStr; +use std::path::Path; +use std::time::Duration; +use tracing_subscriber::prelude::*; +use tracing_subscriber::Registry; +#[cfg(feature = "chrome-trace")] +use { + tracing_chrome::FlushGuard, + tracing_subscriber::fmt::{format::DefaultFields, FormattedFields}, +}; + +/// Profiler state. This will likely need to be updated or reworked when adding new tracing backends. +#[allow(missing_debug_implementations)] +pub struct Profiler { + #[cfg(feature = "chrome-trace")] + /// [`FlushGuard`] must not be dropped until the application scope is dropped for accurate tracing. + _guard: FlushGuard, +} + +impl Profiler { + /// Initializes the [`Profiler`]. + pub fn init() -> Self { + // Registry stores the spans & generates unique span IDs + let subscriber = Registry::default(); + + let default_path = Path::new(env!("CARGO_MANIFEST_DIR")); + let curr_exe = std::env::current_exe() + .unwrap_or_else(|_| default_path.to_path_buf()); + let out_dir = curr_exe.parent().unwrap_or(default_path).join("traces"); + + #[cfg(feature = "chrome-trace")] + let (chrome_layer, guard) = { + let mut layer = tracing_chrome::ChromeLayerBuilder::new(); + + // Optional configurable env var: CHROME_TRACE_FILE=/path/to/trace_file/file.json, + // for uploading to chrome://tracing (old) or ui.perfetto.dev (new). + if let Ok(path) = std::env::var("CHROME_TRACE_FILE") { + layer = layer.file(path); + } else if std::fs::create_dir_all(&out_dir).is_ok() { + let time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or(Duration::from_millis(0)) + .as_millis(); + + let curr_exe_name = curr_exe + .file_name() + .unwrap_or_else(|| OsStr::new("trace")) + .to_str() + .unwrap_or("trace"); + + let path = out_dir + .join(format!("{}_trace_{}.json", curr_exe_name, time)); + + layer = layer.file(path); + } else { + layer = layer.file(env!("CARGO_MANIFEST_DIR")) + } + + let (chrome_layer, guard) = layer + .name_fn(Box::new(|event_or_span| match event_or_span { + tracing_chrome::EventOrSpan::Event(event) => { + event.metadata().name().into() + } + tracing_chrome::EventOrSpan::Span(span) => { + if let Some(fields) = span + .extensions() + .get::<FormattedFields<DefaultFields>>() + { + format!( + "{}: {}", + span.metadata().name(), + fields.fields.as_str() + ) + } else { + span.metadata().name().into() + } + } + })) + .build(); + + (chrome_layer, guard) + }; + + let fmt_layer = tracing_subscriber::fmt::Layer::default(); + let subscriber = subscriber.with(fmt_layer); + + #[cfg(feature = "chrome-trace")] + let subscriber = subscriber.with(chrome_layer); + + // create dispatcher which will forward span events to the subscriber + // this can only be set once or will panic + tracing::subscriber::set_global_default(subscriber) + .expect("Tracer could not set the global default subscriber."); + + Profiler { + #[cfg(feature = "chrome-trace")] + _guard: guard, + } + } +} diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index b1076afe..1418e346 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -493,6 +493,22 @@ pub fn key_code( } } +/// Converts some [`UserAttention`] into it's `winit` counterpart. +/// +/// [`UserAttention`]: window::UserAttention +pub fn user_attention( + user_attention: window::UserAttention, +) -> winit::window::UserAttentionType { + match user_attention { + window::UserAttention::Critical => { + winit::window::UserAttentionType::Critical + } + window::UserAttention::Informational => { + winit::window::UserAttentionType::Informational + } + } +} + // As defined in: http://www.unicode.org/faq/private_use.html pub(crate) fn is_private_use_character(c: char) -> bool { matches!( diff --git a/winit/src/lib.rs b/winit/src/lib.rs index b8ed492d..06674109 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -51,6 +51,8 @@ mod proxy; #[cfg(feature = "application")] pub use application::Application; +#[cfg(feature = "trace")] +pub use application::Profiler; pub use clipboard::Clipboard; pub use error::Error; pub use position::Position; diff --git a/winit/src/window.rs b/winit/src/window.rs index f6b43a0f..89db3262 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -2,7 +2,7 @@ use crate::command::{self, Command}; use iced_native::window; -pub use window::{Event, Mode}; +pub use window::{Event, Mode, UserAttention}; /// Closes the current window and exits the application. pub fn close<Message>() -> Command<Message> { |