summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2024-05-23 13:29:45 +0200
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2024-05-23 13:29:45 +0200
commitd8ba6b0673a33724a177f3a1ba59705527280142 (patch)
tree89482c8d1e3a03e00b3a8151abbb81e30ae5898c /core
parent72ed8bcc8def9956e25f3720a3095fc96bb2eef0 (diff)
parent468794d918eb06c1dbebb33c32b10017ad335f05 (diff)
downloadiced-d8ba6b0673a33724a177f3a1ba59705527280142.tar.gz
iced-d8ba6b0673a33724a177f3a1ba59705527280142.tar.bz2
iced-d8ba6b0673a33724a177f3a1ba59705527280142.zip
Merge branch 'master' into feat/text-macro
Diffstat (limited to 'core')
-rw-r--r--core/Cargo.toml13
-rw-r--r--core/src/angle.rs75
-rw-r--r--core/src/content_fit.rs17
-rw-r--r--core/src/element.rs4
-rw-r--r--core/src/hasher.rs2
-rw-r--r--core/src/image.rs216
-rw-r--r--core/src/layout.rs2
-rw-r--r--core/src/lib.rs11
-rw-r--r--core/src/mouse/interaction.rs1
-rw-r--r--core/src/overlay.rs2
-rw-r--r--core/src/rectangle.rs79
-rw-r--r--core/src/renderer.rs33
-rw-r--r--core/src/renderer/null.rs75
-rw-r--r--core/src/rotation.rs72
-rw-r--r--core/src/size.rs45
-rw-r--r--core/src/svg.rs16
-rw-r--r--core/src/text.rs10
-rw-r--r--core/src/text/paragraph.rs6
-rw-r--r--core/src/theme.rs25
-rw-r--r--core/src/theme/palette.rs18
-rw-r--r--core/src/transformation.rs6
-rw-r--r--core/src/vector.rs3
-rw-r--r--core/src/widget.rs2
-rw-r--r--core/src/widget/text.rs199
-rw-r--r--core/src/window/position.rs8
-rw-r--r--core/src/window/redraw_request.rs2
-rw-r--r--core/src/window/settings/wasm.rs12
27 files changed, 694 insertions, 260 deletions
diff --git a/core/Cargo.toml b/core/Cargo.toml
index c273fcb4..3c557bca 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -10,17 +10,28 @@ homepage.workspace = true
categories.workspace = true
keywords.workspace = true
+[lints]
+workspace = true
+
+[features]
+auto-detect-theme = ["dep:dark-light"]
+advanced = []
+
[dependencies]
bitflags.workspace = true
+bytes.workspace = true
glam.workspace = true
log.workspace = true
num-traits.workspace = true
once_cell.workspace = true
palette.workspace = true
+rustc-hash.workspace = true
smol_str.workspace = true
thiserror.workspace = true
web-time.workspace = true
-xxhash-rust.workspace = true
+
+dark-light.workspace = true
+dark-light.optional = true
[target.'cfg(windows)'.dependencies]
raw-window-handle.workspace = true
diff --git a/core/src/angle.rs b/core/src/angle.rs
index dc3c0e93..9c8a9b24 100644
--- a/core/src/angle.rs
+++ b/core/src/angle.rs
@@ -1,12 +1,17 @@
use crate::{Point, Rectangle, Vector};
use std::f32::consts::{FRAC_PI_2, PI};
-use std::ops::{Add, AddAssign, Div, Mul, RangeInclusive, Sub, SubAssign};
+use std::ops::{Add, AddAssign, Div, Mul, RangeInclusive, Rem, Sub, SubAssign};
/// Degrees
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct Degrees(pub f32);
+impl Degrees {
+ /// The range of degrees of a circle.
+ pub const RANGE: RangeInclusive<Self> = Self(0.0)..=Self(360.0);
+}
+
impl PartialEq<f32> for Degrees {
fn eq(&self, other: &f32) -> bool {
self.0.eq(other)
@@ -19,6 +24,52 @@ impl PartialOrd<f32> for Degrees {
}
}
+impl From<f32> for Degrees {
+ fn from(degrees: f32) -> Self {
+ Self(degrees)
+ }
+}
+
+impl From<u8> for Degrees {
+ fn from(degrees: u8) -> Self {
+ Self(f32::from(degrees))
+ }
+}
+
+impl From<Degrees> for f32 {
+ fn from(degrees: Degrees) -> Self {
+ degrees.0
+ }
+}
+
+impl From<Degrees> for f64 {
+ fn from(degrees: Degrees) -> Self {
+ Self::from(degrees.0)
+ }
+}
+
+impl Mul<f32> for Degrees {
+ type Output = Degrees;
+
+ fn mul(self, rhs: f32) -> Self::Output {
+ Self(self.0 * rhs)
+ }
+}
+
+impl num_traits::FromPrimitive for Degrees {
+ fn from_i64(n: i64) -> Option<Self> {
+ Some(Self(n as f32))
+ }
+
+ fn from_u64(n: u64) -> Option<Self> {
+ Some(Self(n as f32))
+ }
+
+ fn from_f64(n: f64) -> Option<Self> {
+ Some(Self(n as f32))
+ }
+}
+
/// Radians
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct Radians(pub f32);
@@ -65,6 +116,12 @@ impl From<u8> for Radians {
}
}
+impl From<Radians> for f32 {
+ fn from(radians: Radians) -> Self {
+ radians.0
+ }
+}
+
impl From<Radians> for f64 {
fn from(radians: Radians) -> Self {
Self::from(radians.0)
@@ -107,6 +164,14 @@ impl Add for Radians {
}
}
+impl Add<Degrees> for Radians {
+ type Output = Self;
+
+ fn add(self, rhs: Degrees) -> Self::Output {
+ Self(self.0 + rhs.0.to_radians())
+ }
+}
+
impl AddAssign for Radians {
fn add_assign(&mut self, rhs: Radians) {
self.0 = self.0 + rhs.0;
@@ -153,6 +218,14 @@ impl Div for Radians {
}
}
+impl Rem for Radians {
+ type Output = Self;
+
+ fn rem(self, rhs: Self) -> Self::Output {
+ Self(self.0 % rhs.0)
+ }
+}
+
impl PartialEq<f32> for Radians {
fn eq(&self, other: &f32) -> bool {
self.0.eq(other)
diff --git a/core/src/content_fit.rs b/core/src/content_fit.rs
index 6bbedc7a..19642716 100644
--- a/core/src/content_fit.rs
+++ b/core/src/content_fit.rs
@@ -1,6 +1,8 @@
//! Control the fit of some content (like an image) within a space.
use crate::Size;
+use std::fmt;
+
/// The strategy used to fit the contents of a widget to its bounding box.
///
/// Each variant of this enum is a strategy that can be applied for resolving
@@ -11,7 +13,7 @@ use crate::Size;
/// in CSS, see [Mozilla's docs][1], or run the `tour` example
///
/// [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
-#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
+#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, Default)]
pub enum ContentFit {
/// Scale as big as it can be without needing to crop or hide parts.
///
@@ -23,6 +25,7 @@ pub enum ContentFit {
/// This is a great fit for when you need to display an image without losing
/// any part of it, particularly when the image itself is the focus of the
/// screen.
+ #[default]
Contain,
/// Scale the image to cover all of the bounding box, cropping if needed.
@@ -117,3 +120,15 @@ impl ContentFit {
}
}
}
+
+impl fmt::Display for ContentFit {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(match self {
+ ContentFit::Contain => "Contain",
+ ContentFit::Cover => "Cover",
+ ContentFit::Fill => "Fill",
+ ContentFit::None => "None",
+ ContentFit::ScaleDown => "Scale Down",
+ })
+ }
+}
diff --git a/core/src/element.rs b/core/src/element.rs
index 989eaa3b..7d918a2e 100644
--- a/core/src/element.rs
+++ b/core/src/element.rs
@@ -95,7 +95,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
///
/// ```no_run
/// # mod iced {
- /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, iced_core::renderer::Null>;
+ /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
/// #
/// # pub mod widget {
/// # pub fn row<'a, Message>(iter: impl IntoIterator<Item = super::Element<'a, Message>>) -> super::Element<'a, Message> {
@@ -109,7 +109,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
/// # pub enum Message {}
/// # pub struct Counter;
/// #
- /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, iced_core::renderer::Null>;
+ /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
/// #
/// # impl Counter {
/// # pub fn view(&self) -> Element<Message> {
diff --git a/core/src/hasher.rs b/core/src/hasher.rs
index a13d78af..13180e41 100644
--- a/core/src/hasher.rs
+++ b/core/src/hasher.rs
@@ -1,7 +1,7 @@
/// The hasher used to compare layouts.
#[allow(missing_debug_implementations)] // Doesn't really make sense to have debug on the hasher state anyways.
#[derive(Default)]
-pub struct Hasher(xxhash_rust::xxh3::Xxh3);
+pub struct Hasher(rustc_hash::FxHasher);
impl core::hash::Hasher for Hasher {
fn write(&mut self, bytes: &[u8]) {
diff --git a/core/src/image.rs b/core/src/image.rs
index e5fdcd83..82ecdd0f 100644
--- a/core/src/image.rs
+++ b/core/src/image.rs
@@ -1,15 +1,45 @@
//! Load and draw raster graphics.
-use crate::{Hasher, Rectangle, Size};
+pub use bytes::Bytes;
-use std::hash::{Hash, Hasher as _};
-use std::path::PathBuf;
-use std::sync::Arc;
+use crate::{Radians, Rectangle, Size};
+
+use rustc_hash::FxHasher;
+use std::hash::{Hash, Hasher};
+use std::path::{Path, PathBuf};
/// A handle of some image data.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct Handle {
- id: u64,
- data: Data,
+#[derive(Clone, PartialEq, Eq)]
+pub enum Handle {
+ /// A file handle. The image data will be read
+ /// from the file path.
+ ///
+ /// Use [`from_path`] to create this variant.
+ ///
+ /// [`from_path`]: Self::from_path
+ Path(Id, PathBuf),
+
+ /// A handle pointing to some encoded image bytes in-memory.
+ ///
+ /// Use [`from_bytes`] to create this variant.
+ ///
+ /// [`from_bytes`]: Self::from_bytes
+ Bytes(Id, Bytes),
+
+ /// A handle pointing to decoded image pixels in RGBA format.
+ ///
+ /// Use [`from_rgba`] to create this variant.
+ ///
+ /// [`from_rgba`]: Self::from_rgba
+ Rgba {
+ /// The id of this handle.
+ id: Id,
+ /// The width of the image.
+ width: u32,
+ /// The height of the image.
+ height: u32,
+ /// The pixels.
+ pixels: Bytes,
+ },
}
impl Handle {
@@ -17,56 +47,48 @@ impl Handle {
///
/// Makes an educated guess about the image format by examining the data in the file.
pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
- Self::from_data(Data::Path(path.into()))
- }
+ let path = path.into();
- /// Creates an image [`Handle`] containing the image pixels directly. This
- /// function expects the input data to be provided as a `Vec<u8>` of RGBA
- /// pixels.
- ///
- /// This is useful if you have already decoded your image.
- pub fn from_pixels(
- width: u32,
- height: u32,
- pixels: impl AsRef<[u8]> + Send + Sync + 'static,
- ) -> Handle {
- Self::from_data(Data::Rgba {
- width,
- height,
- pixels: Bytes::new(pixels),
- })
+ Self::Path(Id::path(&path), path)
}
- /// Creates an image [`Handle`] containing the image data directly.
+ /// Creates an image [`Handle`] containing the encoded image data directly.
///
/// Makes an educated guess about the image format by examining the given data.
///
/// This is useful if you already have your image loaded in-memory, maybe
/// because you downloaded or generated it procedurally.
- pub fn from_memory(
- bytes: impl AsRef<[u8]> + Send + Sync + 'static,
- ) -> Handle {
- Self::from_data(Data::Bytes(Bytes::new(bytes)))
+ pub fn from_bytes(bytes: impl Into<Bytes>) -> Handle {
+ Self::Bytes(Id::unique(), bytes.into())
}
- fn from_data(data: Data) -> Handle {
- let mut hasher = Hasher::default();
- data.hash(&mut hasher);
-
- Handle {
- id: hasher.finish(),
- data,
+ /// Creates an image [`Handle`] containing the decoded image pixels directly.
+ ///
+ /// This function expects the pixel data to be provided as a collection of [`Bytes`]
+ /// of RGBA pixels. Therefore, the length of the pixel data should always be
+ /// `width * height * 4`.
+ ///
+ /// This is useful if you have already decoded your image.
+ pub fn from_rgba(
+ width: u32,
+ height: u32,
+ pixels: impl Into<Bytes>,
+ ) -> Handle {
+ Self::Rgba {
+ id: Id::unique(),
+ width,
+ height,
+ pixels: pixels.into(),
}
}
/// Returns the unique identifier of the [`Handle`].
- pub fn id(&self) -> u64 {
- self.id
- }
-
- /// Returns a reference to the image [`Data`].
- pub fn data(&self) -> &Data {
- &self.data
+ pub fn id(&self) -> Id {
+ match self {
+ Handle::Path(id, _)
+ | Handle::Bytes(id, _)
+ | Handle::Rgba { id, .. } => *id,
+ }
}
}
@@ -79,90 +101,46 @@ where
}
}
-impl Hash for Handle {
- fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
- self.id.hash(state);
- }
-}
-
-/// A wrapper around raw image data.
-///
-/// It behaves like a `&[u8]`.
-#[derive(Clone)]
-pub struct Bytes(Arc<dyn AsRef<[u8]> + Send + Sync + 'static>);
-
-impl Bytes {
- /// Creates new [`Bytes`] around `data`.
- pub fn new(data: impl AsRef<[u8]> + Send + Sync + 'static) -> Self {
- Self(Arc::new(data))
- }
-}
-
-impl std::fmt::Debug for Bytes {
+impl std::fmt::Debug for Handle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- self.0.as_ref().as_ref().fmt(f)
+ match self {
+ Self::Path(_, path) => write!(f, "Path({path:?})"),
+ Self::Bytes(_, _) => write!(f, "Bytes(...)"),
+ Self::Rgba { width, height, .. } => {
+ write!(f, "Pixels({width} * {height})")
+ }
+ }
}
}
-impl std::hash::Hash for Bytes {
- fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
- self.0.as_ref().as_ref().hash(state);
- }
-}
+/// The unique identifier of some [`Handle`] data.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Id(_Id);
-impl PartialEq for Bytes {
- fn eq(&self, other: &Self) -> bool {
- let a = self.as_ref();
- let b = other.as_ref();
- core::ptr::eq(a, b) || a == b
- }
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+enum _Id {
+ Unique(u64),
+ Hash(u64),
}
-impl Eq for Bytes {}
-
-impl AsRef<[u8]> for Bytes {
- fn as_ref(&self) -> &[u8] {
- self.0.as_ref().as_ref()
- }
-}
+impl Id {
+ fn unique() -> Self {
+ use std::sync::atomic::{self, AtomicU64};
-impl std::ops::Deref for Bytes {
- type Target = [u8];
+ static NEXT_ID: AtomicU64 = AtomicU64::new(0);
- fn deref(&self) -> &[u8] {
- self.0.as_ref().as_ref()
+ Self(_Id::Unique(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)))
}
-}
-/// The data of a raster image.
-#[derive(Clone, PartialEq, Eq, Hash)]
-pub enum Data {
- /// File data
- Path(PathBuf),
+ fn path(path: impl AsRef<Path>) -> Self {
+ let hash = {
+ let mut hasher = FxHasher::default();
+ path.as_ref().hash(&mut hasher);
- /// In-memory data
- Bytes(Bytes),
-
- /// Decoded image pixels in RGBA format.
- Rgba {
- /// The width of the image.
- width: u32,
- /// The height of the image.
- height: u32,
- /// The pixels.
- pixels: Bytes,
- },
-}
+ hasher.finish()
+ };
-impl std::fmt::Debug for Data {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Data::Path(path) => write!(f, "Path({path:?})"),
- Data::Bytes(_) => write!(f, "Bytes(...)"),
- Data::Rgba { width, height, .. } => {
- write!(f, "Pixels({width} * {height})")
- }
- }
+ Self(_Id::Hash(hash))
}
}
@@ -183,17 +161,19 @@ pub trait Renderer: crate::Renderer {
/// The image Handle to be displayed. Iced exposes its own default implementation of a [`Handle`]
///
/// [`Handle`]: Self::Handle
- type Handle: Clone + Hash;
+ type Handle: Clone;
/// Returns the dimensions of an image for the given [`Handle`].
- fn dimensions(&self, handle: &Self::Handle) -> Size<u32>;
+ fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
/// Draws an image with the given [`Handle`] and inside the provided
/// `bounds`.
- fn draw(
+ fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: FilterMethod,
bounds: Rectangle,
+ rotation: Radians,
+ opacity: f32,
);
}
diff --git a/core/src/layout.rs b/core/src/layout.rs
index 95720aba..98d05602 100644
--- a/core/src/layout.rs
+++ b/core/src/layout.rs
@@ -54,7 +54,7 @@ impl<'a> Layout<'a> {
}
/// Returns an iterator over the [`Layout`] of the children of a [`Node`].
- pub fn children(self) -> impl Iterator<Item = Layout<'a>> {
+ pub fn children(self) -> impl DoubleEndedIterator<Item = Layout<'a>> {
self.node.children().iter().map(move |node| {
Layout::with_offset(
Vector::new(self.position.x, self.position.y),
diff --git a/core/src/lib.rs b/core/src/lib.rs
index d076413e..32156441 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -9,13 +9,6 @@
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
-#![forbid(unsafe_code, rust_2018_idioms)]
-#![deny(
- missing_debug_implementations,
- missing_docs,
- unused_results,
- rustdoc::broken_intra_doc_links
-)]
pub mod alignment;
pub mod border;
pub mod clipboard;
@@ -41,12 +34,12 @@ mod background;
mod color;
mod content_fit;
mod element;
-mod hasher;
mod length;
mod padding;
mod pixels;
mod point;
mod rectangle;
+mod rotation;
mod shadow;
mod shell;
mod size;
@@ -64,7 +57,6 @@ pub use element::Element;
pub use event::Event;
pub use font::Font;
pub use gradient::Gradient;
-pub use hasher::Hasher;
pub use layout::Layout;
pub use length::Length;
pub use overlay::Overlay;
@@ -73,6 +65,7 @@ pub use pixels::Pixels;
pub use point::Point;
pub use rectangle::Rectangle;
pub use renderer::Renderer;
+pub use rotation::Rotation;
pub use shadow::Shadow;
pub use shell::Shell;
pub use size::Size;
diff --git a/core/src/mouse/interaction.rs b/core/src/mouse/interaction.rs
index 6ad66229..065eb8e7 100644
--- a/core/src/mouse/interaction.rs
+++ b/core/src/mouse/interaction.rs
@@ -3,6 +3,7 @@
#[allow(missing_docs)]
pub enum Interaction {
#[default]
+ None,
Idle,
Pointer,
Grab,
diff --git a/core/src/overlay.rs b/core/src/overlay.rs
index 03076a30..3a57fe16 100644
--- a/core/src/overlay.rs
+++ b/core/src/overlay.rs
@@ -79,7 +79,7 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse::Interaction::Idle
+ mouse::Interaction::None
}
/// Returns true if the cursor is over the [`Overlay`].
diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs
index c1c2eeac..1556e072 100644
--- a/core/src/rectangle.rs
+++ b/core/src/rectangle.rs
@@ -1,6 +1,6 @@
-use crate::{Point, Size, Vector};
+use crate::{Point, Radians, Size, Vector};
-/// A rectangle.
+/// An axis-aligned rectangle.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Rectangle<T = f32> {
/// X coordinate of the top-left corner.
@@ -16,24 +16,32 @@ pub struct Rectangle<T = f32> {
pub height: T,
}
-impl Rectangle<f32> {
- /// Creates a new [`Rectangle`] with its top-left corner in the given
- /// [`Point`] and with the provided [`Size`].
- pub fn new(top_left: Point, size: Size) -> Self {
+impl<T> Rectangle<T>
+where
+ T: Default,
+{
+ /// Creates a new [`Rectangle`] with its top-left corner at the origin
+ /// and with the provided [`Size`].
+ pub fn with_size(size: Size<T>) -> Self {
Self {
- x: top_left.x,
- y: top_left.y,
+ x: T::default(),
+ y: T::default(),
width: size.width,
height: size.height,
}
}
+}
- /// Creates a new [`Rectangle`] with its top-left corner at the origin
- /// and with the provided [`Size`].
- pub fn with_size(size: Size) -> Self {
+impl Rectangle<f32> {
+ /// A rectangle starting at [`Point::ORIGIN`] with infinite width and height.
+ pub const INFINITE: Self = Self::new(Point::ORIGIN, Size::INFINITY);
+
+ /// Creates a new [`Rectangle`] with its top-left corner in the given
+ /// [`Point`] and with the provided [`Size`].
+ pub const fn new(top_left: Point, size: Size) -> Self {
Self {
- x: 0.0,
- y: 0.0,
+ x: top_left.x,
+ y: top_left.y,
width: size.width,
height: size.height,
}
@@ -139,13 +147,20 @@ impl Rectangle<f32> {
}
/// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
- pub fn snap(self) -> Rectangle<u32> {
- Rectangle {
+ pub fn snap(self) -> Option<Rectangle<u32>> {
+ let width = self.width as u32;
+ let height = self.height as u32;
+
+ if width < 1 || height < 1 {
+ return None;
+ }
+
+ Some(Rectangle {
x: self.x as u32,
y: self.y as u32,
- width: self.width as u32,
- height: self.height as u32,
- }
+ width,
+ height,
+ })
}
/// Expands the [`Rectangle`] a given amount.
@@ -157,6 +172,18 @@ impl Rectangle<f32> {
height: self.height + amount * 2.0,
}
}
+
+ /// Rotates the [`Rectangle`] and returns the smallest [`Rectangle`]
+ /// containing it.
+ pub fn rotate(self, rotation: Radians) -> Self {
+ let size = self.size().rotate(rotation);
+ let position = Point::new(
+ self.center_x() - size.width / 2.0,
+ self.center_y() - size.height / 2.0,
+ );
+
+ Self::new(position, size)
+ }
}
impl std::ops::Mul<f32> for Rectangle<f32> {
@@ -212,3 +239,19 @@ where
}
}
}
+
+impl<T> std::ops::Mul<Vector<T>> for Rectangle<T>
+where
+ T: std::ops::Mul<Output = T> + Copy,
+{
+ type Output = Rectangle<T>;
+
+ fn mul(self, scale: Vector<T>) -> Self {
+ Rectangle {
+ x: self.x * scale.x,
+ y: self.y * scale.y,
+ width: self.width * scale.x,
+ height: self.height * scale.y,
+ }
+ }
+}
diff --git a/core/src/renderer.rs b/core/src/renderer.rs
index 1139b41c..a2785ae8 100644
--- a/core/src/renderer.rs
+++ b/core/src/renderer.rs
@@ -2,26 +2,47 @@
#[cfg(debug_assertions)]
mod null;
-#[cfg(debug_assertions)]
-pub use null::Null;
-
use crate::{
Background, Border, Color, Rectangle, Shadow, Size, Transformation, Vector,
};
/// A component that can be used by widgets to draw themselves on a screen.
-pub trait Renderer: Sized {
+pub trait Renderer {
+ /// Starts recording a new layer.
+ fn start_layer(&mut self, bounds: Rectangle);
+
+ /// Ends recording a new layer.
+ ///
+ /// The new layer will clip its contents to the provided `bounds`.
+ fn end_layer(&mut self);
+
/// Draws the primitives recorded in the given closure in a new layer.
///
/// The layer will clip its contents to the provided `bounds`.
- fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self));
+ fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
+ self.start_layer(bounds);
+ f(self);
+ self.end_layer();
+ }
+
+ /// Starts recording with a new [`Transformation`].
+ fn start_transformation(&mut self, transformation: Transformation);
+
+ /// Ends recording a new layer.
+ ///
+ /// The new layer will clip its contents to the provided `bounds`.
+ fn end_transformation(&mut self);
/// Applies a [`Transformation`] to the primitives recorded in the given closure.
fn with_transformation(
&mut self,
transformation: Transformation,
f: impl FnOnce(&mut Self),
- );
+ ) {
+ self.start_transformation(transformation);
+ f(self);
+ self.end_transformation();
+ }
/// Applies a translation to the primitives recorded in the given closure.
fn with_translation(
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index 83688ff7..e8709dbc 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -1,34 +1,21 @@
use crate::alignment;
+use crate::image;
use crate::renderer::{self, Renderer};
+use crate::svg;
use crate::text::{self, Text};
use crate::{
- Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
+ Background, Color, Font, Pixels, Point, Radians, Rectangle, Size,
+ Transformation,
};
-use std::borrow::Cow;
+impl Renderer for () {
+ fn start_layer(&mut self, _bounds: Rectangle) {}
-/// A renderer that does nothing.
-///
-/// It can be useful if you are writing tests!
-#[derive(Debug, Clone, Copy, Default)]
-pub struct Null;
+ fn end_layer(&mut self) {}
-impl Null {
- /// Creates a new [`Null`] renderer.
- pub fn new() -> Null {
- Null
- }
-}
-
-impl Renderer for Null {
- fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
+ fn start_transformation(&mut self, _transformation: Transformation) {}
- fn with_transformation(
- &mut self,
- _transformation: Transformation,
- _f: impl FnOnce(&mut Self),
- ) {
- }
+ fn end_transformation(&mut self) {}
fn clear(&mut self) {}
@@ -40,7 +27,7 @@ impl Renderer for Null {
}
}
-impl text::Renderer for Null {
+impl text::Renderer for () {
type Font = Font;
type Paragraph = ();
type Editor = ();
@@ -57,8 +44,6 @@ impl text::Renderer for Null {
Pixels(16.0)
}
- fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
-
fn fill_paragraph(
&mut self,
_paragraph: &Self::Paragraph,
@@ -79,7 +64,7 @@ impl text::Renderer for Null {
fn fill_text(
&mut self,
- _paragraph: Text<'_, Self::Font>,
+ _paragraph: Text,
_position: Point,
_color: Color,
_clip_bounds: Rectangle,
@@ -90,11 +75,11 @@ impl text::Renderer for Null {
impl text::Paragraph for () {
type Font = Font;
- fn with_text(_text: Text<'_, Self::Font>) -> Self {}
+ fn with_text(_text: Text<&str>) -> Self {}
fn resize(&mut self, _new_bounds: Size) {}
- fn compare(&self, _text: Text<'_, Self::Font>) -> text::Difference {
+ fn compare(&self, _text: Text<&str>) -> text::Difference {
text::Difference::None
}
@@ -174,3 +159,37 @@ impl text::Editor for () {
) {
}
}
+
+impl image::Renderer for () {
+ type Handle = ();
+
+ fn measure_image(&self, _handle: &Self::Handle) -> Size<u32> {
+ Size::default()
+ }
+
+ fn draw_image(
+ &mut self,
+ _handle: Self::Handle,
+ _filter_method: image::FilterMethod,
+ _bounds: Rectangle,
+ _rotation: Radians,
+ _opacity: f32,
+ ) {
+ }
+}
+
+impl svg::Renderer for () {
+ fn measure_svg(&self, _handle: &svg::Handle) -> Size<u32> {
+ Size::default()
+ }
+
+ fn draw_svg(
+ &mut self,
+ _handle: svg::Handle,
+ _color: Option<Color>,
+ _bounds: Rectangle,
+ _rotation: Radians,
+ _opacity: f32,
+ ) {
+ }
+}
diff --git a/core/src/rotation.rs b/core/src/rotation.rs
new file mode 100644
index 00000000..afa8d79e
--- /dev/null
+++ b/core/src/rotation.rs
@@ -0,0 +1,72 @@
+//! Control the rotation of some content (like an image) within a space.
+use crate::{Degrees, Radians, Size};
+
+/// The strategy used to rotate the content.
+///
+/// This is used to control the behavior of the layout when the content is rotated
+/// by a certain angle.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum Rotation {
+ /// The element will float while rotating. The layout will be kept exactly as it was
+ /// before the rotation.
+ ///
+ /// This is especially useful when used for animations, as it will avoid the
+ /// layout being shifted or resized when smoothly i.e. an icon.
+ ///
+ /// This is the default.
+ Floating(Radians),
+ /// The element will be solid while rotating. The layout will be adjusted to fit
+ /// the rotated content.
+ ///
+ /// This allows you to rotate an image and have the layout adjust to fit the new
+ /// size of the image.
+ Solid(Radians),
+}
+
+impl Rotation {
+ /// Returns the angle of the [`Rotation`] in [`Radians`].
+ pub fn radians(self) -> Radians {
+ match self {
+ Rotation::Floating(radians) | Rotation::Solid(radians) => radians,
+ }
+ }
+
+ /// Returns a mutable reference to the angle of the [`Rotation`] in [`Radians`].
+ pub fn radians_mut(&mut self) -> &mut Radians {
+ match self {
+ Rotation::Floating(radians) | Rotation::Solid(radians) => radians,
+ }
+ }
+
+ /// Returns the angle of the [`Rotation`] in [`Degrees`].
+ pub fn degrees(self) -> Degrees {
+ Degrees(self.radians().0.to_degrees())
+ }
+
+ /// Applies the [`Rotation`] to the given [`Size`], returning
+ /// the minimum [`Size`] containing the rotated one.
+ pub fn apply(self, size: Size) -> Size {
+ match self {
+ Self::Floating(_) => size,
+ Self::Solid(rotation) => size.rotate(rotation),
+ }
+ }
+}
+
+impl Default for Rotation {
+ fn default() -> Self {
+ Self::Floating(Radians(0.0))
+ }
+}
+
+impl From<Radians> for Rotation {
+ fn from(radians: Radians) -> Self {
+ Self::Floating(radians)
+ }
+}
+
+impl From<f32> for Rotation {
+ fn from(radians: f32) -> Self {
+ Self::Floating(Radians(radians))
+ }
+}
diff --git a/core/src/size.rs b/core/src/size.rs
index 267fc90e..d7459355 100644
--- a/core/src/size.rs
+++ b/core/src/size.rs
@@ -1,7 +1,7 @@
-use crate::Vector;
+use crate::{Radians, Vector};
/// An amount of space in 2 dimensions.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Size<T = f32> {
/// The width.
pub width: T,
@@ -51,6 +51,19 @@ impl Size {
height: self.height + other.height,
}
}
+
+ /// Rotates the given [`Size`] and returns the minimum [`Size`]
+ /// containing it.
+ pub fn rotate(self, rotation: Radians) -> Size {
+ let radians = f32::from(rotation);
+
+ Size {
+ width: (self.width * radians.cos()).abs()
+ + (self.height * radians.sin()).abs(),
+ height: (self.width * radians.sin()).abs()
+ + (self.height * radians.cos()).abs(),
+ }
+ }
}
impl<T> From<[T; 2]> for Size<T> {
@@ -99,3 +112,31 @@ where
}
}
}
+
+impl<T> std::ops::Mul<T> for Size<T>
+where
+ T: std::ops::Mul<Output = T> + Copy,
+{
+ type Output = Size<T>;
+
+ fn mul(self, rhs: T) -> Self::Output {
+ Size {
+ width: self.width * rhs,
+ height: self.height * rhs,
+ }
+ }
+}
+
+impl<T> std::ops::Mul<Vector<T>> for Size<T>
+where
+ T: std::ops::Mul<Output = T> + Copy,
+{
+ type Output = Size<T>;
+
+ fn mul(self, scale: Vector<T>) -> Self::Output {
+ Size {
+ width: self.width * scale.x,
+ height: self.height * scale.y,
+ }
+ }
+}
diff --git a/core/src/svg.rs b/core/src/svg.rs
index d63e3c95..946b8156 100644
--- a/core/src/svg.rs
+++ b/core/src/svg.rs
@@ -1,6 +1,7 @@
//! Load and draw vector graphics.
-use crate::{Color, Hasher, Rectangle, Size};
+use crate::{Color, Radians, Rectangle, Size};
+use rustc_hash::FxHasher;
use std::borrow::Cow;
use std::hash::{Hash, Hasher as _};
use std::path::PathBuf;
@@ -30,7 +31,7 @@ impl Handle {
}
fn from_data(data: Data) -> Handle {
- let mut hasher = Hasher::default();
+ let mut hasher = FxHasher::default();
data.hash(&mut hasher);
Handle {
@@ -91,8 +92,15 @@ impl std::fmt::Debug for Data {
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// Returns the default dimensions of an SVG for the given [`Handle`].
- fn dimensions(&self, handle: &Handle) -> Size<u32>;
+ fn measure_svg(&self, handle: &Handle) -> Size<u32>;
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
- fn draw(&mut self, handle: Handle, color: Option<Color>, bounds: Rectangle);
+ fn draw_svg(
+ &mut self,
+ handle: Handle,
+ color: Option<Color>,
+ bounds: Rectangle,
+ rotation: Radians,
+ opacity: f32,
+ );
}
diff --git a/core/src/text.rs b/core/src/text.rs
index edef79c2..b30feae0 100644
--- a/core/src/text.rs
+++ b/core/src/text.rs
@@ -11,14 +11,13 @@ pub use paragraph::Paragraph;
use crate::alignment;
use crate::{Color, Pixels, Point, Rectangle, Size};
-use std::borrow::Cow;
use std::hash::{Hash, Hasher};
/// A paragraph.
#[derive(Debug, Clone, Copy)]
-pub struct Text<'a, Font> {
+pub struct Text<Content = String, Font = crate::Font> {
/// The content of the paragraph.
- pub content: &'a str,
+ pub content: Content,
/// The bounds of the paragraph.
pub bounds: Size,
@@ -192,9 +191,6 @@ pub trait Renderer: crate::Renderer {
/// Returns the default size of [`Text`].
fn default_size(&self) -> Pixels;
- /// Loads a [`Self::Font`] from its bytes.
- fn load_font(&mut self, font: Cow<'static, [u8]>);
-
/// Draws the given [`Paragraph`] at the given position and with the given
/// [`Color`].
fn fill_paragraph(
@@ -219,7 +215,7 @@ pub trait Renderer: crate::Renderer {
/// [`Color`].
fn fill_text(
&mut self,
- text: Text<'_, Self::Font>,
+ text: Text<String, Self::Font>,
position: Point,
color: Color,
clip_bounds: Rectangle,
diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs
index de1fb74d..8ff04015 100644
--- a/core/src/text/paragraph.rs
+++ b/core/src/text/paragraph.rs
@@ -8,14 +8,14 @@ pub trait Paragraph: Sized + Default {
type Font: Copy + PartialEq;
/// Creates a new [`Paragraph`] laid out with the given [`Text`].
- fn with_text(text: Text<'_, Self::Font>) -> Self;
+ fn with_text(text: Text<&str, Self::Font>) -> Self;
/// Lays out the [`Paragraph`] with some new boundaries.
fn resize(&mut self, new_bounds: Size);
/// Compares the [`Paragraph`] with some desired [`Text`] and returns the
/// [`Difference`].
- fn compare(&self, text: Text<'_, Self::Font>) -> Difference;
+ fn compare(&self, text: Text<&str, Self::Font>) -> Difference;
/// Returns the horizontal alignment of the [`Paragraph`].
fn horizontal_alignment(&self) -> alignment::Horizontal;
@@ -35,7 +35,7 @@ pub trait Paragraph: Sized + Default {
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>;
/// Updates the [`Paragraph`] to match the given [`Text`], if needed.
- fn update(&mut self, text: Text<'_, Self::Font>) {
+ fn update(&mut self, text: Text<&str, Self::Font>) {
match self.compare(text) {
Difference::None => {}
Difference::Bounds => {
diff --git a/core/src/theme.rs b/core/src/theme.rs
index 948aaf83..6b2c04da 100644
--- a/core/src/theme.rs
+++ b/core/src/theme.rs
@@ -7,10 +7,9 @@ use std::fmt;
use std::sync::Arc;
/// A built-in theme.
-#[derive(Debug, Clone, PartialEq, Default)]
+#[derive(Debug, Clone, PartialEq)]
pub enum Theme {
/// The built-in light variant.
- #[default]
Light,
/// The built-in dark variant.
Dark,
@@ -161,6 +160,28 @@ impl Theme {
}
}
+impl Default for Theme {
+ fn default() -> Self {
+ #[cfg(feature = "auto-detect-theme")]
+ {
+ use once_cell::sync::Lazy;
+
+ static DEFAULT: Lazy<Theme> =
+ Lazy::new(|| match dark_light::detect() {
+ dark_light::Mode::Dark => Theme::Dark,
+ dark_light::Mode::Light | dark_light::Mode::Default => {
+ Theme::Light
+ }
+ });
+
+ DEFAULT.clone()
+ }
+
+ #[cfg(not(feature = "auto-detect-theme"))]
+ Theme::Light
+ }
+}
+
impl fmt::Display for Theme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
diff --git a/core/src/theme/palette.rs b/core/src/theme/palette.rs
index ca91c248..e0ff397a 100644
--- a/core/src/theme/palette.rs
+++ b/core/src/theme/palette.rs
@@ -613,10 +613,15 @@ fn mix(a: Color, b: Color, factor: f32) -> Color {
fn readable(background: Color, text: Color) -> Color {
if is_readable(background, text) {
text
- } else if is_dark(background) {
- Color::WHITE
} else {
- Color::BLACK
+ let white_contrast = relative_contrast(background, Color::WHITE);
+ let black_contrast = relative_contrast(background, Color::BLACK);
+
+ if white_contrast >= black_contrast {
+ Color::WHITE
+ } else {
+ Color::BLACK
+ }
}
}
@@ -631,6 +636,13 @@ fn is_readable(a: Color, b: Color) -> bool {
a_srgb.has_enhanced_contrast_text(b_srgb)
}
+fn relative_contrast(a: Color, b: Color) -> f32 {
+ let a_srgb = Rgb::from(a);
+ let b_srgb = Rgb::from(b);
+
+ a_srgb.relative_contrast(b_srgb)
+}
+
fn to_hsl(color: Color) -> Hsl {
Hsl::from_color(Rgb::from(color))
}
diff --git a/core/src/transformation.rs b/core/src/transformation.rs
index b2c488b0..74183147 100644
--- a/core/src/transformation.rs
+++ b/core/src/transformation.rs
@@ -42,6 +42,12 @@ impl Transformation {
}
}
+impl Default for Transformation {
+ fn default() -> Self {
+ Transformation::IDENTITY
+ }
+}
+
impl Mul for Transformation {
type Output = Self;
diff --git a/core/src/vector.rs b/core/src/vector.rs
index 1380c3b3..049e648f 100644
--- a/core/src/vector.rs
+++ b/core/src/vector.rs
@@ -18,6 +18,9 @@ impl<T> Vector<T> {
impl Vector {
/// The zero [`Vector`].
pub const ZERO: Self = Self::new(0.0, 0.0);
+
+ /// The unit [`Vector`].
+ pub const UNIT: Self = Self::new(0.0, 0.0);
}
impl<T> std::ops::Add for Vector<T>
diff --git a/core/src/widget.rs b/core/src/widget.rs
index 58a9f19b..b02e3a4f 100644
--- a/core/src/widget.rs
+++ b/core/src/widget.rs
@@ -137,7 +137,7 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse::Interaction::Idle
+ mouse::Interaction::None
}
/// Returns the overlay of the [`Widget`], if there is any.
diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs
index 66e2d066..f1f0b345 100644
--- a/core/src/widget/text.rs
+++ b/core/src/widget/text.rs
@@ -18,9 +18,10 @@ pub use text::{LineHeight, Shaping};
#[allow(missing_debug_implementations)]
pub struct Text<'a, Theme, Renderer>
where
+ Theme: Catalog,
Renderer: text::Renderer,
{
- content: Cow<'a, str>,
+ fragment: Fragment<'a>,
size: Option<Pixels>,
line_height: LineHeight,
width: Length,
@@ -29,20 +30,18 @@ where
vertical_alignment: alignment::Vertical,
font: Option<Renderer::Font>,
shaping: Shaping,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
where
+ Theme: Catalog,
Renderer: text::Renderer,
{
/// Create a new fragment of [`Text`] with the given contents.
- pub fn new(content: impl Into<Cow<'a, str>>) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
+ pub fn new(fragment: impl IntoFragment<'a>) -> Self {
Text {
- content: content.into(),
+ fragment: fragment.into_fragment(),
size: None,
line_height: LineHeight::default(),
font: None,
@@ -51,7 +50,7 @@ where
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: Shaping::Basic,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
@@ -75,25 +74,6 @@ where
self
}
- /// Sets the style of the [`Text`].
- pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self {
- self.style = Box::new(style);
- self
- }
-
- /// Sets the [`Color`] of the [`Text`].
- pub fn color(self, color: impl Into<Color>) -> Self {
- self.color_maybe(Some(color))
- }
-
- /// Sets the [`Color`] of the [`Text`], if `Some`.
- pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
- let color = color.map(Into::into);
-
- self.style = Box::new(move |_theme| Appearance { color });
- self
- }
-
/// Sets the width of the [`Text`] boundaries.
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
@@ -129,6 +109,42 @@ where
self.shaping = shaping;
self
}
+
+ /// Sets the style of the [`Text`].
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the [`Color`] of the [`Text`].
+ pub fn color(self, color: impl Into<Color>) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.color_maybe(Some(color))
+ }
+
+ /// Sets the [`Color`] of the [`Text`], if `Some`.
+ pub fn color_maybe(self, color: Option<impl Into<Color>>) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ let color = color.map(Into::into);
+
+ self.style(move |_theme| Style { color })
+ }
+
+ /// Sets the style class of the [`Text`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
+ self
+ }
}
/// The internal state of a [`Text`] widget.
@@ -138,6 +154,7 @@ pub struct State<P: Paragraph>(P);
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Text<'a, Theme, Renderer>
where
+ Theme: Catalog,
Renderer: text::Renderer,
{
fn tag(&self) -> tree::Tag {
@@ -167,7 +184,7 @@ where
limits,
self.width,
self.height,
- &self.content,
+ &self.fragment,
self.line_height,
self.size,
self.font,
@@ -182,15 +199,15 @@ where
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
- style: &renderer::Style,
+ defaults: &renderer::Style,
layout: Layout<'_>,
_cursor_position: mouse::Cursor,
viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
- let appearance = (self.style)(theme);
+ let style = theme.style(&self.class);
- draw(renderer, style, layout, state, appearance, viewport);
+ draw(renderer, defaults, layout, state, style, viewport);
}
}
@@ -250,7 +267,7 @@ pub fn draw<Renderer>(
style: &renderer::Style,
layout: Layout<'_>,
state: &State<Renderer::Paragraph>,
- appearance: Appearance,
+ appearance: Style,
viewport: &Rectangle,
) where
Renderer: text::Renderer,
@@ -281,7 +298,7 @@ pub fn draw<Renderer>(
impl<'a, Message, Theme, Renderer> From<Text<'a, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
- Theme: 'a,
+ Theme: Catalog + 'a,
Renderer: text::Renderer + 'a,
{
fn from(
@@ -293,7 +310,7 @@ where
impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer>
where
- Theme: DefaultStyle + 'a,
+ Theme: Catalog + 'a,
Renderer: text::Renderer,
{
fn from(content: &'a str) -> Self {
@@ -304,7 +321,7 @@ where
impl<'a, Message, Theme, Renderer> From<&'a str>
for Element<'a, Message, Theme, Renderer>
where
- Theme: DefaultStyle + 'a,
+ Theme: Catalog + 'a,
Renderer: text::Renderer + 'a,
{
fn from(content: &'a str) -> Self {
@@ -314,30 +331,116 @@ where
/// The appearance of some text.
#[derive(Debug, Clone, Copy, Default)]
-pub struct Appearance {
+pub struct Style {
/// The [`Color`] of the text.
///
/// The default, `None`, means using the inherited color.
pub color: Option<Color>,
}
-/// The style of some [`Text`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>;
+/// The theme catalog of a [`Text`].
+pub trait Catalog: Sized {
+ /// The item class of this [`Catalog`].
+ type Class<'a>;
+
+ /// The default class produced by this [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
-/// The default style of some [`Text`].
-pub trait DefaultStyle {
- /// Returns the default style of some [`Text`].
- fn default_style(&self) -> Appearance;
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, item: &Self::Class<'_>) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self) -> Appearance {
- Appearance::default()
+/// A styling function for a [`Text`].
+///
+/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(|_theme| Style::default())
+ }
+
+ fn style(&self, class: &Self::Class<'_>) -> Style {
+ class(self)
+ }
+}
+
+/// A fragment of [`Text`].
+///
+/// This is just an alias to a string that may be either
+/// borrowed or owned.
+pub type Fragment<'a> = Cow<'a, str>;
+
+/// A trait for converting a value to some text [`Fragment`].
+pub trait IntoFragment<'a> {
+ /// Converts the value to some text [`Fragment`].
+ fn into_fragment(self) -> Fragment<'a>;
+}
+
+impl<'a> IntoFragment<'a> for Fragment<'a> {
+ fn into_fragment(self) -> Fragment<'a> {
+ self
}
}
-impl DefaultStyle for Color {
- fn default_style(&self) -> Appearance {
- Appearance { color: Some(*self) }
+impl<'a, 'b> IntoFragment<'a> for &'a Fragment<'b> {
+ fn into_fragment(self) -> Fragment<'a> {
+ Fragment::Borrowed(self)
}
}
+
+impl<'a> IntoFragment<'a> for &'a str {
+ fn into_fragment(self) -> Fragment<'a> {
+ Fragment::Borrowed(self)
+ }
+}
+
+impl<'a> IntoFragment<'a> for &'a String {
+ fn into_fragment(self) -> Fragment<'a> {
+ Fragment::Borrowed(self.as_str())
+ }
+}
+
+impl<'a> IntoFragment<'a> for String {
+ fn into_fragment(self) -> Fragment<'a> {
+ Fragment::Owned(self)
+ }
+}
+
+macro_rules! into_fragment {
+ ($type:ty) => {
+ impl<'a> IntoFragment<'a> for $type {
+ fn into_fragment(self) -> Fragment<'a> {
+ Fragment::Owned(self.to_string())
+ }
+ }
+
+ impl<'a> IntoFragment<'a> for &$type {
+ fn into_fragment(self) -> Fragment<'a> {
+ Fragment::Owned(self.to_string())
+ }
+ }
+ };
+}
+
+into_fragment!(char);
+into_fragment!(bool);
+
+into_fragment!(u8);
+into_fragment!(u16);
+into_fragment!(u32);
+into_fragment!(u64);
+into_fragment!(u128);
+into_fragment!(usize);
+
+into_fragment!(i8);
+into_fragment!(i16);
+into_fragment!(i32);
+into_fragment!(i64);
+into_fragment!(i128);
+into_fragment!(isize);
+
+into_fragment!(f32);
+into_fragment!(f64);
diff --git a/core/src/window/position.rs b/core/src/window/position.rs
index 73391e75..1c8e86b6 100644
--- a/core/src/window/position.rs
+++ b/core/src/window/position.rs
@@ -1,4 +1,4 @@
-use crate::Point;
+use crate::{Point, Size};
/// The position of a window in a given screen.
#[derive(Debug, Clone, Copy, PartialEq)]
@@ -15,6 +15,12 @@ pub enum Position {
/// at (0, 0) you would have to set the position to
/// `(PADDING_X, PADDING_Y)`.
Specific(Point),
+ /// Like [`Specific`], but the window is positioned with the specific coordinates returned by the function.
+ ///
+ /// The function receives the window size and the monitor's resolution as input.
+ ///
+ /// [`Specific`]: Self::Specific
+ SpecificWith(fn(Size, Size) -> Point),
}
impl Default for Position {
diff --git a/core/src/window/redraw_request.rs b/core/src/window/redraw_request.rs
index 8a59e83c..b0c000d6 100644
--- a/core/src/window/redraw_request.rs
+++ b/core/src/window/redraw_request.rs
@@ -13,7 +13,7 @@ pub enum RedrawRequest {
#[cfg(test)]
mod tests {
use super::*;
- use crate::time::{Duration, Instant};
+ use crate::time::Duration;
#[test]
fn ordering() {
diff --git a/core/src/window/settings/wasm.rs b/core/src/window/settings/wasm.rs
index 8e0f1bbc..30e60b6a 100644
--- a/core/src/window/settings/wasm.rs
+++ b/core/src/window/settings/wasm.rs
@@ -1,11 +1,21 @@
//! Platform specific settings for WebAssembly.
/// The platform specific window settings of an application.
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PlatformSpecific {
/// The identifier of a DOM element that will be replaced with the
/// application.
///
/// If set to `None`, the application will be appended to the HTML body.
+ ///
+ /// By default, it is set to `"iced"`.
pub target: Option<String>,
}
+
+impl Default for PlatformSpecific {
+ fn default() -> Self {
+ Self {
+ target: Some(String::from("iced")),
+ }
+ }
+}