diff options
Diffstat (limited to '')
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | examples/cached/Cargo.toml | 10 | ||||
-rw-r--r-- | examples/cached/src/main.rs | 139 | ||||
-rw-r--r-- | examples/integration_wgpu/src/main.rs | 2 | ||||
-rw-r--r-- | examples/lazy/Cargo.toml | 10 | ||||
-rw-r--r-- | examples/lazy/src/main.rs | 139 | ||||
-rw-r--r-- | examples/todos/Cargo.toml | 2 | ||||
-rw-r--r-- | examples/todos/src/main.rs | 6 | ||||
-rw-r--r-- | examples/websocket/Cargo.toml | 2 | ||||
-rw-r--r-- | examples/websocket/src/main.rs | 5 | ||||
-rw-r--r-- | glutin/Cargo.toml | 1 | ||||
-rw-r--r-- | graphics/Cargo.toml | 2 | ||||
-rw-r--r-- | graphics/src/window/compositor.rs | 6 | ||||
-rw-r--r-- | lazy/src/lazy.rs | 362 | ||||
-rw-r--r-- | lazy/src/lib.rs | 15 | ||||
-rw-r--r-- | native/src/widget/pane_grid.rs | 46 | ||||
-rw-r--r-- | native/src/widget/pane_grid/content.rs | 17 | ||||
-rw-r--r-- | native/src/widget/pick_list.rs | 6 | ||||
-rw-r--r-- | native/src/widget/text_input.rs | 8 | ||||
-rw-r--r-- | native/src/window/action.rs | 20 | ||||
-rw-r--r-- | style/Cargo.toml | 4 | ||||
-rw-r--r-- | style/src/theme/palette.rs | 11 | ||||
-rw-r--r-- | wgpu/Cargo.toml | 6 | ||||
-rw-r--r-- | wgpu/src/window/compositor.rs | 9 | ||||
-rw-r--r-- | winit/src/application.rs | 12 | ||||
-rw-r--r-- | winit/src/window.rs | 20 |
26 files changed, 799 insertions, 62 deletions
@@ -71,6 +71,7 @@ members = [ "examples/game_of_life", "examples/integration_opengl", "examples/integration_wgpu", + "examples/lazy", "examples/modern_art", "examples/multitouch", "examples/pane_grid", diff --git a/examples/cached/Cargo.toml b/examples/cached/Cargo.toml new file mode 100644 index 00000000..2c7edde2 --- /dev/null +++ b/examples/cached/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "cached" +version = "0.1.0" +authors = ["Nick Senger <dev@nsenger.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } +iced_lazy = { path = "../../lazy" } diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs new file mode 100644 index 00000000..8845b874 --- /dev/null +++ b/examples/cached/src/main.rs @@ -0,0 +1,139 @@ +use iced::theme; +use iced::widget::{ + button, column, horizontal_space, row, scrollable, text, text_input, +}; +use iced::{Element, Length, Sandbox, Settings}; +use iced_lazy::lazy; + +use std::collections::HashSet; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +struct App { + options: HashSet<String>, + input: String, + order: Order, +} + +impl Default for App { + fn default() -> Self { + Self { + options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"] + .into_iter() + .map(ToString::to_string) + .collect(), + input: Default::default(), + order: Order::Ascending, + } + } +} + +#[derive(Debug, Clone)] +enum Message { + InputChanged(String), + ToggleOrder, + DeleteOption(String), + AddOption(String), +} + +impl Sandbox for App { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Cached - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::InputChanged(input) => { + self.input = input; + } + Message::ToggleOrder => { + self.order = match self.order { + Order::Ascending => Order::Descending, + Order::Descending => Order::Ascending, + } + } + Message::AddOption(option) => { + self.options.insert(option); + self.input.clear(); + } + Message::DeleteOption(option) => { + self.options.remove(&option); + } + } + } + + fn view(&self) -> Element<Message> { + let options = lazy((&self.order, self.options.len()), || { + let mut options: Vec<_> = self.options.iter().collect(); + + options.sort_by(|a, b| match self.order { + Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), + Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()), + }); + + column( + options + .into_iter() + .map(|option| { + row![ + text(option), + horizontal_space(Length::Fill), + button("Delete") + .on_press(Message::DeleteOption( + option.to_string(), + ),) + .style(theme::Button::Destructive) + ] + .into() + }) + .collect(), + ) + .spacing(10) + }); + + column![ + scrollable(options).height(Length::Fill), + row![ + text_input( + "Add a new option", + &self.input, + Message::InputChanged, + ) + .on_submit(Message::AddOption(self.input.clone())), + button(text(format!("Toggle Order ({})", self.order))) + .on_press(Message::ToggleOrder) + ] + .spacing(10) + ] + .spacing(20) + .padding(20) + .into() + } +} + +#[derive(Debug, Hash)] +enum Order { + Ascending, + Descending, +} + +impl std::fmt::Display for Order { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Ascending => "Ascending", + Self::Descending => "Descending", + } + ) + } +} diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration_wgpu/src/main.rs index 69d46c3e..70f9a48b 100644 --- a/examples/integration_wgpu/src/main.rs +++ b/examples/integration_wgpu/src/main.rs @@ -119,6 +119,7 @@ pub fn main() { width: physical_size.width, height: physical_size.height, present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: wgpu::CompositeAlphaMode::Auto, }, ); @@ -213,6 +214,7 @@ pub fn main() { width: size.width, height: size.height, present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: wgpu::CompositeAlphaMode::Auto }, ); diff --git a/examples/lazy/Cargo.toml b/examples/lazy/Cargo.toml new file mode 100644 index 00000000..79255c25 --- /dev/null +++ b/examples/lazy/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "lazy" +version = "0.1.0" +authors = ["Nick Senger <dev@nsenger.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } +iced_lazy = { path = "../../lazy" } diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs new file mode 100644 index 00000000..8845b874 --- /dev/null +++ b/examples/lazy/src/main.rs @@ -0,0 +1,139 @@ +use iced::theme; +use iced::widget::{ + button, column, horizontal_space, row, scrollable, text, text_input, +}; +use iced::{Element, Length, Sandbox, Settings}; +use iced_lazy::lazy; + +use std::collections::HashSet; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +struct App { + options: HashSet<String>, + input: String, + order: Order, +} + +impl Default for App { + fn default() -> Self { + Self { + options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"] + .into_iter() + .map(ToString::to_string) + .collect(), + input: Default::default(), + order: Order::Ascending, + } + } +} + +#[derive(Debug, Clone)] +enum Message { + InputChanged(String), + ToggleOrder, + DeleteOption(String), + AddOption(String), +} + +impl Sandbox for App { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Cached - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::InputChanged(input) => { + self.input = input; + } + Message::ToggleOrder => { + self.order = match self.order { + Order::Ascending => Order::Descending, + Order::Descending => Order::Ascending, + } + } + Message::AddOption(option) => { + self.options.insert(option); + self.input.clear(); + } + Message::DeleteOption(option) => { + self.options.remove(&option); + } + } + } + + fn view(&self) -> Element<Message> { + let options = lazy((&self.order, self.options.len()), || { + let mut options: Vec<_> = self.options.iter().collect(); + + options.sort_by(|a, b| match self.order { + Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), + Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()), + }); + + column( + options + .into_iter() + .map(|option| { + row![ + text(option), + horizontal_space(Length::Fill), + button("Delete") + .on_press(Message::DeleteOption( + option.to_string(), + ),) + .style(theme::Button::Destructive) + ] + .into() + }) + .collect(), + ) + .spacing(10) + }); + + column![ + scrollable(options).height(Length::Fill), + row![ + text_input( + "Add a new option", + &self.input, + Message::InputChanged, + ) + .on_submit(Message::AddOption(self.input.clone())), + button(text(format!("Toggle Order ({})", self.order))) + .on_press(Message::ToggleOrder) + ] + .spacing(10) + ] + .spacing(20) + .padding(20) + .into() + } +} + +#[derive(Debug, Hash)] +enum Order { + Ascending, + Descending, +} + +impl std::fmt::Display for Order { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Ascending => "Ascending", + Self::Descending => "Descending", + } + ) + } +} diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index 2326ffc6..7ad4d558 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -9,7 +9,7 @@ publish = false iced = { path = "../..", features = ["async-std", "debug"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -lazy_static = "1.4" +once_cell = "1.15" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] async-std = "1.0" diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index bddc0e71..be48ae8c 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -11,12 +11,10 @@ use iced::window; use iced::{Application, Element}; use iced::{Color, Command, Font, Length, Settings, Subscription}; -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -lazy_static! { - static ref INPUT_ID: text_input::Id = text_input::Id::unique(); -} +static INPUT_ID: Lazy<text_input::Id> = Lazy::new(text_input::Id::unique); pub fn main() -> iced::Result { Todos::run(Settings { diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index 3981f699..c25f067b 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -9,7 +9,7 @@ publish = false iced = { path = "../..", features = ["tokio", "debug"] } iced_native = { path = "../../native" } iced_futures = { path = "../../futures" } -lazy_static = "1.4" +once_cell = "1.15" [dependencies.async-tungstenite] version = "0.16" diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 3902e04c..ff2929da 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -8,6 +8,7 @@ use iced::widget::{ use iced::{ Application, Color, Command, Element, Length, Settings, Subscription, Theme, }; +use once_cell::sync::Lazy; pub fn main() -> iced::Result { WebSocket::run(Settings::default()) @@ -165,6 +166,4 @@ impl Default for State { } } -lazy_static::lazy_static! { - static ref MESSAGE_LOG: scrollable::Id = scrollable::Id::unique(); -} +static MESSAGE_LOG: Lazy<scrollable::Id> = Lazy::new(scrollable::Id::unique); diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index d84f9d70..65b16947 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -29,6 +29,7 @@ path = "../native" [dependencies.iced_winit] version = "0.4" path = "../winit" +features = ["application"] [dependencies.iced_graphics] version = "0.3" diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index ff6fcd4a..3b0e5236 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -20,7 +20,7 @@ opengl = [] [dependencies] glam = "0.21.3" -raw-window-handle = "0.4" +raw-window-handle = "0.5" thiserror = "1.0" [dependencies.bytemuck] diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs index 0c4cadcd..52255666 100644 --- a/graphics/src/window/compositor.rs +++ b/graphics/src/window/compositor.rs @@ -2,7 +2,7 @@ //! surfaces. use crate::{Color, Error, Viewport}; -use raw_window_handle::HasRawWindowHandle; +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use thiserror::Error; /// A graphics compositor that can draw to windows. @@ -17,7 +17,7 @@ pub trait Compositor: Sized { type Surface; /// Creates a new [`Compositor`]. - fn new<W: HasRawWindowHandle>( + fn new<W: HasRawWindowHandle + HasRawDisplayHandle>( settings: Self::Settings, compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error>; @@ -25,7 +25,7 @@ pub trait Compositor: Sized { /// Crates a new [`Surface`] for the given window. /// /// [`Surface`]: Self::Surface - fn create_surface<W: HasRawWindowHandle>( + fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( &mut self, window: &W, ) -> Self::Surface; diff --git a/lazy/src/lazy.rs b/lazy/src/lazy.rs new file mode 100644 index 00000000..d61cc77e --- /dev/null +++ b/lazy/src/lazy.rs @@ -0,0 +1,362 @@ +use iced_native::event; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::widget::tree::{self, Tree}; +use iced_native::widget::{self, Widget}; +use iced_native::Element; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; + +use ouroboros::self_referencing; +use std::cell::{Ref, RefCell, RefMut}; +use std::hash::{Hash, Hasher as H}; +use std::marker::PhantomData; +use std::rc::Rc; + +#[allow(missing_debug_implementations)] +pub struct Lazy<'a, Message, Renderer, Dependency, View> { + dependency: Dependency, + view: Box<dyn Fn() -> View + 'a>, + element: RefCell<Option<Rc<RefCell<Element<'static, Message, Renderer>>>>>, +} + +impl<'a, Message, Renderer, Dependency, View> + Lazy<'a, Message, Renderer, Dependency, View> +where + Dependency: Hash + 'a, + View: Into<Element<'static, Message, Renderer>>, +{ + pub fn new(dependency: Dependency, view: impl Fn() -> View + 'a) -> Self { + Self { + dependency, + view: Box::new(view), + element: RefCell::new(None), + } + } + + fn with_element<T>( + &self, + f: impl FnOnce(Ref<Element<Message, Renderer>>) -> T, + ) -> T { + f(self.element.borrow().as_ref().unwrap().borrow()) + } + + fn with_element_mut<T>( + &self, + f: impl FnOnce(RefMut<Element<Message, Renderer>>) -> T, + ) -> T { + f(self.element.borrow().as_ref().unwrap().borrow_mut()) + } +} + +struct Internal<Message, Renderer> { + element: Rc<RefCell<Element<'static, Message, Renderer>>>, + hash: u64, +} + +impl<'a, Message, Renderer, Dependency, View> Widget<Message, Renderer> + for Lazy<'a, Message, Renderer, Dependency, View> +where + View: Into<Element<'static, Message, Renderer>> + 'static, + Dependency: Hash + 'a, + Message: 'static, + Renderer: iced_native::Renderer + 'static, +{ + fn tag(&self) -> tree::Tag { + struct Tag<T>(T); + tree::Tag::of::<Tag<View>>() + } + + fn state(&self) -> tree::State { + let mut hasher = Hasher::default(); + self.dependency.hash(&mut hasher); + let hash = hasher.finish(); + + let element = Rc::new(RefCell::new((self.view)().into())); + + (*self.element.borrow_mut()) = Some(element.clone()); + + tree::State::new(Internal { element, hash }) + } + + fn children(&self) -> Vec<Tree> { + vec![Tree::new( + self.element.borrow().as_ref().unwrap().borrow().as_widget(), + )] + } + + fn diff(&self, tree: &mut Tree) { + let current = tree.state.downcast_mut::<Internal<Message, Renderer>>(); + + let mut hasher = Hasher::default(); + self.dependency.hash(&mut hasher); + let new_hash = hasher.finish(); + + if current.hash != new_hash { + current.hash = new_hash; + + let element = (self.view)().into(); + current.element = Rc::new(RefCell::new(element)); + + (*self.element.borrow_mut()) = Some(current.element.clone()); + tree.diff_children(std::slice::from_ref( + &self.element.borrow().as_ref().unwrap().borrow().as_widget(), + )); + } else { + (*self.element.borrow_mut()) = Some(current.element.clone()); + } + } + + fn width(&self) -> Length { + self.with_element(|element| element.as_widget().width()) + } + + fn height(&self) -> Length { + self.with_element(|element| element.as_widget().height()) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.with_element(|element| { + element.as_widget().layout(renderer, limits) + }) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn widget::Operation<Message>, + ) { + self.with_element(|element| { + element.as_widget().operate( + &mut tree.children[0], + layout, + operation, + ); + }); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.with_element_mut(|mut element| { + element.as_widget_mut().on_event( + &mut tree.children[0], + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.with_element(|element| { + element.as_widget().mouse_interaction( + &tree.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + }) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.with_element(|element| { + element.as_widget().draw( + &tree.children[0], + renderer, + theme, + style, + layout, + cursor_position, + viewport, + ) + }) + } + + fn overlay<'b>( + &'b self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option<overlay::Element<'_, Message, Renderer>> { + let overlay = OverlayBuilder { + cached: self, + tree: &mut tree.children[0], + types: PhantomData, + element_ref_builder: |cached| cached.element.borrow(), + element_builder: |element_ref| { + element_ref.as_ref().unwrap().borrow() + }, + overlay_builder: |element, tree| { + element.as_widget().overlay(tree, layout, renderer) + }, + } + .build(); + + let has_overlay = overlay.with_overlay(|overlay| { + overlay.as_ref().map(overlay::Element::position) + }); + + has_overlay + .map(|position| overlay::Element::new(position, Box::new(overlay))) + } +} + +#[self_referencing] +struct Overlay<'a, 'b, Message, Renderer, Dependency, View> { + cached: &'a Lazy<'b, Message, Renderer, Dependency, View>, + tree: &'a mut Tree, + types: PhantomData<(Message, Dependency, View)>, + + #[borrows(cached)] + #[covariant] + element_ref: + Ref<'this, Option<Rc<RefCell<Element<'static, Message, Renderer>>>>>, + + #[borrows(element_ref)] + #[covariant] + element: Ref<'this, Element<'static, Message, Renderer>>, + + #[borrows(element, mut tree)] + #[covariant] + overlay: Option<overlay::Element<'this, Message, Renderer>>, +} + +impl<'a, 'b, Message, Renderer, Dependency, View> + Overlay<'a, 'b, Message, Renderer, Dependency, View> +{ + fn with_overlay_maybe<T>( + &self, + f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option<T> { + self.borrow_overlay().as_ref().map(f) + } + + fn with_overlay_mut_maybe<T>( + &mut self, + f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option<T> { + self.with_overlay_mut(|overlay| overlay.as_mut().map(f)) + } +} + +impl<'a, 'b, Message, Renderer, Dependency, View> + overlay::Overlay<Message, Renderer> + for Overlay<'a, 'b, Message, Renderer, Dependency, View> +where + Renderer: iced_native::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + self.with_overlay_maybe(|overlay| { + let vector = position - overlay.position(); + + overlay.layout(renderer, bounds).translate(vector) + }) + .unwrap_or_default() + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + ) { + let _ = self.with_overlay_maybe(|overlay| { + overlay.draw(renderer, theme, style, layout, cursor_position); + }); + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.with_overlay_maybe(|overlay| { + overlay.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) + }) + .unwrap_or_default() + } + + fn on_event( + &mut self, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.with_overlay_mut_maybe(|overlay| { + overlay.on_event( + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }) + .unwrap_or(iced_native::event::Status::Ignored) + } +} + +impl<'a, Message, Renderer, Dependency, View> + From<Lazy<'a, Message, Renderer, Dependency, View>> + for Element<'a, Message, Renderer> +where + View: Into<Element<'static, Message, Renderer>> + 'static, + Renderer: iced_native::Renderer + 'static, + Message: 'static, + Dependency: Hash + 'a, +{ + fn from(lazy: Lazy<'a, Message, Renderer, Dependency, View>) -> Self { + Self::new(lazy) + } +} diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs index 3827746c..f49fe4b6 100644 --- a/lazy/src/lib.rs +++ b/lazy/src/lib.rs @@ -17,15 +17,30 @@ clippy::type_complexity )] #![cfg_attr(docsrs, feature(doc_cfg))] +mod lazy; + pub mod component; pub mod responsive; pub use component::Component; +pub use lazy::Lazy; pub use responsive::Responsive; mod cache; use iced_native::{Element, Size}; +use std::hash::Hash; + +pub fn lazy<'a, Message, Renderer, Dependency, View>( + dependency: Dependency, + view: impl Fn() -> View + 'a, +) -> Lazy<'a, Message, Renderer, Dependency, View> +where + Dependency: Hash + 'a, + View: Into<Element<'static, Message, Renderer>>, +{ + Lazy::new(dependency, view) +} /// Turns an implementor of [`Component`] into an [`Element`] that can be /// embedded in any application. diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index d84fb7a0..96cf78ef 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -341,6 +341,7 @@ where cursor_position, viewport, renderer, + self.on_drag.is_some(), ) }) .max() @@ -648,7 +649,7 @@ pub fn mouse_interaction( resize_leeway: Option<u16>, ) -> Option<mouse::Interaction> { if action.picked_pane().is_some() { - return Some(mouse::Interaction::Grab); + return Some(mouse::Interaction::Grabbing); } let resize_axis = @@ -756,27 +757,12 @@ pub fn draw<Renderer, T>( cursor_position }; + let mut render_picked_pane = None; + for ((id, pane), layout) in elements.zip(layout.children()) { match picked_pane { Some((dragging, origin)) if id == dragging => { - let bounds = layout.bounds(); - - renderer.with_translation( - cursor_position - - Point::new(bounds.x + origin.x, bounds.y + origin.y), - |renderer| { - renderer.with_layer(bounds, |renderer| { - draw_pane( - pane, - renderer, - default_style, - layout, - pane_cursor_position, - viewport, - ); - }); - }, - ); + render_picked_pane = Some((pane, origin, layout)); } _ => { draw_pane( @@ -791,6 +777,28 @@ pub fn draw<Renderer, T>( } } + // Render picked pane last + if let Some((pane, origin, layout)) = render_picked_pane { + let bounds = layout.bounds(); + + renderer.with_translation( + cursor_position + - Point::new(bounds.x + origin.x, bounds.y + origin.y), + |renderer| { + renderer.with_layer(bounds, |renderer| { + draw_pane( + pane, + renderer, + default_style, + layout, + pane_cursor_position, + viewport, + ); + }); + }, + ); + }; + if let Some((axis, split_region, is_picked)) = picked_split { let highlight = if is_picked { theme.picked_split(style) diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 98ce2c4b..c236d820 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -115,25 +115,25 @@ where let show_controls = bounds.contains(cursor_position); - title_bar.draw( - &tree.children[1], + self.body.as_widget().draw( + &tree.children[0], renderer, theme, style, - title_bar_layout, + body_layout, cursor_position, viewport, - show_controls, ); - self.body.as_widget().draw( - &tree.children[0], + title_bar.draw( + &tree.children[1], renderer, theme, style, - body_layout, + title_bar_layout, cursor_position, viewport, + show_controls, ); } else { self.body.as_widget().draw( @@ -238,6 +238,7 @@ where cursor_position: Point, viewport: &Rectangle, renderer: &Renderer, + drag_enabled: bool, ) -> mouse::Interaction { let (body_layout, title_bar_interaction) = if let Some(title_bar) = &self.title_bar { @@ -247,7 +248,7 @@ where let is_over_pick_area = title_bar .is_over_pick_area(title_bar_layout, cursor_position); - if is_over_pick_area { + if is_over_pick_area && drag_enabled { return mouse::Interaction::Grab; } diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index c334804e..896f5b35 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -348,9 +348,9 @@ where let state = state(); let event_status = if state.is_open { - // TODO: Encode cursor availability in the type system - state.is_open = - cursor_position.x < 0.0 || cursor_position.y < 0.0; + // Event wasn't processed by overlay, so cursor was clicked either outside it's + // bounds or on the drop-down, either way we close the overlay. + state.is_open = false; event::Status::Captured } else if layout.bounds().contains(cursor_position) { diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index c2d25520..54a6aaf8 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -92,7 +92,7 @@ where is_secure: false, font: Default::default(), width: Length::Fill, - padding: Padding::ZERO, + padding: Padding::new(5), size: None, on_change: Box::new(on_change), on_paste: None, @@ -712,14 +712,14 @@ where } return event::Status::Captured; + } else { + state.is_pasting = None; } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { let state = state(); - if state.is_focused { - state.keyboard_modifiers = modifiers; - } + state.keyboard_modifiers = modifiers; } _ => {} } diff --git a/native/src/window/action.rs b/native/src/window/action.rs index 73338e22..009dcc27 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -5,6 +5,12 @@ use std::fmt; /// An operation to be performed on some window. pub enum Action<T> { + /// Moves the window with the left mouse button until the button is + /// released. + /// + /// There’s no guarantee that this will work unless the left mouse + /// button was pressed immediately before this function is called. + Drag, /// Resize the window. Resize { /// The new logical width of the window @@ -12,6 +18,10 @@ pub enum Action<T> { /// The new logical height of the window height: u32, }, + /// Sets the window to maximized or back + Maximize(bool), + /// Set the window to minimized or back + Minimize(bool), /// Move the window. /// /// Unsupported on Wayland. @@ -23,6 +33,8 @@ pub enum Action<T> { }, /// Set the [`Mode`] of the window. SetMode(Mode), + /// Sets the window to maximized or back + ToggleMaximize, /// Fetch the current [`Mode`] of the window. FetchMode(Box<dyn FnOnce(Mode) -> T + 'static>), } @@ -37,9 +49,13 @@ impl<T> Action<T> { T: 'static, { match self { + Self::Drag => Action::Drag, Self::Resize { width, height } => Action::Resize { width, height }, + 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::ToggleMaximize => Action::ToggleMaximize, Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))), } } @@ -48,15 +64,19 @@ impl<T> Action<T> { impl<T> fmt::Debug for Action<T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Drag => write!(f, "Action::Drag"), Self::Resize { width, height } => write!( f, "Action::Resize {{ widget: {}, height: {} }}", width, height ), + 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) } Self::SetMode(mode) => write!(f, "Action::SetMode({:?})", mode), + Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"), Self::FetchMode(_) => write!(f, "Action::FetchMode"), } } diff --git a/style/Cargo.toml b/style/Cargo.toml index cf9d328b..4a482a4f 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -18,5 +18,5 @@ features = ["palette"] [dependencies.palette] version = "0.6" -[dependencies.lazy_static] -version = "1.4" +[dependencies.once_cell] +version = "1.15" diff --git a/style/src/theme/palette.rs b/style/src/theme/palette.rs index 81aa9cc7..4fb5e4c8 100644 --- a/style/src/theme/palette.rs +++ b/style/src/theme/palette.rs @@ -1,6 +1,6 @@ use iced_core::Color; -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use palette::{FromColor, Hsl, Mix, RelativeContrast, Srgb}; #[derive(Debug, Clone, Copy, PartialEq)] @@ -66,11 +66,10 @@ pub struct Extended { pub danger: Danger, } -lazy_static! { - pub static ref EXTENDED_LIGHT: Extended = - Extended::generate(Palette::LIGHT); - pub static ref EXTENDED_DARK: Extended = Extended::generate(Palette::DARK); -} +pub static EXTENDED_LIGHT: Lazy<Extended> = + Lazy::new(|| Extended::generate(Palette::LIGHT)); +pub static EXTENDED_DARK: Lazy<Extended> = + Lazy::new(|| Extended::generate(Palette::DARK)); impl Extended { pub fn generate(palette: Palette) -> Self { diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 7174f80c..9a57e58b 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -28,10 +28,10 @@ spirv = ["wgpu/spirv"] webgl = ["wgpu/webgl"] [dependencies] -wgpu = "0.13" -wgpu_glyph = "0.17" +wgpu = "0.14" +wgpu_glyph = "0.18" glyph_brush = "0.7" -raw-window-handle = "0.4" +raw-window-handle = "0.5" log = "0.4" guillotiere = "0.6" futures = "0.3" diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index a36d2a87..6d0c36f6 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -4,7 +4,7 @@ use futures::stream::{self, StreamExt}; use iced_graphics::compositor; use iced_native::futures; -use raw_window_handle::HasRawWindowHandle; +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; @@ -27,7 +27,7 @@ impl<Theme> Compositor<Theme> { /// Requests a new [`Compositor`] with the given [`Settings`]. /// /// Returns `None` if no compatible graphics adapter could be found. - pub async fn request<W: HasRawWindowHandle>( + pub async fn request<W: HasRawWindowHandle + HasRawDisplayHandle>( settings: Settings, compatible_window: Option<&W>, ) -> Option<Self> { @@ -123,7 +123,7 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> { type Renderer = Renderer<Theme>; type Surface = wgpu::Surface; - fn new<W: HasRawWindowHandle>( + fn new<W: HasRawWindowHandle + HasRawDisplayHandle>( settings: Self::Settings, compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { @@ -138,7 +138,7 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> { Ok((compositor, Renderer::new(backend))) } - fn create_surface<W: HasRawWindowHandle>( + fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( &mut self, window: &W, ) -> wgpu::Surface { @@ -162,6 +162,7 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> { present_mode: self.settings.present_mode, width, height, + alpha_mode: wgpu::CompositeAlphaMode::Auto, }, ); } diff --git a/winit/src/application.rs b/winit/src/application.rs index 0496aea9..939a50c9 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -615,12 +615,21 @@ pub fn run_command<A, E>( } }, command::Action::Window(action) => match action { + window::Action::Drag => { + let _res = window.drag_window(); + } window::Action::Resize { width, height } => { window.set_inner_size(winit::dpi::LogicalSize { width, height, }); } + window::Action::Maximize(value) => { + window.set_maximized(value); + } + window::Action::Minimize(value) => { + window.set_minimized(value); + } window::Action::Move { x, y } => { window.set_outer_position(winit::dpi::LogicalPosition { x, @@ -634,6 +643,9 @@ pub fn run_command<A, E>( mode, )); } + window::Action::ToggleMaximize => { + window.set_maximized(!window.is_maximized()) + } window::Action::FetchMode(tag) => { let mode = if window.is_visible().unwrap_or(true) { conversion::mode(window.fullscreen()) diff --git a/winit/src/window.rs b/winit/src/window.rs index 265139f7..1e704c5b 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -4,6 +4,11 @@ use iced_native::window; pub use window::{Event, Mode}; +/// Begins dragging the window while the left mouse button is held. +pub fn drag<Message>() -> Command<Message> { + Command::single(command::Action::Window(window::Action::Drag)) +} + /// Resizes the window to the given logical dimensions. pub fn resize<Message>(width: u32, height: u32) -> Command<Message> { Command::single(command::Action::Window(window::Action::Resize { @@ -12,6 +17,16 @@ pub fn resize<Message>(width: u32, height: u32) -> Command<Message> { })) } +/// Sets the window to maximized or back. +pub fn maximize<Message>(value: bool) -> Command<Message> { + Command::single(command::Action::Window(window::Action::Maximize(value))) +} + +/// Set the window to minimized or back. +pub fn minimize<Message>(value: bool) -> Command<Message> { + Command::single(command::Action::Window(window::Action::Minimize(value))) +} + /// Moves a window to the given logical coordinates. pub fn move_to<Message>(x: i32, y: i32) -> Command<Message> { Command::single(command::Action::Window(window::Action::Move { x, y })) @@ -22,6 +37,11 @@ pub fn set_mode<Message>(mode: Mode) -> Command<Message> { Command::single(command::Action::Window(window::Action::SetMode(mode))) } +/// Sets the window to maximized or back. +pub fn toggle_maximize<Message>() -> Command<Message> { + Command::single(command::Action::Window(window::Action::ToggleMaximize)) +} + /// Fetches the current [`Mode`] of the window. pub fn fetch_mode<Message>( f: impl FnOnce(Mode) -> Message + 'static, |