Merge branch 'master' into beacon

This commit is contained in:
Héctor Ramón Jiménez 2024-05-09 12:32:25 +02:00
commit aaf396256e
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
284 changed files with 18747 additions and 15450 deletions

View file

@ -10,18 +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
palette.workspace = true
palette.optional = true
dark-light.workspace = true
dark-light.optional = true
serde.workspace = true
serde.optional = true

View file

@ -1,12 +1,75 @@
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)
}
}
impl PartialOrd<f32> for Degrees {
fn partial_cmp(&self, other: &f32) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(other)
}
}
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);
@ -53,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)
@ -95,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;
@ -140,3 +217,23 @@ impl Div for Radians {
Self(self.0 / rhs.0)
}
}
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)
}
}
impl PartialOrd<f32> for Radians {
fn partial_cmp(&self, other: &f32) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(other)
}
}

View file

@ -11,6 +11,19 @@ pub enum Background {
// TODO: Add image variant
}
impl Background {
/// Scales the alpha channel of the [`Background`] by the given
/// factor.
pub fn scale_alpha(self, factor: f32) -> Self {
match self {
Self::Color(color) => Self::Color(color.scale_alpha(factor)),
Self::Gradient(gradient) => {
Self::Gradient(gradient.scale_alpha(factor))
}
}
}
}
impl From<Color> for Background {
fn from(color: Color) -> Self {
Background::Color(color)

View file

@ -1,5 +1,5 @@
//! Draw lines around containers.
use crate::Color;
use crate::{Color, Pixels};
/// A border.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
@ -15,11 +15,38 @@ pub struct Border {
}
impl Border {
/// Creates a new default [`Border`] with the given [`Radius`].
pub fn with_radius(radius: impl Into<Radius>) -> Self {
/// Creates a new default rounded [`Border`] with the given [`Radius`].
///
/// ```
/// # use iced_core::Border;
/// #
/// assert_eq!(Border::rounded(10), Border::default().with_radius(10));
/// ```
pub fn rounded(radius: impl Into<Radius>) -> Self {
Self::default().with_radius(radius)
}
/// Updates the [`Color`] of the [`Border`].
pub fn with_color(self, color: impl Into<Color>) -> Self {
Self {
color: color.into(),
..self
}
}
/// Updates the [`Radius`] of the [`Border`].
pub fn with_radius(self, radius: impl Into<Radius>) -> Self {
Self {
radius: radius.into(),
..Self::default()
..self
}
}
/// Updates the width of the [`Border`].
pub fn with_width(self, width: impl Into<Pixels>) -> Self {
Self {
width: width.into().0,
..self
}
}
}

View file

@ -1,4 +1,3 @@
#[cfg(feature = "palette")]
use palette::rgb::{Srgb, Srgba};
/// A color in the `sRGB` color space.
@ -152,6 +151,14 @@ impl Color {
pub fn inverse(self) -> Color {
Color::new(1.0f32 - self.r, 1.0f32 - self.g, 1.0f32 - self.b, self.a)
}
/// Scales the alpha channel of the [`Color`] by the given factor.
pub fn scale_alpha(self, factor: f32) -> Color {
Self {
a: self.a * factor,
..self
}
}
}
impl From<[f32; 3]> for Color {
@ -203,7 +210,6 @@ macro_rules! color {
}};
}
#[cfg(feature = "palette")]
/// Converts from palette's `Rgba` type to a [`Color`].
impl From<Srgba> for Color {
fn from(rgba: Srgba) -> Self {
@ -211,7 +217,6 @@ impl From<Srgba> for Color {
}
}
#[cfg(feature = "palette")]
/// Converts from [`Color`] to palette's `Rgba` type.
impl From<Color> for Srgba {
fn from(c: Color) -> Self {
@ -219,7 +224,6 @@ impl From<Color> for Srgba {
}
}
#[cfg(feature = "palette")]
/// Converts from palette's `Rgb` type to a [`Color`].
impl From<Srgb> for Color {
fn from(rgb: Srgb) -> Self {
@ -227,7 +231,6 @@ impl From<Srgb> for Color {
}
}
#[cfg(feature = "palette")]
/// Converts from [`Color`] to palette's `Rgb` type.
impl From<Color> for Srgb {
fn from(c: Color) -> Self {
@ -235,7 +238,6 @@ impl From<Color> for Srgb {
}
}
#[cfg(feature = "palette")]
#[cfg(test)]
mod tests {
use super::*;

View file

@ -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",
})
}
}

View file

@ -94,52 +94,34 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
/// producing them. Let's implement our __view logic__ now:
///
/// ```no_run
/// # mod counter {
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
/// # pub struct Counter;
/// # mod iced {
/// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
/// #
/// # impl Counter {
/// # pub fn view(
/// # &self,
/// # ) -> iced_core::Element<Message, (), iced_core::renderer::Null> {
/// # pub mod widget {
/// # pub fn row<'a, Message>(iter: impl IntoIterator<Item = super::Element<'a, Message>>) -> super::Element<'a, Message> {
/// # unimplemented!()
/// # }
/// # }
/// # }
/// #
/// # mod iced {
/// # pub use iced_core::renderer::Null as Renderer;
/// # pub use iced_core::Element;
/// # mod counter {
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
/// # pub struct Counter;
/// #
/// # pub mod widget {
/// # pub struct Row<Message> {
/// # _t: std::marker::PhantomData<Message>,
/// # }
/// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
/// #
/// # impl<Message> Row<Message> {
/// # pub fn new() -> Self {
/// # unimplemented!()
/// # }
/// #
/// # pub fn spacing(mut self, _: u32) -> Self {
/// # unimplemented!()
/// # }
/// #
/// # pub fn push(
/// # mut self,
/// # _: iced_core::Element<Message, (), iced_core::renderer::Null>,
/// # ) -> Self {
/// # unimplemented!()
/// # }
/// # impl Counter {
/// # pub fn view(&self) -> Element<Message> {
/// # unimplemented!()
/// # }
/// # }
/// # }
/// #
/// use counter::Counter;
///
/// use iced::widget::Row;
/// use iced::{Element, Renderer};
/// use iced::widget::row;
/// use iced::Element;
///
/// struct ManyCounters {
/// counters: Vec<Counter>,
@ -151,24 +133,21 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
/// }
///
/// impl ManyCounters {
/// pub fn view(&mut self) -> Row<Message> {
/// // We can quickly populate a `Row` by folding over our counters
/// self.counters.iter_mut().enumerate().fold(
/// Row::new().spacing(20),
/// |row, (index, counter)| {
/// // We display the counter
/// let element: Element<counter::Message, _, _> =
/// counter.view().into();
///
/// row.push(
/// pub fn view(&self) -> Element<Message> {
/// // We can quickly populate a `row` by mapping our counters
/// row(
/// self.counters
/// .iter()
/// .map(Counter::view)
/// .enumerate()
/// .map(|(index, counter)| {
/// // Here we turn our `Element<counter::Message>` into
/// // an `Element<Message>` by combining the `index` and the
/// // message of the `element`.
/// element
/// .map(move |message| Message::Counter(index, message)),
/// )
/// },
/// counter.map(move |message| Message::Counter(index, message))
/// }),
/// )
/// .into()
/// }
/// }
/// ```

View file

@ -12,17 +12,13 @@ pub enum Gradient {
}
impl Gradient {
/// Adjust the opacity of the gradient by a multiplier applied to each color stop.
pub fn mul_alpha(mut self, alpha_multiplier: f32) -> Self {
match &mut self {
/// Scales the alpha channel of the [`Gradient`] by the given factor.
pub fn scale_alpha(self, factor: f32) -> Self {
match self {
Gradient::Linear(linear) => {
for stop in linear.stops.iter_mut().flatten() {
stop.color.a *= alpha_multiplier;
}
Gradient::Linear(linear.scale_alpha(factor))
}
}
self
}
}
@ -100,4 +96,14 @@ impl Linear {
self
}
/// Scales the alpha channel of the [`Linear`] gradient by the given
/// factor.
pub fn scale_alpha(mut self, factor: f32) -> Self {
for stop in self.stops.iter_mut().flatten() {
stop.color.a *= factor;
}
self
}
}

View file

@ -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]) {

View file

@ -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();
Self::Path(Id::path(&path), path)
}
/// 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),
})
}
/// 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,93 +101,49 @@ 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 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.as_ref().as_ref().fmt(f)
}
}
impl std::hash::Hash for Bytes {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.as_ref().as_ref().hash(state);
}
}
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
}
}
impl Eq for Bytes {}
impl AsRef<[u8]> for Bytes {
fn as_ref(&self) -> &[u8] {
self.0.as_ref().as_ref()
}
}
impl std::ops::Deref for Bytes {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.0.as_ref().as_ref()
}
}
/// The data of a raster image.
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum Data {
/// File data
Path(PathBuf),
/// 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,
},
}
impl std::fmt::Debug for Data {
impl std::fmt::Debug for Handle {
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, .. } => {
Self::Path(_, path) => write!(f, "Path({path:?})"),
Self::Bytes(_, _) => write!(f, "Bytes(...)"),
Self::Rgba { width, height, .. } => {
write!(f, "Pixels({width} * {height})")
}
}
}
}
/// The unique identifier of some [`Handle`] data.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Id(_Id);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum _Id {
Unique(u64),
Hash(u64),
}
impl Id {
fn unique() -> Self {
use std::sync::atomic::{self, AtomicU64};
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
Self(_Id::Unique(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)))
}
fn path(path: impl AsRef<Path>) -> Self {
let hash = {
let mut hasher = FxHasher::default();
path.as_ref().hash(&mut hasher);
hasher.finish()
};
Self(_Id::Hash(hash))
}
}
/// Image filtering strategy.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum FilterMethod {
@ -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,
);
}

View file

@ -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),

View file

@ -80,14 +80,9 @@ where
let mut fill_main_sum = 0;
let mut cross = match axis {
Axis::Horizontal => match height {
Length::Shrink => 0.0,
_ => max_cross,
},
Axis::Vertical => match width {
Length::Shrink => 0.0,
_ => max_cross,
},
Axis::Vertical if width == Length::Shrink => 0.0,
Axis::Horizontal if height == Length::Shrink => 0.0,
_ => max_cross,
};
let mut available = axis.main(limits.max()) - total_spacing;
@ -103,35 +98,14 @@ where
};
if fill_main_factor == 0 {
if fill_cross_factor == 0 {
let (max_width, max_height) = axis.pack(available, max_cross);
let child_limits =
Limits::new(Size::ZERO, Size::new(max_width, max_height));
let layout =
child.as_widget().layout(tree, renderer, &child_limits);
let size = layout.size();
available -= axis.main(size);
cross = cross.max(axis.cross(size));
nodes[i] = layout;
}
} else {
fill_main_sum += fill_main_factor;
}
}
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
let (fill_main_factor, fill_cross_factor) = {
let size = child.as_widget().size();
axis.pack(size.width.fill_factor(), size.height.fill_factor())
};
if fill_main_factor == 0 && fill_cross_factor != 0 {
let (max_width, max_height) = axis.pack(available, cross);
let (max_width, max_height) = axis.pack(
available,
if fill_cross_factor == 0 {
max_cross
} else {
cross
},
);
let child_limits =
Limits::new(Size::ZERO, Size::new(max_width, max_height));
@ -141,9 +115,11 @@ where
let size = layout.size();
available -= axis.main(size);
cross = cross.max(axis.cross(layout.size()));
cross = cross.max(axis.cross(size));
nodes[i] = layout;
} else {
fill_main_sum += fill_main_factor;
}
}
@ -175,14 +151,15 @@ where
max_main
};
let max_cross = if fill_cross_factor == 0 {
max_cross
} else {
cross
};
let (min_width, min_height) = axis.pack(min_main, 0.0);
let (max_width, max_height) = axis.pack(max_main, max_cross);
let (max_width, max_height) = axis.pack(
max_main,
if fill_cross_factor == 0 {
max_cross
} else {
cross
},
);
let child_limits = Limits::new(
Size::new(min_width, min_height),

View file

@ -9,14 +9,6 @@
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![forbid(unsafe_code)]
#![deny(
missing_debug_implementations,
missing_docs,
unused_results,
rust_2018_idioms,
rustdoc::broken_intra_doc_links
)]
pub mod alignment;
pub mod border;
pub mod clipboard;
@ -31,6 +23,7 @@ pub mod overlay;
pub mod renderer;
pub mod svg;
pub mod text;
pub mod theme;
pub mod time;
pub mod touch;
pub mod widget;
@ -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,10 +65,12 @@ 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;
pub use text::Text;
pub use theme::Theme;
pub use transformation::Transformation;
pub use vector::Vector;
pub use widget::Widget;

View file

@ -3,6 +3,7 @@
#[allow(missing_docs)]
pub enum Interaction {
#[default]
None,
Idle,
Pointer,
Grab,

View file

@ -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`].

View file

@ -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,
}
}
}

View file

@ -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(

View file

@ -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
}
}
fn start_transformation(&mut self, _transformation: Transformation) {}
impl Renderer for Null {
fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
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,
) {
}
}

72
core/src/rotation.rs Normal file
View file

@ -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))
}
}

View file

@ -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,22 +51,35 @@ 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 From<[f32; 2]> for Size {
fn from([width, height]: [f32; 2]) -> Self {
impl<T> From<[T; 2]> for Size<T> {
fn from([width, height]: [T; 2]) -> Self {
Size { width, height }
}
}
impl From<[u16; 2]> for Size {
fn from([width, height]: [u16; 2]) -> Self {
Size::new(width.into(), height.into())
impl<T> From<(T, T)> for Size<T> {
fn from((width, height): (T, T)) -> Self {
Self { width, height }
}
}
impl From<Vector<f32>> for Size {
fn from(vector: Vector<f32>) -> Self {
impl<T> From<Vector<T>> for Size<T> {
fn from(vector: Vector<T>) -> Self {
Size {
width: vector.x,
height: vector.y,
@ -74,20 +87,23 @@ impl From<Vector<f32>> for Size {
}
}
impl From<Size> for [f32; 2] {
fn from(size: Size) -> [f32; 2] {
impl<T> From<Size<T>> for [T; 2] {
fn from(size: Size<T>) -> Self {
[size.width, size.height]
}
}
impl From<Size> for Vector<f32> {
fn from(size: Size) -> Self {
impl<T> From<Size<T>> for Vector<T> {
fn from(size: Size<T>) -> Self {
Vector::new(size.width, size.height)
}
}
impl std::ops::Sub for Size {
type Output = Size;
impl<T> std::ops::Sub for Size<T>
where
T: std::ops::Sub<Output = T>,
{
type Output = Size<T>;
fn sub(self, rhs: Self) -> Self::Output {
Size {
@ -96,3 +112,31 @@ impl std::ops::Sub for Size {
}
}
}
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,
}
}
}

View file

@ -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,
);
}

View file

@ -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,

View file

@ -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 => {

248
core/src/theme.rs Normal file
View file

@ -0,0 +1,248 @@
//! Use the built-in theme and styles.
pub mod palette;
pub use palette::Palette;
use std::fmt;
use std::sync::Arc;
/// A built-in theme.
#[derive(Debug, Clone, PartialEq)]
pub enum Theme {
/// The built-in light variant.
Light,
/// The built-in dark variant.
Dark,
/// The built-in Dracula variant.
Dracula,
/// The built-in Nord variant.
Nord,
/// The built-in Solarized Light variant.
SolarizedLight,
/// The built-in Solarized Dark variant.
SolarizedDark,
/// The built-in Gruvbox Light variant.
GruvboxLight,
/// The built-in Gruvbox Dark variant.
GruvboxDark,
/// The built-in Catppuccin Latte variant.
CatppuccinLatte,
/// The built-in Catppuccin Frappé variant.
CatppuccinFrappe,
/// The built-in Catppuccin Macchiato variant.
CatppuccinMacchiato,
/// The built-in Catppuccin Mocha variant.
CatppuccinMocha,
/// The built-in Tokyo Night variant.
TokyoNight,
/// The built-in Tokyo Night Storm variant.
TokyoNightStorm,
/// The built-in Tokyo Night Light variant.
TokyoNightLight,
/// The built-in Kanagawa Wave variant.
KanagawaWave,
/// The built-in Kanagawa Dragon variant.
KanagawaDragon,
/// The built-in Kanagawa Lotus variant.
KanagawaLotus,
/// The built-in Moonfly variant.
Moonfly,
/// The built-in Nightfly variant.
Nightfly,
/// The built-in Oxocarbon variant.
Oxocarbon,
/// The built-in Ferra variant:
Ferra,
/// A [`Theme`] that uses a [`Custom`] palette.
Custom(Arc<Custom>),
}
impl Theme {
/// A list with all the defined themes.
pub const ALL: &'static [Self] = &[
Self::Light,
Self::Dark,
Self::Dracula,
Self::Nord,
Self::SolarizedLight,
Self::SolarizedDark,
Self::GruvboxLight,
Self::GruvboxDark,
Self::CatppuccinLatte,
Self::CatppuccinFrappe,
Self::CatppuccinMacchiato,
Self::CatppuccinMocha,
Self::TokyoNight,
Self::TokyoNightStorm,
Self::TokyoNightLight,
Self::KanagawaWave,
Self::KanagawaDragon,
Self::KanagawaLotus,
Self::Moonfly,
Self::Nightfly,
Self::Oxocarbon,
Self::Ferra,
];
/// Creates a new custom [`Theme`] from the given [`Palette`].
pub fn custom(name: String, palette: Palette) -> Self {
Self::custom_with_fn(name, palette, palette::Extended::generate)
}
/// Creates a new custom [`Theme`] from the given [`Palette`], with
/// a custom generator of a [`palette::Extended`].
pub fn custom_with_fn(
name: String,
palette: Palette,
generate: impl FnOnce(Palette) -> palette::Extended,
) -> Self {
Self::Custom(Arc::new(Custom::with_fn(name, palette, generate)))
}
/// Returns the [`Palette`] of the [`Theme`].
pub fn palette(&self) -> Palette {
match self {
Self::Light => Palette::LIGHT,
Self::Dark => Palette::DARK,
Self::Dracula => Palette::DRACULA,
Self::Nord => Palette::NORD,
Self::SolarizedLight => Palette::SOLARIZED_LIGHT,
Self::SolarizedDark => Palette::SOLARIZED_DARK,
Self::GruvboxLight => Palette::GRUVBOX_LIGHT,
Self::GruvboxDark => Palette::GRUVBOX_DARK,
Self::CatppuccinLatte => Palette::CATPPUCCIN_LATTE,
Self::CatppuccinFrappe => Palette::CATPPUCCIN_FRAPPE,
Self::CatppuccinMacchiato => Palette::CATPPUCCIN_MACCHIATO,
Self::CatppuccinMocha => Palette::CATPPUCCIN_MOCHA,
Self::TokyoNight => Palette::TOKYO_NIGHT,
Self::TokyoNightStorm => Palette::TOKYO_NIGHT_STORM,
Self::TokyoNightLight => Palette::TOKYO_NIGHT_LIGHT,
Self::KanagawaWave => Palette::KANAGAWA_WAVE,
Self::KanagawaDragon => Palette::KANAGAWA_DRAGON,
Self::KanagawaLotus => Palette::KANAGAWA_LOTUS,
Self::Moonfly => Palette::MOONFLY,
Self::Nightfly => Palette::NIGHTFLY,
Self::Oxocarbon => Palette::OXOCARBON,
Self::Ferra => Palette::FERRA,
Self::Custom(custom) => custom.palette,
}
}
/// Returns the [`palette::Extended`] of the [`Theme`].
pub fn extended_palette(&self) -> &palette::Extended {
match self {
Self::Light => &palette::EXTENDED_LIGHT,
Self::Dark => &palette::EXTENDED_DARK,
Self::Dracula => &palette::EXTENDED_DRACULA,
Self::Nord => &palette::EXTENDED_NORD,
Self::SolarizedLight => &palette::EXTENDED_SOLARIZED_LIGHT,
Self::SolarizedDark => &palette::EXTENDED_SOLARIZED_DARK,
Self::GruvboxLight => &palette::EXTENDED_GRUVBOX_LIGHT,
Self::GruvboxDark => &palette::EXTENDED_GRUVBOX_DARK,
Self::CatppuccinLatte => &palette::EXTENDED_CATPPUCCIN_LATTE,
Self::CatppuccinFrappe => &palette::EXTENDED_CATPPUCCIN_FRAPPE,
Self::CatppuccinMacchiato => {
&palette::EXTENDED_CATPPUCCIN_MACCHIATO
}
Self::CatppuccinMocha => &palette::EXTENDED_CATPPUCCIN_MOCHA,
Self::TokyoNight => &palette::EXTENDED_TOKYO_NIGHT,
Self::TokyoNightStorm => &palette::EXTENDED_TOKYO_NIGHT_STORM,
Self::TokyoNightLight => &palette::EXTENDED_TOKYO_NIGHT_LIGHT,
Self::KanagawaWave => &palette::EXTENDED_KANAGAWA_WAVE,
Self::KanagawaDragon => &palette::EXTENDED_KANAGAWA_DRAGON,
Self::KanagawaLotus => &palette::EXTENDED_KANAGAWA_LOTUS,
Self::Moonfly => &palette::EXTENDED_MOONFLY,
Self::Nightfly => &palette::EXTENDED_NIGHTFLY,
Self::Oxocarbon => &palette::EXTENDED_OXOCARBON,
Self::Ferra => &palette::EXTENDED_FERRA,
Self::Custom(custom) => &custom.extended,
}
}
}
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 {
Self::Light => write!(f, "Light"),
Self::Dark => write!(f, "Dark"),
Self::Dracula => write!(f, "Dracula"),
Self::Nord => write!(f, "Nord"),
Self::SolarizedLight => write!(f, "Solarized Light"),
Self::SolarizedDark => write!(f, "Solarized Dark"),
Self::GruvboxLight => write!(f, "Gruvbox Light"),
Self::GruvboxDark => write!(f, "Gruvbox Dark"),
Self::CatppuccinLatte => write!(f, "Catppuccin Latte"),
Self::CatppuccinFrappe => write!(f, "Catppuccin Frappé"),
Self::CatppuccinMacchiato => write!(f, "Catppuccin Macchiato"),
Self::CatppuccinMocha => write!(f, "Catppuccin Mocha"),
Self::TokyoNight => write!(f, "Tokyo Night"),
Self::TokyoNightStorm => write!(f, "Tokyo Night Storm"),
Self::TokyoNightLight => write!(f, "Tokyo Night Light"),
Self::KanagawaWave => write!(f, "Kanagawa Wave"),
Self::KanagawaDragon => write!(f, "Kanagawa Dragon"),
Self::KanagawaLotus => write!(f, "Kanagawa Lotus"),
Self::Moonfly => write!(f, "Moonfly"),
Self::Nightfly => write!(f, "Nightfly"),
Self::Oxocarbon => write!(f, "Oxocarbon"),
Self::Ferra => write!(f, "Ferra"),
Self::Custom(custom) => custom.fmt(f),
}
}
}
/// A [`Theme`] with a customized [`Palette`].
#[derive(Debug, Clone, PartialEq)]
pub struct Custom {
name: String,
palette: Palette,
extended: palette::Extended,
}
impl Custom {
/// Creates a [`Custom`] theme from the given [`Palette`].
pub fn new(name: String, palette: Palette) -> Self {
Self::with_fn(name, palette, palette::Extended::generate)
}
/// Creates a [`Custom`] theme from the given [`Palette`] with
/// a custom generator of a [`palette::Extended`].
pub fn with_fn(
name: String,
palette: Palette,
generate: impl FnOnce(Palette) -> palette::Extended,
) -> Self {
Self {
name,
palette,
extended: generate(palette),
}
}
}
impl fmt::Display for Custom {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}

653
core/src/theme/palette.rs Normal file
View file

@ -0,0 +1,653 @@
//! Define the colors of a theme.
use crate::{color, Color};
use once_cell::sync::Lazy;
use palette::color_difference::Wcag21RelativeContrast;
use palette::rgb::Rgb;
use palette::{FromColor, Hsl, Mix};
/// A color palette.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Palette {
/// The background [`Color`] of the [`Palette`].
pub background: Color,
/// The text [`Color`] of the [`Palette`].
pub text: Color,
/// The primary [`Color`] of the [`Palette`].
pub primary: Color,
/// The success [`Color`] of the [`Palette`].
pub success: Color,
/// The danger [`Color`] of the [`Palette`].
pub danger: Color,
}
impl Palette {
/// The built-in light variant of a [`Palette`].
pub const LIGHT: Self = Self {
background: Color::WHITE,
text: Color::BLACK,
primary: Color::from_rgb(
0x5E as f32 / 255.0,
0x7C as f32 / 255.0,
0xE2 as f32 / 255.0,
),
success: Color::from_rgb(
0x12 as f32 / 255.0,
0x66 as f32 / 255.0,
0x4F as f32 / 255.0,
),
danger: Color::from_rgb(
0xC3 as f32 / 255.0,
0x42 as f32 / 255.0,
0x3F as f32 / 255.0,
),
};
/// The built-in dark variant of a [`Palette`].
pub const DARK: Self = Self {
background: Color::from_rgb(
0x20 as f32 / 255.0,
0x22 as f32 / 255.0,
0x25 as f32 / 255.0,
),
text: Color::from_rgb(0.90, 0.90, 0.90),
primary: Color::from_rgb(
0x5E as f32 / 255.0,
0x7C as f32 / 255.0,
0xE2 as f32 / 255.0,
),
success: Color::from_rgb(
0x12 as f32 / 255.0,
0x66 as f32 / 255.0,
0x4F as f32 / 255.0,
),
danger: Color::from_rgb(
0xC3 as f32 / 255.0,
0x42 as f32 / 255.0,
0x3F as f32 / 255.0,
),
};
/// The built-in [Dracula] variant of a [`Palette`].
///
/// [Dracula]: https://draculatheme.com
pub const DRACULA: Self = Self {
background: color!(0x282A36), // BACKGROUND
text: color!(0xf8f8f2), // FOREGROUND
primary: color!(0xbd93f9), // PURPLE
success: color!(0x50fa7b), // GREEN
danger: color!(0xff5555), // RED
};
/// The built-in [Nord] variant of a [`Palette`].
///
/// [Nord]: https://www.nordtheme.com/docs/colors-and-palettes
pub const NORD: Self = Self {
background: color!(0x2e3440), // nord0
text: color!(0xeceff4), // nord6
primary: color!(0x8fbcbb), // nord7
success: color!(0xa3be8c), // nord14
danger: color!(0xbf616a), // nord11
};
/// The built-in [Solarized] Light variant of a [`Palette`].
///
/// [Solarized]: https://ethanschoonover.com/solarized
pub const SOLARIZED_LIGHT: Self = Self {
background: color!(0xfdf6e3), // base3
text: color!(0x657b83), // base00
primary: color!(0x2aa198), // cyan
success: color!(0x859900), // green
danger: color!(0xdc322f), // red
};
/// The built-in [Solarized] Dark variant of a [`Palette`].
///
/// [Solarized]: https://ethanschoonover.com/solarized
pub const SOLARIZED_DARK: Self = Self {
background: color!(0x002b36), // base03
text: color!(0x839496), // base0
primary: color!(0x2aa198), // cyan
success: color!(0x859900), // green
danger: color!(0xdc322f), // red
};
/// The built-in [Gruvbox] Light variant of a [`Palette`].
///
/// [Gruvbox]: https://github.com/morhetz/gruvbox
pub const GRUVBOX_LIGHT: Self = Self {
background: color!(0xfbf1c7), // light BG_0
text: color!(0x282828), // light FG0_29
primary: color!(0x458588), // light BLUE_4
success: color!(0x98971a), // light GREEN_2
danger: color!(0xcc241d), // light RED_1
};
/// The built-in [Gruvbox] Dark variant of a [`Palette`].
///
/// [Gruvbox]: https://github.com/morhetz/gruvbox
pub const GRUVBOX_DARK: Self = Self {
background: color!(0x282828), // dark BG_0
text: color!(0xfbf1c7), // dark FG0_29
primary: color!(0x458588), // dark BLUE_4
success: color!(0x98971a), // dark GREEN_2
danger: color!(0xcc241d), // dark RED_1
};
/// The built-in [Catppuccin] Latte variant of a [`Palette`].
///
/// [Catppuccin]: https://github.com/catppuccin/catppuccin
pub const CATPPUCCIN_LATTE: Self = Self {
background: color!(0xeff1f5), // Base
text: color!(0x4c4f69), // Text
primary: color!(0x1e66f5), // Blue
success: color!(0x40a02b), // Green
danger: color!(0xd20f39), // Red
};
/// The built-in [Catppuccin] Frappé variant of a [`Palette`].
///
/// [Catppuccin]: https://github.com/catppuccin/catppuccin
pub const CATPPUCCIN_FRAPPE: Self = Self {
background: color!(0x303446), // Base
text: color!(0xc6d0f5), // Text
primary: color!(0x8caaee), // Blue
success: color!(0xa6d189), // Green
danger: color!(0xe78284), // Red
};
/// The built-in [Catppuccin] Macchiato variant of a [`Palette`].
///
/// [Catppuccin]: https://github.com/catppuccin/catppuccin
pub const CATPPUCCIN_MACCHIATO: Self = Self {
background: color!(0x24273a), // Base
text: color!(0xcad3f5), // Text
primary: color!(0x8aadf4), // Blue
success: color!(0xa6da95), // Green
danger: color!(0xed8796), // Red
};
/// The built-in [Catppuccin] Mocha variant of a [`Palette`].
///
/// [Catppuccin]: https://github.com/catppuccin/catppuccin
pub const CATPPUCCIN_MOCHA: Self = Self {
background: color!(0x1e1e2e), // Base
text: color!(0xcdd6f4), // Text
primary: color!(0x89b4fa), // Blue
success: color!(0xa6e3a1), // Green
danger: color!(0xf38ba8), // Red
};
/// The built-in [Tokyo Night] variant of a [`Palette`].
///
/// [Tokyo Night]: https://github.com/enkia/tokyo-night-vscode-theme
pub const TOKYO_NIGHT: Self = Self {
background: color!(0x1a1b26), // Background (Night)
text: color!(0x9aa5ce), // Text
primary: color!(0x2ac3de), // Blue
success: color!(0x9ece6a), // Green
danger: color!(0xf7768e), // Red
};
/// The built-in [Tokyo Night] Storm variant of a [`Palette`].
///
/// [Tokyo Night]: https://github.com/enkia/tokyo-night-vscode-theme
pub const TOKYO_NIGHT_STORM: Self = Self {
background: color!(0x24283b), // Background (Storm)
text: color!(0x9aa5ce), // Text
primary: color!(0x2ac3de), // Blue
success: color!(0x9ece6a), // Green
danger: color!(0xf7768e), // Red
};
/// The built-in [Tokyo Night] Light variant of a [`Palette`].
///
/// [Tokyo Night]: https://github.com/enkia/tokyo-night-vscode-theme
pub const TOKYO_NIGHT_LIGHT: Self = Self {
background: color!(0xd5d6db), // Background
text: color!(0x565a6e), // Text
primary: color!(0x166775), // Blue
success: color!(0x485e30), // Green
danger: color!(0x8c4351), // Red
};
/// The built-in [Kanagawa] Wave variant of a [`Palette`].
///
/// [Kanagawa]: https://github.com/rebelot/kanagawa.nvim
pub const KANAGAWA_WAVE: Self = Self {
background: color!(0x363646), // Sumi Ink 3
text: color!(0xCD7BA), // Fuji White
primary: color!(0x2D4F67), // Wave Blue 2
success: color!(0x76946A), // Autumn Green
danger: color!(0xC34043), // Autumn Red
};
/// The built-in [Kanagawa] Dragon variant of a [`Palette`].
///
/// [Kanagawa]: https://github.com/rebelot/kanagawa.nvim
pub const KANAGAWA_DRAGON: Self = Self {
background: color!(0x181616), // Dragon Black 3
text: color!(0xc5c9c5), // Dragon White
primary: color!(0x223249), // Wave Blue 1
success: color!(0x8a9a7b), // Dragon Green 2
danger: color!(0xc4746e), // Dragon Red
};
/// The built-in [Kanagawa] Lotus variant of a [`Palette`].
///
/// [Kanagawa]: https://github.com/rebelot/kanagawa.nvim
pub const KANAGAWA_LOTUS: Self = Self {
background: color!(0xf2ecbc), // Lotus White 3
text: color!(0x545464), // Lotus Ink 1
primary: color!(0xc9cbd1), // Lotus Violet 3
success: color!(0x6f894e), // Lotus Green
danger: color!(0xc84053), // Lotus Red
};
/// The built-in [Moonfly] variant of a [`Palette`].
///
/// [Moonfly]: https://github.com/bluz71/vim-moonfly-colors
pub const MOONFLY: Self = Self {
background: color!(0x080808), // Background
text: color!(0xbdbdbd), // Foreground
primary: color!(0x80a0ff), // Blue (normal)
success: color!(0x8cc85f), // Green (normal)
danger: color!(0xff5454), // Red (normal)
};
/// The built-in [Nightfly] variant of a [`Palette`].
///
/// [Nightfly]: https://github.com/bluz71/vim-nightfly-colors
pub const NIGHTFLY: Self = Self {
background: color!(0x011627), // Background
text: color!(0xbdc1c6), // Foreground
primary: color!(0x82aaff), // Blue (normal)
success: color!(0xa1cd5e), // Green (normal)
danger: color!(0xfc514e), // Red (normal)
};
/// The built-in [Oxocarbon] variant of a [`Palette`].
///
/// [Oxocarbon]: https://github.com/nyoom-engineering/oxocarbon.nvim
pub const OXOCARBON: Self = Self {
background: color!(0x232323),
text: color!(0xd0d0d0),
primary: color!(0x00b4ff),
success: color!(0x00c15a),
danger: color!(0xf62d0f),
};
/// The built-in [Ferra] variant of a [`Palette`].
///
/// [Ferra]: https://github.com/casperstorm/ferra
pub const FERRA: Self = Self {
background: color!(0x2b292d),
text: color!(0xfecdb2),
primary: color!(0xd1d1e0),
success: color!(0xb1b695),
danger: color!(0xe06b75),
};
}
/// An extended set of colors generated from a [`Palette`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Extended {
/// The set of background colors.
pub background: Background,
/// The set of primary colors.
pub primary: Primary,
/// The set of secondary colors.
pub secondary: Secondary,
/// The set of success colors.
pub success: Success,
/// The set of danger colors.
pub danger: Danger,
/// Whether the palette is dark or not.
pub is_dark: bool,
}
/// The built-in light variant of an [`Extended`] palette.
pub static EXTENDED_LIGHT: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::LIGHT));
/// The built-in dark variant of an [`Extended`] palette.
pub static EXTENDED_DARK: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::DARK));
/// The built-in Dracula variant of an [`Extended`] palette.
pub static EXTENDED_DRACULA: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::DRACULA));
/// The built-in Nord variant of an [`Extended`] palette.
pub static EXTENDED_NORD: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::NORD));
/// The built-in Solarized Light variant of an [`Extended`] palette.
pub static EXTENDED_SOLARIZED_LIGHT: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::SOLARIZED_LIGHT));
/// The built-in Solarized Dark variant of an [`Extended`] palette.
pub static EXTENDED_SOLARIZED_DARK: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::SOLARIZED_DARK));
/// The built-in Gruvbox Light variant of an [`Extended`] palette.
pub static EXTENDED_GRUVBOX_LIGHT: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::GRUVBOX_LIGHT));
/// The built-in Gruvbox Dark variant of an [`Extended`] palette.
pub static EXTENDED_GRUVBOX_DARK: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::GRUVBOX_DARK));
/// The built-in Catppuccin Latte variant of an [`Extended`] palette.
pub static EXTENDED_CATPPUCCIN_LATTE: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::CATPPUCCIN_LATTE));
/// The built-in Catppuccin Frappé variant of an [`Extended`] palette.
pub static EXTENDED_CATPPUCCIN_FRAPPE: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::CATPPUCCIN_FRAPPE));
/// The built-in Catppuccin Macchiato variant of an [`Extended`] palette.
pub static EXTENDED_CATPPUCCIN_MACCHIATO: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::CATPPUCCIN_MACCHIATO));
/// The built-in Catppuccin Mocha variant of an [`Extended`] palette.
pub static EXTENDED_CATPPUCCIN_MOCHA: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::CATPPUCCIN_MOCHA));
/// The built-in Tokyo Night variant of an [`Extended`] palette.
pub static EXTENDED_TOKYO_NIGHT: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::TOKYO_NIGHT));
/// The built-in Tokyo Night Storm variant of an [`Extended`] palette.
pub static EXTENDED_TOKYO_NIGHT_STORM: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::TOKYO_NIGHT_STORM));
/// The built-in Tokyo Night variant of an [`Extended`] palette.
pub static EXTENDED_TOKYO_NIGHT_LIGHT: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::TOKYO_NIGHT_LIGHT));
/// The built-in Kanagawa Wave variant of an [`Extended`] palette.
pub static EXTENDED_KANAGAWA_WAVE: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::KANAGAWA_WAVE));
/// The built-in Kanagawa Dragon variant of an [`Extended`] palette.
pub static EXTENDED_KANAGAWA_DRAGON: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::KANAGAWA_DRAGON));
/// The built-in Kanagawa Lotus variant of an [`Extended`] palette.
pub static EXTENDED_KANAGAWA_LOTUS: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::KANAGAWA_LOTUS));
/// The built-in Moonfly variant of an [`Extended`] palette.
pub static EXTENDED_MOONFLY: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::MOONFLY));
/// The built-in Nightfly variant of an [`Extended`] palette.
pub static EXTENDED_NIGHTFLY: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::NIGHTFLY));
/// The built-in Oxocarbon variant of an [`Extended`] palette.
pub static EXTENDED_OXOCARBON: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::OXOCARBON));
/// The built-in Ferra variant of an [`Extended`] palette.
pub static EXTENDED_FERRA: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::FERRA));
impl Extended {
/// Generates an [`Extended`] palette from a simple [`Palette`].
pub fn generate(palette: Palette) -> Self {
Self {
background: Background::new(palette.background, palette.text),
primary: Primary::generate(
palette.primary,
palette.background,
palette.text,
),
secondary: Secondary::generate(palette.background, palette.text),
success: Success::generate(
palette.success,
palette.background,
palette.text,
),
danger: Danger::generate(
palette.danger,
palette.background,
palette.text,
),
is_dark: is_dark(palette.background),
}
}
}
/// A pair of background and text colors.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Pair {
/// The background color.
pub color: Color,
/// The text color.
///
/// It's guaranteed to be readable on top of the background [`color`].
///
/// [`color`]: Self::color
pub text: Color,
}
impl Pair {
/// Creates a new [`Pair`] from a background [`Color`] and some text [`Color`].
pub fn new(color: Color, text: Color) -> Self {
Self {
color,
text: readable(color, text),
}
}
}
/// A set of background colors.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Background {
/// The base background color.
pub base: Pair,
/// A weaker version of the base background color.
pub weak: Pair,
/// A stronger version of the base background color.
pub strong: Pair,
}
impl Background {
/// Generates a set of [`Background`] colors from the base and text colors.
pub fn new(base: Color, text: Color) -> Self {
let weak = mix(base, text, 0.15);
let strong = mix(base, text, 0.40);
Self {
base: Pair::new(base, text),
weak: Pair::new(weak, text),
strong: Pair::new(strong, text),
}
}
}
/// A set of primary colors.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Primary {
/// The base primary color.
pub base: Pair,
/// A weaker version of the base primary color.
pub weak: Pair,
/// A stronger version of the base primary color.
pub strong: Pair,
}
impl Primary {
/// Generates a set of [`Primary`] colors from the base, background, and text colors.
pub fn generate(base: Color, background: Color, text: Color) -> Self {
let weak = mix(base, background, 0.4);
let strong = deviate(base, 0.1);
Self {
base: Pair::new(base, text),
weak: Pair::new(weak, text),
strong: Pair::new(strong, text),
}
}
}
/// A set of secondary colors.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Secondary {
/// The base secondary color.
pub base: Pair,
/// A weaker version of the base secondary color.
pub weak: Pair,
/// A stronger version of the base secondary color.
pub strong: Pair,
}
impl Secondary {
/// Generates a set of [`Secondary`] colors from the base and text colors.
pub fn generate(base: Color, text: Color) -> Self {
let base = mix(base, text, 0.2);
let weak = mix(base, text, 0.1);
let strong = mix(base, text, 0.3);
Self {
base: Pair::new(base, text),
weak: Pair::new(weak, text),
strong: Pair::new(strong, text),
}
}
}
/// A set of success colors.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Success {
/// The base success color.
pub base: Pair,
/// A weaker version of the base success color.
pub weak: Pair,
/// A stronger version of the base success color.
pub strong: Pair,
}
impl Success {
/// Generates a set of [`Success`] colors from the base, background, and text colors.
pub fn generate(base: Color, background: Color, text: Color) -> Self {
let weak = mix(base, background, 0.4);
let strong = deviate(base, 0.1);
Self {
base: Pair::new(base, text),
weak: Pair::new(weak, text),
strong: Pair::new(strong, text),
}
}
}
/// A set of danger colors.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Danger {
/// The base danger color.
pub base: Pair,
/// A weaker version of the base danger color.
pub weak: Pair,
/// A stronger version of the base danger color.
pub strong: Pair,
}
impl Danger {
/// Generates a set of [`Danger`] colors from the base, background, and text colors.
pub fn generate(base: Color, background: Color, text: Color) -> Self {
let weak = mix(base, background, 0.4);
let strong = deviate(base, 0.1);
Self {
base: Pair::new(base, text),
weak: Pair::new(weak, text),
strong: Pair::new(strong, text),
}
}
}
fn darken(color: Color, amount: f32) -> Color {
let mut hsl = to_hsl(color);
hsl.lightness = if hsl.lightness - amount < 0.0 {
0.0
} else {
hsl.lightness - amount
};
from_hsl(hsl)
}
fn lighten(color: Color, amount: f32) -> Color {
let mut hsl = to_hsl(color);
hsl.lightness = if hsl.lightness + amount > 1.0 {
1.0
} else {
hsl.lightness + amount
};
from_hsl(hsl)
}
fn deviate(color: Color, amount: f32) -> Color {
if is_dark(color) {
lighten(color, amount)
} else {
darken(color, amount)
}
}
fn mix(a: Color, b: Color, factor: f32) -> Color {
let a_lin = Rgb::from(a).into_linear();
let b_lin = Rgb::from(b).into_linear();
let mixed = a_lin.mix(b_lin, factor);
Rgb::from_linear(mixed).into()
}
fn readable(background: Color, text: Color) -> Color {
if is_readable(background, text) {
text
} else {
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
}
}
}
fn is_dark(color: Color) -> bool {
to_hsl(color).lightness < 0.6
}
fn is_readable(a: Color, b: Color) -> bool {
let a_srgb = Rgb::from(a);
let b_srgb = Rgb::from(b);
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))
}
fn from_hsl(hsl: Hsl) -> Color {
Rgb::from_color(hsl).into()
}

View file

@ -42,6 +42,12 @@ impl Transformation {
}
}
impl Default for Transformation {
fn default() -> Self {
Transformation::IDENTITY
}
}
impl Mul for Transformation {
type Output = Self;

View file

@ -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>

View file

@ -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.

View file

@ -6,7 +6,8 @@ use crate::renderer;
use crate::text::{self, Paragraph};
use crate::widget::tree::{self, Tree};
use crate::{
Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget,
Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Theme,
Widget,
};
use std::borrow::Cow;
@ -17,10 +18,10 @@ pub use text::{LineHeight, Shaping};
#[allow(missing_debug_implementations)]
pub struct Text<'a, Theme, Renderer>
where
Theme: StyleSheet,
Theme: Catalog,
Renderer: text::Renderer,
{
content: Cow<'a, str>,
fragment: Fragment<'a>,
size: Option<Pixels>,
line_height: LineHeight,
width: Length,
@ -29,18 +30,18 @@ where
vertical_alignment: alignment::Vertical,
font: Option<Renderer::Font>,
shaping: Shaping,
style: Theme::Style,
class: Theme::Class<'a>,
}
impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
where
Theme: StyleSheet,
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 {
pub fn new(fragment: impl IntoFragment<'a>) -> Self {
Text {
content: content.into(),
fragment: fragment.into_fragment(),
size: None,
line_height: LineHeight::default(),
font: None,
@ -49,7 +50,7 @@ where
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: Shaping::Basic,
style: Default::default(),
class: Theme::default(),
}
}
@ -73,12 +74,6 @@ where
self
}
/// Sets the style of the [`Text`].
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
self.style = style.into();
self
}
/// Sets the width of the [`Text`] boundaries.
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
@ -114,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.
@ -123,7 +154,7 @@ pub struct State<P: Paragraph>(P);
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Text<'a, Theme, Renderer>
where
Theme: StyleSheet,
Theme: Catalog,
Renderer: text::Renderer,
{
fn tag(&self) -> tree::Tag {
@ -153,7 +184,7 @@ where
limits,
self.width,
self.height,
&self.content,
&self.fragment,
self.line_height,
self.size,
self.font,
@ -168,21 +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 style = theme.style(&self.class);
draw(
renderer,
style,
layout,
state,
theme.appearance(self.style.clone()),
viewport,
);
draw(renderer, defaults, layout, state, style, viewport);
}
}
@ -242,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,
@ -273,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: StyleSheet + 'a,
Theme: Catalog + 'a,
Renderer: text::Renderer + 'a,
{
fn from(
@ -283,30 +308,9 @@ where
}
}
impl<'a, Theme, Renderer> Clone for Text<'a, Theme, Renderer>
where
Theme: StyleSheet,
Renderer: text::Renderer,
{
fn clone(&self) -> Self {
Self {
content: self.content.clone(),
size: self.size,
line_height: self.line_height,
width: self.width,
height: self.height,
horizontal_alignment: self.horizontal_alignment,
vertical_alignment: self.vertical_alignment,
font: self.font,
style: self.style.clone(),
shaping: self.shaping,
}
}
}
impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer>
where
Theme: StyleSheet,
Theme: Catalog + 'a,
Renderer: text::Renderer,
{
fn from(content: &'a str) -> Self {
@ -317,7 +321,7 @@ where
impl<'a, Message, Theme, Renderer> From<&'a str>
for Element<'a, Message, Theme, Renderer>
where
Theme: StyleSheet + 'a,
Theme: Catalog + 'a,
Renderer: text::Renderer + 'a,
{
fn from(content: &'a str) -> Self {
@ -325,20 +329,118 @@ where
}
}
/// The style sheet of some text.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default + Clone;
/// Produces the [`Appearance`] of some text.
fn appearance(&self, style: Self::Style) -> Appearance;
}
/// The apperance of some text.
/// 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 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 [`Style`] of a class with the given status.
fn style(&self, item: &Self::Class<'_>) -> Style;
}
/// 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<'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);

View file

@ -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() {

View file

@ -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")),
}
}
}