summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.toml7
-rw-r--r--README.md2
-rw-r--r--glow/Cargo.toml4
-rw-r--r--glow/src/image.rs6
-rw-r--r--glow/src/quad.rs6
-rw-r--r--glow/src/triangle.rs6
-rw-r--r--glutin/Cargo.toml5
-rw-r--r--glutin/src/application.rs44
-rw-r--r--graphics/src/gradient.rs4
-rw-r--r--graphics/src/image/storage.rs2
-rw-r--r--lazy/src/component.rs35
-rw-r--r--lazy/src/responsive.rs6
-rw-r--r--native/src/layout/limits.rs23
-rw-r--r--native/src/widget/checkbox.rs4
-rw-r--r--native/src/widget/helpers.rs4
-rw-r--r--native/src/widget/operation/focusable.rs4
-rw-r--r--native/src/widget/pick_list.rs97
-rw-r--r--native/src/widget/text_input.rs12
-rw-r--r--native/src/widget/toggler.rs18
-rw-r--r--native/src/window.rs2
-rw-r--r--native/src/window/action.rs43
-rw-r--r--native/src/window/user_attention.rs21
-rw-r--r--src/lib.rs2
-rw-r--r--src/widget.rs2
-rw-r--r--style/src/pick_list.rs4
-rw-r--r--style/src/theme.rs4
-rw-r--r--wgpu/Cargo.toml4
-rw-r--r--wgpu/src/backend.rs5
-rw-r--r--wgpu/src/image.rs6
-rw-r--r--wgpu/src/quad.rs9
-rw-r--r--wgpu/src/triangle.rs8
-rw-r--r--winit/Cargo.toml20
-rw-r--r--winit/src/application.rs83
-rw-r--r--winit/src/application/profiler.rs101
-rw-r--r--winit/src/conversion.rs16
-rw-r--r--winit/src/lib.rs2
-rw-r--r--winit/src/window.rs2
38 files changed, 537 insertions, 87 deletions
diff --git a/.gitignore b/.gitignore
index 56faba09..0c46184f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ pkg/
Cargo.lock
.cargo/
dist/
+traces/
diff --git a/Cargo.toml b/Cargo.toml
index 9c45b2f5..681aae5e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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" }
diff --git a/README.md b/README.md
index bc7f5440..457a02ae 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
[![Crates.io](https://img.shields.io/crates/v/iced.svg)](https://crates.io/crates/iced)
[![License](https://img.shields.io/crates/l/iced.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE)
[![Downloads](https://img.shields.io/crates/d/iced.svg)](https://crates.io/crates/iced)
-[![Test Status](https://img.shields.io/github/workflow/status/iced-rs/iced/Test?event=push&label=test)](https://github.com/iced-rs/iced/actions)
+[![Test Status](https://img.shields.io/github/actions/workflow/status/iced-rs/iced/test.yml?branch=master&event=push&label=test)](https://github.com/iced-rs/iced/actions)
[![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](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);
/// ```
///
/// ![Checkbox drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/checkbox.png?raw=true)
@@ -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,
+}
diff --git a/src/lib.rs b/src/lib.rs
index 00176827..a0e31be4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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> {