libcosmic updates

This commit is contained in:
Ashley Wulber 2024-10-16 20:36:46 -04:00 committed by Ashley Wulber
parent 9c62f19e4b
commit 0491c4baaa
91 changed files with 3550 additions and 2300 deletions

View file

@ -9,9 +9,10 @@ use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::Tree;
use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget};
use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget};
pub use iced_style::container::{Appearance, StyleSheet};
use iced_widget::container;
pub use iced_widget::container::{Catalog, Style};
pub fn aspect_ratio_container<'a, Message: 'static, T>(
content: T,
@ -117,22 +118,22 @@ where
/// Centers the contents in the horizontal axis of the [`Container`].
#[must_use]
pub fn center_x(mut self) -> Self {
self.container = self.container.center_x();
pub fn center_x(mut self, width: Length) -> Self {
self.container = self.container.center_x(width);
self
}
/// Centers the contents in the vertical axis of the [`Container`].
#[must_use]
pub fn center_y(mut self) -> Self {
self.container = self.container.center_y();
pub fn center_y(mut self, height: Length) -> Self {
self.container = self.container.center_y(height);
self
}
/// Sets the style of the [`Container`].
#[must_use]
pub fn style(mut self, style: impl Into<<crate::Theme as StyleSheet>::Style>) -> Self {
self.container = self.container.style(style);
pub fn class(mut self, style: impl Into<crate::style::Container<'a>>) -> Self {
self.container = self.container.class(style);
self
}
}
@ -173,9 +174,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation<
iced_core::widget::OperationOutputWrapper<Message>,
>,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
self.container.operate(tree, layout, renderer, operation);
}
@ -241,8 +240,9 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
self.container.overlay(tree, layout, renderer)
self.container.overlay(tree, layout, renderer, translation)
}
}

276
src/widget/autosize.rs Normal file
View file

@ -0,0 +1,276 @@
//! Autosize Container, which will resize the window to its contents.
use cctk::sctk::shell::xdg::window;
use iced_core::event::{self, Event};
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::{Id, Tree};
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
pub use iced_widget::container::{Catalog, Style};
pub fn autosize<'a, Message: 'static, Theme, E>(
content: E,
id: Id,
) -> Autosize<'a, Message, Theme, crate::Renderer>
where
E: Into<Element<'a, Message, Theme, crate::Renderer>>,
Theme: iced_widget::container::Catalog,
<Theme as iced_widget::container::Catalog>::Class<'a>: From<crate::theme::Container<'a>>,
{
Autosize::new(content, id)
}
/// An element decorating some content.
///
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
pub struct Autosize<'a, Message, Theme, Renderer>
where
Renderer: iced_core::Renderer,
{
content: Element<'a, Message, Theme, Renderer>,
id: Id,
limits: layout::Limits,
auto_width: bool,
auto_height: bool,
}
impl<'a, Message, Theme, Renderer> Autosize<'a, Message, Theme, Renderer>
where
Renderer: iced_core::Renderer,
{
/// Creates an empty [`IdContainer`].
pub(crate) fn new<T>(content: T, id: Id) -> Self
where
T: Into<Element<'a, Message, Theme, Renderer>>,
{
Autosize {
content: content.into(),
id,
limits: layout::Limits::NONE,
auto_width: true,
auto_height: true,
}
}
pub fn limits(mut self, limits: layout::Limits) -> Self {
self.limits = limits;
self
}
pub fn auto_width(mut self, auto_width: bool) -> Self {
self.auto_width = auto_width;
self
}
pub fn auto_height(mut self, auto_height: bool) -> Self {
self.auto_height = auto_height;
self
}
pub fn max_width(mut self, v: f32) -> Self {
self.limits = self.limits.max_width(v);
self
}
pub fn max_height(mut self, v: f32) -> Self {
self.limits = self.limits.max_height(v);
self
}
pub fn min_width(mut self, v: f32) -> Self {
self.limits = self.limits.min_width(v);
self
}
pub fn min_height(mut self, v: f32) -> Self {
self.limits = self.limits.min_height(v);
self
}
}
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Autosize<'a, Message, Theme, Renderer>
where
Renderer: iced_core::Renderer,
{
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.content)]
}
fn diff(&mut self, tree: &mut Tree) {
tree.children[0].diff(&mut self.content);
}
fn size(&self) -> iced_core::Size<Length> {
self.content.as_widget().size()
}
fn layout(
&self,
tree: &mut Tree,
renderer: &Renderer,
_limits: &layout::Limits,
) -> layout::Node {
let mut limits = self.limits;
let min = self.limits.min();
let max = self.limits.max();
if self.auto_width {
limits.min_width(min.width);
limits.max_width(max.width);
}
if self.auto_height {
limits.min_height(min.height);
limits.max_height(max.height);
}
let node = self
.content
.as_widget()
.layout(&mut tree.children[0], renderer, &self.limits);
let size = node.size();
layout::Node::with_children(size, vec![node])
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
self.content.as_widget().operate(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
operation,
);
});
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
#[cfg(feature = "wayland")]
if matches!(
event,
Event::PlatformSpecific(event::PlatformSpecific::Wayland(
event::wayland::Event::RequestResize
))
) {
let bounds = layout.bounds().size();
clipboard.request_logical_window_size(bounds.width.max(1.), bounds.height.max(1.));
}
self.content.as_widget_mut().on_event(
&mut tree.children[0],
event.clone(),
layout.children().next().unwrap(),
cursor_position,
renderer,
clipboard,
shell,
viewport,
)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
let content_layout = layout.children().next().unwrap();
self.content.as_widget().mouse_interaction(
&tree.children[0],
content_layout,
cursor_position,
viewport,
renderer,
)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
renderer_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: mouse::Cursor,
viewport: &Rectangle,
) {
let content_layout = layout.children().next().unwrap();
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
renderer_style,
content_layout,
cursor_position,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
translation,
)
}
fn drag_destinations(
&self,
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
let content_layout = layout.children().next().unwrap();
self.content.as_widget().drag_destinations(
&state.children[0],
content_layout,
renderer,
dnd_rectangles,
);
}
fn id(&self) -> Option<crate::widget::Id> {
Some(self.id.clone())
}
fn set_id(&mut self, id: crate::widget::Id) {
self.id = id;
}
}
impl<'a, Message, Theme, Renderer> From<Autosize<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Renderer: 'a + iced_core::Renderer,
Theme: 'a,
{
fn from(c: Autosize<'a, Message, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> {
Element::new(c)
}
}

View file

@ -1,7 +1,7 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use super::{Builder, Style};
use super::{Builder, ButtonClass};
use crate::widget::{
icon::{self, Handle},
tooltip,
@ -48,7 +48,7 @@ impl<'a, Message> Button<'a, Message> {
line_height: 20,
font_size: 14,
font_weight: Weight::Normal,
style: Style::Icon,
class: ButtonClass::Icon,
variant: icon,
}
}
@ -121,7 +121,7 @@ impl<'a, Message> Button<'a, Message> {
pub fn vertical(mut self, vertical: bool) -> Self {
self.variant.vertical = vertical;
self.style = Style::IconVertical;
self.class = ButtonClass::IconVertical;
self
}
}
@ -157,7 +157,7 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
crate::widget::column::with_children(content)
.padding(builder.padding)
.spacing(builder.spacing)
.align_items(Alignment::Center)
.align_x(Alignment::Center)
.apply(super::custom)
} else {
crate::widget::row::with_children(content)
@ -165,7 +165,7 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
.width(builder.width)
.height(builder.height)
.spacing(builder.spacing)
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.apply(super::custom)
};
@ -174,18 +174,22 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
.id(builder.id)
.on_press_maybe(builder.on_press)
.selected(builder.variant.selected)
.style(builder.style);
.class(builder.class);
if builder.tooltip.is_empty() {
button.into()
} else {
tooltip(button, builder.tooltip, tooltip::Position::Top)
.size(builder.font_size)
.font(crate::font::Font {
weight: builder.font_weight,
..crate::font::default()
})
.into()
tooltip(
button,
crate::widget::text(builder.tooltip)
.size(builder.font_size)
.font(crate::font::Font {
weight: builder.font_weight,
..crate::font::default()
}),
tooltip::Position::Top,
)
.into()
}
}
}

View file

@ -42,7 +42,7 @@ impl<'a, Message> Button<'a, Message> {
line_height: 20,
font_size: 14,
font_weight: Weight::Normal,
style: Style::Image,
class: crate::theme::style::Button::Image,
variant,
}
}
@ -80,7 +80,7 @@ where
.selected(builder.variant.selected)
.id(builder.id)
.on_press_maybe(builder.on_press)
.style(builder.style)
.class(builder.class)
.into()
}
}

View file

@ -4,7 +4,7 @@
//! Hyperlink button widget
use super::Builder;
use super::Style;
use super::ButtonClass;
use crate::prelude::*;
use crate::widget::icon::{self, Handle};
use crate::widget::{button, row, tooltip};
@ -44,7 +44,7 @@ impl<'a, Message> Button<'a, Message> {
line_height: 20,
font_size: 14,
font_weight: Weight::Normal,
style: Style::Link,
class: ButtonClass::Link,
variant: link,
}
}
@ -81,23 +81,27 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
.width(builder.width)
.height(builder.height)
.spacing(builder.spacing)
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.apply(button::custom)
.padding(0)
.id(builder.id)
.on_press_maybe(builder.on_press.take())
.style(builder.style);
.class(builder.class);
if builder.tooltip.is_empty() {
button.into()
} else {
tooltip(button, builder.tooltip, tooltip::Position::Top)
.size(builder.font_size)
.font(crate::font::Font {
weight: builder.font_weight,
..crate::font::default()
})
.into()
tooltip(
button,
crate::widget::text(builder.tooltip)
.size(builder.font_size)
.font(crate::font::Font {
weight: builder.font_weight,
..crate::font::default()
}),
tooltip::Position::Top,
)
.into()
}
}
}

View file

@ -3,7 +3,7 @@
//! Button widgets for COSMIC applications.
pub use crate::theme::Button as Style;
pub use crate::theme::Button as ButtonClass;
pub mod link;
use derive_setters::Setters;
@ -26,7 +26,7 @@ pub use image::Button as ImageButton;
mod style;
#[doc(inline)]
pub use style::{Appearance, StyleSheet};
pub use style::{Catalog, Style};
mod text;
#[doc(inline)]
@ -105,7 +105,7 @@ pub struct Builder<'a, Message, Variant> {
font_weight: Weight,
/// The preferred style of the button.
style: Style,
class: ButtonClass,
#[setters(skip)]
variant: Variant,

View file

@ -9,7 +9,7 @@ use crate::theme::THEME;
/// The appearance of a button.
#[must_use]
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
pub struct Style {
/// The amount of offset to apply to the shadow of the button.
pub shadow_offset: Vector,
@ -41,7 +41,7 @@ pub struct Appearance {
pub text_color: Option<Color>,
}
impl Appearance {
impl Style {
// TODO: `Radius` is not `const fn` compatible.
pub fn new() -> Self {
let rad_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0;
@ -60,33 +60,34 @@ impl Appearance {
}
}
impl std::default::Default for Appearance {
impl std::default::Default for Style {
fn default() -> Self {
Self::new()
}
}
// TODO update to match other styles
/// A set of rules that dictate the style of a button.
pub trait StyleSheet {
pub trait Catalog {
/// The supported style of the [`StyleSheet`].
type Style: Default;
type Class: Default;
/// Produces the active [`Appearance`] of a button.
fn active(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance;
fn active(&self, focused: bool, selected: bool, style: &Self::Class) -> Style;
/// Produces the disabled [`Appearance`] of a button.
fn disabled(&self, style: &Self::Style) -> Appearance;
fn disabled(&self, style: &Self::Class) -> Style;
/// [`Appearance`] when the button is the target of a DND operation.
fn drop_target(&self, style: &Self::Style) -> Appearance {
fn drop_target(&self, style: &Self::Class) -> Style {
self.hovered(false, false, style)
}
/// Produces the hovered [`Appearance`] of a button.
fn hovered(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance;
fn hovered(&self, focused: bool, selected: bool, style: &Self::Class) -> Style;
/// Produces the pressed [`Appearance`] of a button.
fn pressed(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance;
fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style;
/// Background color of the selection indicator
fn selection_background(&self) -> Background;

View file

@ -1,7 +1,7 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use super::{Builder, Style};
use super::{Builder, ButtonClass, Style};
use crate::widget::{icon, row, tooltip};
use crate::{ext::CollectionWidget, Element};
use apply::Apply;
@ -14,14 +14,14 @@ pub type Button<'a, Message> = Builder<'a, Message, Text>;
pub fn destructive<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
Button::new(Text::new())
.label(label)
.style(Style::Destructive)
.class(ButtonClass::Destructive)
}
/// A text button with the suggested style
pub fn suggested<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
Button::new(Text::new())
.label(label)
.style(Style::Suggested)
.class(ButtonClass::Suggested)
}
/// A text button with the standard style
@ -31,7 +31,9 @@ pub fn standard<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Messa
/// A text button with the text style
pub fn text<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
Button::new(Text::new()).label(label).style(Style::Text)
Button::new(Text::new())
.label(label)
.class(ButtonClass::Text)
}
/// The text variant of a button.
@ -66,7 +68,7 @@ impl<'a, Message> Button<'a, Message> {
line_height: 20,
font_size: 14,
font_weight: Weight::Normal,
style: Style::Standard,
class: ButtonClass::Standard,
variant: text,
}
}
@ -125,23 +127,27 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
.width(builder.width)
.height(builder.height)
.spacing(builder.spacing)
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.apply(super::custom)
.padding(0)
.id(builder.id)
.on_press_maybe(builder.on_press.take())
.style(builder.style);
.class(builder.class);
if builder.tooltip.is_empty() {
button.into()
} else {
tooltip(button, builder.tooltip, tooltip::Position::Top)
.size(builder.font_size)
.font(crate::font::Font {
weight: builder.font_weight,
..crate::font::default()
})
.into()
tooltip(
button,
crate::widget::text(builder.tooltip)
.size(builder.font_size)
.font(crate::font::Font {
weight: builder.font_weight,
..crate::font::default()
}),
tooltip::Position::Top,
)
.into()
}
}
}

View file

@ -7,7 +7,7 @@
//! A [`Button`] has some local [`State`].
use iced_runtime::core::widget::Id;
use iced_runtime::{keyboard, Command};
use iced_runtime::{keyboard, task, Action, Task};
use iced_core::event::{self, Event};
use iced_core::renderer::{self, Quad, Renderer};
@ -20,11 +20,11 @@ use iced_core::{overlay, Shadow};
use iced_core::{
Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget,
};
use iced_renderer::core::widget::{operation, OperationOutputWrapper};
use iced_renderer::core::widget::operation;
use crate::theme::THEME;
pub use super::style::{Appearance, StyleSheet};
pub use super::style::{Catalog, Style};
/// Internally defines different button widget variants.
enum Variant<Message> {
@ -173,7 +173,7 @@ impl<'a, Message> Button<'a, Message> {
}
/// Sets the style variant of this [`Button`].
pub fn style(mut self, style: crate::theme::Button) -> Self {
pub fn class(mut self, style: crate::theme::Button) -> Self {
self.style = style;
self
}
@ -257,7 +257,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
tree: &mut Tree,
layout: Layout<'_>,
renderer: &crate::Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
operation: &mut dyn Operation<()>,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.content.as_widget().operate(
@ -470,10 +470,11 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
selection_background,
);
iced_core::svg::Renderer::draw(
let svg_handle = svg::Svg::new(crate::widget::common::object_select().clone())
.color(icon_color);
iced_core::svg::Renderer::draw_svg(
renderer,
crate::widget::common::object_select().clone(),
Some(icon_color),
svg_handle,
Rectangle {
width: 16.0,
height: 16.0,
@ -498,11 +499,10 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
},
selection_background,
);
iced_core::svg::Renderer::draw(
let svg_handle = svg::Svg::new(close_icon.clone()).color(icon_color);
iced_core::svg::Renderer::draw_svg(
renderer,
close_icon.clone(),
Some(icon_color),
svg_handle,
Rectangle {
width: 16.0,
height: 16.0,
@ -533,11 +533,16 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &crate::Renderer,
mut translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let mut position = layout.bounds().position();
translation.x += position.x;
translation.y += position.y;
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
translation,
)
}
@ -748,11 +753,11 @@ pub fn update<'a, Message: Clone>(
pub fn draw<Renderer: iced_core::Renderer, Theme>(
renderer: &mut Renderer,
bounds: Rectangle,
styling: &super::style::Appearance,
draw_contents: impl FnOnce(&mut Renderer, &Appearance),
styling: &super::style::Style,
draw_contents: impl FnOnce(&mut Renderer, &Style),
is_image: bool,
) where
Theme: super::style::StyleSheet,
Theme: super::style::Catalog,
{
let doubled_border_width = styling.border_width * 2.0;
let doubled_outline_width = styling.outline_width * 2.0;
@ -909,9 +914,9 @@ pub fn mouse_interaction(
}
}
/// Produces a [`Command`] that focuses the [`Button`] with the given [`Id`].
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
Command::widget(operation::focusable::focus(id))
/// Produces a [`Task`] that focuses the [`Button`] with the given [`Id`].
pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
task::effect(Action::Widget(Box::new(operation::focusable::focus(id))))
}
impl operation::Focusable for State {

View file

@ -86,7 +86,7 @@ where
text(first_day_of_week.to_string())
.size(12)
.width(Length::Fixed(36.0))
.horizontal_alignment(Horizontal::Center),
.align_x(Horizontal::Center),
);
first_day_of_week = first_day_of_week.succ();
@ -138,17 +138,17 @@ fn date_button<Message>(
on_select: &dyn Fn(NaiveDate) -> Message,
) -> crate::widget::Button<'static, Message> {
let style = if is_day {
button::Style::Suggested
button::ButtonClass::Suggested
} else {
button::Style::Text
button::ButtonClass::Text
};
let button = button::custom(
text(format!("{}", date.day()))
.horizontal_alignment(Horizontal::Center)
.vertical_alignment(Vertical::Center),
.align_x(Horizontal::Center)
.align_y(Vertical::Center),
)
.style(style)
.class(style)
.height(Length::Fixed(36.0))
.width(Length::Fixed(36.0));

View file

@ -5,12 +5,12 @@ use iced_core::{Background, Color};
/// Appearance of the cards.
#[derive(Clone, Copy)]
pub struct Appearance {
pub struct Style {
pub card_1: Background,
pub card_2: Background,
}
impl Default for Appearance {
impl Default for Style {
fn default() -> Self {
Self {
card_1: Background::Color(Color::WHITE),
@ -20,7 +20,7 @@ impl Default for Appearance {
}
/// Defines the [`Appearance`] of a cards.
pub trait StyleSheet {
pub trait Catalog {
/// The default [`Appearance`] of the cards.
fn default(&self) -> Appearance;
fn default(&self) -> Style;
}

View file

@ -10,10 +10,10 @@ use std::time::{Duration, Instant};
use crate::theme::iced::Slider;
use crate::theme::{Button, THEME};
use crate::widget::{container, segmented_button::Entity, slider};
use crate::widget::{button::Catalog, container, segmented_button::Entity, slider};
use crate::Element;
use derive_setters::Setters;
use iced::Command;
use iced::Task;
use iced_core::event::{self, Event};
use iced_core::gradient::{ColorStop, Linear};
use iced_core::renderer::Quad;
@ -23,12 +23,11 @@ use iced_core::{
Rectangle, Renderer, Shadow, Shell, Size, Vector, Widget,
};
use iced_style::slider::{HandleShape, RailBackground};
use iced_widget::slider::{HandleShape, RailBackground};
use iced_widget::{canvas, column, horizontal_space, row, scrollable, vertical_space, Row};
use lazy_static::lazy_static;
use palette::{FromColor, RgbHue};
use super::button::StyleSheet;
use super::divider::horizontal;
use super::icon::{self, from_name};
use super::segmented_button::{self, SingleSelect};
@ -135,7 +134,7 @@ impl ColorPickerModel {
)
}
pub fn update<Message>(&mut self, update: ColorPickerUpdate) -> Command<Message> {
pub fn update<Message>(&mut self, update: ColorPickerUpdate) -> Task<Message> {
match update {
ColorPickerUpdate::ActiveColor(c) => {
self.must_clear_cache.store(true, Ordering::SeqCst);
@ -206,7 +205,7 @@ impl ColorPickerModel {
self.copied_at = None;
}
};
Command::none()
Task::none()
}
#[must_use]
@ -298,7 +297,7 @@ where
.width(self.width),
// canvas with gradient for the current color
// still needs the canvas and the handle to be drawn on it
container(vertical_space(self.height))
container(vertical_space().height(self.height))
.width(self.width)
.height(self.height),
slider(
@ -310,16 +309,21 @@ where
on_update(ColorPickerUpdate::ActiveColor(new))
}
)
.style(Slider::Custom {
.class(Slider::Custom {
active: Rc::new(|t| {
let cosmic = t.cosmic();
let mut a = slider::StyleSheet::active(t, &Slider::default());
a.rail.colors = RailBackground::Gradient {
gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
auto_angle: true,
};
let mut a =
slider::Catalog::style(t, &Slider::default(), slider::Status::Active);
// a.rail.colors = RailBackground::Gradient {
// gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
// auto_angle: true,
// };
a.rail.backgrounds = (
Background::Color(Color::TRANSPARENT),
Background::Color(Color::TRANSPARENT),
);
a.rail.width = 8.0;
a.handle.color = Color::TRANSPARENT;
a.handle.background = Color::TRANSPARENT.into();
a.handle.shape = HandleShape::Circle { radius: 8.0 };
a.handle.border_color = cosmic.palette.neutral_10.into();
a.handle.border_width = 4.0;
@ -327,13 +331,15 @@ where
}),
hovered: Rc::new(|t| {
let cosmic = t.cosmic();
let mut a = slider::StyleSheet::active(t, &Slider::default());
a.rail.colors = RailBackground::Gradient {
gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
auto_angle: true,
};
let mut a =
slider::Catalog::style(t, &Slider::default(), slider::Status::Active);
// a.rail.colors = RailBackground::Gradient {
// gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
// auto_angle: true,
// };
a.rail.backgrounds = (Color::TRANSPARENT.into(), Color::TRANSPARENT.into());
a.rail.width = 8.0;
a.handle.color = Color::TRANSPARENT;
a.handle.background = Color::TRANSPARENT.into();
a.handle.shape = HandleShape::Circle { radius: 8.0 };
a.handle.border_color = cosmic.palette.neutral_10.into();
a.handle.border_width = 4.0;
@ -341,13 +347,22 @@ where
}),
dragging: Rc::new(|t| {
let cosmic = t.cosmic();
let mut a = slider::StyleSheet::active(t, &Slider::default());
a.rail.colors = RailBackground::Gradient {
gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
auto_angle: true,
};
let mut a =
slider::Catalog::style(t, &Slider::default(), slider::Status::Active);
// a.rail.backgrounds = (
// RailBackground::Gradient(Gradient {
// gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
// auto_angle: true,
// }),
// RailBackground::Gradient {
// gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
// auto_angle: true,
// },
// );
a.rail.backgrounds =
(iced::Color::TRANSPARENT.into(), Color::TRANSPARENT.into());
a.rail.width = 8.0;
a.handle.color = Color::TRANSPARENT;
a.handle.background = Color::TRANSPARENT.into();
a.handle.shape = HandleShape::Circle { radius: 8.0 };
a.handle.border_color = cosmic.palette.neutral_10.into();
a.handle.border_width = 4.0;
@ -373,7 +388,7 @@ where
from_name("edit-copy-symbolic").size(spacing.space_s).into(),
))
.on_press(on_update(ColorPickerUpdate::Copied(Instant::now())))
.style(Button::Text);
.class(Button::Text);
match self.copied_at.take() {
Some(t) if Instant::now().duration_since(t) > Duration::from_secs(2) => {
@ -381,13 +396,13 @@ where
}
Some(_) => tooltip(
button,
copied_to_clipboard_label,
text(copied_to_clipboard_label),
iced_widget::tooltip::Position::Bottom,
)
.into(),
None => tooltip(
button,
copy_to_clipboard_label,
text(copy_to_clipboard_label),
iced_widget::tooltip::Position::Bottom,
)
.into(),
@ -431,7 +446,7 @@ where
)
.width(self.width)
.direction(iced_widget::scrollable::Direction::Horizontal(
scrollable::Properties::new().alignment(scrollable::Alignment::End),
scrollable::Scrollbar::new().anchor(scrollable::Anchor::End),
))
}]
.spacing(spacing.space_xxs),
@ -445,7 +460,7 @@ where
button::custom(
text(reset_to_default)
.width(self.width)
.horizontal_alignment(iced_core::alignment::Horizontal::Center)
.align_x(iced_core::alignment::Horizontal::Center)
)
.width(self.width)
.on_press(on_update(ColorPickerUpdate::Reset))
@ -461,18 +476,18 @@ where
button::custom(
text(cancel)
.width(self.width)
.horizontal_alignment(iced_core::alignment::Horizontal::Center)
.align_x(iced_core::alignment::Horizontal::Center)
)
.width(self.width)
.on_press(on_update(ColorPickerUpdate::Cancel)),
button::custom(
text(save)
.width(self.width)
.horizontal_alignment(iced_core::alignment::Horizontal::Center)
.align_x(iced_core::alignment::Horizontal::Center)
)
.width(self.width)
.on_press(on_update(ColorPickerUpdate::AppliedColor))
.style(Button::Suggested)
.class(Button::Suggested)
]
.spacing(spacing.space_xs)
.width(self.width),
@ -589,7 +604,7 @@ where
let translation = Vector::new(canvas_layout.bounds().x, canvas_layout.bounds().y);
iced_core::Renderer::with_translation(renderer, translation, |renderer| {
canvas::Renderer::draw(renderer, vec![geo]);
iced_renderer::geometry::Renderer::draw_geometry(renderer, geo);
});
let bounds = canvas_layout.bounds();
@ -657,10 +672,11 @@ where
state: &'b mut Tree,
layout: Layout<'_>,
renderer: &crate::Renderer,
translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
self.inner
.as_widget_mut()
.overlay(&mut state.children[0], layout, renderer)
.overlay(&mut state.children[0], layout, renderer, translation)
}
fn on_event(
@ -782,12 +798,12 @@ pub fn color_button<'a, Message: 'static>(
let spacing = THEME.lock().unwrap().cosmic().spacing;
button::custom(if color.is_some() {
Element::from(vertical_space(Length::Fixed(f32::from(spacing.space_s))))
Element::from(vertical_space().height(Length::Fixed(f32::from(spacing.space_s))))
} else {
Element::from(column![
vertical_space(Length::FillPortion(6)),
vertical_space().height(Length::FillPortion(6)),
row![
horizontal_space(Length::FillPortion(6)),
horizontal_space().width(Length::FillPortion(6)),
Icon::from(
icon::from_name("list-add-symbolic")
.prefer_svg(true)
@ -797,17 +813,17 @@ pub fn color_button<'a, Message: 'static>(
.width(icon_portion)
.height(Length::Fill)
.content_fit(iced_core::ContentFit::Contain),
horizontal_space(Length::FillPortion(6)),
horizontal_space().width(Length::FillPortion(6)),
]
.height(icon_portion)
.width(Length::Fill),
vertical_space(Length::FillPortion(6)),
vertical_space().height(Length::FillPortion(6)),
])
})
.width(Length::Fixed(f32::from(spacing.space_s)))
.height(Length::Fixed(f32::from(spacing.space_s)))
.on_press_maybe(on_press)
.style(crate::theme::Button::Custom {
.class(crate::theme::Button::Custom {
active: Box::new(move |focused, theme| {
let cosmic = theme.cosmic();
@ -817,7 +833,7 @@ pub fn color_button<'a, Message: 'static>(
(0.0, Color::TRANSPARENT)
};
let standard = theme.active(focused, false, &Button::Standard);
button::Appearance {
button::Style {
shadow_offset: Vector::default(),
background: color.map(Background::from).or(standard.background),
border_radius: cosmic.radius_xs().into(),
@ -834,7 +850,7 @@ pub fn color_button<'a, Message: 'static>(
let cosmic = theme.cosmic();
let standard = theme.disabled(&Button::Standard);
button::Appearance {
button::Style {
shadow_offset: Vector::default(),
background: color.map(Background::from).or(standard.background),
border_radius: cosmic.radius_xs().into(),
@ -857,7 +873,7 @@ pub fn color_button<'a, Message: 'static>(
};
let standard = theme.hovered(focused, false, &Button::Standard);
button::Appearance {
button::Style {
shadow_offset: Vector::default(),
background: color.map(Background::from).or(standard.background),
border_radius: cosmic.radius_xs().into(),
@ -880,7 +896,7 @@ pub fn color_button<'a, Message: 'static>(
};
let standard = theme.pressed(focused, false, &Button::Standard);
button::Appearance {
button::Style {
shadow_offset: Vector::default(),
background: color.map(Background::from).or(standard.background),
border_radius: cosmic.radius_xs().into(),

View file

@ -4,13 +4,14 @@
use crate::Element;
use iced::advanced::layout::{self, Layout};
use iced::advanced::widget::{self, Operation, OperationOutputWrapper};
use iced::advanced::widget::{self, Operation};
use iced::advanced::{overlay, renderer};
use iced::advanced::{Clipboard, Shell};
use iced::{event, mouse, Event, Point, Rectangle, Size};
use iced_core::Renderer;
pub(super) struct Overlay<'a, 'b, Message> {
pub(crate) position: Point,
pub(super) content: &'b mut Element<'a, Message>,
pub(super) tree: &'b mut widget::Tree,
pub(super) width: f32,
@ -21,13 +22,8 @@ impl<'a, 'b, Message> overlay::Overlay<Message, crate::Theme, crate::Renderer>
where
Message: Clone,
{
fn layout(
&mut self,
renderer: &crate::Renderer,
bounds: Size,
position: Point,
_translation: iced::Vector,
) -> layout::Node {
fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
let position = self.position;
let limits = layout::Limits::new(Size::ZERO, bounds)
.width(self.width)
.height(bounds.height - 8.0 - position.y);
@ -98,7 +94,7 @@ where
&mut self,
layout: Layout<'_>,
renderer: &crate::Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
operation: &mut dyn Operation<()>,
) {
self.content
.as_widget_mut()
@ -122,8 +118,9 @@ where
layout: Layout<'_>,
renderer: &crate::Renderer,
) -> Option<overlay::Element<'c, Message, crate::Theme, crate::Renderer>> {
let translation = iced::Vector::new(self.position.x, self.position.y);
self.content
.as_widget_mut()
.overlay(self.tree, layout, renderer)
.overlay(self.tree, layout, renderer, translation)
}
}

View file

@ -13,11 +13,9 @@ use iced_core::event::{self, Event};
use iced_core::widget::{Operation, Tree};
use iced_core::{
layout, mouse, overlay as iced_overlay, renderer, Clipboard, Layout, Length, Padding,
Rectangle, Shell, Widget,
Rectangle, Shell, Vector, Widget,
};
use iced_renderer::core::widget::OperationOutputWrapper;
#[must_use]
pub struct ContextDrawer<'a, Message> {
id: Option<iced_core::widget::Id>,
@ -48,19 +46,19 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
text::heading(header)
.width(Length::FillPortion(1))
.height(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center)
.vertical_alignment(alignment::Vertical::Center),
.align_x(alignment::Horizontal::Center)
.align_y(alignment::Vertical::Center),
)
.push(
button::text("Close")
.trailing_icon(icon::from_name("go-next-symbolic"))
.on_press(on_close)
.style(crate::theme::Button::Link)
.class(crate::theme::Button::Link)
.apply(container)
.width(Length::FillPortion(1))
.height(Length::Fill)
.align_x(alignment::Horizontal::Right)
.center_y(),
.center_y(Length::Fill),
)
// XXX must be done after pushing elements or it may be overwritten by size hints from contents
.height(Length::Fixed(80.0))
@ -84,7 +82,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
container(
LayerContainer::new(pane)
.layer(cosmic_theme::Layer::Primary)
.style(crate::style::Container::ContextDrawer)
.class(crate::style::Container::ContextDrawer)
.width(Length::Fill)
.height(Length::Fill)
.max_width(max_width),
@ -159,7 +157,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, Renderer> for ContextDraw
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
operation: &mut dyn Operation<()>,
) {
self.content
.as_widget()
@ -232,17 +230,20 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, Renderer> for ContextDraw
tree: &'b mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,
translation: Vector,
) -> Option<iced_overlay::Element<'b, Message, crate::Theme, Renderer>> {
let bounds = layout.bounds();
Some(iced_overlay::Element::new(
layout.position(),
Box::new(Overlay {
content: &mut self.drawer,
tree: &mut tree.children[1],
width: bounds.width,
}),
))
let mut position = layout.position();
position.x += translation.x;
position.y += translation.y;
Some(iced_overlay::Element::new(Box::new(Overlay {
content: &mut self.drawer,
tree: &mut tree.children[1],
width: bounds.width,
position,
})))
}
#[cfg(feature = "a11y")]

View file

@ -8,7 +8,7 @@ use crate::widget::menu::{
};
use derive_setters::Setters;
use iced::touch::Finger;
use iced::Event;
use iced::{Event, Vector};
use iced_core::widget::{tree, Tree, Widget};
use iced_core::{event, mouse, touch, Length, Point, Size};
use std::collections::HashSet;
@ -144,9 +144,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
tree: &mut Tree,
layout: iced_core::Layout<'_>,
renderer: &crate::Renderer,
operation: &mut dyn iced_core::widget::Operation<
iced_core::widget::OperationOutputWrapper<Message>,
>,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
self.content
.as_widget()
@ -213,6 +211,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
tree: &'b mut Tree,
layout: iced_core::Layout<'_>,
_renderer: &crate::Renderer,
translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let state = tree.state.downcast_ref::<LocalState>();
@ -247,6 +246,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
root_bounds_list: vec![bounds],
path_highlight: Some(PathHighlight::MenuActive),
style: &crate::theme::menu_bar::MenuBarStyle::Default,
position: Point::new(translation.x, translation.y),
}
.overlay(),
)

View file

@ -72,11 +72,13 @@ impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Mes
let mut content_col = widget::column::with_capacity(3 + dialog.controls.len() * 2);
content_col = content_col.push(widget::text::title3(dialog.title));
if let Some(body) = dialog.body {
content_col = content_col.push(widget::vertical_space(Length::Fixed(space_xxs.into())));
content_col =
content_col.push(widget::vertical_space().height(Length::Fixed(space_xxs.into())));
content_col = content_col.push(widget::text::body(body));
}
for control in dialog.controls {
content_col = content_col.push(widget::vertical_space(Length::Fixed(space_s.into())));
content_col =
content_col.push(widget::vertical_space().height(Length::Fixed(space_s.into())));
content_col = content_col.push(control);
}
@ -90,7 +92,7 @@ impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Mes
if let Some(button) = dialog.tertiary_action {
button_row = button_row.push(button);
}
button_row = button_row.push(widget::horizontal_space(Length::Fill));
button_row = button_row.push(widget::horizontal_space().width(Length::Fill));
if let Some(button) = dialog.secondary_action {
button_row = button_row.push(button);
}
@ -103,7 +105,7 @@ impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Mes
widget::column::with_children(vec![content_row.into(), button_row.into()])
.spacing(space_l),
)
.style(style::Container::Dialog)
.class(style::Container::Dialog)
.padding(space_m)
.width(Length::Fixed(570.0)),
)

View file

@ -3,6 +3,8 @@ use std::{
sync::atomic::{AtomicU64, Ordering},
};
use iced::Vector;
use crate::{
iced::{
clipboard::{
@ -280,9 +282,7 @@ impl<'a, Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
tree: &mut Tree,
layout: layout::Layout<'_>,
renderer: &crate::Renderer,
operation: &mut dyn iced_core::widget::Operation<
iced_core::widget::OperationOutputWrapper<Message>,
>,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
self.container
.as_widget()
@ -496,10 +496,11 @@ impl<'a, Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
tree: &'b mut Tree,
layout: layout::Layout<'_>,
renderer: &crate::Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
self.container
.as_widget_mut()
.overlay(&mut tree.children[0], layout, renderer)
.overlay(&mut tree.children[0], layout, renderer, translation)
}
fn drag_destinations(

View file

@ -1,69 +1,77 @@
use std::any::Any;
use iced_core::window;
use crate::{
iced::{
clipboard::dnd::{DndAction, DndEvent, SourceEvent},
event, mouse, overlay, Event, Length, Point, Rectangle,
event, mouse, overlay, Event, Length, Point, Rectangle, Vector,
},
iced_core::{
self, layout, renderer,
widget::{tree, Tree},
Clipboard, Shell,
},
iced_style,
widget::{container, Id, Widget},
Element,
};
pub fn dnd_source<
'a,
Message: 'static,
AppMessage: 'static,
Message: Clone + 'static,
D: iced::clipboard::mime::AsMimeTypes + Send + 'static,
>(
child: impl Into<Element<'a, Message>>,
) -> DndSource<'a, Message, AppMessage, D> {
) -> DndSource<'a, Message, D> {
DndSource::new(child)
}
pub struct DndSource<'a, Message, AppMessage, D> {
pub struct DndSource<'a, Message, D> {
id: Id,
action: DndAction,
container: Element<'a, Message>,
window: Option<window::Id>,
drag_content: Option<Box<dyn Fn() -> D>>,
drag_icon: Option<Box<dyn Fn() -> (Element<'static, AppMessage>, tree::State)>>,
drag_icon: Option<Box<dyn Fn() -> (Element<'static, ()>, tree::State)>>,
on_start: Option<Message>,
on_cancelled: Option<Message>,
on_finish: Option<Message>,
drag_threshold: f32,
_phantom: std::marker::PhantomData<AppMessage>,
}
impl<
'a,
Message: 'static,
AppMessage: 'static,
Message: Clone + 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> DndSource<'a, Message, AppMessage, D>
> DndSource<'a, Message, D>
{
pub fn new(child: impl Into<Element<'a, Message>>) -> Self {
Self {
id: Id::unique(),
window: None,
action: DndAction::Copy | DndAction::Move,
container: container(child).into(),
drag_content: None,
drag_icon: None,
drag_threshold: 8.0,
_phantom: std::marker::PhantomData,
on_start: None,
on_cancelled: None,
on_finish: None,
}
}
pub fn with_id(child: impl Into<Element<'a, Message>>, id: Id) -> Self {
Self {
id,
window: None,
action: DndAction::Copy | DndAction::Move,
container: container(child).into(),
drag_content: None,
drag_icon: None,
drag_threshold: 8.0,
_phantom: std::marker::PhantomData,
on_start: None,
on_cancelled: None,
on_finish: None,
}
}
@ -82,7 +90,7 @@ impl<
#[must_use]
pub fn drag_icon(
mut self,
f: impl Fn() -> (Element<'static, AppMessage>, tree::State) + 'static,
f: impl Fn() -> (Element<'static, ()>, tree::State) + 'static,
) -> Self {
self.drag_icon = Some(Box::new(f));
self
@ -98,10 +106,15 @@ impl<
let Some(content) = self.drag_content.as_ref().map(|f| f()) else {
return;
};
iced_core::clipboard::start_dnd(
clipboard,
false,
Some(iced_core::clipboard::DndSource::Widget(self.id.clone())),
if let Some(window) = self.window.as_ref() {
Some(iced_core::clipboard::DndSource::Surface(window.clone()))
} else {
Some(iced_core::clipboard::DndSource::Widget(self.id.clone()))
},
self.drag_icon.as_ref().map(|f| {
let (icon, state) = f();
(
@ -116,14 +129,33 @@ impl<
self.action,
);
}
pub fn on_start(mut self, on_start: Option<Message>) -> Self {
self.on_start = on_start;
self
}
pub fn on_cancel(mut self, on_cancelled: Option<Message>) -> Self {
self.on_cancelled = on_cancelled;
self
}
pub fn on_finish(mut self, on_finish: Option<Message>) -> Self {
self.on_finish = on_finish;
self
}
pub fn window(mut self, window: window::Id) -> Self {
self.window = Some(window);
self
}
}
impl<
'a,
Message: 'static,
AppMessage: 'static,
Message: Clone + 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> Widget<Message, crate::Theme, crate::Renderer> for DndSource<'a, Message, AppMessage, D>
> Widget<Message, crate::Theme, crate::Renderer> for DndSource<'a, Message, D>
{
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.container)]
@ -165,9 +197,7 @@ impl<
tree: &mut Tree,
layout: layout::Layout<'_>,
renderer: &crate::Renderer,
operation: &mut dyn iced_core::widget::Operation<
iced_core::widget::OperationOutputWrapper<Message>,
>,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
operation.custom((&mut tree.state) as &mut dyn Any, Some(&self.id));
operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
@ -210,7 +240,6 @@ impl<
}
state.left_pressed_position = Some(position);
// dbg!(&state, &self.id);
return event::Status::Captured;
}
}
@ -229,8 +258,10 @@ impl<
return ret;
}
if let Some(left_pressed_position) = state.left_pressed_position {
// dbg!(&state);
if position.distance(left_pressed_position) > self.drag_threshold {
if let Some(on_start) = self.on_start.as_ref() {
shell.publish(on_start.clone())
}
self.start_dnd(clipboard, state.cached_bounds);
state.is_dragging = true;
state.left_pressed_position = None;
@ -249,8 +280,21 @@ impl<
}
_ => return ret,
},
Event::Dnd(DndEvent::Source(SourceEvent::Cancelled | SourceEvent::Finished)) => {
Event::Dnd(DndEvent::Source(SourceEvent::Cancelled)) => {
if state.is_dragging {
if let Some(m) = self.on_cancelled.as_ref() {
shell.publish(m.clone());
}
state.is_dragging = false;
return event::Status::Captured;
}
return ret;
}
Event::Dnd(DndEvent::Source(SourceEvent::Finished)) => {
if state.is_dragging {
if let Some(m) = self.on_finish.as_ref() {
shell.publish(m.clone());
}
state.is_dragging = false;
return event::Status::Captured;
}
@ -308,10 +352,11 @@ impl<
tree: &'b mut Tree,
layout: layout::Layout<'_>,
renderer: &crate::Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
self.container
.as_widget_mut()
.overlay(&mut tree.children[0], layout, renderer)
.overlay(&mut tree.children[0], layout, renderer, translation)
}
fn drag_destinations(
@ -319,7 +364,7 @@ impl<
state: &Tree,
layout: layout::Layout<'_>,
renderer: &crate::Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
self.container.as_widget().drag_destinations(
&state.children[0],
@ -340,12 +385,11 @@ impl<
impl<
'a,
Message: 'static,
AppMessage: 'static,
Message: Clone + 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> From<DndSource<'a, Message, AppMessage, D>> for Element<'a, Message>
> From<DndSource<'a, Message, D>> for Element<'a, Message>
{
fn from(e: DndSource<'a, Message, AppMessage, D>) -> Element<'a, Message> {
fn from(e: DndSource<'a, Message, D>) -> Element<'a, Message> {
Element::new(e)
}
}

View file

@ -97,7 +97,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Menu<'a, S, Message> {
position: Point,
target_height: f32,
) -> overlay::Element<'a, Message, crate::Theme, crate::Renderer> {
overlay::Element::new(position, Box::new(Overlay::new(self, target_height)))
overlay::Element::new(Box::new(Overlay::new(self, target_height, position)))
}
}
@ -129,10 +129,15 @@ struct Overlay<'a, Message> {
width: f32,
target_height: f32,
style: (),
position: Point,
}
impl<'a, Message: 'a> Overlay<'a, Message> {
pub fn new<S: AsRef<str>>(menu: Menu<'a, S, Message>, target_height: f32) -> Self {
pub fn new<S: AsRef<str>>(
menu: Menu<'a, S, Message>,
target_height: f32,
position: Point,
) -> Self {
let Menu {
state,
options,
@ -160,7 +165,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
container = container
.padding(padding)
.style(crate::style::Container::Dropdown);
.class(crate::style::Container::Dropdown);
state.tree.diff(&mut container as &mut dyn Widget<_, _, _>);
@ -170,6 +175,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
width,
target_height,
style,
position,
}
}
}
@ -177,13 +183,8 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
for Overlay<'a, Message>
{
fn layout(
&mut self,
renderer: &crate::Renderer,
bounds: Size,
position: Point,
_translation: iced::Vector,
) -> layout::Node {
fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
let position = self.position;
let space_below = bounds.height - (position.y + self.target_height);
let space_above = position.y;
@ -447,10 +448,13 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
appearance.selected_background,
);
svg::Renderer::draw(
let svg_handle =
iced_core::Svg::new(crate::widget::common::object_select().clone())
.color(appearance.selected_text_color)
.border_radius(appearance.border_radius);
svg::Renderer::draw_svg(
renderer,
crate::widget::common::object_select().clone(),
Some(appearance.selected_text_color),
svg_handle,
Rectangle {
x: item_x + item_width - 16.0 - 8.0,
y: bounds.y + (bounds.height / 2.0 - 8.0),
@ -494,7 +498,7 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
text::Renderer::fill_text(
renderer,
Text {
content: option.as_ref(),
content: option.as_ref().to_string(),
bounds: bounds.size(),
size: Pixels(text_size),
line_height: self.text_line_height,
@ -502,7 +506,7 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
},
bounds.position(),
color,

View file

@ -97,7 +97,7 @@ where
position: Point,
target_height: f32,
) -> overlay::Element<'a, Message, crate::Theme, crate::Renderer> {
overlay::Element::new(position, Box::new(Overlay::new(self, target_height)))
overlay::Element::new(Box::new(Overlay::new(self, target_height, position)))
}
}
@ -129,12 +129,14 @@ struct Overlay<'a, Message> {
width: f32,
target_height: f32,
style: (),
position: Point,
}
impl<'a, Message: 'a> Overlay<'a, Message> {
pub fn new<S: AsRef<str>, Item: Clone + PartialEq>(
menu: Menu<'a, S, Item, Message>,
target_height: f32,
position: Point,
) -> Self {
let Menu {
state,
@ -163,7 +165,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
container = container
.padding(padding)
.style(crate::style::Container::Dropdown);
.class(crate::style::Container::Dropdown);
state.tree.diff(&mut container as &mut dyn Widget<_, _, _>);
@ -173,6 +175,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
width,
target_height,
style,
position,
}
}
}
@ -180,13 +183,8 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
for Overlay<'a, Message>
{
fn layout(
&mut self,
renderer: &crate::Renderer,
bounds: Size,
position: Point,
_translation: iced::Vector,
) -> layout::Node {
fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
let position = self.position;
let space_below = bounds.height - (position.y + self.target_height);
let space_above = position.y;
@ -537,10 +535,13 @@ where
appearance.selected_background,
);
svg::Renderer::draw(
let svg_handle =
svg::Svg::new(crate::widget::common::object_select().clone())
.color(appearance.selected_text_color)
.border_radius(appearance.border_radius);
svg::Renderer::draw_svg(
renderer,
crate::widget::common::object_select().clone(),
Some(appearance.selected_text_color),
svg_handle,
Rectangle {
x: item_x + item_width - 16.0 - 8.0,
y: bounds.y + (bounds.height / 2.0 - 8.0),
@ -587,7 +588,7 @@ where
text::Renderer::fill_text(
renderer,
Text {
content: option.as_ref(),
content: option.as_ref().to_string(),
bounds: bounds.size(),
size: iced::Pixels(text_size),
line_height: self.text_line_height,
@ -595,7 +596,7 @@ where
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
},
bounds.position(),
color,
@ -636,7 +637,7 @@ where
text::Renderer::fill_text(
renderer,
Text {
content: description.as_ref(),
content: description.as_ref().to_string(),
bounds: bounds.size(),
size: iced::Pixels(text_size),
line_height: text::LineHeight::Absolute(Pixels(text_line_height + 4.0)),
@ -644,7 +645,7 @@ where
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
},
bounds.position(),
appearance.description_color,

View file

@ -9,7 +9,7 @@ pub mod menu;
pub use menu::Menu;
mod widget;
pub use widget::{Appearance, Dropdown, StyleSheet};
pub use widget::{Catalog, Dropdown, Style};
pub fn dropdown<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>(
model: &'a Model<S, Item>,

View file

@ -9,10 +9,13 @@ use iced_core::event::{self, Event};
use iced_core::text::{self, Paragraph, Text};
use iced_core::widget::tree::{self, Tree};
use iced_core::{alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, Shadow};
use iced_core::{Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Widget};
use iced_core::{
Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
};
use iced_widget::pick_list;
use std::ffi::OsStr;
pub use iced_widget::style::pick_list::{Appearance, StyleSheet};
pub use iced_widget::pick_list::{Catalog, Style};
/// A widget for selecting a single value from a list of selections.
#[derive(Setters)]
@ -98,7 +101,7 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
for (_, item) in &list.options {
state
.selections
.push((item.clone(), crate::Paragraph::new()));
.push((item.clone(), crate::Plain::default()));
}
}
}
@ -182,6 +185,7 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &crate::Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let state = tree.state.downcast_mut::<State<Item>>();
@ -216,8 +220,8 @@ pub struct State<Item: Clone + PartialEq + 'static> {
keyboard_modifiers: keyboard::Modifiers,
is_open: bool,
hovered_option: Option<Item>,
selections: Vec<(Item, crate::Paragraph)>,
descriptions: Vec<crate::Paragraph>,
selections: Vec<(Item, crate::Plain)>,
descriptions: Vec<crate::Plain>,
}
impl<Item: Clone + PartialEq + 'static> State<Item> {
@ -259,7 +263,7 @@ pub fn layout(
text_size: f32,
text_line_height: text::LineHeight,
font: Option<crate::font::Font>,
selection: Option<(&str, &mut crate::Paragraph)>,
selection: Option<(&str, &mut crate::Plain)>,
) -> layout::Node {
use std::f32;
@ -267,7 +271,7 @@ pub fn layout(
let max_width = match width {
Length::Shrink => {
let measure = move |(label, paragraph): (_, &mut crate::Paragraph)| -> f32 {
let measure = move |(label, paragraph): (_, &mut crate::Plain)| -> f32 {
paragraph.update(Text {
content: label,
bounds: Size::new(f32::MAX, f32::MAX),
@ -277,7 +281,7 @@ pub fn layout(
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
});
paragraph.min_width().round()
};
@ -410,7 +414,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
)
.width({
let measure =
|label: &str, paragraph: &mut crate::Paragraph, line_height: text::LineHeight| {
|label: &str, paragraph: &mut crate::Plain, line_height: text::LineHeight| {
paragraph.update(Text {
content: label,
bounds: Size::new(f32::MAX, f32::MAX),
@ -420,7 +424,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
});
paragraph.min_width().round()
};
@ -433,7 +437,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
let paragraph = if state.descriptions.len() > desc_count {
&mut state.descriptions[desc_count]
} else {
state.descriptions.push(crate::Paragraph::new());
state.descriptions.push(crate::Plain::default());
state.descriptions.last_mut().unwrap()
};
desc_count += 1;
@ -448,7 +452,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
None => {
state
.selections
.push((item.clone(), crate::Paragraph::new()));
.push((item.clone(), crate::Plain::default()));
state.selections.len() - 1
}
};
@ -499,9 +503,9 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
let is_mouse_over = cursor.is_over(bounds);
let style = if is_mouse_over {
theme.hovered(&())
theme.style(&(), pick_list::Status::Hovered)
} else {
theme.active(&())
theme.style(&(), pick_list::Status::Active)
};
iced_core::Renderer::fill_quad(
@ -515,10 +519,10 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
);
if let Some(handle) = state.icon.clone() {
svg::Renderer::draw(
let svg_handle = iced_core::Svg::new(handle).color(style.text_color);
svg::Renderer::draw_svg(
renderer,
handle,
Some(style.text_color),
svg_handle,
Rectangle {
x: bounds.x + bounds.width - gap - 16.0,
y: bounds.center_y() - 8.0,
@ -541,7 +545,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
text::Renderer::fill_text(
renderer,
Text {
content,
content: content.to_string(),
size: iced::Pixels(text_size),
line_height: text_line_height,
font,
@ -549,7 +553,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
},
bounds.position(),
style.text_color,

View file

@ -5,16 +5,18 @@
use super::menu::{self, Menu};
use crate::widget::icon;
use derive_setters::Setters;
use iced::Radians;
use iced_core::event::{self, Event};
use iced_core::text::{self, Paragraph, Text};
use iced_core::widget::tree::{self, Tree};
use iced_core::{alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, Shadow};
use iced_core::{Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Widget};
use iced_core::{
Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
};
use iced_widget::pick_list::{self, Catalog};
use std::ffi::OsStr;
use std::hash::{DefaultHasher, Hash, Hasher};
pub use iced_widget::style::pick_list::{Appearance, StyleSheet};
/// A widget for selecting a single value from a list of selections.
#[derive(Setters)]
pub struct Dropdown<'a, S: AsRef<str>, Message> {
@ -62,6 +64,29 @@ impl<'a, S: AsRef<str>, Message> Dropdown<'a, S, Message> {
font: None,
}
}
fn update_paragraphs(&self, state: &mut tree::State) {
let state = state.downcast_mut::<State>();
state
.selections
.resize_with(self.selections.len(), crate::Plain::default);
for (i, selection) in self.selections.iter().enumerate() {
state.selections[i].update(Text {
content: selection.as_ref(),
bounds: Size::INFINITY,
// TODO use the renderer default size
size: iced::Pixels(self.text_size.unwrap_or(14.0)),
line_height: self.text_line_height,
font: self.font.unwrap_or(crate::font::default()),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
wrapping: text::Wrapping::default(),
});
}
}
}
impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Renderer>
@ -80,7 +105,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
state
.selections
.resize_with(self.selections.len(), crate::Paragraph::new);
.resize_with(self.selections.len(), crate::Plain::default);
state.hashes.resize(self.selections.len(), 0);
for (i, selection) in self.selections.iter().enumerate() {
@ -103,7 +128,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
});
}
}
@ -202,6 +227,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &crate::Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let state = tree.state.downcast_mut::<State>();
@ -237,8 +263,8 @@ pub struct State {
keyboard_modifiers: keyboard::Modifiers,
is_open: bool,
hovered_option: Option<usize>,
selections: Vec<crate::Paragraph>,
hashes: Vec<u64>,
selections: Vec<crate::Plain>,
}
impl State {
@ -280,7 +306,7 @@ pub fn layout(
text_size: f32,
text_line_height: text::LineHeight,
font: Option<crate::font::Font>,
selection: Option<(&str, &mut crate::Paragraph)>,
selection: Option<(&str, &mut crate::Plain)>,
) -> layout::Node {
use std::f32;
@ -288,7 +314,7 @@ pub fn layout(
let max_width = match width {
Length::Shrink => {
let measure = move |(label, paragraph): (_, &mut crate::Paragraph)| -> f32 {
let measure = move |(label, paragraph): (_, &mut crate::Plain)| -> f32 {
paragraph.update(Text {
content: label,
bounds: Size::new(f32::MAX, f32::MAX),
@ -298,7 +324,7 @@ pub fn layout(
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
});
paragraph.min_width().round()
};
@ -430,14 +456,14 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a>(
None,
)
.width({
let measure = |_label: &str, selection_paragraph: &mut crate::Paragraph| -> f32 {
let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 {
selection_paragraph.min_width().round()
};
selections
.iter()
.zip(state.selections.iter_mut())
.map(|(label, selection)| measure(label.as_ref(), selection))
.map(|(label, selection)| measure(label.as_ref(), selection.raw()))
.fold(0.0, |next, current| current.max(next))
+ gap
+ 16.0
@ -477,9 +503,9 @@ pub fn draw<'a, S>(
let is_mouse_over = cursor.is_over(bounds);
let style = if is_mouse_over {
theme.hovered(&())
theme.style(&(), pick_list::Status::Hovered)
} else {
theme.active(&())
theme.style(&(), pick_list::Status::Active)
};
iced_core::Renderer::fill_quad(
@ -493,10 +519,11 @@ pub fn draw<'a, S>(
);
if let Some(handle) = state.icon.clone() {
svg::Renderer::draw(
let svg_handle = svg::Svg::new(handle).color(style.text_color);
svg::Renderer::draw_svg(
renderer,
handle,
Some(style.text_color),
svg_handle,
Rectangle {
x: bounds.x + bounds.width - gap - 16.0,
y: bounds.center_y() - 8.0,
@ -517,7 +544,7 @@ pub fn draw<'a, S>(
text::Renderer::fill_text(
renderer,
Text {
content,
content: content.to_string(),
size: iced::Pixels(text_size),
line_height: text_line_height,
font,
@ -525,7 +552,7 @@ pub fn draw<'a, S>(
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
},
bounds.position(),
style.text_color,

View file

@ -6,9 +6,9 @@ use derive_setters::Setters;
use iced_core::event::{self, Event};
use iced_core::widget::{Operation, Tree};
use iced_core::{
layout, mouse, overlay, renderer, Clipboard, Layout, Length, Padding, Rectangle, Shell, Widget,
layout, mouse, overlay, renderer, Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector,
Widget,
};
use iced_renderer::core::widget::OperationOutputWrapper;
/// Responsively generates rows and columns of widgets based on its dimmensions.
#[derive(Setters)]
@ -132,7 +132,7 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer>
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
operation: &mut dyn Operation<()>,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.children
@ -225,8 +225,9 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer>
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
overlay::from_children(&mut self.children, tree, layout, renderer)
overlay::from_children(&mut self.children, tree, layout, renderer, translation)
}
#[cfg(feature = "a11y")]
@ -252,7 +253,7 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer>
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
for ((e, layout), state) in self
.children

View file

@ -7,9 +7,8 @@ use iced_core::event::{self, Event};
use iced_core::widget::{Operation, Tree};
use iced_core::{
layout, mouse, overlay, renderer, Alignment, Clipboard, Layout, Length, Padding, Rectangle,
Shell, Widget,
Shell, Vector, Widget,
};
use iced_renderer::core::widget::OperationOutputWrapper;
/// Responsively generates rows and columns of widgets based on its dimmensions.
#[must_use]
@ -154,7 +153,7 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for G
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
operation: &mut dyn Operation<()>,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.children
@ -247,8 +246,9 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for G
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
overlay::from_children(&mut self.children, tree, layout, renderer)
overlay::from_children(&mut self.children, tree, layout, renderer, translation)
}
#[cfg(feature = "a11y")]
@ -274,7 +274,7 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for G
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
for ((e, layout), state) in self
.children

View file

@ -6,7 +6,7 @@ use crate::{ext::CollectionWidget, widget, Element};
use apply::Apply;
use derive_setters::Setters;
use iced::Length;
use iced_core::{widget::tree, Widget};
use iced_core::{widget::tree, Vector, Widget};
use std::borrow::Cow;
#[must_use]
@ -221,9 +221,7 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
state: &mut tree::Tree,
layout: iced_core::Layout<'_>,
renderer: &crate::Renderer,
operation: &mut dyn iced_core::widget::Operation<
iced_core::widget::OperationOutputWrapper<Message>,
>,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
let child_tree = &mut state.children[0];
let child_layout = layout.children().next().unwrap();
@ -237,12 +235,16 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
state: &'b mut tree::Tree,
layout: iced_core::Layout<'_>,
renderer: &crate::Renderer,
translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let child_tree = &mut state.children[0];
let child_layout = layout.children().next().unwrap();
self.header_bar_inner
.as_widget_mut()
.overlay(child_tree, child_layout, renderer)
self.header_bar_inner.as_widget_mut().overlay(
child_tree,
child_layout,
renderer,
translation,
)
}
fn drag_destinations(
@ -250,7 +252,7 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
state: &tree::Tree,
layout: iced_core::Layout<'_>,
renderer: &crate::Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
if let Some((child_tree, child_layout)) =
state.children.iter().zip(layout.children()).next()
@ -274,7 +276,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
let mut end = std::mem::take(&mut self.end);
// Also packs the window controls at the very end.
end.push(widget::horizontal_space(Length::Fixed(12.0)).into());
end.push(widget::horizontal_space().width(Length::Fixed(12.0)).into());
end.push(self.window_controls());
let height = match self.density.unwrap_or_else(crate::config::header_size) {
@ -288,7 +290,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
// If elements exist in the start region, append them here.
.push(
widget::row::with_children(start)
.align_items(iced::Alignment::Center)
.align_y(iced::Alignment::Center)
.apply(widget::container)
.align_x(iced::alignment::Horizontal::Left)
.width(Length::Shrink),
@ -297,32 +299,32 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
// This will otherwise use the title as a widget if a title was defined.
.push(if !center.is_empty() {
widget::row::with_children(center)
.align_items(iced::Alignment::Center)
.align_y(iced::Alignment::Center)
.apply(widget::container)
.align_x(iced::alignment::Horizontal::Center)
.width(Length::Fill)
.into()
} else if self.title.is_empty() {
widget::horizontal_space(Length::Fill).into()
widget::horizontal_space().width(Length::Fill).into()
} else {
self.title_widget()
})
.push(
widget::row::with_children(end)
.align_items(iced::Alignment::Center)
.align_y(iced::Alignment::Center)
.apply(widget::container)
.align_x(iced::alignment::Horizontal::Right)
.width(Length::Shrink),
)
.align_items(iced::Alignment::Center)
.align_y(iced::Alignment::Center)
.height(Length::Fixed(height))
.padding([0, 8])
.spacing(8)
.apply(widget::container)
.style(crate::theme::Container::HeaderBar {
.class(crate::theme::Container::HeaderBar {
focused: self.focused,
})
.center_y()
.center_y(Length::Shrink)
.apply(widget::mouse_area);
// Assigns a message to emit when the headerbar is dragged.
@ -350,10 +352,8 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
widget::text::heading(title)
.apply(widget::container)
.center_x()
.center_y()
.width(Length::Fill)
.height(Length::Fill)
.center_x(Length::Fill)
.center_y(Length::Fill)
.into()
}
@ -380,7 +380,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
.padding(8)
};
icon.style(crate::theme::Button::HeaderBar)
icon.class(crate::theme::Button::HeaderBar)
.selected(self.focused)
.icon_size($size)
.on_press($on_press)
@ -405,8 +405,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
)
.spacing(8)
.apply(widget::container)
.height(Length::Fill)
.center_y()
.center_y(Length::Fill)
.into()
}
}

View file

@ -55,7 +55,10 @@ pub fn from_raster_bytes(
) -> Handle {
Handle {
symbolic: false,
data: Data::Image(image::Handle::from_memory(bytes)),
data: match bytes.into() {
Cow::Owned(b) => Data::Image(image::Handle::from_bytes(b)),
Cow::Borrowed(b) => Data::Image(image::Handle::from_bytes(b)),
},
}
}
@ -66,12 +69,14 @@ pub fn from_raster_pixels(
pixels: impl Into<Cow<'static, [u8]>>
+ std::convert::AsRef<[u8]>
+ std::marker::Send
+ std::marker::Sync
+ 'static,
+ std::marker::Sync,
) -> Handle {
Handle {
symbolic: false,
data: Data::Image(image::Handle::from_pixels(width, height, pixels)),
data: match pixels.into() {
Cow::Owned(pixels) => Data::Image(image::Handle::from_bytes(pixels)),
Cow::Borrowed(pixels) => Data::Image(image::Handle::from_bytes(pixels)),
},
}
}

View file

@ -24,7 +24,7 @@ pub fn icon(handle: Handle) -> Icon {
handle,
height: None,
size: 16,
style: crate::theme::Svg::default(),
class: crate::theme::Svg::default(),
width: None,
}
}
@ -40,7 +40,7 @@ pub fn from_name(name: impl Into<Arc<str>>) -> Named {
pub struct Icon {
#[setters(skip)]
handle: Handle,
style: crate::theme::Svg,
class: crate::theme::Svg,
pub(super) size: u16,
content_fit: ContentFit,
#[setters(strip_option)]
@ -86,7 +86,7 @@ impl Icon {
let from_svg = |handle| {
Svg::<crate::Theme>::new(handle)
.style(self.style.clone())
.class(self.class.clone())
.width(
self.width
.unwrap_or_else(|| Length::Fixed(f32::from(self.size))),

View file

@ -4,8 +4,8 @@ use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::{Id, Tree};
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Widget};
pub use iced_style::container::{Appearance, StyleSheet};
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
pub use iced_widget::container::{Catalog, Style};
pub fn id_container<'a, Message: 'static, Theme, E>(
content: E,
@ -13,8 +13,8 @@ pub fn id_container<'a, Message: 'static, Theme, E>(
) -> IdContainer<'a, Message, Theme, crate::Renderer>
where
E: Into<Element<'a, Message, Theme, crate::Renderer>>,
Theme: iced_style::container::StyleSheet,
<Theme as iced_widget::container::StyleSheet>::Style: From<crate::theme::Container>,
Theme: iced_widget::container::Catalog,
<Theme as iced_widget::container::Catalog>::Class<'a>: From<crate::theme::Container<'a>>,
{
IdContainer::new(content, id)
}
@ -83,9 +83,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation<
iced_core::widget::OperationOutputWrapper<Message>,
>,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
self.content.as_widget().operate(
@ -165,11 +163,13 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
translation,
)
}
@ -178,7 +178,7 @@ where
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
let content_layout = layout.children().next().unwrap();
self.content.as_widget().drag_destinations(

View file

@ -1,3 +1,4 @@
use crate::Theme;
use cosmic_theme::LayeredTheme;
use iced::widget::Container;
use iced_core::alignment;
@ -7,16 +8,14 @@ use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::Tree;
use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget};
pub use iced_style::container::{Appearance, StyleSheet};
use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget};
pub use iced_widget::container::{Catalog, Style};
pub fn layer_container<'a, Message: 'static, Theme, E>(
pub fn layer_container<'a, Message: 'static, E>(
content: E,
) -> LayerContainer<'a, Message, Theme, crate::Renderer>
) -> LayerContainer<'a, Message, crate::Renderer>
where
E: Into<Element<'a, Message, Theme, crate::Renderer>>,
Theme: iced_style::container::StyleSheet + LayeredTheme,
<Theme as iced_widget::container::StyleSheet>::Style: From<crate::theme::Container>,
{
LayerContainer::new(content)
}
@ -25,20 +24,18 @@ where
///
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
pub struct LayerContainer<'a, Message, Theme, Renderer>
pub struct LayerContainer<'a, Message, Renderer>
where
Renderer: iced_core::Renderer,
Theme: iced_style::container::StyleSheet + LayeredTheme,
{
layer: Option<cosmic_theme::Layer>,
container: Container<'a, Message, Theme, Renderer>,
}
impl<'a, Message, Theme, Renderer> LayerContainer<'a, Message, Theme, Renderer>
impl<'a, Message, Renderer> LayerContainer<'a, Message, Renderer>
where
Renderer: iced_core::Renderer,
Theme: iced_style::container::StyleSheet + LayeredTheme,
<Theme as iced_style::container::StyleSheet>::Style: From<crate::theme::Container>,
// iced_widget::container::Style: From<crate::theme::Container>,
{
/// Creates an empty [`Container`].
pub(crate) fn new<T>(content: T) -> Self
@ -55,7 +52,7 @@ where
#[must_use]
pub fn layer(mut self, layer: cosmic_theme::Layer) -> Self {
self.layer = Some(layer);
self.style(match layer {
self.class(match layer {
cosmic_theme::Layer::Background => crate::theme::Container::Background,
cosmic_theme::Layer::Primary => crate::theme::Container::Primary,
cosmic_theme::Layer::Secondary => crate::theme::Container::Secondary,
@ -113,31 +110,30 @@ where
/// Centers the contents in the horizontal axis of the [`LayerContainer`].
#[must_use]
pub fn center_x(mut self) -> Self {
self.container = self.container.center_x();
pub fn center_x(mut self, width: Length) -> Self {
self.container = self.container.center_x(width);
self
}
/// Centers the contents in the vertical axis of the [`LayerContainer`].
#[must_use]
pub fn center_y(mut self) -> Self {
self.container = self.container.center_y();
pub fn center_y(mut self, height: Length) -> Self {
self.container = self.container.center_y(height);
self
}
/// Sets the style of the [`LayerContainer`].
#[must_use]
pub fn style(mut self, style: impl Into<<Theme as StyleSheet>::Style>) -> Self {
self.container = self.container.style(style);
pub fn class(mut self, style: impl Into<crate::style::iced::Container<'a>>) -> Self {
self.container = self.container.class(style);
self
}
}
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for LayerContainer<'a, Message, Theme, Renderer>
impl<'a, Message, Renderer> Widget<Message, Theme, Renderer>
for LayerContainer<'a, Message, Renderer>
where
Renderer: iced_core::Renderer,
Theme: iced_style::container::StyleSheet + LayeredTheme + Clone,
{
fn children(&self) -> Vec<Tree> {
self.container.children()
@ -173,9 +169,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation<
iced_core::widget::OperationOutputWrapper<Message>,
>,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
self.container.operate(tree, layout, renderer, operation);
}
@ -232,6 +226,7 @@ where
} else {
theme.clone()
};
self.container.draw(
tree,
renderer,
@ -248,8 +243,9 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.container.overlay(tree, layout, renderer)
self.container.overlay(tree, layout, renderer, translation)
}
fn drag_destinations(
@ -257,7 +253,7 @@ where
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
self.container
.drag_destinations(state, layout, renderer, dnd_rectangles);
@ -272,15 +268,14 @@ where
}
}
impl<'a, Message, Theme, Renderer> From<LayerContainer<'a, Message, Theme, Renderer>>
impl<'a, Message, Renderer> From<LayerContainer<'a, Message, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Renderer: 'a + iced_core::Renderer,
Theme: iced_style::container::StyleSheet + LayeredTheme + 'a + Clone,
{
fn from(
column: LayerContainer<'a, Message, Theme, Renderer>,
column: LayerContainer<'a, Message, Renderer>,
) -> Element<'a, Message, Theme, Renderer> {
Element::new(column)
}

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: MPL-2.0
use iced_core::Padding;
use iced_style::container::StyleSheet;
use iced_widget::container::Catalog;
use crate::{theme, widget::divider, Apply, Element};
@ -14,7 +14,7 @@ pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> {
pub struct ListColumn<'a, Message> {
spacing: u16,
padding: Padding,
style: <crate::Theme as StyleSheet>::Style,
style: crate::theme::Container<'a>,
children: Vec<Element<'a, Message>>,
}
@ -23,7 +23,7 @@ impl<'a, Message: 'static> Default for ListColumn<'a, Message> {
Self {
spacing: theme::THEME.lock().unwrap().cosmic().spacing.space_xxs,
padding: Padding::from(0),
style: <crate::Theme as StyleSheet>::Style::List,
style: crate::theme::Container::List,
children: Vec::with_capacity(4),
}
}
@ -41,9 +41,11 @@ impl<'a, Message: 'static> ListColumn<'a, Message> {
}
// Ensure a minimum height of 32.
let container = crate::widget::container(item)
.min_height(32)
.align_y(iced::alignment::Vertical::Center);
let container = iced::widget::row![
crate::widget::container(item).align_y(iced::alignment::Vertical::Center),
crate::widget::vertical_space().height(iced::Length::Fixed(32.))
]
.align_y(iced::alignment::Vertical::Center);
self.children.push(container.into());
self
@ -55,7 +57,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> {
}
/// Sets the style variant of this [`Circular`].
pub fn style(mut self, style: <crate::Theme as StyleSheet>::Style) -> Self {
pub fn style(mut self, style: <crate::Theme as Catalog>::Class<'a>) -> Self {
self.style = style;
self
}
@ -72,7 +74,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> {
.padding(self.padding)
.apply(super::container)
.padding([self.spacing, 8])
.style(self.style)
.class(self.style)
.into()
}
}

View file

@ -13,6 +13,6 @@ pub fn container<'a, Message>(
) -> Container<'a, Message, crate::Theme, crate::Renderer> {
super::container(content)
.padding([16, 6])
.style(crate::theme::Container::List)
.class(crate::theme::Container::List)
.width(iced::Length::Fill)
}

View file

@ -9,6 +9,7 @@ use super::{
};
use crate::style::menu_bar::StyleSheet;
use iced::{Point, Vector};
use iced_core::Border;
use iced_widget::core::{
event,
@ -434,6 +435,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
let state = tree.state.downcast_ref::<MenuBarState>();
if !state.open {
@ -455,6 +457,7 @@ where
root_bounds_list: layout.children().map(|lo| lo.bounds()).collect(),
path_highlight: self.path_highlight,
style: &self.style,
position: Point::new(translation.x, translation.y),
}
.overlay(),
)

View file

@ -276,7 +276,7 @@ impl MenuBounds {
let offset_bounds = Rectangle::new(offset_position, offset_size);
let children_bounds = Rectangle::new(children_position, children_size);
let check_bounds = pad_rectangle(children_bounds, [bounds_expand; 4].into());
let check_bounds = pad_rectangle(children_bounds, bounds_expand.into());
Self {
child_positions,
@ -445,13 +445,14 @@ where
pub(crate) root_bounds_list: Vec<Rectangle>,
pub(crate) path_highlight: Option<PathHighlight>,
pub(crate) style: &'b <crate::Theme as StyleSheet>::Style,
pub(crate) position: Point,
}
impl<'a, 'b, Message, Renderer> Menu<'a, 'b, Message, Renderer>
where
Renderer: renderer::Renderer,
{
pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, Renderer> {
overlay::Element::new(Point::ORIGIN, Box::new(self))
overlay::Element::new(Box::new(self))
}
}
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer>
@ -459,14 +460,9 @@ impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer
where
Renderer: renderer::Renderer,
{
fn layout(
&mut self,
renderer: &Renderer,
bounds: Size,
position: Point,
_translation: iced::Vector,
) -> Node {
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
// layout children
let position = self.position;
let state = self.tree.state.downcast_mut::<MenuBarState>();
let overlay_offset = Point::ORIGIN - position;
let tree_children = &mut self.tree.children;

View file

@ -146,14 +146,14 @@ pub fn menu_button<'a, Message: 'a>(
) -> crate::widget::Button<'a, Message> {
widget::button::custom(
widget::Row::with_children(children)
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.height(Length::Fill)
.width(Length::Fill),
)
.height(Length::Fixed(36.0))
.padding([4, 16])
.width(Length::Fill)
.style(theme::Button::MenuItem)
.class(theme::Button::MenuItem)
}
/// Represents a menu item that performs an action when selected or a separator between menu items.
@ -197,7 +197,7 @@ where
{
widget::button::custom(widget::text(label))
.padding([4, 12])
.style(theme::Button::MenuRoot)
.class(theme::Button::MenuRoot)
.into()
}
@ -245,7 +245,7 @@ where
let key = find_key(&action, key_binds);
let menu_button = menu_button(vec![
widget::text(label).into(),
widget::horizontal_space(Length::Fill).into(),
widget::horizontal_space().width(Length::Fill).into(),
widget::text(key).into(),
])
.on_press(action.message());
@ -256,7 +256,7 @@ where
let key = find_key(&action, key_binds);
let menu_button = menu_button(vec![
widget::text(label).into(),
widget::horizontal_space(Length::Fill).into(),
widget::horizontal_space().width(Length::Fill).into(),
widget::text(key).into(),
]);
@ -270,8 +270,8 @@ where
widget::icon::from_name("object-select-symbolic")
.size(16)
.icon()
.style(theme::Svg::Custom(Rc::new(|theme| {
crate::iced_style::svg::Appearance {
.class(theme::Svg::Custom(Rc::new(|theme| {
iced_widget::svg::Style {
color: Some(theme.cosmic().accent_color().into()),
}
})))
@ -282,9 +282,9 @@ where
},
widget::Space::with_width(Length::Fixed(8.0)).into(),
widget::text(label)
.horizontal_alignment(iced::alignment::Horizontal::Left)
.align_x(iced::alignment::Horizontal::Left)
.into(),
widget::horizontal_space(Length::Fill).into(),
widget::horizontal_space().width(Length::Fill).into(),
widget::text(key).into(),
])
.on_press(action.message()),
@ -294,13 +294,13 @@ where
trees.push(MenuTree::<Message, Renderer>::with_children(
menu_button(vec![
widget::text(label).into(),
widget::horizontal_space(Length::Fill).into(),
widget::horizontal_space().width(Length::Fill).into(),
widget::icon::from_name("pan-end-symbolic")
.size(16)
.icon()
.into(),
])
.style(
.class(
// Menu folders have no on_press so they take on the disabled style by default
if children.is_empty() {
// This will make the folder use the disabled style if it has no children

View file

@ -0,0 +1,319 @@
mod subscription;
use iced::futures::channel::mpsc::UnboundedSender;
use iced::widget::Container;
use iced::Vector;
pub use subscription::*;
use iced_core::alignment;
use iced_core::event::{self, Event};
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::Tree;
use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget};
use std::{fmt::Debug, hash::Hash};
pub use iced_widget::container::{Catalog, Style};
pub fn min_size_tracker<'a, Message, I, T>(
content: T,
id: I,
tx: UnboundedSender<(I, Rectangle)>,
) -> MinSizeTrackerContainer<'a, Message, crate::Renderer, I>
where
I: Hash + Copy + Send + Sync + Debug + 'a,
T: Into<Element<'a, Message, crate::Theme, crate::Renderer>>,
{
MinSizeTrackerContainer::new(content, id, tx)
}
pub fn subscription<
I: 'static + Hash + Copy + Send + Sync + Debug,
R: 'static + Hash + Copy + Send + Sync + Debug + Eq,
>(
id: I,
) -> iced::Subscription<(I, RectangleUpdate<R>)> {
subscription::rectangle_tracker_subscription(id)
}
#[derive(Clone, Debug)]
pub struct MinSizeTracker<I> {
tx: UnboundedSender<(I, Rectangle)>,
}
impl<I> MinSizeTracker<I>
where
I: Hash + Copy + Send + Sync + Debug,
{
pub fn container<'a, Message: 'static, T>(
&self,
id: I,
content: T,
) -> MinSizeTracker<'a, Message, crate::Renderer, I>
where
I: 'a,
T: Into<Element<'a, Message, crate::Theme, crate::Renderer>>,
{
MinSizeTracker::new(content, id, self.tx.clone())
}
}
/// An element decorating some content.
///
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
pub struct MinSizeTrackerContainer<'a, Message, Renderer, I>
where
Renderer: iced_core::Renderer,
{
tx: UnboundedSender<(I, Rectangle)>,
id: I,
container: Container<'a, Message, crate::Theme, Renderer>,
ignore_bounds: bool,
}
impl<'a, Message, Renderer, I> MinSizeTrackerContainer<'a, Message, Renderer, I>
where
Renderer: iced_core::Renderer,
I: 'a + Hash + Copy + Send + Sync + Debug,
{
/// Creates an empty [`Container`].
pub(crate) fn new<T>(content: T, id: I, tx: UnboundedSender<(I, Rectangle)>) -> Self
where
T: Into<Element<'a, Message, crate::Theme, Renderer>>,
{
MinSizeTrackerContainer {
id,
tx,
container: Container::new(content),
ignore_bounds: false,
}
}
pub fn diff(&mut self, tree: &mut Tree) {
self.container.diff(tree);
}
/// Sets the [`Padding`] of the [`Container`].
#[must_use]
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.container = self.container.padding(padding);
self
}
/// Sets the width of the [`self.`].
#[must_use]
pub fn width(mut self, width: Length) -> Self {
self.container = self.container.width(width);
self
}
/// Sets the height of the [`Container`].
#[must_use]
pub fn height(mut self, height: Length) -> Self {
self.container = self.container.height(height);
self
}
/// Sets the maximum width of the [`Container`].
#[must_use]
pub fn max_width(mut self, max_width: f32) -> Self {
self.container = self.container.max_width(max_width);
self
}
/// Sets the maximum height of the [`Container`] in pixels.
#[must_use]
pub fn max_height(mut self, max_height: f32) -> Self {
self.container = self.container.max_height(max_height);
self
}
/// Sets the content alignment for the horizontal axis of the [`Container`].
#[must_use]
pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self {
self.container = self.container.align_x(alignment);
self
}
/// Sets the content alignment for the vertical axis of the [`Container`].
#[must_use]
pub fn align_y(mut self, alignment: alignment::Vertical) -> Self {
self.container = self.container.align_y(alignment);
self
}
/// Centers the contents in the horizontal axis of the [`Container`].
#[must_use]
pub fn center_x(mut self, width: Length) -> Self {
self.container = self.container.center_x(width);
self
}
/// Centers the contents in the vertical axis of the [`Container`].
#[must_use]
pub fn center_y(mut self, height: Length) -> Self {
self.container = self.container.center_y(height);
self
}
/// Sets the style of the [`Container`].
#[must_use]
pub fn style(mut self, style: impl Into<<crate::Theme as Catalog>::Class<'a>>) -> Self {
self.container = self.container.class(style);
self
}
/// Set to true to ignore parent container bounds when performing layout.
/// This can be useful for widgets that are in auto-sized surfaces.
#[must_use]
pub fn ignore_bounds(mut self, ignore_bounds: bool) -> Self {
self.ignore_bounds = ignore_bounds;
self
}
}
impl<'a, Message, Renderer, I> Widget<Message, crate::Theme, Renderer>
for MinSizeTrackerContainer<'a, Message, Renderer, I>
where
Renderer: iced_core::Renderer,
I: 'a + Hash + Copy + Send + Sync + Debug,
{
fn children(&self) -> Vec<Tree> {
self.container.children()
}
fn state(&self) -> iced_core::widget::tree::State {
self.container.state()
}
fn diff(&mut self, tree: &mut Tree) {
self.container.diff(tree);
}
fn size(&self) -> iced_core::Size<Length> {
self.container.size()
}
fn layout(
&self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.container.layout(
tree,
renderer,
if self.ignore_bounds {
&layout::Limits::NONE
} else {
limits
},
)
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
self.container.operate(tree, layout, renderer, operation);
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &iced_core::Rectangle,
) -> event::Status {
self.container.on_event(
tree,
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
viewport,
)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.container
.mouse_interaction(tree, layout, cursor_position, viewport, renderer)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &crate::Theme,
renderer_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: mouse::Cursor,
viewport: &Rectangle,
) {
let _ = self.tx.unbounded_send((self.id, layout.bounds()));
self.container.draw(
tree,
renderer,
theme,
renderer_style,
layout,
cursor_position,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
self.container.overlay(tree, layout, renderer, translation)
}
fn drag_destinations(
&self,
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
self.container
.drag_destinations(state, layout, renderer, dnd_rectangles);
}
}
impl<'a, Message, Renderer, I> From<MinSizeTrackerContainer<'a, Message, Renderer, I>>
for Element<'a, Message, crate::Theme, Renderer>
where
Message: 'a,
Renderer: 'a + iced_core::Renderer,
I: 'a + Hash + Copy + Send + Sync + Debug,
{
fn from(
column: MinSizeTrackerContainer<'a, Message, Renderer, I>,
) -> Element<'a, Message, crate::Theme, Renderer> {
Element::new(column)
}
}

View file

@ -0,0 +1,88 @@
use iced::{
futures::{
channel::mpsc::{unbounded, UnboundedReceiver},
stream, StreamExt,
},
Rectangle,
};
use iced_futures::Subscription;
use std::{collections::HashMap, fmt::Debug, hash::Hash};
use super::MinSizeTrackerContainer;
pub fn rectangle_tracker_subscription<
I: 'static + Hash + Copy + Send + Sync + Debug,
R: 'static + Hash + Copy + Send + Sync + Debug + Eq,
>(
id: I,
) -> Subscription<(I, RectangleUpdate<R>)> {
Subscription::run_with_id(
id,
stream::unfold(State::Ready, move |state| start_listening(id, state)),
)
}
pub enum State<I> {
Ready,
Waiting(UnboundedReceiver<(I, Rectangle)>, HashMap<I, Rectangle>),
Finished,
}
async fn start_listening<I: Copy, R: 'static + Hash + Copy + Send + Sync + Debug + Eq>(
id: I,
mut state: State<R>,
) -> Option<((I, RectangleUpdate<R>), State<R>)> {
loop {
let (update, new_state) = match state {
State::Ready => {
let (tx, rx) = unbounded();
(
Some((id, RectangleUpdate::Init(MinSizeTracker { tx }))),
State::Waiting(rx, HashMap::new()),
)
}
State::Waiting(mut rx, mut map) => match rx.next().await {
Some(u) => {
if let Some(prev) = map.get(&u.0) {
let new = u.1;
if (prev.width - new.width).abs() > 0.1
|| (prev.height - new.height).abs() > 0.1
|| (prev.x - new.x).abs() > 0.1
|| (prev.y - new.y).abs() > 0.1
{
map.insert(u.0, new);
(
Some((id, RectangleUpdate::Rectangle(u))),
State::Waiting(rx, map),
)
} else {
(None, State::Waiting(rx, map))
}
} else {
map.insert(u.0, u.1);
(
Some((id, RectangleUpdate::Rectangle(u))),
State::Waiting(rx, map),
)
}
}
None => (None, State::Finished),
},
State::Finished => return None,
};
state = new_state;
if let Some(u) = update {
return Some((u, state));
}
}
}
#[derive(Clone, Debug)]
pub enum RectangleUpdate<I>
where
I: 'static + Hash + Copy + Send + Sync + Debug,
{
Rectangle((I, Rectangle)),
Init(MinSizeTrackerContainer<I>),
}

View file

@ -60,7 +60,7 @@ pub use iced::widget::{combo_box, ComboBox};
pub use iced::widget::{container, Container};
#[doc(inline)]
pub use iced::widget::{horizontal_space, space, vertical_space, Space};
pub use iced::widget::{horizontal_space, vertical_space, Space};
#[doc(inline)]
pub use iced::widget::{image, Image};
@ -94,6 +94,9 @@ pub use iced_core::widget::{Id, Operation, Widget};
pub mod aspect_ratio;
#[cfg(feature = "autosize")]
pub mod autosize;
pub mod button;
#[doc(inline)]
pub use button::{Button, IconButton, LinkButton, TextButton};
@ -166,20 +169,20 @@ pub mod divider {
/// Horizontal divider with default thickness
#[must_use]
pub fn default() -> Rule<crate::Theme> {
horizontal_rule(1).style(crate::theme::Rule::Default)
pub fn default<'a>() -> Rule<'a, crate::Theme> {
horizontal_rule(1).class(crate::theme::Rule::Default)
}
/// Horizontal divider with light thickness
#[must_use]
pub fn light() -> Rule<crate::Theme> {
horizontal_rule(1).style(crate::theme::Rule::LightDivider)
pub fn light<'a>() -> Rule<'a, crate::Theme> {
horizontal_rule(1).class(crate::theme::Rule::LightDivider)
}
/// Horizontal divider with heavy thickness.
#[must_use]
pub fn heavy() -> Rule<crate::Theme> {
horizontal_rule(4).style(crate::theme::Rule::HeavyDivider)
pub fn heavy<'a>() -> Rule<'a, crate::Theme> {
horizontal_rule(4).class(crate::theme::Rule::HeavyDivider)
}
}
@ -189,20 +192,20 @@ pub mod divider {
/// Vertical divider with default thickness
#[must_use]
pub fn default() -> Rule<crate::Theme> {
vertical_rule(1).style(crate::theme::Rule::Default)
pub fn default<'a>() -> Rule<'a, crate::Theme> {
vertical_rule(1).class(crate::theme::Rule::Default)
}
/// Vertical divider with light thickness
#[must_use]
pub fn light() -> Rule<crate::Theme> {
vertical_rule(4).style(crate::theme::Rule::LightDivider)
pub fn light<'a>() -> Rule<'a, crate::Theme> {
vertical_rule(4).class(crate::theme::Rule::LightDivider)
}
/// Vertical divider with heavy thickness.
#[must_use]
pub fn heavy() -> Rule<crate::Theme> {
vertical_rule(10).style(crate::theme::Rule::HeavyDivider)
pub fn heavy<'a>() -> Rule<'a, crate::Theme> {
vertical_rule(10).class(crate::theme::Rule::HeavyDivider)
}
}
}
@ -268,7 +271,7 @@ pub use radio::{radio, Radio};
pub mod rectangle_tracker;
#[doc(inline)]
pub use rectangle_tracker::{rectangle_tracker, RectangleTracker};
pub use rectangle_tracker::{rectangle_tracking_container, RectangleTracker};
#[doc(inline)]
pub use row::{row, Row};
@ -343,13 +346,13 @@ pub mod tooltip {
pub fn tooltip<'a, Message>(
content: impl Into<Element<'a, Message>>,
tooltip: impl Into<Cow<'a, str>>,
tooltip: impl Into<Element<'a, Message>>,
position: Position,
) -> Tooltip<'a, Message> {
let xxs = crate::theme::active().cosmic().space_xxs();
Tooltip::new(content, tooltip, position)
.style(crate::theme::Container::Tooltip)
.class(crate::theme::Container::Tooltip)
.padding(xxs)
.gap(1)
}

View file

@ -147,11 +147,12 @@ impl<'a, Message: Clone + 'static> From<NavBar<'a, Message>>
.spacing(space_xxs)
.style(crate::theme::SegmentedButton::TabBar)
.apply(scrollable)
.class(crate::style::iced::Scrollable::Minimal)
.height(Length::Fill)
.apply(container)
.padding(space_xxs)
.height(Length::Fill)
.style(theme::Container::custom(nav_bar_style))
.class(theme::Container::custom(nav_bar_style))
}
}
@ -162,9 +163,9 @@ impl<'a, Message: Clone + 'static> From<NavBar<'a, Message>> for crate::Element<
}
#[must_use]
pub fn nav_bar_style(theme: &Theme) -> iced_style::container::Appearance {
pub fn nav_bar_style(theme: &Theme) -> iced_widget::container::Style {
let cosmic = &theme.cosmic();
iced_style::container::Appearance {
iced_widget::container::Style {
icon_color: Some(cosmic.on_bg_color().into()),
text_color: Some(cosmic.on_bg_color().into()),
background: Some(Background::Color(cosmic.primary.base.into())),

View file

@ -11,7 +11,7 @@ pub struct NavBarToggle<Message> {
active: bool,
#[setters(strip_option)]
on_toggle: Option<Message>,
style: crate::theme::Button,
class: crate::theme::Button,
selected: bool,
}
@ -20,7 +20,7 @@ pub fn nav_bar_toggle<Message>() -> NavBarToggle<Message> {
NavBarToggle {
active: false,
on_toggle: None,
style: crate::theme::Button::Text,
class: crate::theme::Button::Text,
selected: false,
}
}
@ -43,7 +43,7 @@ impl<'a, Message: 'static + Clone> From<NavBarToggle<Message>> for Element<'a, M
.padding([8, 16])
.on_press_maybe(nav_bar_toggle.on_toggle)
.selected(nav_bar_toggle.selected)
.style(nav_bar_toggle.style)
.class(nav_bar_toggle.class)
.into()
}
}

View file

@ -9,12 +9,12 @@ use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::touch;
use iced_core::widget::{tree, Operation, OperationOutputWrapper, Tree};
use iced_core::widget::{tree, Operation, Tree};
use iced_core::{
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
};
pub use iced_style::container::{Appearance, StyleSheet};
pub use iced_widget::container::{Catalog, Style};
pub fn popover<'a, Message, Renderer>(
content: impl Into<Element<'a, Message, crate::Theme, Renderer>>,
@ -123,7 +123,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
operation: &mut dyn Operation<()>,
) {
self.content
.as_widget()
@ -214,6 +214,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
mut translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
if !tree.state.downcast_mut::<State>().is_open {
return None;
@ -239,19 +240,22 @@ where
// Round position to prevent rendering issues
overlay_position.x = overlay_position.x.round();
overlay_position.y = overlay_position.y.round();
translation.x += overlay_position.x;
translation.y += overlay_position.y;
Some(overlay::Element::new(
overlay_position,
Box::new(Overlay {
tree: &mut tree.children[1],
content: popup,
position: self.position,
}),
))
Some(overlay::Element::new(Box::new(Overlay {
tree: &mut tree.children[1],
content: popup,
position: self.position,
pos: Point::new(translation.x, translation.y),
})))
} else {
self.content
.as_widget_mut()
.overlay(&mut tree.children[0], layout, renderer)
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout,
renderer,
translation,
)
}
}
@ -286,6 +290,7 @@ pub struct Overlay<'a, 'b, Message, Renderer> {
tree: &'a mut Tree,
content: &'a mut Element<'b, Message, crate::Theme, Renderer>,
position: Position,
pos: Point,
}
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer>
@ -294,13 +299,8 @@ where
Message: Clone,
Renderer: iced_core::Renderer,
{
fn layout(
&mut self,
renderer: &Renderer,
bounds: Size,
mut position: Point,
_translation: iced::Vector,
) -> layout::Node {
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
let mut position = self.pos;
let limits = layout::Limits::new(Size::UNIT, bounds);
let node = self
.content
@ -342,7 +342,7 @@ where
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
operation: &mut dyn Operation<()>,
) {
self.content
.as_widget()
@ -413,7 +413,7 @@ where
) -> Option<overlay::Element<'c, Message, crate::Theme, Renderer>> {
self.content
.as_widget_mut()
.overlay(&mut self.tree, layout, renderer)
.overlay(&mut self.tree, layout, renderer, Default::default())
}
}

View file

@ -1,4 +1,6 @@
//! Create choices using radio buttons.
use crate::Theme;
use iced::border;
use iced_core::event::{self, Event};
use iced_core::layout;
use iced_core::mouse;
@ -7,17 +9,18 @@ use iced_core::renderer;
use iced_core::touch;
use iced_core::widget::tree::Tree;
use iced_core::{
Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Widget,
Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Vector, Widget,
};
pub use iced_style::radio::{Appearance, StyleSheet};
use iced_widget::radio as iced_radio;
pub use iced_widget::radio::Catalog;
pub fn radio<'a, Message: Clone, Theme: StyleSheet, V, F>(
pub fn radio<'a, Message: Clone, V, F>(
label: impl Into<Element<'a, Message, Theme, crate::Renderer>>,
value: V,
selected: Option<V>,
f: F,
) -> Radio<'a, Message, Theme, crate::Renderer>
) -> Radio<'a, Message, crate::Renderer>
where
V: Eq + Copy,
F: FnOnce(V) -> Message,
@ -83,9 +86,8 @@ where
/// let content = column![a, b, c, all];
/// ```
#[allow(missing_debug_implementations)]
pub struct Radio<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
pub struct Radio<'a, Message, Renderer = crate::Renderer>
where
Theme: StyleSheet,
Renderer: iced_core::Renderer,
{
is_selected: bool,
@ -94,13 +96,11 @@ where
width: Length,
size: f32,
spacing: f32,
style: Theme::Style,
}
impl<'a, Message, Theme, Renderer> Radio<'a, Message, Theme, Renderer>
impl<'a, Message, Renderer> Radio<'a, Message, Renderer>
where
Message: Clone,
Theme: StyleSheet,
Renderer: iced_core::Renderer,
{
/// The default size of a [`Radio`] button.
@ -130,7 +130,6 @@ where
width: Length::Shrink,
size: Self::DEFAULT_SIZE,
spacing: Self::DEFAULT_SPACING,
style: Default::default(),
}
}
@ -154,20 +153,11 @@ where
self.spacing = spacing.into().0;
self
}
#[must_use]
/// Sets the style of the [`Radio`] button.
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
self.style = style.into();
self
}
}
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Radio<'a, Message, Theme, Renderer>
impl<'a, Message, Renderer> Widget<Message, Theme, Renderer> for Radio<'a, Message, Renderer>
where
Message: Clone,
Theme: StyleSheet,
Renderer: iced_core::Renderer,
{
fn children(&self) -> Vec<Tree> {
@ -207,9 +197,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation<
iced_core::widget::OperationOutputWrapper<Message>,
>,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
self.label.as_widget().operate(
&mut tree.children[0],
@ -302,9 +290,19 @@ where
let mut children = layout.children();
let custom_style = if is_mouse_over {
theme.hovered(&self.style, self.is_selected)
theme.style(
&(),
iced_radio::Status::Hovered {
is_selected: self.is_selected,
},
)
} else {
theme.active(&self.style, self.is_selected)
theme.style(
&(),
iced_radio::Status::Active {
is_selected: self.is_selected,
},
)
};
{
@ -336,7 +334,7 @@ where
width: dot_size,
height: dot_size,
},
border: Border::with_radius(dot_size / 2.0),
border: border::rounded(dot_size / 2.0),
..renderer::Quad::default()
},
custom_style.dot_color,
@ -363,11 +361,13 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.label.as_widget_mut().overlay(
&mut tree.children[0],
layout.children().nth(1).unwrap(),
renderer,
translation,
)
}
@ -376,7 +376,7 @@ where
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
self.label.as_widget().drag_destinations(
&state.children[0],
@ -387,14 +387,13 @@ where
}
}
impl<'a, Message, Theme, Renderer> From<Radio<'a, Message, Theme, Renderer>>
impl<'a, Message, Renderer> From<Radio<'a, Message, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a + Clone,
Theme: 'a + StyleSheet,
Renderer: 'a + iced_core::Renderer,
{
fn from(radio: Radio<'a, Message, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> {
fn from(radio: Radio<'a, Message, Renderer>) -> Element<'a, Message, Theme, Renderer> {
Element::new(radio)
}
}

View file

@ -2,6 +2,7 @@ mod subscription;
use iced::futures::channel::mpsc::UnboundedSender;
use iced::widget::Container;
use iced::Vector;
pub use subscription::*;
use iced_core::alignment;
@ -14,9 +15,9 @@ use iced_core::widget::Tree;
use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget};
use std::{fmt::Debug, hash::Hash};
pub use iced_style::container::{Appearance, StyleSheet};
pub use iced_widget::container::{Catalog, Style};
pub fn rectangle_tracker<'a, Message, I, T>(
pub fn rectangle_tracking_container<'a, Message, I, T>(
content: T,
id: I,
tx: UnboundedSender<(I, Rectangle)>,
@ -71,6 +72,7 @@ where
id: I,
container: Container<'a, Message, crate::Theme, Renderer>,
ignore_bounds: bool,
request_size: bool,
}
impl<'a, Message, Renderer, I> RectangleTrackingContainer<'a, Message, Renderer, I>
@ -88,6 +90,7 @@ where
tx,
container: Container::new(content),
ignore_bounds: false,
request_size: true,
}
}
@ -146,22 +149,22 @@ where
/// Centers the contents in the horizontal axis of the [`Container`].
#[must_use]
pub fn center_x(mut self) -> Self {
self.container = self.container.center_x();
pub fn center_x(mut self, width: Length) -> Self {
self.container = self.container.center_x(width);
self
}
/// Centers the contents in the vertical axis of the [`Container`].
#[must_use]
pub fn center_y(mut self) -> Self {
self.container = self.container.center_y();
pub fn center_y(mut self, height: Length) -> Self {
self.container = self.container.center_y(height);
self
}
/// Sets the style of the [`Container`].
#[must_use]
pub fn style(mut self, style: impl Into<<crate::Theme as StyleSheet>::Style>) -> Self {
self.container = self.container.style(style);
pub fn style(mut self, style: impl Into<<crate::Theme as Catalog>::Class<'a>>) -> Self {
self.container = self.container.class(style);
self
}
@ -202,7 +205,7 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.container.layout(
let layout = self.container.layout(
tree,
renderer,
if self.ignore_bounds {
@ -210,7 +213,10 @@ where
} else {
limits
},
)
);
if self.request_size {}
layout
}
fn operate(
@ -218,9 +224,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation<
iced_core::widget::OperationOutputWrapper<Message>,
>,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
self.container.operate(tree, layout, renderer, operation);
}
@ -271,7 +275,6 @@ where
viewport: &Rectangle,
) {
let _ = self.tx.unbounded_send((self.id, layout.bounds()));
self.container.draw(
tree,
renderer,
@ -288,8 +291,9 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
self.container.overlay(tree, layout, renderer)
self.container.overlay(tree, layout, renderer, translation)
}
fn drag_destinations(
@ -297,7 +301,7 @@ where
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
self.container
.drag_destinations(state, layout, renderer, dnd_rectangles);

View file

@ -1,10 +1,11 @@
use iced::{
futures::{
channel::mpsc::{unbounded, UnboundedReceiver},
StreamExt,
stream, StreamExt,
},
subscription, Rectangle,
Rectangle,
};
use iced_futures::Subscription;
use std::{collections::HashMap, fmt::Debug, hash::Hash};
use super::RectangleTracker;
@ -14,8 +15,11 @@ pub fn rectangle_tracker_subscription<
R: 'static + Hash + Copy + Send + Sync + Debug + Eq,
>(
id: I,
) -> iced::Subscription<(I, RectangleUpdate<R>)> {
subscription::unfold(id, State::Ready, move |state| start_listening(id, state))
) -> Subscription<(I, RectangleUpdate<R>)> {
Subscription::run_with_id(
id,
stream::unfold(State::Ready, move |state| start_listening(id, state)),
)
}
pub enum State<I> {
@ -27,7 +31,7 @@ pub enum State<I> {
async fn start_listening<I: Copy, R: 'static + Hash + Copy + Send + Sync + Debug + Eq>(
id: I,
mut state: State<R>,
) -> ((I, RectangleUpdate<R>), State<R>) {
) -> Option<((I, RectangleUpdate<R>), State<R>)> {
loop {
let (update, new_state) = match state {
State::Ready => {
@ -65,11 +69,11 @@ async fn start_listening<I: Copy, R: 'static + Hash + Copy + Send + Sync + Debug
}
None => (None, State::Finished),
},
State::Finished => iced::futures::future::pending().await,
State::Finished => return None,
};
state = new_state;
if let Some(u) = update {
return (u, state);
return Some((u, state));
}
}
}

View file

@ -16,14 +16,15 @@ use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, O
use iced::clipboard::mime::AllowedMimeTypes;
use iced::touch::Finger;
use iced::{
alignment, event, keyboard, mouse, touch, Alignment, Background, Color, Command, Event, Length,
Padding, Rectangle, Size,
alignment, event, keyboard, mouse, touch, Alignment, Background, Color, Event, Length, Padding,
Rectangle, Size, Task, Vector,
};
use iced_core::mouse::ScrollDelta;
use iced_core::text::{LineHeight, Paragraph, Renderer as TextRenderer, Shaping, Wrap};
use iced_core::text::{LineHeight, Paragraph, Renderer as TextRenderer, Shaping, Wrapping};
use iced_core::widget::{self, operation, tree};
use iced_core::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget};
use iced_core::{Border, Gradient, Point, Renderer as IcedRenderer, Shadow, Text};
use iced_runtime::{task, Action};
use slotmap::{Key, SecondaryMap};
use std::borrow::Cow;
use std::collections::hash_map::DefaultHasher;
@ -34,8 +35,8 @@ use std::mem;
use std::time::{Duration, Instant};
/// A command that focuses a segmented item stored in a widget.
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
Command::widget(operation::focusable::focus(id.0))
pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
task::effect(Action::Widget(Box::new(operation::focusable::focus(id.0))))
}
pub enum ItemBounds {
@ -436,15 +437,15 @@ where
if !text.is_empty() {
icon_spacing = f32::from(self.button_spacing);
let paragraph = entry.or_insert_with(|| {
crate::Paragraph::with_text(Text {
content: text,
crate::Plain::new(Text {
content: text.as_ref(),
size: iced::Pixels(self.font_size),
bounds: Size::INFINITY,
font,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: Shaping::Advanced,
wrap: Wrap::default(),
wrapping: Wrapping::default(),
line_height: self.line_height,
})
});
@ -622,23 +623,21 @@ where
}
let text = Text {
content: text,
content: text.as_ref(),
size: iced::Pixels(self.font_size),
bounds: Size::INFINITY,
font,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: Shaping::Advanced,
wrap: Wrap::default(),
wrapping: Wrapping::default(),
line_height: self.line_height,
};
if let Some(paragraph) = state.paragraphs.get_mut(key) {
paragraph.update(text);
} else {
state
.paragraphs
.insert(key, crate::Paragraph::with_text(text));
state.paragraphs.insert(key, crate::Plain::new(text));
}
}
}
@ -1079,9 +1078,7 @@ where
tree: &mut Tree,
_layout: Layout<'_>,
_renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation<
iced_core::widget::OperationOutputWrapper<Message>,
>,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
let state = tree.state.downcast_mut::<LocalState>();
operation.focusable(state, Some(&self.id.0));
@ -1492,7 +1489,7 @@ where
if self.model.text(key).is_some_and(|text| !text.is_empty()) {
// Draw the text for this segmented button or tab.
renderer.fill_paragraph(
&state.paragraphs[key],
state.paragraphs[key].raw(),
bounds.position(),
apply_alpha(status_appearance.text_color),
Rectangle {
@ -1528,6 +1525,7 @@ where
tree: &'b mut Tree,
layout: iced_core::Layout<'_>,
_renderer: &Renderer,
translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
let state = tree.state.downcast_ref::<LocalState>();
@ -1575,6 +1573,7 @@ where
root_bounds_list: vec![bounds],
path_highlight: Some(PathHighlight::MenuActive),
style: &crate::theme::menu_bar::MenuBarStyle::Default,
position: Point::new(translation.x, translation.y),
}
.overlay(),
)
@ -1645,7 +1644,7 @@ pub struct LocalState {
/// Dimensions of internal buttons when shrinking
pub(super) internal_layout: Vec<(Size, Size)>,
/// The paragraphs for each text.
paragraphs: SecondaryMap<Entity, crate::Paragraph>,
paragraphs: SecondaryMap<Entity, crate::Plain>,
/// Used to detect changes in text.
text_hashes: SecondaryMap<Entity, u64>,
/// Location of cursor when context menu was opened.

View file

@ -9,7 +9,7 @@ use crate::{
Element,
};
use derive_setters::Setters;
use iced_core::{text::Wrap, Length};
use iced_core::{text::Wrapping, Length};
use taffy::AlignContent;
/// A settings item aligned in a row
@ -20,8 +20,8 @@ pub fn item<'a, Message: 'static>(
widget: impl Into<Element<'a, Message>> + 'a,
) -> Row<'a, Message> {
item_row(vec![
text(title).wrap(Wrap::Word).into(),
horizontal_space(iced::Length::Fill).into(),
text(title).wrapping(Wrapping::Word).into(),
horizontal_space().width(iced::Length::Fill).into(),
widget.into(),
])
}
@ -35,7 +35,7 @@ pub fn item_row<Message>(children: Vec<Element<Message>>) -> Row<Message> {
} = theme::THEME.lock().unwrap().cosmic().spacing;
row::with_children(children)
.spacing(space_xs)
.align_items(iced::Alignment::Center)
.align_y(iced::Alignment::Center)
.padding([0, space_s])
}
@ -46,7 +46,10 @@ pub fn flex_item<'a, Message: 'static>(
widget: impl Into<Element<'a, Message>> + 'a,
) -> FlexRow<'a, Message> {
flex_item_row(vec![
text(title).wrap(Wrap::Word).width(Length::Fill).into(),
text(title)
.wrapping(Wrapping::Word)
.width(Length::Fill)
.into(),
container(widget).into(),
])
}
@ -111,8 +114,8 @@ impl<'a, Message: 'static> Item<'a, Message> {
if let Some(description) = self.description {
let column = column::with_capacity(2)
.spacing(2)
.push(text(self.title).wrap(Wrap::Word))
.push(text(description).wrap(Wrap::Word).size(10))
.push(text(self.title).wrapping(Wrapping::Word))
.push(text(description).wrapping(Wrapping::Word).size(10))
.width(Length::Fill);
contents.push(column.into());
@ -129,6 +132,6 @@ impl<'a, Message: 'static> Item<'a, Message> {
is_checked: bool,
message: impl Fn(bool) -> Message + 'static,
) -> Row<'a, Message> {
self.control(crate::widget::toggler(None, is_checked, message))
self.control(crate::widget::toggler(is_checked, message))
}
}

View file

@ -56,11 +56,10 @@ impl<'a, Message: 'static> SpinButton<'a, Message> {
.apply(button::custom)
.width(Length::Fixed(32.0))
.height(Length::Fixed(32.0))
.style(theme::Button::Text)
.class(theme::Button::Text)
.on_press(model::Message::Decrement)
.into(),
text::title4(label)
.vertical_alignment(Vertical::Center)
.apply(container)
.width(Length::Fixed(48.0))
.align_x(Horizontal::Center)
@ -76,18 +75,18 @@ impl<'a, Message: 'static> SpinButton<'a, Message> {
.apply(button::custom)
.width(Length::Fixed(32.0))
.height(Length::Fixed(32.0))
.style(theme::Button::Text)
.class(theme::Button::Text)
.on_press(model::Message::Increment)
.into(),
])
.width(Length::Shrink)
.height(Length::Fixed(32.0))
.align_items(Alignment::Center),
.align_y(Alignment::Center),
)
.align_y(Vertical::Center)
.width(Length::Shrink)
.height(Length::Fixed(32.0))
.style(theme::Container::custom(container_style))
.class(theme::Container::custom(container_style))
.apply(Element::from)
.map(on_change)
}
@ -100,13 +99,13 @@ impl<'a, Message: 'static> From<SpinButton<'a, Message>> for Element<'a, Message
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn container_style(theme: &crate::Theme) -> iced_style::container::Appearance {
fn container_style(theme: &crate::Theme) -> iced_widget::container::Style {
let basic = &theme.cosmic();
let mut neutral_10 = basic.palette.neutral_10;
neutral_10.alpha = 0.1;
let accent = &basic.accent;
let corners = &basic.corner_radii;
iced_style::container::Appearance {
iced_widget::container::Style {
icon_color: Some(basic.palette.neutral_10.into()),
text_color: Some(basic.palette.neutral_10.into()),
background: None,

View file

@ -7,7 +7,7 @@ use std::borrow::Cow;
///
/// [`Text`]: widget::Text
pub fn text<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text).font(crate::font::default())
Text::new(text.into()).font(crate::font::default())
}
/// Available presets for text typography
@ -26,7 +26,7 @@ pub enum Typography {
/// [`Text`] widget with the Title 1 typography preset.
pub fn title1<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text)
Text::new(text.into())
.size(32.0)
.line_height(LineHeight::Absolute(44.0.into()))
.font(crate::font::semibold())
@ -34,7 +34,7 @@ pub fn title1<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
/// [`Text`] widget with the Title 2 typography preset.
pub fn title2<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text)
Text::new(text.into())
.size(28.0)
.line_height(LineHeight::Absolute(36.0.into()))
.font(crate::font::default())
@ -42,7 +42,7 @@ pub fn title2<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
/// [`Text`] widget with the Title 3 typography preset.
pub fn title3<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text)
Text::new(text.into())
.size(24.0)
.line_height(LineHeight::Absolute(32.0.into()))
.font(crate::font::default())
@ -50,7 +50,7 @@ pub fn title3<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
/// [`Text`] widget with the Title 4 typography preset.
pub fn title4<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text)
Text::new(text.into())
.size(20.0)
.line_height(LineHeight::Absolute(28.0.into()))
.font(crate::font::default())
@ -58,7 +58,7 @@ pub fn title4<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
/// [`Text`] widget with the Heading typography preset.
pub fn heading<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text)
Text::new(text.into())
.size(14.0)
.line_height(LineHeight::Absolute(iced::Pixels(20.0)))
.font(crate::font::semibold())
@ -66,7 +66,7 @@ pub fn heading<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
/// [`Text`] widget with the Caption Heading typography preset.
pub fn caption_heading<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text)
Text::new(text.into())
.size(10.0)
.line_height(LineHeight::Absolute(iced::Pixels(14.0)))
.font(crate::font::semibold())
@ -74,7 +74,7 @@ pub fn caption_heading<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate
/// [`Text`] widget with the Body typography preset.
pub fn body<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text)
Text::new(text.into())
.size(14.0)
.line_height(LineHeight::Absolute(20.0.into()))
.font(crate::font::default())
@ -82,7 +82,7 @@ pub fn body<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Re
/// [`Text`] widget with the Caption typography preset.
pub fn caption<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text)
Text::new(text.into())
.size(10.0)
.line_height(LineHeight::Absolute(14.0.into()))
.font(crate::font::default())
@ -90,7 +90,7 @@ pub fn caption<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
/// [`Text`] widget with the Monotext typography preset.
pub fn monotext<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text)
Text::new(text.into())
.size(14.0)
.line_height(LineHeight::Absolute(20.0.into()))
.font(crate::font::mono())

View file

@ -18,6 +18,9 @@ use super::style::StyleSheet;
pub use super::value::Value;
use apply::Apply;
use cosmic_theme::Theme;
use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent};
use iced::clipboard::mime::AsMimeTypes;
use iced::Limits;
use iced_core::event::{self, Event};
use iced_core::mouse::{self, click};
@ -39,15 +42,9 @@ use iced_core::{
};
#[cfg(feature = "wayland")]
use iced_renderer::core::event::{wayland, PlatformSpecific};
use iced_renderer::core::widget::OperationOutputWrapper;
#[cfg(feature = "wayland")]
use iced_runtime::command::platform_specific;
use iced_runtime::Command;
#[cfg(feature = "wayland")]
use cctk::sctk::reexports::client::protocol::wl_data_device_manager::DndAction;
#[cfg(feature = "wayland")]
use iced_runtime::command::platform_specific::wayland::data_device::{DataFromMimeType, DndIcon};
use iced_runtime::platform_specific;
use iced_runtime::{task, Action, Task};
thread_local! {
// Prevents two inputs from being focused at the same time.
@ -146,7 +143,7 @@ where
})
.size(16)
.apply(crate::widget::button::custom)
.style(crate::theme::Button::Icon)
.class(crate::theme::Button::Icon)
.on_press(msg)
.padding(8)
.into(),
@ -173,7 +170,6 @@ where
.padding(spacing)
}
#[cfg(feature = "wayland")]
pub(crate) const SUPPORTED_TEXT_MIME_TYPES: &[&str; 6] = &[
"text/plain;charset=utf-8",
"text/plain;charset=UTF-8",
@ -182,17 +178,12 @@ pub(crate) const SUPPORTED_TEXT_MIME_TYPES: &[&str; 6] = &[
"text/plain",
"TEXT",
];
#[cfg(feature = "wayland")]
pub type DnDCommand =
Box<dyn Send + Sync + Fn() -> platform_specific::wayland::data_device::ActionInner>;
#[cfg(not(feature = "wayland"))]
pub type DnDCommand = ();
/// A field that can be filled with text.
#[allow(missing_debug_implementations)]
#[must_use]
pub struct TextInput<'a, Message> {
id: Option<Id>,
id: Id,
placeholder: Cow<'a, str>,
value: Value,
is_secure: bool,
@ -215,7 +206,6 @@ pub struct TextInput<'a, Message> {
trailing_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>,
style: <crate::Theme as StyleSheet>::Style,
on_create_dnd_source: Option<Box<dyn Fn(State) -> Message + 'a>>,
on_dnd_command_produced: Option<Box<dyn Fn(DnDCommand) -> Message + 'a>>,
surface_ids: Option<(window::Id, window::Id)>,
dnd_icon: bool,
line_height: text::LineHeight,
@ -237,7 +227,7 @@ where
let v: Cow<'a, str> = value.into();
TextInput {
id: None,
id: Id::unique(),
placeholder: placeholder.into(),
value: Value::new(v.as_ref()),
is_secure: false,
@ -258,7 +248,6 @@ where
trailing_icon: None,
error: None,
style: crate::theme::TextInput::default(),
on_dnd_command_produced: None,
on_create_dnd_source: None,
surface_ids: None,
dnd_icon: false,
@ -269,6 +258,15 @@ where
}
}
fn dnd_id(&self) -> u128 {
match &self.id.0 {
iced_core::id::Internal::Custom(id, _) | iced_core::id::Internal::Unique(id) => {
*id as u128
}
_ => unreachable!(),
}
}
/// Sets the input to be always active.
/// This makes it behave as if it was always focused.
pub fn always_active(mut self) -> Self {
@ -290,7 +288,7 @@ where
/// Sets the [`Id`] of the [`TextInput`].
pub fn id(mut self, id: Id) -> Self {
self.id = Some(id);
self.id = id;
self
}
@ -433,10 +431,12 @@ where
value: Option<&Value>,
style: &renderer::Style,
) {
let text_layout = self.text_layout(layout.clone());
draw(
renderer,
theme,
layout,
text_layout,
cursor_position,
tree,
value.unwrap_or(&self.value),
@ -467,20 +467,6 @@ where
self
}
/// Sets the dnd command produced handler of the [`TextInput`].
/// Commands should be returned in the update function of the application.
#[cfg(feature = "wayland")]
pub fn on_dnd_command_produced(
mut self,
on_dnd_command_produced: impl Fn(
Box<dyn Send + Sync + Fn() -> platform_specific::wayland::data_device::ActionInner>,
) -> Message
+ 'a,
) -> Self {
self.on_dnd_command_produced = Some(Box::new(on_dnd_command_produced));
self
}
/// Sets the window id of the [`TextInput`] and the window id of the drag icon.
/// Both ids are required to be unique.
/// This is required for the dnd to work.
@ -500,7 +486,7 @@ where
crate::widget::icon::from_name("edit-clear-symbolic")
.size(16)
.apply(crate::widget::button::custom)
.style(crate::theme::Button::Icon)
.class(crate::theme::Button::Icon)
.on_press(on_clear)
.padding(8)
.into(),
@ -550,6 +536,7 @@ where
}
let old_value = state
.value
.raw()
.buffer()
.lines
.iter()
@ -559,6 +546,7 @@ where
|| old_value != self.value.to_string()
|| state
.label
.raw()
.buffer()
.lines
.iter()
@ -567,6 +555,7 @@ where
!= self.label.unwrap_or_default()
|| state
.helper_text
.raw()
.buffer()
.lines
.iter()
@ -651,7 +640,7 @@ where
let v = self.value.to_string();
value_paragraph.update(Text {
content: if self.value.is_empty() {
&self.placeholder
self.placeholder.as_ref()
} else {
&v
},
@ -662,7 +651,7 @@ where
vertical_alignment: alignment::Vertical::Center,
line_height: text::LineHeight::default(),
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
});
let Size { width, height } =
@ -717,12 +706,13 @@ where
tree: &mut Tree,
_layout: Layout<'_>,
_renderer: &crate::Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
operation: &mut dyn Operation<()>,
) {
let state = tree.state.downcast_mut::<State>();
operation.focusable(state, self.id.as_ref());
operation.text_input(state, self.id.as_ref());
operation.custom(state, Some(&self.id));
operation.focusable(state, Some(&self.id));
operation.text_input(state, Some(&self.id));
}
fn overlay<'b>(
@ -730,6 +720,7 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &crate::Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let mut layout_ = Vec::with_capacity(2);
if self.leading_icon.is_some() {
@ -752,7 +743,9 @@ where
.zip(&mut tree.children)
.zip(layout_)
.filter_map(|((child, state), layout)| {
child.as_widget_mut().overlay(state, layout, renderer)
child
.as_widget_mut()
.overlay(state, layout, renderer, translation)
})
.collect::<Vec<_>>();
@ -814,8 +807,10 @@ where
}
}
}
let dnd_id = self.dnd_id();
let id = Widget::id(self);
update(
id,
event,
text_layout.children().next().unwrap(),
trailing_icon_layout,
@ -833,9 +828,7 @@ where
self.on_toggle_edit.as_deref(),
|| tree.state.downcast_mut::<State>(),
self.on_create_dnd_source.as_deref(),
self.dnd_icon,
self.on_dnd_command_produced.as_deref(),
self.surface_ids,
dnd_id,
line_height,
layout,
)
@ -851,10 +844,12 @@ where
cursor_position: mouse::Cursor,
viewport: &Rectangle,
) {
let text_layout = self.text_layout(layout.clone());
draw(
renderer,
theme,
layout,
text_layout,
cursor_position,
tree,
&self.value,
@ -934,11 +929,43 @@ where
}
fn id(&self) -> Option<Id> {
self.id.clone()
Some(self.id.clone())
}
fn set_id(&mut self, id: Id) {
self.id = Some(id);
self.id = id;
}
fn drag_destinations(
&self,
state: &Tree,
layout: Layout<'_>,
renderer: &crate::Renderer,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
if let Some(input) = layout.children().last() {
let Rectangle {
x,
y,
width,
height,
} = input.bounds();
dnd_rectangles.push(iced::clipboard::dnd::DndDestinationRectangle {
id: self.dnd_id(),
rectangle: iced::clipboard::dnd::Rectangle {
x: x as f64,
y: y as f64,
width: width as f64,
height: height as f64,
},
mime_types: SUPPORTED_TEXT_MIME_TYPES
.iter()
.map(|s| Cow::Borrowed(*s))
.collect(),
actions: DndAction::Move,
preferred: DndAction::Move,
});
}
}
}
@ -954,32 +981,38 @@ where
}
}
/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`].
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
Command::widget(operation::focusable::focus(id))
/// Produces a [`Task`] that focuses the [`TextInput`] with the given [`Id`].
pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
task::effect(Action::widget(operation::focusable::focus(id)))
}
/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// end.
pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Command<Message> {
Command::widget(operation::text_input::move_cursor_to_end(id))
pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Task<Message> {
task::effect(Action::widget(operation::text_input::move_cursor_to_end(
id,
)))
}
/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// front.
pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Command<Message> {
Command::widget(operation::text_input::move_cursor_to_front(id))
pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Task<Message> {
task::effect(Action::widget(operation::text_input::move_cursor_to_front(
id,
)))
}
/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// provided position.
pub fn move_cursor_to<Message: 'static>(id: Id, position: usize) -> Command<Message> {
Command::widget(operation::text_input::move_cursor_to(id, position))
pub fn move_cursor_to<Message: 'static>(id: Id, position: usize) -> Task<Message> {
task::effect(Action::widget(operation::text_input::move_cursor_to(
id, position,
)))
}
/// Produces a [`Command`] that selects all the content of the [`TextInput`] with the given [`Id`].
pub fn select_all<Message: 'static>(id: Id) -> Command<Message> {
Command::widget(operation::text_input::select_all(id))
/// Produces a [`Task`] that selects all the content of the [`TextInput`] with the given [`Id`].
pub fn select_all<Message: 'static>(id: Id) -> Task<Message> {
task::effect(Action::widget(operation::text_input::select_all(id)))
}
/// Computes the layout of a [`TextInput`].
@ -1019,7 +1052,7 @@ pub fn layout<Message>(
vertical_alignment: alignment::Vertical::Center,
line_height,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
});
let label_size = label_paragraph.min_bounds();
@ -1158,7 +1191,7 @@ pub fn layout<Message>(
vertical_alignment: alignment::Vertical::Center,
line_height: helper_text_line_height,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
});
let helper_text_size = helper_text_paragraph.min_bounds();
let helper_text_node = layout::Node::new(helper_text_size).translate(helper_pos);
@ -1188,7 +1221,8 @@ pub fn layout<Message>(
#[allow(clippy::missing_panics_doc)]
#[allow(clippy::cast_lossless)]
#[allow(clippy::cast_possible_truncation)]
pub fn update<'a, Message>(
pub fn update<'a, Message: 'static>(
id: Option<Id>,
event: Event,
text_layout: Layout<'_>,
trailing_icon_layout: Option<Layout<'_>>,
@ -1206,9 +1240,7 @@ pub fn update<'a, Message>(
on_toggle_edit: Option<&dyn Fn(bool) -> Message>,
state: impl FnOnce() -> &'a mut State,
#[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>,
#[allow(unused_variables)] dnd_icon: bool,
#[allow(unused_variables)] on_dnd_command_produced: Option<&dyn Fn(DnDCommand) -> Message>,
#[allow(unused_variables)] surface_ids: Option<(window::Id, window::Id)>,
#[allow(unused_variables)] dnd_id: u128,
line_height: text::LineHeight,
layout: Layout<'_>,
) -> event::Status
@ -1253,7 +1285,8 @@ where
let text_layout = layout.children().next().unwrap();
let target = cursor_position.x - text_layout.bounds().x;
let click = mouse::Click::new(cursor_position, state.last_click);
let click =
mouse::Click::new(cursor_position, mouse::Button::Left, state.last_click);
match (
&state.dragging_state,
@ -1266,28 +1299,18 @@ where
// single click that is on top of the selected text
// is the click on selected text?
if let (
Some(on_start_dnd),
Some(on_dnd_command_produced),
Some((window_id, icon_id)),
Some(on_input),
) = (
on_start_dnd_source,
on_dnd_command_produced,
surface_ids,
on_input,
) {
if let Some(on_input) = on_input {
let left = start.min(end);
let right = end.max(start);
let (left_position, _left_offset) = measure_cursor_and_scroll_offset(
&state.value,
state.value.raw(),
text_layout.bounds(),
left,
);
let (right_position, _right_offset) = measure_cursor_and_scroll_offset(
&state.value,
state.value.raw(),
text_layout.bounds(),
right,
);
@ -1305,10 +1328,12 @@ where
if is_secure {
return event::Status::Ignored;
}
let text =
let input_text =
state.selected_text(&value.to_string()).unwrap_or_default();
state.dragging_state =
Some(DraggingState::Dnd(DndAction::empty(), text.clone()));
state.dragging_state = Some(DraggingState::Dnd(
DndAction::empty(),
input_text.clone(),
));
let mut editor = Editor::new(unsecured_value, &mut state.cursor);
editor.delete();
@ -1316,23 +1341,25 @@ where
let unsecured_value = Value::new(&contents);
let message = (on_input)(contents);
shell.publish(message);
shell.publish(on_start_dnd(state.clone()));
if let Some(on_start_dnd) = on_start_dnd_source {
shell.publish(on_start_dnd(state.clone()));
}
let state_clone = state.clone();
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::StartDnd {
mime_types: SUPPORTED_TEXT_MIME_TYPES
.iter()
.map(std::string::ToString::to_string)
.collect(),
actions: DndAction::Move,
origin_id: window_id,
icon_id: Some((
DndIcon::Widget(icon_id, Box::new(state_clone.clone())),
iced::Vector::ZERO,
)),
data: Box::new(TextInputString(text.clone())),
}
})));
iced_core::clipboard::start_dnd(
clipboard,
false,
id.map(|id| iced_core::clipboard::DndSource::Widget(id)),
Some((
Element::from(
TextInput::<'static, ()>::new("", input_text.clone())
.dnd_icon(true),
),
iced_core::widget::tree::State::new(state_clone),
)),
Box::new(TextInputString(input_text)),
DndAction::Move,
);
update_cache(state, &unsecured_value);
} else {
@ -1435,7 +1462,7 @@ where
return event::Status::Ignored;
};
let click = mouse::Click::new(pos, state.last_click);
let click = mouse::Click::new(pos, mouse::Button::Left, state.last_click);
match (
&state.dragging_state,
@ -1621,7 +1648,10 @@ where
{
if !is_secure {
if let Some((start, end)) = state.cursor.selection(value) {
clipboard.write(value.select(start, end).to_string());
clipboard.write(
iced_core::clipboard::Kind::Primary,
value.select(start, end).to_string(),
);
}
}
}
@ -1632,7 +1662,10 @@ where
{
if !is_secure {
if let Some((start, end)) = state.cursor.selection(value) {
clipboard.write(value.select(start, end).to_string());
clipboard.write(
iced_core::clipboard::Kind::Primary,
value.select(start, end).to_string(),
);
}
let mut editor = Editor::new(value, &mut state.cursor);
@ -1650,7 +1683,7 @@ where
content
} else {
let content: String = clipboard
.read()
.read(iced_core::clipboard::Kind::Primary)
.unwrap_or_default()
.chars()
.filter(|c| !c.is_control())
@ -1760,7 +1793,7 @@ where
state.keyboard_modifiers = modifiers;
}
Event::Window(_, window::Event::RedrawRequested(now)) => {
Event::Window(window::Event::RedrawRequested(now)) => {
let state = state();
if let Some(focus) = &mut state.is_focused {
@ -1775,9 +1808,7 @@ where
}
}
#[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DataSource(
wayland::DataSourceEvent::DndFinished | wayland::DataSourceEvent::Cancelled,
))) => {
Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
let state = state();
if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
state.dragging_state = None;
@ -1785,54 +1816,32 @@ where
}
}
#[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DataSource(
wayland::DataSourceEvent::DndActionAccepted(action),
))) => {
let state = state();
if let Some(DraggingState::Dnd(_, text)) = state.dragging_state.as_ref() {
state.dragging_state = Some(DraggingState::Dnd(action, text.clone()));
return event::Status::Captured;
}
}
#[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer(
wayland::DndOfferEvent::Enter { x, y, mime_types },
))) => {
let Some(on_dnd_command_produced) = on_dnd_command_produced else {
return event::Status::Ignored;
};
Event::Dnd(DndEvent::Offer(
rectangle,
OfferEvent::Enter {
x,
y,
mime_types,
surface,
},
)) if rectangle == Some(dnd_id) => {
let state = state();
let is_clicked = text_layout.bounds().contains(Point {
x: x as f32,
y: y as f32,
});
if !is_clicked {
state.dnd_offer = DndOfferState::OutsideWidget(mime_types, DndAction::None);
return event::Status::Captured;
}
let mut accepted = false;
for m in &mime_types {
if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) {
let clone = m.clone();
accepted = true;
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::Accept(Some(
clone.clone(),
))
})));
}
}
if accepted {
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::SetActions {
preferred: DndAction::Move,
accepted: DndAction::Move.union(DndAction::Copy),
}
})));
let target = x as f32 - text_layout.bounds().x;
state.dnd_offer = DndOfferState::HandlingOffer(mime_types.clone(), DndAction::None);
state.dnd_offer =
DndOfferState::HandlingOffer(mime_types.clone(), DndAction::empty());
// existing logic for setting the selection
let position = if target > 0.0 {
update_cache(state, value);
@ -1846,57 +1855,11 @@ where
}
}
#[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer(
wayland::DndOfferEvent::Motion { x, y },
))) => {
let Some(on_dnd_command_produced) = on_dnd_command_produced else {
return event::Status::Ignored;
};
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y }))
if rectangle == Some(dnd_id) =>
{
let state = state();
let is_clicked = text_layout.bounds().contains(Point {
x: x as f32,
y: y as f32,
});
if !is_clicked {
if let DndOfferState::HandlingOffer(mime_types, action) = state.dnd_offer.clone() {
state.dnd_offer = DndOfferState::OutsideWidget(mime_types, action);
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::SetActions {
preferred: DndAction::None,
accepted: DndAction::None,
}
})));
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::Accept(None)
})));
}
return event::Status::Captured;
} else if let DndOfferState::OutsideWidget(mime_types, action) = state.dnd_offer.clone()
{
let mut accepted = false;
for m in &mime_types {
if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) {
accepted = true;
let clone = m.clone();
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::Accept(Some(
clone.clone(),
))
})));
}
}
if accepted {
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::SetActions {
preferred: DndAction::Move,
accepted: DndAction::Move.union(DndAction::Copy),
}
})));
state.dnd_offer = DndOfferState::HandlingOffer(mime_types.clone(), action);
}
};
let target = x as f32 - text_layout.bounds().x;
// existing logic for setting the selection
let position = if target > 0.0 {
@ -1910,13 +1873,7 @@ where
return event::Status::Captured;
}
#[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer(
wayland::DndOfferEvent::DropPerformed,
))) => {
let Some(on_dnd_command_produced) = on_dnd_command_produced else {
return event::Status::Ignored;
};
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if rectangle == Some(dnd_id) => {
let state = state();
if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES
@ -1927,21 +1884,15 @@ where
return event::Status::Captured;
};
state.dnd_offer = DndOfferState::Dropped;
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::RequestDndData(
(*mime_type).to_string(),
)
})));
} else if let DndOfferState::OutsideWidget(..) = &state.dnd_offer {
state.dnd_offer = DndOfferState::None;
return event::Status::Captured;
}
return event::Status::Ignored;
}
#[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer(
wayland::DndOfferEvent::Leave,
))) => {
Event::Dnd(DndEvent::Offer(
rectangle,
OfferEvent::Leave | OfferEvent::LeaveDestination,
)) if rectangle == Some(dnd_id) => {
let state = state();
// ASHLEY TODO we should be able to reset but for now we don't if we are handling a
// drop
@ -1954,13 +1905,9 @@ where
return event::Status::Captured;
}
#[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer(
wayland::DndOfferEvent::DndData { mime_type, data },
))) => {
let Some(on_dnd_command_produced) = on_dnd_command_produced else {
return event::Status::Ignored;
};
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type }))
if rectangle == Some(dnd_id) =>
{
let state = state();
if let DndOfferState::Dropped = state.dnd_offer.clone() {
state.dnd_offer = DndOfferState::None;
@ -1982,9 +1929,6 @@ where
shell.publish(message);
}
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::DndFinished
})));
let value = if is_secure {
unsecured_value.secure()
} else {
@ -1995,26 +1939,6 @@ where
}
return event::Status::Ignored;
}
#[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer(
wayland::DndOfferEvent::SourceActions(actions),
))) => {
let Some(on_dnd_command_produced) = on_dnd_command_produced else {
return event::Status::Ignored;
};
let state = state();
if let DndOfferState::HandlingOffer(..) = state.dnd_offer.clone() {
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::SetActions {
preferred: actions.intersection(DndAction::Move),
accepted: actions,
}
})));
return event::Status::Captured;
}
return event::Status::Ignored;
}
_ => {}
}
@ -2032,6 +1956,7 @@ pub fn draw<'a, Message>(
renderer: &mut crate::Renderer,
theme: &crate::Theme,
layout: Layout<'_>,
text_layout: Layout<'_>,
cursor_position: mouse::Cursor,
tree: &Tree,
value: &Value,
@ -2083,7 +2008,7 @@ pub fn draw<'a, Message>(
let mut children_layout = layout.children();
let bounds = layout.bounds();
let text_bounds = children_layout.next().unwrap().bounds();
let text_bounds = text_layout.bounds();
let is_mouse_over = cursor_position.is_over(bounds);
@ -2172,7 +2097,7 @@ pub fn draw<'a, Message>(
if let (Some(label_layout), Some(label)) = (label_layout, label) {
renderer.fill_text(
Text {
content: label,
content: label.to_string(),
size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
font: font.unwrap_or_else(|| renderer.default_font()),
bounds: label_layout.bounds().size(),
@ -2180,7 +2105,7 @@ pub fn draw<'a, Message>(
vertical_alignment: alignment::Vertical::Top,
line_height,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
},
label_layout.bounds().position(),
appearance.label_color,
@ -2214,14 +2139,24 @@ pub fn draw<'a, Message>(
let size = size.unwrap_or_else(|| renderer.default_size().0);
let radius_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0.into();
let (cursor, offset) = if let Some(focus) = &state.is_focused {
#[cfg(feature = "wayland")]
let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None);
#[cfg(not(feature = "wayland"))]
let handling_dnd_offer = false;
let (cursor, offset) = if let Some(focus) = &state.is_focused.or_else(|| {
handling_dnd_offer.then(|| Focus {
updated_at: Instant::now(),
now: Instant::now(),
})
}) {
match state.cursor.state(value) {
cursor::State::Index(position) => {
let (text_value_width, offset) =
measure_cursor_and_scroll_offset(&state.value, text_bounds, position);
measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, position);
let is_cursor_visible =
((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS) % 2
let is_cursor_visible = handling_dnd_offer
|| ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS)
% 2
== 0;
if is_cursor_visible {
@ -2263,10 +2198,10 @@ pub fn draw<'a, Message>(
let value_paragraph = &state.value;
let (left_position, left_offset) =
measure_cursor_and_scroll_offset(value_paragraph, text_bounds, left);
measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, left);
let (right_position, right_offset) =
measure_cursor_and_scroll_offset(value_paragraph, text_bounds, right);
measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, right);
let width = right_position - left_position;
@ -2331,7 +2266,11 @@ pub fn draw<'a, Message>(
renderer.fill_text(
Text {
content: if text.is_empty() { placeholder } else { &text },
content: if text.is_empty() {
placeholder.to_string()
} else {
text.clone()
},
font,
bounds: bounds.size(),
size: iced::Pixels(size),
@ -2339,7 +2278,7 @@ pub fn draw<'a, Message>(
vertical_alignment: alignment::Vertical::Center,
line_height: text::LineHeight::default(),
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
},
bounds.position(),
color,
@ -2374,7 +2313,7 @@ pub fn draw<'a, Message>(
if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) {
renderer.fill_text(
Text {
content: helper_text,
content: helper_text.to_string(), // TODO remove to_string?
size: iced::Pixels(helper_text_size),
font,
bounds: helper_text_layout.bounds().size(),
@ -2382,7 +2321,7 @@ pub fn draw<'a, Message>(
vertical_alignment: alignment::Vertical::Top,
line_height: helper_line_height,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
},
helper_text_layout.bounds().position(),
text_color,
@ -2414,11 +2353,23 @@ pub fn mouse_interaction(
pub struct TextInputString(pub String);
#[cfg(feature = "wayland")]
impl DataFromMimeType for TextInputString {
fn from_mime_type(&self, mime_type: &str) -> Option<Vec<u8>> {
SUPPORTED_TEXT_MIME_TYPES
.contains(&mime_type)
.then(|| self.0.as_bytes().to_vec())
impl AsMimeTypes for TextInputString {
fn available(&self) -> Cow<'static, [String]> {
Cow::Owned(
SUPPORTED_TEXT_MIME_TYPES
.iter()
.cloned()
.map(String::from)
.collect::<Vec<_>>(),
)
}
fn as_bytes(&self, mime_type: &str) -> Option<Cow<'static, [u8]>> {
if SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type) {
Some(Cow::Owned(self.0.clone().as_bytes().to_vec()))
} else {
None
}
}
}
@ -2434,7 +2385,6 @@ pub(crate) enum DraggingState {
pub(crate) enum DndOfferState {
#[default]
None,
OutsideWidget(Vec<String>, DndAction),
HandlingOffer(Vec<String>, DndAction),
Dropped,
}
@ -2446,17 +2396,16 @@ pub(crate) struct DndOfferState;
#[derive(Debug, Default, Clone)]
#[must_use]
pub struct State {
pub value: crate::Paragraph,
pub placeholder: crate::Paragraph,
pub label: crate::Paragraph,
pub helper_text: crate::Paragraph,
pub value: crate::Plain,
pub placeholder: crate::Plain,
pub label: crate::Plain,
pub helper_text: crate::Plain,
pub dirty: bool,
pub is_secure: bool,
pub is_read_only: bool,
select_on_focus: bool,
is_focused: Option<Focus>,
dragging_state: Option<DraggingState>,
#[cfg(feature = "wayland")]
dnd_offer: DndOfferState,
is_pasting: Option<Value>,
last_click: Option<mouse::Click>,
@ -2522,15 +2471,14 @@ impl State {
pub fn focused(is_secure: bool, is_read_only: bool) -> Self {
Self {
is_secure,
value: crate::Paragraph::new(),
placeholder: crate::Paragraph::new(),
label: crate::Paragraph::new(),
helper_text: crate::Paragraph::new(),
value: crate::Plain::default(),
placeholder: crate::Plain::default(),
label: crate::Plain::default(),
helper_text: crate::Plain::default(),
is_read_only,
is_focused: None,
select_on_focus: false,
dragging_state: None,
#[cfg(feature = "wayland")]
dnd_offer: DndOfferState::default(),
is_pasting: None,
last_click: None,
@ -2654,6 +2602,7 @@ fn find_cursor_position(
let char_offset = state
.value
.raw()
.hit_test(Point::new(x + offset, text_bounds.height / 2.0))
.map(text::Hit::cursor)?;
@ -2677,7 +2626,7 @@ fn replace_paragraph(
let mut children_layout = layout.children();
let text_bounds = children_layout.next().unwrap().bounds();
state.value = crate::Paragraph::with_text(Text {
state.value = crate::Plain::new(Text {
font,
line_height,
content: &value.to_string(),
@ -2686,7 +2635,7 @@ fn replace_paragraph(
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(),
wrapping: text::Wrapping::default(),
});
}
@ -2714,7 +2663,7 @@ fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 {
};
let (_, offset) =
measure_cursor_and_scroll_offset(&state.value, text_bounds, focus_position);
measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, focus_position);
offset
} else {

View file

@ -8,7 +8,7 @@ use std::rc::Rc;
use crate::widget::container;
use crate::widget::Column;
use crate::Command;
use iced::{Padding, Task};
use iced_core::Element;
use slotmap::new_key_type;
use slotmap::SlotMap;
@ -47,15 +47,15 @@ pub fn toaster<'a, Message: Clone + 'static>(
button::icon(icon::from_name("window-close-symbolic"))
.on_press((toasts.on_close)(id)),
)
.align_items(iced::Alignment::Center)
.align_y(iced::Alignment::Center)
.spacing(space_xxs),
)
.align_items(iced::Alignment::Center)
.align_y(iced::Alignment::Center)
.spacing(space_s);
container(row)
.padding([space_xxs, space_s, space_xxs, space_m])
.style(crate::style::Container::Tooltip)
.class(crate::style::Container::Tooltip)
};
let col = toasts
@ -175,7 +175,7 @@ impl<Message: Clone + Send + 'static> Toasts<Message> {
}
/// Add a new [`Toast`]
pub fn push(&mut self, toast: Toast<Message>) -> Command<Message> {
pub fn push(&mut self, toast: Toast<Message>) -> Task<Message> {
while self.toasts.len() >= self.limit {
self.toasts.remove(
self.queue
@ -200,7 +200,7 @@ impl<Message: Clone + Send + 'static> Toasts<Message> {
}
#[cfg(not(feature = "tokio"))]
{
Command::none()
Task::none()
}
}

View file

@ -14,7 +14,6 @@ use iced_core::widget::Operation;
use iced_core::Element;
use iced_core::Overlay;
use iced_core::{Clipboard, Layout, Length, Point, Rectangle, Shell, Vector, Widget};
use iced_renderer::core::widget::OperationOutputWrapper;
pub struct Toaster<'a, Message, Theme, Renderer> {
toasts: Element<'a, Message, Theme, Renderer>,
@ -90,7 +89,7 @@ where
state: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
operation: &mut dyn Operation<()>,
) {
self.content
.as_widget()
@ -142,22 +141,23 @@ where
state: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
//TODO: this hides the overlay of the content during the toast
if self.is_empty {
self.content
.as_widget_mut()
.overlay(&mut state.children[0], layout, renderer)
self.content.as_widget_mut().overlay(
&mut state.children[0],
layout,
renderer,
translation,
)
} else {
let bounds = layout.bounds();
Some(overlay::Element::new(
bounds.position(),
Box::new(ToasterOverlay::new(
&mut state.children[1],
&mut self.toasts,
)),
))
Some(overlay::Element::new(Box::new(ToasterOverlay::new(
&mut state.children[1],
&mut self.toasts,
))))
}
}
@ -166,7 +166,7 @@ where
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
self.content.as_widget().drag_destinations(
&state.children[0],
@ -196,13 +196,7 @@ impl<'a, 'b, Message, Theme, Renderer> Overlay<Message, Theme, Renderer>
where
Renderer: renderer::Renderer,
{
fn layout(
&mut self,
renderer: &Renderer,
bounds: Size,
position: Point,
_translation: Vector,
) -> Node {
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
let limits = Limits::new(Size::ZERO, bounds);
let mut node = self
@ -276,7 +270,7 @@ where
) -> Option<overlay::Element<'c, Message, Theme, Renderer>> {
self.element
.as_widget_mut()
.overlay(self.state, layout, renderer)
.overlay(self.state, layout, renderer, Default::default())
}
}

View file

@ -4,15 +4,15 @@
use iced::{widget, Length};
use iced_core::text;
pub fn toggler<'a, Message, Theme: iced_widget::toggler::StyleSheet, Renderer>(
label: impl Into<Option<String>>,
pub fn toggler<'a, Message, Theme: iced_widget::toggler::Catalog, Renderer>(
is_checked: bool,
f: impl Fn(bool) -> Message + 'a,
) -> widget::Toggler<'a, Message, Theme, Renderer>
where
Renderer: iced_core::Renderer + text::Renderer,
{
widget::Toggler::new(label, is_checked, f)
widget::Toggler::new(is_checked)
.on_toggle(f)
.size(24)
.width(Length::Shrink)
}

View file

@ -41,9 +41,9 @@ impl<'a, Message: 'static + Clone> Warning<'a, Message> {
widget::row::with_capacity(2)
.push(label)
.push(close_button)
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.apply(widget::container)
.style(theme::Container::custom(warning_container))
.class(theme::Container::custom(warning_container))
.padding(10)
.align_y(alignment::Vertical::Center)
.width(Length::Fill)
@ -57,9 +57,9 @@ impl<'a, Message: 'static + Clone> From<Warning<'a, Message>> for Element<'a, Me
}
#[must_use]
pub fn warning_container(theme: &Theme) -> widget::container::Appearance {
pub fn warning_container(theme: &Theme) -> widget::container::Style {
let cosmic = theme.cosmic();
widget::container::Appearance {
widget::container::Style {
icon_color: Some(theme.cosmic().warning.on.into()),
text_color: Some(theme.cosmic().warning.on.into()),
background: Some(Background::Color(theme.cosmic().warning_color().into())),