This commit is contained in:
Ashley Wulber 2026-02-03 16:45:44 -05:00
parent a489a6b790
commit e2918e0de9
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
19 changed files with 9291 additions and 60 deletions

9071
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -22,6 +22,7 @@ a11y = ["iced_accessibility"]
wayland = ["cctk"] wayland = ["cctk"]
[dependencies] [dependencies]
palette = "0.7"
bitflags.workspace = true bitflags.workspace = true
bytes.workspace = true bytes.workspace = true
glam.workspace = true glam.workspace = true

View file

@ -24,6 +24,18 @@ impl Background {
} }
} }
impl From<palette::Srgba<f32>> for Background {
fn from(color: palette::Srgba<f32>) -> Self {
Background::Color(Color::from(color))
}
}
impl From<palette::Srgba<u8>> for Background {
fn from(color: palette::Srgba<u8>) -> Self {
Background::Color(Color::from(color))
}
}
impl From<Color> for Background { impl From<Color> for Background {
fn from(color: Color) -> Self { fn from(color: Color) -> Self {
Background::Color(color) Background::Color(color)

View file

@ -269,6 +269,47 @@ impl std::fmt::Display for Color {
} }
} }
impl From<palette::Srgb<u8>> for Color {
fn from(srgb: palette::Srgb<u8>) -> Self {
Color::from_rgb8(srgb.red, srgb.green, srgb.blue)
}
}
impl From<palette::Srgba<u8>> for Color {
fn from(srgba: palette::Srgba<u8>) -> Self {
Color::from_rgba8(
srgba.red,
srgba.green,
srgba.blue,
srgba.alpha as f32 / 255.0,
)
}
}
impl From<palette::Srgb<f32>> for Color {
fn from(srgb: palette::Srgb<f32>) -> Self {
Color::from_rgb(srgb.red, srgb.green, srgb.blue)
}
}
impl From<palette::Srgba<f32>> for Color {
fn from(srgba: palette::Srgba<f32>) -> Self {
Color::from_rgba(srgba.red, srgba.green, srgba.blue, srgba.alpha)
}
}
impl From<Color> for palette::Srgb<f32> {
fn from(color: Color) -> Self {
palette::Srgb::new(color.r, color.g, color.b)
}
}
impl From<Color> for palette::Srgba<f32> {
fn from(color: Color) -> Self {
palette::Srgba::new(color.r, color.g, color.b, color.a)
}
}
/// Creates a [`Color`] with shorter and cleaner syntax. /// Creates a [`Color`] with shorter and cleaner syntax.
/// ///
/// # Examples /// # Examples

View file

@ -78,6 +78,12 @@ impl From<f32> for Length {
} }
} }
impl From<u16> for Length {
fn from(amount: u16) -> Self {
Length::Fixed(amount as f32)
}
}
impl From<u32> for Length { impl From<u32> for Length {
fn from(units: u32) -> Self { fn from(units: u32) -> Self {
Length::Fixed(units as f32) Length::Fixed(units as f32)

View file

@ -26,6 +26,18 @@ impl From<u32> for Pixels {
} }
} }
impl From<u16> for Pixels {
fn from(amount: u16) -> Self {
Self(amount as f32)
}
}
impl From<i32> for Pixels {
fn from(amount: i32) -> Self {
Self(amount as f32)
}
}
impl From<Pixels> for f32 { impl From<Pixels> for f32 {
fn from(pixels: Pixels) -> Self { fn from(pixels: Pixels) -> Self {
pixels.0 pixels.0

View file

@ -445,7 +445,7 @@ where
{ {
let controls = row![ let controls = row![
button(text("Cancel").center().width(Fill)) button(text("Cancel").center().width(Fill))
.width(100) .width(100u16)
.on_press(Message::CancelSetup) .on_press(Message::CancelSetup)
.style(button::danger), .style(button::danger),
space::horizontal(), space::horizontal(),
@ -457,7 +457,7 @@ where
.center() .center()
.width(Fill) .width(Fill)
) )
.width(100) .width(100u16)
.on_press(Message::InstallComet) .on_press(Message::InstallComet)
.style(button::success), .style(button::success),
]; ];
@ -544,7 +544,7 @@ where
) )
.spacing(10) .spacing(10)
.width(Fill) .width(Fill)
.height(300) .height(300u16)
.anchor_bottom(), .anchor_bottom(),
) )
.padding(10) .padding(10)

View file

@ -3,20 +3,20 @@ use std::{
sync::atomic::{AtomicU64, Ordering}, sync::atomic::{AtomicU64, Ordering},
}; };
use iced::{Element, id::Id};
use iced::{ use iced::{
Event, Length, Rectangle,
clipboard::{ clipboard::{
dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}, dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent},
mime::AllowedMimeTypes, mime::AllowedMimeTypes,
}, },
event, event,
id::Internal, id::Internal,
mouse, overlay, Event, Length, Rectangle, mouse, overlay,
}; };
use iced::{id::Id, Element};
use iced_core::{ use iced_core::{
self, layout, self, Clipboard, Layout, Shell, Widget, layout,
widget::{tree, Tree}, widget::{Tree, tree},
Clipboard, Layout, Shell, Widget,
}; };
pub fn dnd_destination<'a, Message: 'static>( pub fn dnd_destination<'a, Message: 'static>(
@ -318,7 +318,7 @@ impl<'a, Message: 'static> Widget<Message, iced::Theme, iced::Renderer>
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn on_event( fn update(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: Event, event: Event,
@ -340,7 +340,8 @@ impl<'a, Message: 'static> Widget<Message, iced::Theme, iced::Renderer>
viewport, viewport,
); );
if matches!(s, event::Status::Captured) { if matches!(s, event::Status::Captured) {
return event::Status::Captured; shell.capture_event();
return;
} }
let state = tree.state.downcast_mut::<State<()>>(); let state = tree.state.downcast_mut::<State<()>>();
@ -381,7 +382,8 @@ impl<'a, Message: 'static> Widget<Message, iced::Theme, iced::Renderer>
viewport, viewport,
); );
} }
return event::Status::Captured; shell.capture_event();
return;
} }
Event::Dnd(DndEvent::Offer(id, OfferEvent::Leave)) Event::Dnd(DndEvent::Offer(id, OfferEvent::Leave))
if id == Some(my_id) => if id == Some(my_id) =>
@ -404,7 +406,8 @@ impl<'a, Message: 'static> Widget<Message, iced::Theme, iced::Renderer>
viewport, viewport,
); );
} }
return event::Status::Captured; shell.capture_event();
return;
} }
Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y }))
if id == Some(my_id) => if id == Some(my_id) =>
@ -437,7 +440,8 @@ impl<'a, Message: 'static> Widget<Message, iced::Theme, iced::Renderer>
viewport, viewport,
); );
} }
return event::Status::Captured; shell.capture_event();
return;
} }
Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination))
if id == Some(my_id) => if id == Some(my_id) =>
@ -447,7 +451,8 @@ impl<'a, Message: 'static> Widget<Message, iced::Theme, iced::Renderer>
) { ) {
shell.publish(msg); shell.publish(msg);
} }
return event::Status::Captured; shell.capture_event();
return;
} }
Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop))
if id == Some(my_id) => if id == Some(my_id) =>
@ -457,7 +462,8 @@ impl<'a, Message: 'static> Widget<Message, iced::Theme, iced::Renderer>
) { ) {
shell.publish(msg); shell.publish(msg);
} }
return event::Status::Captured; shell.capture_event();
return;
} }
Event::Dnd(DndEvent::Offer( Event::Dnd(DndEvent::Offer(
id, id,
@ -471,7 +477,8 @@ impl<'a, Message: 'static> Widget<Message, iced::Theme, iced::Renderer>
) { ) {
shell.publish(msg); shell.publish(msg);
} }
return event::Status::Captured; shell.capture_event();
return;
} }
Event::Dnd(DndEvent::Offer( Event::Dnd(DndEvent::Offer(
id, id,
@ -489,7 +496,8 @@ impl<'a, Message: 'static> Widget<Message, iced::Theme, iced::Renderer>
shell.publish(msg); shell.publish(msg);
return ret; return ret;
} }
return event::Status::Captured; shell.capture_event();
return;
} }
_ => {} _ => {}
} }

View file

@ -1,16 +1,16 @@
use std::any::Any; use std::any::Any;
use iced::Element;
use iced::id::Id; use iced::id::Id;
use iced::widget::container; use iced::widget::container;
use iced::Element;
use iced::{ use iced::{
Event, Length, Point, Rectangle,
clipboard::dnd::{DndAction, DndEvent, SourceEvent}, clipboard::dnd::{DndAction, DndEvent, SourceEvent},
event, mouse, overlay, Event, Length, Point, Rectangle, event, mouse, overlay,
}; };
use iced_core::{ use iced_core::{
layout, renderer, Clipboard, Shell, layout, renderer,
widget::{tree, Tree}, widget::{Tree, tree},
Clipboard, Shell,
}; };
use iced_core::{Layout, Widget}; use iced_core::{Layout, Widget};
@ -37,11 +37,11 @@ pub struct DndSource<'a, Message, AppMessage, D> {
} }
impl< impl<
'a, 'a,
Message: 'static, Message: 'static,
AppMessage: 'static, AppMessage: 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> DndSource<'a, Message, AppMessage, D> > DndSource<'a, Message, AppMessage, D>
{ {
pub fn new(child: impl Into<Element<'a, Message>>) -> Self { pub fn new(child: impl Into<Element<'a, Message>>) -> Self {
Self { Self {
@ -119,11 +119,11 @@ impl<
} }
impl< impl<
'a, 'a,
Message: 'static, Message: 'static,
AppMessage: 'static, AppMessage: 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> Widget<Message, iced::Theme, iced::Renderer> > Widget<Message, iced::Theme, iced::Renderer>
for DndSource<'a, Message, AppMessage, D> for DndSource<'a, Message, AppMessage, D>
{ {
fn children(&self) -> Vec<Tree> { fn children(&self) -> Vec<Tree> {
@ -184,7 +184,7 @@ impl<
); );
} }
fn on_event( fn update(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: Event, event: Event,
@ -218,14 +218,16 @@ impl<
state.left_pressed_position = Some(position); state.left_pressed_position = Some(position);
// dbg!(&state, &self.id); // dbg!(&state, &self.id);
return event::Status::Captured; shell.capture_event();
return;
} }
} }
mouse::Event::ButtonReleased(mouse::Button::Left) mouse::Event::ButtonReleased(mouse::Button::Left)
if state.left_pressed_position.is_some() => if state.left_pressed_position.is_some() =>
{ {
state.left_pressed_position = None; state.left_pressed_position = None;
return event::Status::Captured; shell.capture_event();
return;
} }
mouse::Event::CursorMoved { .. } => { mouse::Event::CursorMoved { .. } => {
if let Some(position) = cursor.position() { if let Some(position) = cursor.position() {
@ -258,7 +260,8 @@ impl<
} else if cursor.is_over(layout.bounds()) { } else if cursor.is_over(layout.bounds()) {
state.hovered = true; state.hovered = true;
} }
return event::Status::Captured; shell.capture_event();
return;
} }
} }
_ => return ret, _ => return ret,
@ -268,7 +271,8 @@ impl<
)) => { )) => {
if state.is_dragging { if state.is_dragging {
state.is_dragging = false; state.is_dragging = false;
return event::Status::Captured; shell.capture_event();
return;
} }
return ret; return ret;
} }
@ -355,11 +359,11 @@ impl<
} }
impl< impl<
'a, 'a,
Message: 'static, Message: 'static,
AppMessage: 'static, AppMessage: 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> From<DndSource<'a, Message, AppMessage, D>> for Element<'a, Message> > From<DndSource<'a, Message, AppMessage, D>> for Element<'a, Message>
{ {
fn from(e: DndSource<'a, Message, AppMessage, D>) -> Element<'a, Message> { fn from(e: DndSource<'a, Message, AppMessage, D>) -> Element<'a, Message> {
Element::new(e) Element::new(e)

View file

@ -273,7 +273,7 @@ where
}); });
} }
fn on_event( fn update(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: Event, event: Event,

View file

@ -10,7 +10,7 @@ tester = ["iced/tester"]
[dependencies] [dependencies]
iced.workspace = true iced.workspace = true
iced.features = ["tokio", "debug", "time-travel", "winit"] iced.features = ["tokio", "debug", "time-travel", "winit", "wgpu"]
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View file

@ -452,7 +452,7 @@ fn empty_message(message: &str) -> Element<'_, Message> {
.align_x(Center) .align_x(Center)
.style(subtle), .style(subtle),
) )
.height(200) .height(200u16)
.into() .into()
} }
@ -461,7 +461,7 @@ fn empty_message(message: &str) -> Element<'_, Message> {
fn icon(unicode: char) -> Text<'static> { fn icon(unicode: char) -> Text<'static> {
text(unicode.to_string()) text(unicode.to_string())
.font(Font::with_name("Iced-Todos-Icons")) .font(Font::with_name("Iced-Todos-Icons"))
.width(20) .width(20u16)
.align_x(Center) .align_x(Center)
.shaping(text::Shaping::Basic) .shaping(text::Shaping::Basic)
} }

View file

@ -12,7 +12,7 @@ use crate::task::{self, Task};
pub use raw_window_handle; pub use raw_window_handle;
use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, WindowHandle};
/// An operation to be performed on some window. /// An operation to be performed on some window.
pub enum Action { pub enum Action {
@ -90,6 +90,9 @@ pub enum Action {
/// - **Web:** Unsupported. /// - **Web:** Unsupported.
ToggleDecorations(Id), ToggleDecorations(Id),
/// Runs the closure with the native window handle of the window with the given [`Id`].
RunWithHandle(Id, Box<dyn FnOnce(WindowHandle<'_>) + Send>),
/// Request 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, /// is already focused. How requesting for user attention manifests is platform dependent,
/// see [`UserAttention`] for details. /// see [`UserAttention`] for details.
@ -505,6 +508,26 @@ where
}) })
} }
/// Runs the given callback with the native window handle for the window with the given id.
///
/// Note that if the window closes before this call is processed the callback will not be run.
pub fn run_with_handle<T>(
id: Id,
f: impl FnOnce(WindowHandle<'_>) -> T + Send + 'static,
) -> Task<T>
where
T: Send + 'static,
{
task::oneshot(move |channel| {
crate::Action::Window(Action::RunWithHandle(
id,
Box::new(move |handle| {
let _ = channel.send(f(handle));
}),
))
})
}
/// Captures a [`Screenshot`] from the window. /// Captures a [`Screenshot`] from the window.
pub fn screenshot(id: Id) -> Task<Screenshot> { pub fn screenshot(id: Id) -> Task<Screenshot> {
task::oneshot(move |channel| { task::oneshot(move |channel| {

View file

@ -293,7 +293,7 @@ impl State {
image.opacity, image.opacity,
image.snap, image.snap,
atlas_entry, atlas_entry,
match handle.filter_method { match image.filter_method {
crate::core::image::FilterMethod::Nearest => { crate::core::image::FilterMethod::Nearest => {
&mut self.nearest_instances &mut self.nearest_instances
} }

View file

@ -153,6 +153,18 @@ where
self self
} }
/// Maybe adds an [`Element`] to the [`Column`].
pub fn push_maybe(
self,
child: Option<impl Into<Element<'a, Message, Theme, Renderer>>>,
) -> Self {
if let Some(child) = child {
self.push(child)
} else {
self
}
}
/// Extends the [`Column`] with the given children. /// Extends the [`Column`] with the given children.
pub fn extend( pub fn extend(
self, self,

View file

@ -180,7 +180,7 @@ where
); );
} }
fn on_event( fn update(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: Event, event: Event,
@ -465,7 +465,7 @@ where
.unwrap_or_default() .unwrap_or_default()
} }
fn on_event( fn update(
&mut self, &mut self,
event: Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,

View file

@ -144,6 +144,18 @@ where
self self
} }
/// Maybe adds an [`Element`] to the [`Row`].
pub fn push_maybe(
self,
child: Option<impl Into<Element<'a, Message, Theme, Renderer>>>,
) -> Self {
if let Some(child) = child {
self.push(child)
} else {
self
}
}
/// Extends the [`Row`] with the given children. /// Extends the [`Row`] with the given children.
pub fn extend( pub fn extend(
self, self,
@ -240,9 +252,12 @@ where
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.children()) .zip(layout.children())
.for_each(|((child, state), c_layout)| { .for_each(|((child, state), c_layout)| {
child child.as_widget_mut().operate(
.as_widget_mut() state,
.operate(state, c_layout.with_virtual_offset(layout.virtual_offset()), renderer, operation); c_layout.with_virtual_offset(layout.virtual_offset()),
renderer,
operation,
);
}); });
}); });
} }
@ -265,7 +280,13 @@ where
.zip(layout.children()) .zip(layout.children())
{ {
child.as_widget_mut().update( child.as_widget_mut().update(
tree, event, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, shell, tree,
event,
c_layout.with_virtual_offset(layout.virtual_offset()),
cursor,
renderer,
clipboard,
shell,
viewport, viewport,
); );
} }
@ -284,9 +305,13 @@ where
.zip(&tree.children) .zip(&tree.children)
.zip(layout.children()) .zip(layout.children())
.map(|((child, tree), c_layout)| { .map(|((child, tree), c_layout)| {
child child.as_widget().mouse_interaction(
.as_widget() tree,
.mouse_interaction(tree, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer) c_layout.with_virtual_offset(layout.virtual_offset()),
cursor,
viewport,
renderer,
)
}) })
.max() .max()
.unwrap_or_default() .unwrap_or_default()
@ -317,7 +342,13 @@ where
.filter(|(_, layout)| layout.bounds().intersects(viewport)) .filter(|(_, layout)| layout.bounds().intersects(viewport))
{ {
child.as_widget().draw( child.as_widget().draw(
tree, renderer, theme, style, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, tree,
renderer,
theme,
style,
c_layout.with_virtual_offset(layout.virtual_offset()),
cursor,
viewport,
); );
} }
} }

View file

@ -24,10 +24,10 @@ pub use iced_program as program;
pub use program::core; pub use program::core;
pub use program::graphics; pub use program::graphics;
pub use program::runtime; pub use program::runtime;
use raw_window_handle::HasWindowHandle;
pub use runtime::futures; pub use runtime::futures;
use window_clipboard::mime::ClipboardStoreData; use window_clipboard::mime::ClipboardStoreData;
pub use winit; pub use winit;
use winit::raw_window_handle::HasWindowHandle;
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]
pub mod a11y; pub mod a11y;
@ -2271,6 +2271,16 @@ where
window.raw.set_blur(false); window.raw.set_blur(false);
} }
} }
window::Action::RunWithHandle(id, f) => {
use window::raw_window_handle::HasWindowHandle;
if let Some(handle) = window_manager
.get_mut(id)
.and_then(|window| window.raw.window_handle().ok())
{
f(handle);
}
}
}, },
Action::System(action) => match action { Action::System(action) => match action {
system::Action::GetInformation(_channel) => { system::Action::GetInformation(_channel) => {

View file

@ -10,7 +10,7 @@ use iced_runtime::{
core::{Vector, window}, core::{Vector, window},
platform_specific, user_interface, platform_specific, user_interface,
}; };
use raw_window_handle::HasWindowHandle; use winit::raw_window_handle::HasWindowHandle;
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "wayland", target_os = "linux"))]
pub mod wayland; pub mod wayland;