summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--core/src/vector.rs5
-rw-r--r--examples/download_progress/src/main.rs2
-rw-r--r--examples/events/src/main.rs2
-rw-r--r--examples/game_of_life/src/main.rs2
-rw-r--r--examples/game_of_life/src/preset.rs9
-rw-r--r--examples/integration_opengl/src/controls.rs2
-rw-r--r--examples/integration_opengl/src/scene.rs2
-rw-r--r--examples/integration_wgpu/src/controls.rs2
-rw-r--r--examples/integration_wgpu/src/main.rs2
-rw-r--r--examples/pokedex/src/main.rs8
-rw-r--r--examples/styling/src/main.rs2
-rw-r--r--examples/system_information/src/main.rs5
-rw-r--r--examples/toast/Cargo.toml10
-rw-r--r--examples/toast/src/main.rs670
-rw-r--r--examples/todos/src/main.rs2
-rw-r--r--examples/tour/src/main.rs8
-rw-r--r--examples/websocket/src/echo.rs2
-rw-r--r--examples/websocket/src/echo/server.rs2
-rw-r--r--futures/src/backend/native/async_std.rs1
-rw-r--r--futures/src/backend/native/tokio.rs1
-rw-r--r--futures/src/command.rs1
-rw-r--r--futures/src/subscription.rs1
-rw-r--r--glow/src/program.rs2
-rw-r--r--graphics/src/renderer.rs4
-rw-r--r--graphics/src/widget/canvas/cache.rs6
-rw-r--r--lazy/src/component.rs17
-rw-r--r--lazy/src/lazy.rs11
-rw-r--r--lazy/src/responsive.rs11
-rw-r--r--native/src/command.rs1
-rw-r--r--native/src/command/action.rs6
-rw-r--r--native/src/debug/basic.rs8
-rw-r--r--native/src/image.rs4
-rw-r--r--native/src/overlay.rs18
-rw-r--r--native/src/overlay/element.rs19
-rw-r--r--native/src/overlay/group.rs174
-rw-r--r--native/src/overlay/menu.rs5
-rw-r--r--native/src/renderer.rs4
-rw-r--r--native/src/shell.rs5
-rw-r--r--native/src/svg.rs2
-rw-r--r--native/src/user_interface.rs37
-rw-r--r--native/src/widget/action.rs12
-rw-r--r--native/src/widget/image.rs68
-rw-r--r--native/src/widget/operation.rs2
-rw-r--r--native/src/widget/pane_grid.rs13
-rw-r--r--native/src/widget/pick_list.rs131
-rw-r--r--native/src/widget/text_input.rs2
-rw-r--r--native/src/window/action.rs34
-rw-r--r--src/widget.rs4
-rw-r--r--wgpu/src/settings.rs2
-rw-r--r--winit/src/application.rs24
-rw-r--r--winit/src/icon.rs15
-rw-r--r--winit/src/profiler.rs4
-rw-r--r--winit/src/settings.rs3
-rw-r--r--winit/src/window.rs49
55 files changed, 1218 insertions, 224 deletions
diff --git a/README.md b/README.md
index b3790478..8ebd0e9c 100644
--- a/README.md
+++ b/README.md
@@ -15,10 +15,10 @@ A cross-platform GUI library for Rust focused on simplicity and type-safety.
Inspired by [Elm].
<a href="https://gfycat.com/littlesanehalicore">
- <img src="https://thumbs.gfycat.com/LittleSaneHalicore-small.gif" height="350px">
+ <img src="https://thumbs.gfycat.com/LittleSaneHalicore-small.gif" width="275px">
</a>
<a href="https://gfycat.com/politeadorableiberianmole">
- <img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif" height="350px">
+ <img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif" width="273px">
</a>
</div>
diff --git a/core/src/vector.rs b/core/src/vector.rs
index b550869c..1380c3b3 100644
--- a/core/src/vector.rs
+++ b/core/src/vector.rs
@@ -15,6 +15,11 @@ impl<T> Vector<T> {
}
}
+impl Vector {
+ /// The zero [`Vector`].
+ pub const ZERO: Self = Self::new(0.0, 0.0);
+}
+
impl<T> std::ops::Add for Vector<T>
where
T: std::ops::Add<Output = T>,
diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs
index 3ef9ef7a..001a1f8f 100644
--- a/examples/download_progress/src/main.rs
+++ b/examples/download_progress/src/main.rs
@@ -177,7 +177,7 @@ impl Download {
.into()
}
State::Downloading { .. } => {
- text(format!("Downloading... {:.2}%", current_progress)).into()
+ text(format!("Downloading... {current_progress:.2}%")).into()
}
State::Errored => column![
"Something went wrong :(",
diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs
index f519fc3d..9f5c9971 100644
--- a/examples/events/src/main.rs
+++ b/examples/events/src/main.rs
@@ -77,7 +77,7 @@ impl Application for Events {
let events = Column::with_children(
self.last
.iter()
- .map(|event| text(format!("{:?}", event)).size(40))
+ .map(|event| text(format!("{event:?}")).size(40))
.map(Element::from)
.collect(),
);
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
index b0f1c96d..ed911160 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -176,7 +176,7 @@ fn view_controls<'a>(
let speed_controls = row![
slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
- text(format!("x{}", speed)).size(16),
+ text(format!("x{speed}")).size(16),
]
.width(Length::Fill)
.align_items(Alignment::Center)
diff --git a/examples/game_of_life/src/preset.rs b/examples/game_of_life/src/preset.rs
index 964b9120..552527b1 100644
--- a/examples/game_of_life/src/preset.rs
+++ b/examples/game_of_life/src/preset.rs
@@ -1,6 +1,7 @@
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Preset {
Custom,
+ #[default]
Xkcd,
Glider,
SmallExploder,
@@ -114,12 +115,6 @@ impl Preset {
}
}
-impl Default for Preset {
- fn default() -> Preset {
- Preset::Xkcd
- }
-}
-
impl std::fmt::Display for Preset {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
diff --git a/examples/integration_opengl/src/controls.rs b/examples/integration_opengl/src/controls.rs
index 076d37d3..22c41066 100644
--- a/examples/integration_opengl/src/controls.rs
+++ b/examples/integration_opengl/src/controls.rs
@@ -90,7 +90,7 @@ impl Program for Controls {
)
.push(sliders)
.push(
- Text::new(format!("{:?}", background_color))
+ Text::new(format!("{background_color:?}"))
.size(14)
.style(Color::WHITE),
),
diff --git a/examples/integration_opengl/src/scene.rs b/examples/integration_opengl/src/scene.rs
index fc74b78a..c1d05b65 100644
--- a/examples/integration_opengl/src/scene.rs
+++ b/examples/integration_opengl/src/scene.rs
@@ -49,7 +49,7 @@ impl Scene {
.expect("Cannot create shader");
gl.shader_source(
shader,
- &format!("{}\n{}", shader_version, shader_source),
+ &format!("{shader_version}\n{shader_source}"),
);
gl.compile_shader(shader);
if !gl.get_shader_compile_status(shader) {
diff --git a/examples/integration_wgpu/src/controls.rs b/examples/integration_wgpu/src/controls.rs
index 6c41738c..92300a45 100644
--- a/examples/integration_wgpu/src/controls.rs
+++ b/examples/integration_wgpu/src/controls.rs
@@ -96,7 +96,7 @@ impl Program for Controls {
)
.push(sliders)
.push(
- Text::new(format!("{:?}", background_color))
+ Text::new(format!("{background_color:?}"))
.size(14)
.style(Color::WHITE),
)
diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration_wgpu/src/main.rs
index 219573ea..1f42013b 100644
--- a/examples/integration_wgpu/src/main.rs
+++ b/examples/integration_wgpu/src/main.rs
@@ -275,7 +275,7 @@ pub fn main() {
}
Err(error) => match error {
wgpu::SurfaceError::OutOfMemory => {
- panic!("Swapchain error: {}. Rendering cannot continue.", error)
+ panic!("Swapchain error: {error}. Rendering cannot continue.")
}
_ => {
// Try rendering again next frame.
diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs
index 4fe2d07c..748acae0 100644
--- a/examples/pokedex/src/main.rs
+++ b/examples/pokedex/src/main.rs
@@ -41,7 +41,7 @@ impl Application for Pokedex {
Pokedex::Errored { .. } => "Whoops!",
};
- format!("{} - Pokédex", subtitle)
+ format!("{subtitle} - Pokédex")
}
fn update(&mut self, message: Message) -> Command<Message> {
@@ -157,8 +157,7 @@ impl Pokemon {
};
let fetch_entry = async {
- let url =
- format!("https://pokeapi.co/api/v2/pokemon-species/{}", id);
+ let url = format!("https://pokeapi.co/api/v2/pokemon-species/{id}");
reqwest::get(&url).await?.json().await
};
@@ -187,8 +186,7 @@ impl Pokemon {
async fn fetch_image(id: u16) -> Result<image::Handle, reqwest::Error> {
let url = format!(
- "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png",
- id
+ "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{id}.png"
);
#[cfg(not(target_arch = "wasm32"))]
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index e16860ad..49bedce7 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -78,7 +78,7 @@ impl Sandbox for Styling {
column![text("Choose a theme:")].spacing(10),
|column, theme| {
column.push(radio(
- format!("{:?}", theme),
+ format!("{theme:?}"),
*theme,
Some(match self.theme {
Theme::Light => ThemeType::Light,
diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs
index 175b4387..633b6e2b 100644
--- a/examples/system_information/src/main.rs
+++ b/examples/system_information/src/main.rs
@@ -114,13 +114,12 @@ impl Application for Example {
{
let memory_readable = ByteSize::kb(memory_used).to_string();
- format!("{} kb ({})", memory_used, memory_readable)
+ format!("{memory_used} kb ({memory_readable})")
} else {
String::from("None")
};
- let memory_used =
- text(format!("Memory (used): {}", memory_text));
+ let memory_used = text(format!("Memory (used): {memory_text}"));
let graphics_adapter = text(format!(
"Graphics adapter: {}",
diff --git a/examples/toast/Cargo.toml b/examples/toast/Cargo.toml
new file mode 100644
index 00000000..f1f986aa
--- /dev/null
+++ b/examples/toast/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "toast"
+version = "0.1.0"
+authors = ["tarkah <admin@tarkah.dev>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = [] }
+iced_native = { path = "../../native" }
diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs
new file mode 100644
index 00000000..e74b3ee6
--- /dev/null
+++ b/examples/toast/src/main.rs
@@ -0,0 +1,670 @@
+use iced::widget::{
+ self, button, column, container, pick_list, row, slider, text, text_input,
+};
+use iced::{
+ executor, keyboard, subscription, Alignment, Application, Command, Element,
+ Event, Length, Settings, Subscription,
+};
+
+use toast::{Status, Toast};
+
+pub fn main() -> iced::Result {
+ App::run(Settings::default())
+}
+
+#[derive(Default)]
+struct App {
+ toasts: Vec<Toast>,
+ editing: Toast,
+ timeout_secs: u64,
+}
+
+#[derive(Debug, Clone)]
+#[allow(clippy::enum_variant_names)]
+enum Message {
+ Add,
+ Close(usize),
+ Title(String),
+ Body(String),
+ Status(Status),
+ Timeout(f64),
+ Event(Event),
+}
+
+impl Application for App {
+ type Executor = executor::Default;
+ type Message = Message;
+ type Theme = iced::Theme;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (Self, Command<Message>) {
+ (
+ App {
+ toasts: vec![Toast {
+ title: "Example Toast".into(),
+ body: "Add more toasts in the form below!".into(),
+ status: Status::Primary,
+ }],
+ timeout_secs: toast::DEFAULT_TIMEOUT,
+ ..Default::default()
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ String::from("Toast - Iced")
+ }
+
+ fn subscription(&self) -> Subscription<Self::Message> {
+ subscription::events().map(Message::Event)
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::Add => {
+ if !self.editing.title.is_empty()
+ && !self.editing.body.is_empty()
+ {
+ self.toasts.push(std::mem::take(&mut self.editing));
+ }
+ Command::none()
+ }
+ Message::Close(index) => {
+ self.toasts.remove(index);
+ Command::none()
+ }
+ Message::Title(title) => {
+ self.editing.title = title;
+ Command::none()
+ }
+ Message::Body(body) => {
+ self.editing.body = body;
+ Command::none()
+ }
+ Message::Status(status) => {
+ self.editing.status = status;
+ Command::none()
+ }
+ Message::Timeout(timeout) => {
+ self.timeout_secs = timeout as u64;
+ Command::none()
+ }
+ Message::Event(Event::Keyboard(keyboard::Event::KeyPressed {
+ key_code: keyboard::KeyCode::Tab,
+ modifiers,
+ })) if modifiers.shift() => widget::focus_previous(),
+ Message::Event(Event::Keyboard(keyboard::Event::KeyPressed {
+ key_code: keyboard::KeyCode::Tab,
+ ..
+ })) => widget::focus_next(),
+ Message::Event(_) => Command::none(),
+ }
+ }
+
+ fn view<'a>(&'a self) -> Element<'a, Message> {
+ let subtitle = |title, content: Element<'a, Message>| {
+ column![text(title).size(14), content]
+ .width(Length::Fill)
+ .spacing(5)
+ };
+
+ let mut add_toast = button("Add Toast");
+
+ if !self.editing.body.is_empty() && !self.editing.title.is_empty() {
+ add_toast = add_toast.on_press(Message::Add);
+ }
+
+ let content = container(
+ column![
+ subtitle(
+ "Title",
+ text_input("", &self.editing.title, Message::Title)
+ .on_submit(Message::Add)
+ .into()
+ ),
+ subtitle(
+ "Message",
+ text_input("", &self.editing.body, Message::Body)
+ .on_submit(Message::Add)
+ .into()
+ ),
+ subtitle(
+ "Status",
+ pick_list(
+ toast::Status::ALL,
+ Some(self.editing.status),
+ Message::Status
+ )
+ .width(Length::Fill)
+ .into()
+ ),
+ subtitle(
+ "Timeout",
+ row![
+ text(format!("{:0>2} sec", self.timeout_secs)),
+ slider(
+ 1.0..=30.0,
+ self.timeout_secs as f64,
+ Message::Timeout
+ )
+ .step(1.0)
+ .width(Length::Fill)
+ ]
+ .spacing(5)
+ .into()
+ ),
+ column![add_toast]
+ .width(Length::Fill)
+ .align_items(Alignment::End)
+ ]
+ .spacing(10)
+ .max_width(200),
+ )
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y();
+
+ toast::Manager::new(content, &self.toasts, Message::Close)
+ .timeout(self.timeout_secs)
+ .into()
+ }
+}
+
+mod toast {
+ use std::fmt;
+ use std::time::{Duration, Instant};
+
+ use iced::theme;
+ use iced::widget::{
+ button, column, container, horizontal_rule, horizontal_space, row, text,
+ };
+ use iced::{
+ Alignment, Element, Length, Point, Rectangle, Renderer, Size, Theme,
+ Vector,
+ };
+ use iced_native::widget::{tree, Operation, Tree};
+ use iced_native::{event, layout, mouse, overlay, renderer, window};
+ use iced_native::{Clipboard, Event, Layout, Shell, Widget};
+
+ pub const DEFAULT_TIMEOUT: u64 = 5;
+
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+ pub enum Status {
+ #[default]
+ Primary,
+ Secondary,
+ Success,
+ Danger,
+ }
+
+ impl Status {
+ pub const ALL: &[Self] =
+ &[Self::Primary, Self::Secondary, Self::Success, Self::Danger];
+ }
+
+ impl container::StyleSheet for Status {
+ type Style = Theme;
+
+ fn appearance(&self, theme: &Theme) -> container::Appearance {
+ let palette = theme.extended_palette();
+
+ let pair = match self {
+ Status::Primary => palette.primary.weak,
+ Status::Secondary => palette.secondary.weak,
+ Status::Success => palette.success.weak,
+ Status::Danger => palette.danger.weak,
+ };
+
+ container::Appearance {
+ background: pair.color.into(),
+ text_color: pair.text.into(),
+ ..Default::default()
+ }
+ }
+ }
+
+ impl fmt::Display for Status {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Status::Primary => "Primary",
+ Status::Secondary => "Secondary",
+ Status::Success => "Success",
+ Status::Danger => "Danger",
+ }
+ .fmt(f)
+ }
+ }
+
+ #[derive(Debug, Clone, Default)]
+ pub struct Toast {
+ pub title: String,
+ pub body: String,
+ pub status: Status,
+ }
+
+ pub struct Manager<'a, Message> {
+ content: Element<'a, Message>,
+ toasts: Vec<Element<'a, Message>>,
+ timeout_secs: u64,
+ on_close: Box<dyn Fn(usize) -> Message + 'a>,
+ }
+
+ impl<'a, Message> Manager<'a, Message>
+ where
+ Message: 'a + Clone,
+ {
+ pub fn new(
+ content: impl Into<Element<'a, Message>>,
+ toasts: &'a [Toast],
+ on_close: impl Fn(usize) -> Message + 'a,
+ ) -> Self {
+ let toasts = toasts
+ .iter()
+ .enumerate()
+ .map(|(index, toast)| {
+ container(column![
+ container(
+ row![
+ text(toast.title.as_str()),
+ horizontal_space(Length::Fill),
+ button("X")
+ .on_press((on_close)(index))
+ .padding(3),
+ ]
+ .align_items(Alignment::Center)
+ )
+ .width(Length::Fill)
+ .padding(5)
+ .style(
+ theme::Container::Custom(Box::new(toast.status))
+ ),
+ horizontal_rule(1),
+ container(text(toast.body.as_str()))
+ .width(Length::Fill)
+ .padding(5)
+ .style(theme::Container::Box),
+ ])
+ .max_width(200)
+ .into()
+ })
+ .collect();
+
+ Self {
+ content: content.into(),
+ toasts,
+ timeout_secs: DEFAULT_TIMEOUT,
+ on_close: Box::new(on_close),
+ }
+ }
+
+ pub fn timeout(self, seconds: u64) -> Self {
+ Self {
+ timeout_secs: seconds,
+ ..self
+ }
+ }
+ }
+
+ impl<'a, Message> Widget<Message, Renderer> for Manager<'a, Message> {
+ fn width(&self) -> Length {
+ self.content.as_widget().width()
+ }
+
+ fn height(&self) -> Length {
+ self.content.as_widget().height()
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ self.content.as_widget().layout(renderer, limits)
+ }
+
+ fn tag(&self) -> tree::Tag {
+ struct Marker(Vec<Instant>);
+ iced_native::widget::tree::Tag::of::<Marker>()
+ }
+
+ fn state(&self) -> tree::State {
+ iced_native::widget::tree::State::new(Vec::<Option<Instant>>::new())
+ }
+
+ fn children(&self) -> Vec<Tree> {
+ std::iter::once(Tree::new(&self.content))
+ .chain(self.toasts.iter().map(Tree::new))
+ .collect()
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ let instants = tree.state.downcast_mut::<Vec<Option<Instant>>>();
+
+ // Invalidating removed instants to None allows us to remove
+ // them here so that diffing for removed / new toast instants
+ // is accurate
+ instants.retain(Option::is_some);
+
+ match (instants.len(), self.toasts.len()) {
+ (old, new) if old > new => {
+ instants.truncate(new);
+ }
+ (old, new) if old < new => {
+ instants.extend(
+ std::iter::repeat(Some(Instant::now())).take(new - old),
+ );
+ }
+ _ => {}
+ }
+
+ tree.diff_children(
+ &std::iter::once(&self.content)
+ .chain(self.toasts.iter())
+ .collect::<Vec<_>>(),
+ );
+ }
+
+ fn operate(
+ &self,
+ state: &mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn Operation<Message>,
+ ) {
+ operation.container(None, &mut |operation| {
+ self.content.as_widget().operate(
+ &mut state.children[0],
+ layout,
+ renderer,
+ operation,
+ );
+ });
+ }
+
+ fn on_event(
+ &mut self,
+ state: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ self.content.as_widget_mut().on_event(
+ &mut state.children[0],
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ )
+ }
+
+ fn draw(
+ &self,
+ state: &Tree,
+ renderer: &mut Renderer,
+ theme: &Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ self.content.as_widget().draw(
+ &state.children[0],
+ renderer,
+ theme,
+ style,
+ layout,
+ cursor_position,
+ viewport,
+ );
+ }
+
+ fn mouse_interaction(
+ &self,
+ state: &Tree,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.content.as_widget().mouse_interaction(
+ &state.children[0],
+ layout,
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ }
+
+ fn overlay<'b>(
+ &'b mut self,
+ state: &'b mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ let instants = state.state.downcast_mut::<Vec<Option<Instant>>>();
+
+ let (content_state, toasts_state) = state.children.split_at_mut(1);
+
+ let content = self.content.as_widget_mut().overlay(
+ &mut content_state[0],
+ layout,
+ renderer,
+ );
+
+ let toasts = (!self.toasts.is_empty()).then(|| {
+ overlay::Element::new(
+ layout.bounds().position(),
+ Box::new(Overlay {
+ toasts: &mut self.toasts,
+ state: toasts_state,
+ instants,
+ on_close: &self.on_close,
+ timeout_secs: self.timeout_secs,
+ }),
+ )
+ });
+ let overlays =
+ content.into_iter().chain(toasts).collect::<Vec<_>>();
+
+ (!overlays.is_empty())
+ .then(|| overlay::Group::with_children(overlays).overlay())
+ }
+ }
+
+ struct Overlay<'a, 'b, Message> {
+ toasts: &'b mut [Element<'a, Message>],
+ state: &'b mut [Tree],
+ instants: &'b mut [Option<Instant>],
+ on_close: &'b dyn Fn(usize) -> Message,
+ timeout_secs: u64,
+ }
+
+ impl<'a, 'b, Message> overlay::Overlay<Message, Renderer>
+ for Overlay<'a, 'b, Message>
+ {
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ bounds: Size,
+ position: Point,
+ ) -> layout::Node {
+ let limits = layout::Limits::new(Size::ZERO, bounds)
+ .width(Length::Fill)
+ .height(Length::Fill);
+
+ layout::flex::resolve(
+ layout::flex::Axis::Vertical,
+ renderer,
+ &limits,
+ 10.into(),
+ 10.0,
+ Alignment::End,
+ self.toasts,
+ )
+ .translate(Vector::new(position.x, position.y))
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ if let Event::Window(window::Event::RedrawRequested(now)) = &event {
+ let mut next_redraw: Option<window::RedrawRequest> = None;
+
+ self.instants.iter_mut().enumerate().for_each(
+ |(index, maybe_instant)| {
+ if let Some(instant) = maybe_instant.as_mut() {
+ let remaining =
+ Duration::from_secs(self.timeout_secs)
+ .saturating_sub(instant.elapsed());
+
+ if remaining == Duration::ZERO {
+ maybe_instant.take();
+ shell.publish((self.on_close)(index));
+ next_redraw =
+ Some(window::RedrawRequest::NextFrame);
+ } else {
+ let redraw_at =
+ window::RedrawRequest::At(*now + remaining);
+ next_redraw = next_redraw
+ .map(|redraw| redraw.min(redraw_at))
+ .or(Some(redraw_at));
+ }
+ }
+ },
+ );
+
+ if let Some(redraw) = next_redraw {
+ shell.request_redraw(redraw);
+ }
+ }
+
+ self.toasts
+ .iter_mut()
+ .zip(self.state.iter_mut())
+ .zip(layout.children())
+ .zip(self.instants.iter_mut())
+ .map(|(((child, state), layout), instant)| {
+ let mut local_messages = vec![];
+ let mut local_shell = Shell::new(&mut local_messages);
+
+ let status = child.as_widget_mut().on_event(
+ state,
+ event.clone(),
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ &mut local_shell,
+ );
+
+ if !local_shell.is_empty() {
+ instant.take();
+ }
+
+ shell.merge(local_shell, std::convert::identity);
+
+ status
+ })
+ .fold(event::Status::Ignored, event::Status::merge)
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ theme: &<Renderer as iced_native::Renderer>::Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) {
+ let viewport = layout.bounds();
+
+ for ((child, state), layout) in self
+ .toasts
+ .iter()
+ .zip(self.state.iter())
+ .zip(layout.children())
+ {
+ child.as_widget().draw(
+ state,
+ renderer,
+ theme,
+ style,
+ layout,
+ cursor_position,
+ &viewport,
+ );
+ }
+ }
+
+ fn operate(
+ &mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn iced_native::widget::Operation<Message>,
+ ) {
+ operation.container(None, &mut |operation| {
+ self.toasts
+ .iter()
+ .zip(self.state.iter_mut())
+ .zip(layout.children())
+ .for_each(|((child, state), layout)| {
+ child
+ .as_widget()
+ .operate(state, layout, renderer, operation);
+ })
+ });
+ }
+
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.toasts
+ .iter()
+ .zip(self.state.iter())
+ .zip(layout.children())
+ .map(|((child, state), layout)| {
+ child.as_widget().mouse_interaction(
+ state,
+ layout,
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ })
+ .max()
+ .unwrap_or_default()
+ }
+
+ fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ layout
+ .children()
+ .any(|layout| layout.bounds().contains(cursor_position))
+ }
+ }
+
+ impl<'a, Message> From<Manager<'a, Message>> for Element<'a, Message>
+ where
+ Message: 'a,
+ {
+ fn from(manager: Manager<'a, Message>) -> Self {
+ Element::new(manager)
+ }
+ }
+}
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 690d9c09..04411ed7 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -303,7 +303,7 @@ pub enum TaskMessage {
impl Task {
fn text_input_id(i: usize) -> text_input::Id {
- text_input::Id::new(format!("task-{}", i))
+ text_input::Id::new(format!("task-{i}"))
}
fn new(description: String) -> Self {
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index 378508e1..5ee65562 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -388,7 +388,7 @@ impl<'a> Step {
let spacing_section = column![
slider(0..=80, spacing, StepMessage::SpacingChanged),
- text(format!("{} px", spacing))
+ text(format!("{spacing} px"))
.width(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center),
]
@@ -412,7 +412,7 @@ impl<'a> Step {
fn text(size: u16, color: Color) -> Column<'a, StepMessage> {
let size_section = column![
"You can change its size:",
- text(format!("This text is {} pixels", size)).size(size),
+ text(format!("This text is {size} pixels")).size(size),
slider(10..=70, size, StepMessage::TextSizeChanged),
]
.padding(20)
@@ -427,7 +427,7 @@ impl<'a> Step {
let color_section = column![
"And its color:",
- text(format!("{:?}", color)).style(color),
+ text(format!("{color:?}")).style(color),
color_sliders,
]
.padding(20)
@@ -497,7 +497,7 @@ impl<'a> Step {
.push(ferris(width))
.push(slider(100..=500, width, StepMessage::ImageWidthChanged))
.push(
- text(format!("Width: {} px", width))
+ text(format!("Width: {width} px"))
.width(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center),
)
diff --git a/examples/websocket/src/echo.rs b/examples/websocket/src/echo.rs
index ae65e064..e74768a6 100644
--- a/examples/websocket/src/echo.rs
+++ b/examples/websocket/src/echo.rs
@@ -141,7 +141,7 @@ impl fmt::Display for Message {
Message::Disconnected => {
write!(f, "Connection lost... Retrying...")
}
- Message::User(message) => write!(f, "{}", message),
+ Message::User(message) => write!(f, "{message}"),
}
}
}
diff --git a/examples/websocket/src/echo/server.rs b/examples/websocket/src/echo/server.rs
index fef89a12..dd234984 100644
--- a/examples/websocket/src/echo/server.rs
+++ b/examples/websocket/src/echo/server.rs
@@ -41,7 +41,7 @@ async fn user_connected(ws: WebSocket) {
tokio::task::spawn(async move {
while let Some(message) = rx.next().await {
user_ws_tx.send(message).await.unwrap_or_else(|e| {
- eprintln!("websocket send error: {}", e);
+ eprintln!("websocket send error: {e}");
});
}
});
diff --git a/futures/src/backend/native/async_std.rs b/futures/src/backend/native/async_std.rs
index e8641626..b324dbf1 100644
--- a/futures/src/backend/native/async_std.rs
+++ b/futures/src/backend/native/async_std.rs
@@ -10,6 +10,7 @@ impl crate::Executor for Executor {
Ok(Self)
}
+ #[allow(clippy::let_underscore_future)]
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
let _ = async_std::task::spawn(future);
}
diff --git a/futures/src/backend/native/tokio.rs b/futures/src/backend/native/tokio.rs
index f86b0ea3..dd818bd1 100644
--- a/futures/src/backend/native/tokio.rs
+++ b/futures/src/backend/native/tokio.rs
@@ -9,6 +9,7 @@ impl crate::Executor for Executor {
tokio::runtime::Runtime::new()
}
+ #[allow(clippy::let_underscore_future)]
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
let _ = tokio::runtime::Runtime::spawn(self, future);
}
diff --git a/futures/src/command.rs b/futures/src/command.rs
index 05c3a1d0..3d1ec3f9 100644
--- a/futures/src/command.rs
+++ b/futures/src/command.rs
@@ -1,4 +1,5 @@
/// A set of asynchronous actions to be performed by some runtime.
+#[must_use = "`Command` must be returned to runtime to take effect"]
#[derive(Debug)]
pub struct Command<T>(Internal<T>);
diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs
index e96fa704..d18ec4f7 100644
--- a/futures/src/subscription.rs
+++ b/futures/src/subscription.rs
@@ -20,6 +20,7 @@ use crate::BoxStream;
/// `Hasher`.
///
/// [`Command`]: crate::Command
+#[must_use = "`Subscription` must be returned to runtime to take effect"]
pub struct Subscription<Hasher, Event, Output> {
recipes: Vec<Box<dyn Recipe<Hasher, Event, Output = Output>>>,
}
diff --git a/glow/src/program.rs b/glow/src/program.rs
index 1eb9c535..e2155222 100644
--- a/glow/src/program.rs
+++ b/glow/src/program.rs
@@ -54,7 +54,7 @@ impl Version {
String::from("#version 120\n#define in varying"),
),
// OpenGL 1.1+
- _ => panic!("Incompatible context version: {:?}", version),
+ _ => panic!("Incompatible context version: {version:?}"),
};
log::info!("Shader directive: {}", vertex.lines().next().unwrap());
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index aabdf7fc..298cf4a1 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -53,9 +53,9 @@ where
{
type Theme = T;
- fn layout<'a, Message>(
+ fn layout<Message>(
&mut self,
- element: &Element<'a, Message, Self>,
+ element: &Element<'_, Message, Self>,
limits: &layout::Limits,
) -> layout::Node {
let layout = element.as_widget().layout(self, limits);
diff --git a/graphics/src/widget/canvas/cache.rs b/graphics/src/widget/canvas/cache.rs
index 49873ac9..52217bbb 100644
--- a/graphics/src/widget/canvas/cache.rs
+++ b/graphics/src/widget/canvas/cache.rs
@@ -49,7 +49,11 @@ impl Cache {
/// Otherwise, the previously stored [`Geometry`] will be returned. The
/// [`Cache`] is not cleared in this case. In other words, it will keep
/// returning the stored [`Geometry`] if needed.
- pub fn draw(&self, bounds: Size, draw_fn: impl Fn(&mut Frame)) -> Geometry {
+ pub fn draw(
+ &self,
+ bounds: Size,
+ draw_fn: impl FnOnce(&mut Frame),
+ ) -> Geometry {
use std::ops::Deref;
if let State::Filled {
diff --git a/lazy/src/component.rs b/lazy/src/component.rs
index d8f21f8a..b23da9f7 100644
--- a/lazy/src/component.rs
+++ b/lazy/src/component.rs
@@ -227,6 +227,10 @@ where
local_shell.revalidate_layout(|| shell.invalidate_layout());
+ if let Some(redraw_request) = local_shell.redraw_request() {
+ shell.request_redraw(redraw_request);
+ }
+
if !local_messages.is_empty() {
let mut heads = self.state.take().unwrap().into_heads();
@@ -307,6 +311,8 @@ where
}
self.with_element(|element| {
+ tree.diff_children(std::slice::from_ref(&element));
+
element.as_widget().operate(
&mut tree.children[0],
layout,
@@ -451,9 +457,9 @@ where
position: Point,
) -> layout::Node {
self.with_overlay_maybe(|overlay| {
- let vector = position - overlay.position();
+ let translation = position - overlay.position();
- overlay.layout(renderer, bounds).translate(vector)
+ overlay.layout(renderer, bounds, translation)
})
.unwrap_or_default()
}
@@ -559,4 +565,11 @@ where
event_status
}
+
+ fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ self.with_overlay_maybe(|overlay| {
+ overlay.is_over(layout, cursor_position)
+ })
+ .unwrap_or_default()
+ }
}
diff --git a/lazy/src/lazy.rs b/lazy/src/lazy.rs
index 933def96..5e909a49 100644
--- a/lazy/src/lazy.rs
+++ b/lazy/src/lazy.rs
@@ -313,9 +313,9 @@ where
position: Point,
) -> layout::Node {
self.with_overlay_maybe(|overlay| {
- let vector = position - overlay.position();
+ let translation = position - overlay.position();
- overlay.layout(renderer, bounds).translate(vector)
+ overlay.layout(renderer, bounds, translation)
})
.unwrap_or_default()
}
@@ -372,6 +372,13 @@ where
})
.unwrap_or(iced_native::event::Status::Ignored)
}
+
+ fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ self.with_overlay_maybe(|overlay| {
+ overlay.is_over(layout, cursor_position)
+ })
+ .unwrap_or_default()
+ }
}
impl<'a, Message, Renderer, Dependency, View>
diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs
index 52badda2..93069493 100644
--- a/lazy/src/responsive.rs
+++ b/lazy/src/responsive.rs
@@ -356,9 +356,9 @@ where
position: Point,
) -> layout::Node {
self.with_overlay_maybe(|overlay| {
- let vector = position - overlay.position();
+ let translation = position - overlay.position();
- overlay.layout(renderer, bounds).translate(vector)
+ overlay.layout(renderer, bounds, translation)
})
.unwrap_or_default()
}
@@ -415,4 +415,11 @@ where
})
.unwrap_or(iced_native::event::Status::Ignored)
}
+
+ fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ self.with_overlay_maybe(|overlay| {
+ overlay.is_over(layout, cursor_position)
+ })
+ .unwrap_or_default()
+ }
}
diff --git a/native/src/command.rs b/native/src/command.rs
index 89ee7375..ca9d0b64 100644
--- a/native/src/command.rs
+++ b/native/src/command.rs
@@ -11,6 +11,7 @@ use std::fmt;
use std::future::Future;
/// A set of asynchronous actions to be performed by some runtime.
+#[must_use = "`Command` must be returned to runtime to take effect"]
pub struct Command<T>(iced_futures::Command<Action<T>>);
impl<T> Command<T> {
diff --git a/native/src/command/action.rs b/native/src/command/action.rs
index 924f95e6..dcb79672 100644
--- a/native/src/command/action.rs
+++ b/native/src/command/action.rs
@@ -58,12 +58,12 @@ impl<T> fmt::Debug for Action<T> {
match self {
Self::Future(_) => write!(f, "Action::Future"),
Self::Clipboard(action) => {
- write!(f, "Action::Clipboard({:?})", action)
+ write!(f, "Action::Clipboard({action:?})")
}
Self::Window(id, action) => {
- write!(f, "Action::Window({:?}, {:?})", id, action)
+ write!(f, "Action::Window({id:?}, {action:?})")
}
- Self::System(action) => write!(f, "Action::System({:?})", action),
+ Self::System(action) => write!(f, "Action::System({action:?})"),
Self::Widget(_action) => write!(f, "Action::Widget"),
}
}
diff --git a/native/src/debug/basic.rs b/native/src/debug/basic.rs
index 603f2fd5..92f614da 100644
--- a/native/src/debug/basic.rs
+++ b/native/src/debug/basic.rs
@@ -133,7 +133,7 @@ impl Debug {
}
pub fn log_message<Message: std::fmt::Debug>(&mut self, message: &Message) {
- self.last_messages.push_back(format!("{:?}", message));
+ self.last_messages.push_back(format!("{message:?}"));
if self.last_messages.len() > 10 {
let _ = self.last_messages.pop_front();
@@ -150,7 +150,7 @@ impl Debug {
let mut lines = Vec::new();
fn key_value<T: std::fmt::Debug>(key: &str, value: T) -> String {
- format!("{} {:?}", key, value)
+ format!("{key} {value:?}")
}
lines.push(format!(
@@ -176,9 +176,9 @@ impl Debug {
lines.push(String::from("Last messages:"));
lines.extend(self.last_messages.iter().map(|msg| {
if msg.len() <= 100 {
- format!(" {}", msg)
+ format!(" {msg}")
} else {
- format!(" {:.100}...", msg)
+ format!(" {msg:.100}...")
}
}));
diff --git a/native/src/image.rs b/native/src/image.rs
index 06fd7ae6..5d2843c9 100644
--- a/native/src/image.rs
+++ b/native/src/image.rs
@@ -107,10 +107,10 @@ pub enum Data {
impl std::fmt::Debug for Data {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- Data::Path(path) => write!(f, "Path({:?})", path),
+ Data::Path(path) => write!(f, "Path({path:?})"),
Data::Bytes(_) => write!(f, "Bytes(...)"),
Data::Rgba { width, height, .. } => {
- write!(f, "Pixels({} * {})", width, height)
+ write!(f, "Pixels({width} * {height})")
}
}
}
diff --git a/native/src/overlay.rs b/native/src/overlay.rs
index 22f8b6ec..6cada416 100644
--- a/native/src/overlay.rs
+++ b/native/src/overlay.rs
@@ -1,9 +1,11 @@
//! Display interactive elements on top of other widgets.
mod element;
+mod group;
pub mod menu;
pub use element::Element;
+pub use group::Group;
pub use menu::Menu;
use crate::event::{self, Event};
@@ -87,9 +89,17 @@ where
) -> mouse::Interaction {
mouse::Interaction::Idle
}
+
+ /// Returns true if the cursor is over the [`Overlay`].
+ ///
+ /// By default, it returns true if the bounds of the `layout` contain
+ /// the `cursor_position`.
+ fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ layout.bounds().contains(cursor_position)
+ }
}
-/// Obtains the first overlay [`Element`] found in the given children.
+/// Returns a [`Group`] of overlay [`Element`] children.
///
/// This method will generally only be used by advanced users that are
/// implementing the [`Widget`](crate::Widget) trait.
@@ -102,12 +112,14 @@ pub fn from_children<'a, Message, Renderer>(
where
Renderer: crate::Renderer,
{
- children
+ let children = children
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.filter_map(|((child, state), layout)| {
child.as_widget_mut().overlay(state, layout, renderer)
})
- .next()
+ .collect::<Vec<_>>();
+
+ (!children.is_empty()).then(|| Group::with_children(children).overlay())
}
diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs
index 41a8a597..237d25d1 100644
--- a/native/src/overlay/element.rs
+++ b/native/src/overlay/element.rs
@@ -53,8 +53,14 @@ where
}
/// Computes the layout of the [`Element`] in the given bounds.
- pub fn layout(&self, renderer: &Renderer, bounds: Size) -> layout::Node {
- self.overlay.layout(renderer, bounds, self.position)
+ pub fn layout(
+ &self,
+ renderer: &Renderer,
+ bounds: Size,
+ translation: Vector,
+ ) -> layout::Node {
+ self.overlay
+ .layout(renderer, bounds, self.position + translation)
}
/// Processes a runtime [`Event`].
@@ -115,6 +121,11 @@ where
) {
self.overlay.operate(layout, renderer, operation);
}
+
+ /// Returns true if the cursor is over the [`Element`].
+ pub fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ self.overlay.is_over(layout, cursor_position)
+ }
}
struct Map<'a, A, B, Renderer> {
@@ -252,4 +263,8 @@ where
self.content
.draw(renderer, theme, style, layout, cursor_position)
}
+
+ fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ self.content.is_over(layout, cursor_position)
+ }
}
diff --git a/native/src/overlay/group.rs b/native/src/overlay/group.rs
new file mode 100644
index 00000000..1126f0cf
--- /dev/null
+++ b/native/src/overlay/group.rs
@@ -0,0 +1,174 @@
+use iced_core::{Point, Rectangle, Size};
+
+use crate::event;
+use crate::layout;
+use crate::mouse;
+use crate::overlay;
+use crate::renderer;
+use crate::widget;
+use crate::{Clipboard, Event, Layout, Overlay, Shell};
+
+/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
+/// children.
+#[allow(missing_debug_implementations)]
+pub struct Group<'a, Message, Renderer> {
+ children: Vec<overlay::Element<'a, Message, Renderer>>,
+}
+
+impl<'a, Message, Renderer> Group<'a, Message, Renderer>
+where
+ Renderer: 'a + crate::Renderer,
+ Message: 'a,
+{
+ /// Creates an empty [`Group`].
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Creates a [`Group`] with the given elements.
+ pub fn with_children(
+ children: Vec<overlay::Element<'a, Message, Renderer>>,
+ ) -> Self {
+ Group { children }
+ }
+
+ /// Adds an [`overlay::Element`] to the [`Group`].
+ pub fn push(
+ mut self,
+ child: impl Into<overlay::Element<'a, Message, Renderer>>,
+ ) -> Self {
+ self.children.push(child.into());
+ self
+ }
+
+ /// Turns the [`Group`] into an overlay [`overlay::Element`].
+ pub fn overlay(self) -> overlay::Element<'a, Message, Renderer> {
+ overlay::Element::new(Point::ORIGIN, Box::new(self))
+ }
+}
+
+impl<'a, Message, Renderer> Default for Group<'a, Message, Renderer>
+where
+ Renderer: 'a + crate::Renderer,
+ Message: 'a,
+{
+ fn default() -> Self {
+ Self::with_children(Vec::new())
+ }
+}
+
+impl<'a, Message, Renderer> Overlay<Message, Renderer>
+ for Group<'a, Message, Renderer>
+where
+ Renderer: crate::Renderer,
+{
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ bounds: Size,
+ position: Point,
+ ) -> layout::Node {
+ let translation = position - Point::ORIGIN;
+
+ layout::Node::with_children(
+ bounds,
+ self.children
+ .iter()
+ .map(|child| child.layout(renderer, bounds, translation))
+ .collect(),
+ )
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ self.children
+ .iter_mut()
+ .zip(layout.children())
+ .map(|(child, layout)| {
+ child.on_event(
+ event.clone(),
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ )
+ })
+ .fold(event::Status::Ignored, event::Status::merge)
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ theme: &<Renderer as crate::Renderer>::Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) {
+ for (child, layout) in self.children.iter().zip(layout.children()) {
+ child.draw(renderer, theme, style, layout, cursor_position);
+ }
+ }
+
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.children
+ .iter()
+ .zip(layout.children())
+ .map(|(child, layout)| {
+ child.mouse_interaction(
+ layout,
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ })
+ .max()
+ .unwrap_or_default()
+ }
+
+ fn operate(
+ &mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn widget::Operation<Message>,
+ ) {
+ operation.container(None, &mut |operation| {
+ self.children.iter_mut().zip(layout.children()).for_each(
+ |(child, layout)| {
+ child.operate(layout, renderer, operation);
+ },
+ )
+ });
+ }
+
+ fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ self.children
+ .iter()
+ .zip(layout.children())
+ .any(|(child, layout)| child.is_over(layout, cursor_position))
+ }
+}
+
+impl<'a, Message, Renderer> From<Group<'a, Message, Renderer>>
+ for overlay::Element<'a, Message, Renderer>
+where
+ Renderer: 'a + crate::Renderer,
+ Message: 'a,
+{
+ fn from(group: Group<'a, Message, Renderer>) -> Self {
+ group.overlay()
+ }
+}
diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs
index 099b1a97..9e37380f 100644
--- a/native/src/overlay/menu.rs
+++ b/native/src/overlay/menu.rs
@@ -281,10 +281,7 @@ where
renderer.fill_quad(
renderer::Quad {
- bounds: Rectangle {
- width: bounds.width - 1.0,
- ..bounds
- },
+ bounds,
border_color: appearance.border_color,
border_width: appearance.border_width,
border_radius: appearance.border_radius.into(),
diff --git a/native/src/renderer.rs b/native/src/renderer.rs
index d5329acd..2ac78982 100644
--- a/native/src/renderer.rs
+++ b/native/src/renderer.rs
@@ -16,9 +16,9 @@ pub trait Renderer: Sized {
///
/// You should override this if you need to perform any operations before or
/// after layouting. For instance, trimming the measurements cache.
- fn layout<'a, Message>(
+ fn layout<Message>(
&mut self,
- element: &Element<'a, Message, Self>,
+ element: &Element<'_, Message, Self>,
limits: &layout::Limits,
) -> layout::Node {
element.as_widget().layout(self, limits)
diff --git a/native/src/shell.rs b/native/src/shell.rs
index f1ddb48e..74a5c616 100644
--- a/native/src/shell.rs
+++ b/native/src/shell.rs
@@ -25,6 +25,11 @@ impl<'a, Message> Shell<'a, Message> {
}
}
+ /// Returns true if the [`Shell`] contains no published messages
+ pub fn is_empty(&self) -> bool {
+ self.messages.is_empty()
+ }
+
/// Publish the given `Message` for an application to process it.
pub fn publish(&mut self, message: Message) {
self.messages.push(message);
diff --git a/native/src/svg.rs b/native/src/svg.rs
index 2168e409..9b98877a 100644
--- a/native/src/svg.rs
+++ b/native/src/svg.rs
@@ -71,7 +71,7 @@ pub enum Data {
impl std::fmt::Debug for Data {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- Data::Path(path) => write!(f, "Path({:?})", path),
+ Data::Path(path) => write!(f, "Path({path:?})"),
Data::Bytes(_) => write!(f, "Bytes(...)"),
}
}
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index 29cc3472..2358bff1 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -6,7 +6,9 @@ use crate::mouse;
use crate::renderer;
use crate::widget;
use crate::window;
-use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
+use crate::{
+ Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector,
+};
/// A set of interactive graphical elements with a specific [`Layout`].
///
@@ -203,7 +205,7 @@ where
let bounds = self.bounds;
let mut overlay = manual_overlay.as_mut().unwrap();
- let mut layout = overlay.layout(renderer, bounds);
+ let mut layout = overlay.layout(renderer, bounds, Vector::ZERO);
let mut event_statuses = Vec::new();
for event in events.iter().cloned() {
@@ -252,7 +254,7 @@ where
overlay = manual_overlay.as_mut().unwrap();
shell.revalidate_layout(|| {
- layout = overlay.layout(renderer, bounds);
+ layout = overlay.layout(renderer, bounds, Vector::ZERO);
});
}
@@ -261,12 +263,16 @@ where
}
}
- let base_cursor = if layout.bounds().contains(cursor_position) {
- // TODO: Type-safe cursor availability
- Point::new(-1.0, -1.0)
- } else {
- cursor_position
- };
+ let base_cursor = manual_overlay
+ .as_ref()
+ .filter(|overlay| {
+ overlay.is_over(Layout::new(&layout), cursor_position)
+ })
+ .map(|_| {
+ // TODO: Type-safe cursor availability
+ Point::new(-1.0, -1.0)
+ })
+ .unwrap_or(cursor_position);
self.overlay = Some(layout);
@@ -430,10 +436,9 @@ where
.as_widget_mut()
.overlay(&mut self.state, Layout::new(&self.base), renderer)
{
- let overlay_layout = self
- .overlay
- .take()
- .unwrap_or_else(|| overlay.layout(renderer, self.bounds));
+ let overlay_layout = self.overlay.take().unwrap_or_else(|| {
+ overlay.layout(renderer, self.bounds, Vector::ZERO)
+ });
let new_cursor_position =
if overlay_layout.bounds().contains(cursor_position) {
@@ -504,7 +509,8 @@ where
);
});
- if overlay_bounds.contains(cursor_position) {
+ if overlay.is_over(Layout::new(layout), cursor_position)
+ {
overlay_interaction
} else {
base_interaction
@@ -533,7 +539,8 @@ where
renderer,
) {
if self.overlay.is_none() {
- self.overlay = Some(overlay.layout(renderer, self.bounds));
+ self.overlay =
+ Some(overlay.layout(renderer, self.bounds, Vector::ZERO));
}
overlay.operate(
diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs
index 1e21ff38..3f1b6b6c 100644
--- a/native/src/widget/action.rs
+++ b/native/src/widget/action.rs
@@ -1,4 +1,6 @@
-use crate::widget::operation::{self, Focusable, Operation, Scrollable};
+use crate::widget::operation::{
+ self, Focusable, Operation, Scrollable, TextInput,
+};
use crate::widget::Id;
use iced_futures::MaybeSend;
@@ -86,6 +88,14 @@ where
self.operation.focusable(state, id);
}
+ fn text_input(
+ &mut self,
+ state: &mut dyn TextInput,
+ id: Option<&Id>,
+ ) {
+ self.operation.text_input(state, id);
+ }
+
fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
self.operation.custom(state, id);
}
diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs
index 8bd8ca1e..3ff06a76 100644
--- a/native/src/widget/image.rs
+++ b/native/src/widget/image.rs
@@ -111,6 +111,45 @@ where
layout::Node::new(final_size)
}
+/// Draws an [`Image`]
+pub fn draw<Renderer, Handle>(
+ renderer: &mut Renderer,
+ layout: Layout<'_>,
+ handle: &Handle,
+ content_fit: ContentFit,
+) where
+ Renderer: image::Renderer<Handle = Handle>,
+ Handle: Clone + Hash,
+{
+ let Size { width, height } = renderer.dimensions(handle);
+ let image_size = Size::new(width as f32, height as f32);
+
+ let bounds = layout.bounds();
+ let adjusted_fit = content_fit.fit(image_size, bounds.size());
+
+ let render = |renderer: &mut Renderer| {
+ let offset = Vector::new(
+ (bounds.width - adjusted_fit.width).max(0.0) / 2.0,
+ (bounds.height - adjusted_fit.height).max(0.0) / 2.0,
+ );
+
+ let drawing_bounds = Rectangle {
+ width: adjusted_fit.width,
+ height: adjusted_fit.height,
+ ..bounds
+ };
+
+ renderer.draw(handle.clone(), drawing_bounds + offset)
+ };
+
+ if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height
+ {
+ renderer.with_layer(bounds, render);
+ } else {
+ render(renderer)
+ }
+}
+
impl<Message, Renderer, Handle> Widget<Message, Renderer> for Image<Handle>
where
Renderer: image::Renderer<Handle = Handle>,
@@ -149,34 +188,7 @@ where
_cursor_position: Point,
_viewport: &Rectangle,
) {
- let Size { width, height } = renderer.dimensions(&self.handle);
- let image_size = Size::new(width as f32, height as f32);
-
- let bounds = layout.bounds();
- let adjusted_fit = self.content_fit.fit(image_size, bounds.size());
-
- let render = |renderer: &mut Renderer| {
- let offset = Vector::new(
- (bounds.width - adjusted_fit.width).max(0.0) / 2.0,
- (bounds.height - adjusted_fit.height).max(0.0) / 2.0,
- );
-
- let drawing_bounds = Rectangle {
- width: adjusted_fit.width,
- height: adjusted_fit.height,
- ..bounds
- };
-
- renderer.draw(self.handle.clone(), drawing_bounds + offset)
- };
-
- if adjusted_fit.width > bounds.width
- || adjusted_fit.height > bounds.height
- {
- renderer.with_layer(bounds, render);
- } else {
- render(renderer)
- }
+ draw(renderer, layout, &self.handle, self.content_fit)
}
}
diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs
index 73e6a6b0..53688a21 100644
--- a/native/src/widget/operation.rs
+++ b/native/src/widget/operation.rs
@@ -62,7 +62,7 @@ where
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "Outcome::None"),
- Self::Some(output) => write!(f, "Outcome::Some({:?})", output),
+ Self::Some(output) => write!(f, "Outcome::Some({output:?})"),
Self::Chain(_) => write!(f, "Outcome::Chain(...)"),
}
}
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index 8dbd1825..eb04c0ba 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -35,7 +35,7 @@ pub use iced_style::pane_grid::{Line, StyleSheet};
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
-use crate::overlay;
+use crate::overlay::{self, Group};
use crate::renderer;
use crate::touch;
use crate::widget;
@@ -450,14 +450,17 @@ where
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
- self.contents
+ let children = self
+ .contents
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
- .filter_map(|(((_, pane), tree), layout)| {
- pane.overlay(tree, layout, renderer)
+ .filter_map(|(((_, content), state), layout)| {
+ content.overlay(state, layout, renderer)
})
- .next()
+ .collect::<Vec<_>>();
+
+ (!children.is_empty()).then(|| Group::with_children(children).overlay())
}
}
diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs
index c2853314..b1cdfad4 100644
--- a/native/src/widget/pick_list.rs
+++ b/native/src/widget/pick_list.rs
@@ -20,60 +20,6 @@ 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>
@@ -90,7 +36,7 @@ where
padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
- handle: Handle<Renderer>,
+ handle: Handle<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@@ -161,7 +107,7 @@ where
}
/// Sets the [`Handle`] of the [`PickList`].
- pub fn handle(mut self, handle: Handle<Renderer>) -> Self {
+ pub fn handle(mut self, handle: Handle<Renderer::Font>) -> Self {
self.handle = handle;
self
}
@@ -258,7 +204,7 @@ where
fn draw(
&self,
- _tree: &Tree,
+ tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
@@ -278,6 +224,7 @@ where
self.selected.as_ref(),
&self.handle,
&self.style,
+ || tree.state.downcast_ref::<State<T>>(),
)
}
@@ -349,6 +296,46 @@ impl<T> Default for State<T> {
}
}
+/// The handle to the right side of the [`PickList`].
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Handle<Font> {
+ /// Displays an arrow icon (▼).
+ ///
+ /// This is the default.
+ Arrow {
+ /// Font size of the content.
+ size: Option<u16>,
+ },
+ /// A custom static handle.
+ Static(Icon<Font>),
+ /// A custom dynamic handle.
+ Dynamic {
+ /// The [`Icon`] used when [`PickList`] is closed.
+ closed: Icon<Font>,
+ /// The [`Icon`] used when [`PickList`] is open.
+ open: Icon<Font>,
+ },
+ /// No handle will be shown.
+ None,
+}
+
+impl<Font> Default for Handle<Font> {
+ fn default() -> Self {
+ Self::Arrow { size: None }
+ }
+}
+
+/// The icon of a [`Handle`].
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Icon<Font> {
+ /// Font that will be used to display the `code_point`,
+ pub font: Font,
+ /// The unicode code point that will be used as the icon.
+ pub code_point: char,
+ /// Font size of the content.
+ pub size: Option<u16>,
+}
+
/// Computes the layout of a [`PickList`].
pub fn layout<Renderer, T>(
renderer: &Renderer,
@@ -568,7 +555,7 @@ where
}
/// Draws a [`PickList`].
-pub fn draw<T, Renderer>(
+pub fn draw<'a, T, Renderer>(
renderer: &mut Renderer,
theme: &Renderer::Theme,
layout: Layout<'_>,
@@ -578,12 +565,13 @@ pub fn draw<T, Renderer>(
font: &Renderer::Font,
placeholder: Option<&str>,
selected: Option<&T>,
- handle: &Handle<Renderer>,
+ handle: &Handle<Renderer::Font>,
style: &<Renderer::Theme as StyleSheet>::Style,
+ state: impl FnOnce() -> &'a State<T>,
) where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
- T: ToString,
+ T: ToString + 'a,
{
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
@@ -605,11 +593,30 @@ pub fn draw<T, Renderer>(
style.background,
);
- if let Some((font, text, size)) = handle.content() {
+ let handle = match handle {
+ Handle::Arrow { size } => {
+ Some((Renderer::ICON_FONT, Renderer::ARROW_DOWN_ICON, *size))
+ }
+ Handle::Static(Icon {
+ font,
+ code_point,
+ size,
+ }) => Some((font.clone(), *code_point, *size)),
+ Handle::Dynamic { open, closed } => {
+ if state().is_open {
+ Some((open.font.clone(), open.code_point, open.size))
+ } else {
+ Some((closed.font.clone(), closed.code_point, closed.size))
+ }
+ }
+ Handle::None => None,
+ };
+
+ if let Some((font, code_point, size)) = handle {
let size = f32::from(size.unwrap_or_else(|| renderer.default_size()));
renderer.fill_text(Text {
- content: &text,
+ content: &code_point.to_string(),
size,
font,
color: style.handle_color,
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index a62d9f35..bf87e817 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -389,8 +389,8 @@ where
let padding = padding.fit(Size::ZERO, limits.max());
let limits = limits
- .pad(padding)
.width(width)
+ .pad(padding)
.height(Length::Units(text_size));
let mut text = layout::Node::new(limits.resolve(Size::ZERO));
diff --git a/native/src/window/action.rs b/native/src/window/action.rs
index 929663ec..63858bc8 100644
--- a/native/src/window/action.rs
+++ b/native/src/window/action.rs
@@ -1,4 +1,4 @@
-use crate::window;
+use crate::window::{Mode, UserAttention};
use iced_futures::MaybeSend;
use std::fmt;
@@ -38,20 +38,21 @@ pub enum Action<T> {
/// The new logical y location of the window
y: i32,
},
- /// Set the [`Mode`] of the window.
- SetMode(window::Mode),
+ /// Change the [`Mode`] of the window.
+ ChangeMode(Mode),
/// Fetch the current [`Mode`] of the window.
- FetchMode(Box<dyn FnOnce(window::Mode) -> T + 'static>),
- /// Sets the window to maximized or back
+ FetchMode(Box<dyn FnOnce(Mode) -> T + 'static>),
+ /// Toggle the window to maximized or back
ToggleMaximize,
- /// Toggles whether window has decorations
+ /// Toggle whether window has decorations.
+ ///
/// ## Platform-specific
/// - **X11:** Not implemented.
/// - **Web:** Unsupported.
ToggleDecorations,
- /// Requests user attention to the window, this has no effect if the application
+ /// Request 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.
+ /// see [`UserAttention`] 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.
@@ -62,8 +63,8 @@ pub enum Action<T> {
/// - **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<window::UserAttention>),
- /// Brings the window to the front and sets input focus. Has no effect if the window is
+ RequestUserAttention(Option<UserAttention>),
+ /// Bring 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
@@ -93,7 +94,7 @@ impl<T> Action<T> {
Self::Maximize(bool) => Action::Maximize(bool),
Self::Minimize(bool) => Action::Minimize(bool),
Self::Move { x, y } => Action::Move { x, y },
- Self::SetMode(mode) => Action::SetMode(mode),
+ Self::ChangeMode(mode) => Action::ChangeMode(mode),
Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))),
Self::ToggleMaximize => Action::ToggleMaximize,
Self::ToggleDecorations => Action::ToggleDecorations,
@@ -115,15 +116,14 @@ impl<T> fmt::Debug for Action<T> {
}
Self::Resize { width, height } => write!(
f,
- "Action::Resize {{ widget: {}, height: {} }}",
- width, height
+ "Action::Resize {{ widget: {width}, height: {height} }}"
),
- Self::Maximize(value) => write!(f, "Action::Maximize({})", value),
- Self::Minimize(value) => write!(f, "Action::Minimize({}", value),
+ Self::Maximize(value) => write!(f, "Action::Maximize({value})"),
+ Self::Minimize(value) => write!(f, "Action::Minimize({value}"),
Self::Move { x, y } => {
- write!(f, "Action::Move {{ x: {}, y: {} }}", x, y)
+ write!(f, "Action::Move {{ x: {x}, y: {y} }}")
}
- Self::SetMode(mode) => write!(f, "Action::SetMode({:?})", mode),
+ Self::ChangeMode(mode) => write!(f, "Action::SetMode({mode:?})"),
Self::FetchMode(_) => write!(f, "Action::FetchMode"),
Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"),
Self::ToggleDecorations => write!(f, "Action::ToggleDecorations"),
diff --git a/src/widget.rs b/src/widget.rs
index f0058f57..5bf7b6b4 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -80,7 +80,9 @@ pub mod pane_grid {
pub mod pick_list {
//! Display a dropdown list of selectable values.
- pub use iced_native::widget::pick_list::{Appearance, Handle, StyleSheet};
+ pub use iced_native::widget::pick_list::{
+ Appearance, Handle, Icon, 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/wgpu/src/settings.rs b/wgpu/src/settings.rs
index 7bc752ff..21c2427d 100644
--- a/wgpu/src/settings.rs
+++ b/wgpu/src/settings.rs
@@ -83,7 +83,7 @@ fn backend_from_env() -> Option<wgpu::Backends> {
"gl" => wgpu::Backends::GL,
"webgpu" => wgpu::Backends::BROWSER_WEBGPU,
"primary" => wgpu::Backends::PRIMARY,
- other => panic!("Unknown backend: {}", other),
+ other => panic!("Unknown backend: {other}"),
}
})
}
diff --git a/winit/src/application.rs b/winit/src/application.rs
index d586fd21..692c2f74 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -145,11 +145,15 @@ where
#[cfg(target_arch = "wasm32")]
let target = settings.window.platform_specific.target.clone();
- let builder = settings.window.into_builder(
- &application.title(),
- event_loop.primary_monitor(),
- settings.id,
- );
+ let should_be_visible = settings.window.visible;
+ let builder = settings
+ .window
+ .into_builder(
+ &application.title(),
+ event_loop.primary_monitor(),
+ settings.id,
+ )
+ .with_visible(false);
log::info!("Window builder: {:#?}", builder);
@@ -200,6 +204,7 @@ where
control_sender,
init_command,
window,
+ should_be_visible,
settings.exit_on_close_request,
);
@@ -266,6 +271,7 @@ async fn run_instance<A, E, C>(
mut control_sender: mpsc::UnboundedSender<winit::event_loop::ControlFlow>,
init_command: Command<A::Message>,
window: winit::window::Window,
+ should_be_visible: bool,
exit_on_close_request: bool,
) where
A: Application + 'static,
@@ -293,6 +299,10 @@ async fn run_instance<A, E, C>(
physical_size.height,
);
+ if should_be_visible {
+ window.set_visible(true);
+ }
+
run_command(
&application,
&mut cache,
@@ -534,7 +544,7 @@ async fn run_instance<A, E, C>(
Err(error) => match error {
// This is an unrecoverable error.
compositor::SurfaceError::OutOfMemory => {
- panic!("{:?}", error);
+ panic!("{error:?}");
}
_ => {
debug.render_finished();
@@ -754,7 +764,7 @@ pub fn run_command<A, E>(
y,
});
}
- window::Action::SetMode(mode) => {
+ window::Action::ChangeMode(mode) => {
window.set_visible(conversion::visible(mode));
window.set_fullscreen(conversion::fullscreen(
window.primary_monitor(),
diff --git a/winit/src/icon.rs b/winit/src/icon.rs
index 84b88b39..73b4e037 100644
--- a/winit/src/icon.rs
+++ b/winit/src/icon.rs
@@ -132,10 +132,9 @@ impl fmt::Display for Error {
Error::InvalidData { byte_count } => {
write!(
f,
- "The provided RGBA data (with length {:?}) isn't divisble by \
+ "The provided RGBA data (with length {byte_count:?}) isn't divisble by \
4. Therefore, it cannot be safely interpreted as 32bpp RGBA \
- pixels.",
- byte_count,
+ pixels."
)
}
Error::DimensionsMismatch {
@@ -145,20 +144,18 @@ impl fmt::Display for Error {
} => {
write!(
f,
- "The number of RGBA pixels ({:?}) does not match the provided \
- dimensions ({:?}x{:?}).",
- pixel_count, width, height,
+ "The number of RGBA pixels ({pixel_count:?}) does not match the provided \
+ dimensions ({width:?}x{height:?})."
)
}
Error::OsError(e) => write!(
f,
"The underlying OS failed to create the window \
- icon: {:?}",
- e
+ icon: {e:?}"
),
#[cfg(feature = "image_rs")]
Error::ImageError(e) => {
- write!(f, "Unable to create icon from a file: {:?}", e)
+ write!(f, "Unable to create icon from a file: {e:?}")
}
}
}
diff --git a/winit/src/profiler.rs b/winit/src/profiler.rs
index 1f638de8..ff9bbdc0 100644
--- a/winit/src/profiler.rs
+++ b/winit/src/profiler.rs
@@ -50,8 +50,8 @@ impl Profiler {
.to_str()
.unwrap_or("trace");
- let path = out_dir
- .join(format!("{}_trace_{}.json", curr_exe_name, time));
+ let path =
+ out_dir.join(format!("{curr_exe_name}_trace_{time}.json"));
layer = layer.file(path);
} else {
diff --git a/winit/src/settings.rs b/winit/src/settings.rs
index 78c8c156..2f73aff6 100644
--- a/winit/src/settings.rs
+++ b/winit/src/settings.rs
@@ -119,8 +119,7 @@ impl Window {
.with_decorations(self.decorations)
.with_transparent(self.transparent)
.with_window_icon(self.icon)
- .with_always_on_top(self.always_on_top)
- .with_visible(self.visible);
+ .with_always_on_top(self.always_on_top);
if let Some(position) = conversion::position(
primary_monitor.as_ref(),
diff --git a/winit/src/window.rs b/winit/src/window.rs
index cc486fc8..fa31dca1 100644
--- a/winit/src/window.rs
+++ b/winit/src/window.rs
@@ -37,7 +37,7 @@ pub fn resize<Message>(
))
}
-/// Sets the window to maximized or back.
+/// Maximizes the window.
pub fn maximize<Message>(id: window::Id, value: bool) -> Command<Message> {
Command::single(command::Action::Window(
id,
@@ -45,7 +45,7 @@ pub fn maximize<Message>(id: window::Id, value: bool) -> Command<Message> {
))
}
-/// Set the window to minimized or back.
+/// Minimes the window.
pub fn minimize<Message>(id: window::Id, value: bool) -> Command<Message> {
Command::single(command::Action::Window(
id,
@@ -58,16 +58,11 @@ pub fn move_to<Message>(id: window::Id, x: i32, y: i32) -> Command<Message> {
Command::single(command::Action::Window(id, window::Action::Move { x, y }))
}
-/// Sets the [`Mode`] of the window.
-pub fn set_mode<Message>(id: window::Id, mode: Mode) -> Command<Message> {
+/// Changes the [`Mode`] of the window.
+pub fn change_mode<Message>(id: window::Id, mode: Mode) -> Command<Message> {
Command::single(command::Action::Window(id, window::Action::SetMode(mode)))
}
-/// Sets the window to maximized or back.
-pub fn toggle_maximize<Message>(id: window::Id) -> Command<Message> {
- Command::single(command::Action::Window(id, window::Action::ToggleMaximize))
-}
-
/// Fetches the current [`Mode`] of the window.
pub fn fetch_mode<Message>(
id: window::Id,
@@ -78,3 +73,39 @@ pub fn fetch_mode<Message>(
window::Action::FetchMode(Box::new(f)),
))
}
+
+/// Toggles the window to maximized or back.
+pub fn toggle_maximize<Message>(id: window::Id) -> Command<Message> {
+ Command::single(command::Action::Window(id, window::Action::ToggleMaximize))
+}
+
+/// Toggles the window decorations.
+pub fn toggle_decorations<Message>(id: window::Id) -> Command<Message> {
+ Command::single(command::Action::Window(id, window::Action::ToggleDecorations))
+}
+
+/// Request 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 [`UserAttention`] 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.
+pub fn request_user_attention<Message>(
+ id: window::Id,
+ user_attention: Option<UserAttention>,
+) -> Command<Message> {
+ Command::single(command::Action::Window(
+ id,
+ window::Action::RequestUserAttention(user_attention),
+ ))
+}
+
+/// 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 [`Command`] 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.
+pub fn gain_focus<Message>(id: window::Id) -> Command<Message> {
+ Command::single(command::Action::Window(id, window::Action::GainFocus))
+}