Merge branch 'master' into beacon
This commit is contained in:
commit
aaf396256e
284 changed files with 18747 additions and 15450 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#[allow(missing_docs)]
|
||||
pub enum Interaction {
|
||||
#[default]
|
||||
None,
|
||||
Idle,
|
||||
Pointer,
|
||||
Grab,
|
||||
|
|
|
|||
|
|
@ -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`].
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
72
core/src/rotation.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
248
core/src/theme.rs
Normal 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
653
core/src/theme/palette.rs
Normal 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()
|
||||
}
|
||||
|
|
@ -42,6 +42,12 @@ impl Transformation {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for Transformation {
|
||||
fn default() -> Self {
|
||||
Transformation::IDENTITY
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Transformation {
|
||||
type Output = Self;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue