summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/src/alignment.rs20
-rw-r--r--core/src/border.rs228
-rw-r--r--core/src/border_radius.rs22
-rw-r--r--core/src/element.rs4
-rw-r--r--core/src/image.rs85
-rw-r--r--core/src/layout/node.rs13
-rw-r--r--core/src/lib.rs4
-rw-r--r--core/src/mouse/click.rs13
-rw-r--r--core/src/overlay.rs4
-rw-r--r--core/src/overlay/element.rs4
-rw-r--r--core/src/overlay/group.rs2
-rw-r--r--core/src/padding.rs96
-rw-r--r--core/src/pixels.rs47
-rw-r--r--core/src/rectangle.rs82
-rw-r--r--core/src/renderer.rs2
-rw-r--r--core/src/renderer/null.rs47
-rw-r--r--core/src/size.rs14
-rw-r--r--core/src/svg.rs69
-rw-r--r--core/src/text.rs318
-rw-r--r--core/src/text/editor.rs8
-rw-r--r--core/src/text/paragraph.rs105
-rw-r--r--core/src/vector.rs12
-rw-r--r--core/src/widget.rs10
-rw-r--r--core/src/widget/operation.rs231
-rw-r--r--core/src/widget/operation/focusable.rs36
-rw-r--r--core/src/widget/operation/scrollable.rs45
-rw-r--r--core/src/widget/text.rs137
-rw-r--r--core/src/widget/tree.rs6
-rw-r--r--core/src/window/event.rs14
-rw-r--r--core/src/window/settings/linux.rs6
-rw-r--r--core/src/window/settings/windows.rs7
31 files changed, 1400 insertions, 291 deletions
diff --git a/core/src/alignment.rs b/core/src/alignment.rs
index 51b7fca9..8f01ef71 100644
--- a/core/src/alignment.rs
+++ b/core/src/alignment.rs
@@ -46,6 +46,16 @@ pub enum Horizontal {
Right,
}
+impl From<Alignment> for Horizontal {
+ fn from(alignment: Alignment) -> Self {
+ match alignment {
+ Alignment::Start => Self::Left,
+ Alignment::Center => Self::Center,
+ Alignment::End => Self::Right,
+ }
+ }
+}
+
/// The vertical [`Alignment`] of some resource.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Vertical {
@@ -58,3 +68,13 @@ pub enum Vertical {
/// Align bottom
Bottom,
}
+
+impl From<Alignment> for Vertical {
+ fn from(alignment: Alignment) -> Self {
+ match alignment {
+ Alignment::Start => Self::Top,
+ Alignment::Center => Self::Center,
+ Alignment::End => Self::Bottom,
+ }
+ }
+}
diff --git a/core/src/border.rs b/core/src/border.rs
index 2df24988..da0aaa28 100644
--- a/core/src/border.rs
+++ b/core/src/border.rs
@@ -10,40 +10,64 @@ pub struct Border {
/// The width of the border.
pub width: f32,
- /// The radius of the border.
+ /// The [`Radius`] of the border.
pub radius: Radius,
}
+/// Creates a new [`Border`] with the given [`Radius`].
+///
+/// ```
+/// # use iced_core::border::{self, Border};
+/// #
+/// assert_eq!(border::rounded(10), Border::default().rounded(10));
+/// ```
+pub fn rounded(radius: impl Into<Radius>) -> Border {
+ Border::default().rounded(radius)
+}
+
+/// Creates a new [`Border`] with the given [`Color`].
+///
+/// ```
+/// # use iced_core::border::{self, Border};
+/// # use iced_core::Color;
+/// #
+/// assert_eq!(border::color(Color::BLACK), Border::default().color(Color::BLACK));
+/// ```
+pub fn color(color: impl Into<Color>) -> Border {
+ Border::default().color(color)
+}
+
+/// Creates a new [`Border`] with the given `width`.
+///
+/// ```
+/// # use iced_core::border::{self, Border};
+/// # use iced_core::Color;
+/// #
+/// assert_eq!(border::width(10), Border::default().width(10));
+/// ```
+pub fn width(width: impl Into<Pixels>) -> Border {
+ Border::default().width(width)
+}
+
impl Border {
- /// 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 {
+ /// Sets the [`Color`] of the [`Border`].
+ pub fn 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 {
+ /// Sets the [`Radius`] of the [`Border`].
+ pub fn rounded(self, radius: impl Into<Radius>) -> Self {
Self {
radius: radius.into(),
..self
}
}
- /// Updates the width of the [`Border`].
- pub fn with_width(self, width: impl Into<Pixels>) -> Self {
+ /// Sets the width of the [`Border`].
+ pub fn width(self, width: impl Into<Pixels>) -> Self {
Self {
width: width.into().0,
..self
@@ -54,11 +78,160 @@ impl Border {
/// The border radii for the corners of a graphics primitive in the order:
/// top-left, top-right, bottom-right, bottom-left.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
-pub struct Radius([f32; 4]);
+pub struct Radius {
+ /// Top left radius
+ pub top_left: f32,
+ /// Top right radius
+ pub top_right: f32,
+ /// Bottom right radius
+ pub bottom_right: f32,
+ /// Bottom left radius
+ pub bottom_left: f32,
+}
+
+/// Creates a new [`Radius`] with the same value for each corner.
+pub fn radius(value: impl Into<Pixels>) -> Radius {
+ Radius::new(value)
+}
+
+/// Creates a new [`Radius`] with the given top left value.
+pub fn top_left(value: impl Into<Pixels>) -> Radius {
+ Radius::default().top_left(value)
+}
+
+/// Creates a new [`Radius`] with the given top right value.
+pub fn top_right(value: impl Into<Pixels>) -> Radius {
+ Radius::default().top_right(value)
+}
+
+/// Creates a new [`Radius`] with the given bottom right value.
+pub fn bottom_right(value: impl Into<Pixels>) -> Radius {
+ Radius::default().bottom_right(value)
+}
+
+/// Creates a new [`Radius`] with the given bottom left value.
+pub fn bottom_left(value: impl Into<Pixels>) -> Radius {
+ Radius::default().bottom_left(value)
+}
+
+/// Creates a new [`Radius`] with the given value as top left and top right.
+pub fn top(value: impl Into<Pixels>) -> Radius {
+ Radius::default().top(value)
+}
+
+/// Creates a new [`Radius`] with the given value as bottom left and bottom right.
+pub fn bottom(value: impl Into<Pixels>) -> Radius {
+ Radius::default().bottom(value)
+}
+
+/// Creates a new [`Radius`] with the given value as top left and bottom left.
+pub fn left(value: impl Into<Pixels>) -> Radius {
+ Radius::default().left(value)
+}
+
+/// Creates a new [`Radius`] with the given value as top right and bottom right.
+pub fn right(value: impl Into<Pixels>) -> Radius {
+ Radius::default().right(value)
+}
+
+impl Radius {
+ /// Creates a new [`Radius`] with the same value for each corner.
+ pub fn new(value: impl Into<Pixels>) -> Self {
+ let value = value.into().0;
+
+ Self {
+ top_left: value,
+ top_right: value,
+ bottom_right: value,
+ bottom_left: value,
+ }
+ }
+
+ /// Sets the top left value of the [`Radius`].
+ pub fn top_left(self, value: impl Into<Pixels>) -> Self {
+ Self {
+ top_left: value.into().0,
+ ..self
+ }
+ }
+
+ /// Sets the top right value of the [`Radius`].
+ pub fn top_right(self, value: impl Into<Pixels>) -> Self {
+ Self {
+ top_right: value.into().0,
+ ..self
+ }
+ }
+
+ /// Sets the bottom right value of the [`Radius`].
+ pub fn bottom_right(self, value: impl Into<Pixels>) -> Self {
+ Self {
+ bottom_right: value.into().0,
+ ..self
+ }
+ }
+
+ /// Sets the bottom left value of the [`Radius`].
+ pub fn bottom_left(self, value: impl Into<Pixels>) -> Self {
+ Self {
+ bottom_left: value.into().0,
+ ..self
+ }
+ }
+
+ /// Sets the top left and top right values of the [`Radius`].
+ pub fn top(self, value: impl Into<Pixels>) -> Self {
+ let value = value.into().0;
+
+ Self {
+ top_left: value,
+ top_right: value,
+ ..self
+ }
+ }
+
+ /// Sets the bottom left and bottom right values of the [`Radius`].
+ pub fn bottom(self, value: impl Into<Pixels>) -> Self {
+ let value = value.into().0;
+
+ Self {
+ bottom_left: value,
+ bottom_right: value,
+ ..self
+ }
+ }
+
+ /// Sets the top left and bottom left values of the [`Radius`].
+ pub fn left(self, value: impl Into<Pixels>) -> Self {
+ let value = value.into().0;
+
+ Self {
+ top_left: value,
+ bottom_left: value,
+ ..self
+ }
+ }
+
+ /// Sets the top right and bottom right values of the [`Radius`].
+ pub fn right(self, value: impl Into<Pixels>) -> Self {
+ let value = value.into().0;
+
+ Self {
+ top_right: value,
+ bottom_right: value,
+ ..self
+ }
+ }
+}
impl From<f32> for Radius {
- fn from(w: f32) -> Self {
- Self([w; 4])
+ fn from(radius: f32) -> Self {
+ Self {
+ top_left: radius,
+ top_right: radius,
+ bottom_right: radius,
+ bottom_left: radius,
+ }
}
}
@@ -80,14 +253,13 @@ impl From<i32> for Radius {
}
}
-impl From<[f32; 4]> for Radius {
- fn from(radi: [f32; 4]) -> Self {
- Self(radi)
- }
-}
-
impl From<Radius> for [f32; 4] {
fn from(radi: Radius) -> Self {
- radi.0
+ [
+ radi.top_left,
+ radi.top_right,
+ radi.bottom_right,
+ radi.bottom_left,
+ ]
}
}
diff --git a/core/src/border_radius.rs b/core/src/border_radius.rs
deleted file mode 100644
index a444dd74..00000000
--- a/core/src/border_radius.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-/// The border radii for the corners of a graphics primitive in the order:
-/// top-left, top-right, bottom-right, bottom-left.
-#[derive(Debug, Clone, Copy, PartialEq, Default)]
-pub struct BorderRadius([f32; 4]);
-
-impl From<f32> for BorderRadius {
- fn from(w: f32) -> Self {
- Self([w; 4])
- }
-}
-
-impl From<[f32; 4]> for BorderRadius {
- fn from(radi: [f32; 4]) -> Self {
- Self(radi)
- }
-}
-
-impl From<BorderRadius> for [f32; 4] {
- fn from(radi: BorderRadius) -> Self {
- radi.0
- }
-}
diff --git a/core/src/element.rs b/core/src/element.rs
index 385d8295..6ebb8a15 100644
--- a/core/src/element.rs
+++ b/core/src/element.rs
@@ -304,7 +304,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
self.widget.operate(tree, layout, renderer, operation);
}
@@ -440,7 +440,7 @@ where
state: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
self.element
.widget
diff --git a/core/src/image.rs b/core/src/image.rs
index 82ecdd0f..f985636a 100644
--- a/core/src/image.rs
+++ b/core/src/image.rs
@@ -7,6 +7,73 @@ use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
+/// A raster image that can be drawn.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Image<H = Handle> {
+ /// The handle of the image.
+ pub handle: H,
+
+ /// The filter method of the image.
+ pub filter_method: FilterMethod,
+
+ /// The rotation to be applied to the image; on its center.
+ pub rotation: Radians,
+
+ /// The opacity of the image.
+ ///
+ /// 0 means transparent. 1 means opaque.
+ pub opacity: f32,
+
+ /// If set to `true`, the image will be snapped to the pixel grid.
+ ///
+ /// This can avoid graphical glitches, specially when using
+ /// [`FilterMethod::Nearest`].
+ pub snap: bool,
+}
+
+impl Image<Handle> {
+ /// Creates a new [`Image`] with the given handle.
+ pub fn new(handle: impl Into<Handle>) -> Self {
+ Self {
+ handle: handle.into(),
+ filter_method: FilterMethod::default(),
+ rotation: Radians(0.0),
+ opacity: 1.0,
+ snap: false,
+ }
+ }
+
+ /// Sets the filter method of the [`Image`].
+ pub fn filter_method(mut self, filter_method: FilterMethod) -> Self {
+ self.filter_method = filter_method;
+ self
+ }
+
+ /// Sets the rotation of the [`Image`].
+ pub fn rotation(mut self, rotation: impl Into<Radians>) -> Self {
+ self.rotation = rotation.into();
+ self
+ }
+
+ /// Sets the opacity of the [`Image`].
+ pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
+ self.opacity = opacity.into();
+ self
+ }
+
+ /// Sets whether the [`Image`] should be snapped to the pixel grid.
+ pub fn snap(mut self, snap: bool) -> Self {
+ self.snap = snap;
+ self
+ }
+}
+
+impl From<&Handle> for Image {
+ fn from(handle: &Handle) -> Self {
+ Image::new(handle.clone())
+ }
+}
+
/// A handle of some image data.
#[derive(Clone, PartialEq, Eq)]
pub enum Handle {
@@ -101,6 +168,12 @@ where
}
}
+impl From<&Handle> for Handle {
+ fn from(value: &Handle) -> Self {
+ value.clone()
+ }
+}
+
impl std::fmt::Debug for Handle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -166,14 +239,6 @@ pub trait Renderer: crate::Renderer {
/// Returns the dimensions of an image for the given [`Handle`].
fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
- /// Draws an image with the given [`Handle`] and inside the provided
- /// `bounds`.
- fn draw_image(
- &mut self,
- handle: Self::Handle,
- filter_method: FilterMethod,
- bounds: Rectangle,
- rotation: Radians,
- opacity: f32,
- );
+ /// Draws an [`Image`] inside the provided `bounds`.
+ fn draw_image(&mut self, image: Image<Self::Handle>, bounds: Rectangle);
}
diff --git a/core/src/layout/node.rs b/core/src/layout/node.rs
index 5743a9bd..0c0f90fb 100644
--- a/core/src/layout/node.rs
+++ b/core/src/layout/node.rs
@@ -103,12 +103,13 @@ impl Node {
}
/// Translates the [`Node`] by the given translation.
- pub fn translate(self, translation: impl Into<Vector>) -> Self {
- let translation = translation.into();
+ pub fn translate(mut self, translation: impl Into<Vector>) -> Self {
+ self.translate_mut(translation);
+ self
+ }
- Self {
- bounds: self.bounds + translation,
- ..self
- }
+ /// Translates the [`Node`] by the given translation.
+ pub fn translate_mut(&mut self, translation: impl Into<Vector>) {
+ self.bounds = self.bounds + translation.into();
}
}
diff --git a/core/src/lib.rs b/core/src/lib.rs
index 32156441..df599f45 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -20,6 +20,7 @@ pub mod keyboard;
pub mod layout;
pub mod mouse;
pub mod overlay;
+pub mod padding;
pub mod renderer;
pub mod svg;
pub mod text;
@@ -35,7 +36,6 @@ mod color;
mod content_fit;
mod element;
mod length;
-mod padding;
mod pixels;
mod point;
mod rectangle;
@@ -57,6 +57,7 @@ pub use element::Element;
pub use event::Event;
pub use font::Font;
pub use gradient::Gradient;
+pub use image::Image;
pub use layout::Layout;
pub use length::Length;
pub use overlay::Overlay;
@@ -69,6 +70,7 @@ pub use rotation::Rotation;
pub use shadow::Shadow;
pub use shell::Shell;
pub use size::Size;
+pub use svg::Svg;
pub use text::Text;
pub use theme::Theme;
pub use transformation::Transformation;
diff --git a/core/src/mouse/click.rs b/core/src/mouse/click.rs
index 6f3844be..07a4db5a 100644
--- a/core/src/mouse/click.rs
+++ b/core/src/mouse/click.rs
@@ -1,4 +1,5 @@
//! Track mouse clicks.
+use crate::mouse::Button;
use crate::time::Instant;
use crate::Point;
@@ -6,6 +7,7 @@ use crate::Point;
#[derive(Debug, Clone, Copy)]
pub struct Click {
kind: Kind,
+ button: Button,
position: Point,
time: Instant,
}
@@ -36,11 +38,17 @@ impl Kind {
impl Click {
/// Creates a new [`Click`] with the given position and previous last
/// [`Click`].
- pub fn new(position: Point, previous: Option<Click>) -> Click {
+ pub fn new(
+ position: Point,
+ button: Button,
+ previous: Option<Click>,
+ ) -> Click {
let time = Instant::now();
let kind = if let Some(previous) = previous {
- if previous.is_consecutive(position, time) {
+ if previous.is_consecutive(position, time)
+ && button == previous.button
+ {
previous.kind.next()
} else {
Kind::Single
@@ -51,6 +59,7 @@ impl Click {
Click {
kind,
+ button,
position,
time,
}
diff --git a/core/src/overlay.rs b/core/src/overlay.rs
index 16f867da..f09de831 100644
--- a/core/src/overlay.rs
+++ b/core/src/overlay.rs
@@ -41,7 +41,7 @@ where
&mut self,
_layout: Layout<'_>,
_renderer: &Renderer,
- _operation: &mut dyn widget::Operation<()>,
+ _operation: &mut dyn widget::Operation,
) {
}
@@ -52,7 +52,7 @@ where
/// * the computed [`Layout`] of the [`Overlay`]
/// * the current cursor position
/// * a mutable `Message` list, allowing the [`Overlay`] to produce
- /// new messages based on user interaction.
+ /// new messages based on user interaction.
/// * the `Renderer`
/// * a [`Clipboard`], if available
///
diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs
index 61e75e8a..32e987a3 100644
--- a/core/src/overlay/element.rs
+++ b/core/src/overlay/element.rs
@@ -92,7 +92,7 @@ where
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
self.overlay.operate(layout, renderer, operation);
}
@@ -144,7 +144,7 @@ where
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
self.content.operate(layout, renderer, operation);
}
diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs
index cd12eac9..6541d311 100644
--- a/core/src/overlay/group.rs
+++ b/core/src/overlay/group.rs
@@ -132,7 +132,7 @@ where
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.children.iter_mut().zip(layout.children()).for_each(
diff --git a/core/src/padding.rs b/core/src/padding.rs
index a63f6e29..e26cdd9b 100644
--- a/core/src/padding.rs
+++ b/core/src/padding.rs
@@ -1,4 +1,5 @@
-use crate::Size;
+//! Space stuff around the perimeter.
+use crate::{Pixels, Size};
/// An amount of space to pad for each side of a box
///
@@ -9,7 +10,6 @@ use crate::Size;
/// #
/// let padding = Padding::from(20); // 20px on all sides
/// let padding = Padding::from([10, 20]); // top/bottom, left/right
-/// let padding = Padding::from([5, 10, 15, 20]); // top, right, bottom, left
/// ```
///
/// Normally, the `padding` method of a widget will ask for an `Into<Padding>`,
@@ -31,9 +31,8 @@ use crate::Size;
///
/// let widget = Widget::new().padding(20); // 20px on all sides
/// let widget = Widget::new().padding([10, 20]); // top/bottom, left/right
-/// let widget = Widget::new().padding([5, 10, 15, 20]); // top, right, bottom, left
/// ```
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq, Default)]
pub struct Padding {
/// Top padding
pub top: f32,
@@ -45,6 +44,31 @@ pub struct Padding {
pub left: f32,
}
+/// Create a [`Padding`] that is equal on all sides.
+pub fn all(padding: impl Into<Pixels>) -> Padding {
+ Padding::new(padding.into().0)
+}
+
+/// Create some top [`Padding`].
+pub fn top(padding: impl Into<Pixels>) -> Padding {
+ Padding::default().top(padding)
+}
+
+/// Create some bottom [`Padding`].
+pub fn bottom(padding: impl Into<Pixels>) -> Padding {
+ Padding::default().bottom(padding)
+}
+
+/// Create some left [`Padding`].
+pub fn left(padding: impl Into<Pixels>) -> Padding {
+ Padding::default().left(padding)
+}
+
+/// Create some right [`Padding`].
+pub fn right(padding: impl Into<Pixels>) -> Padding {
+ Padding::default().right(padding)
+}
+
impl Padding {
/// Padding of zero
pub const ZERO: Padding = Padding {
@@ -54,7 +78,7 @@ impl Padding {
left: 0.0,
};
- /// Create a Padding that is equal on all sides
+ /// Create a [`Padding`] that is equal on all sides.
pub const fn new(padding: f32) -> Padding {
Padding {
top: padding,
@@ -64,6 +88,46 @@ impl Padding {
}
}
+ /// Sets the [`top`] of the [`Padding`].
+ ///
+ /// [`top`]: Self::top
+ pub fn top(self, top: impl Into<Pixels>) -> Self {
+ Self {
+ top: top.into().0,
+ ..self
+ }
+ }
+
+ /// Sets the [`bottom`] of the [`Padding`].
+ ///
+ /// [`bottom`]: Self::bottom
+ pub fn bottom(self, bottom: impl Into<Pixels>) -> Self {
+ Self {
+ bottom: bottom.into().0,
+ ..self
+ }
+ }
+
+ /// Sets the [`left`] of the [`Padding`].
+ ///
+ /// [`left`]: Self::left
+ pub fn left(self, left: impl Into<Pixels>) -> Self {
+ Self {
+ left: left.into().0,
+ ..self
+ }
+ }
+
+ /// Sets the [`right`] of the [`Padding`].
+ ///
+ /// [`right`]: Self::right
+ pub fn right(self, right: impl Into<Pixels>) -> Self {
+ Self {
+ right: right.into().0,
+ ..self
+ }
+ }
+
/// Returns the total amount of vertical [`Padding`].
pub fn vertical(self) -> f32 {
self.top + self.bottom
@@ -111,17 +175,6 @@ impl From<[u16; 2]> for Padding {
}
}
-impl From<[u16; 4]> for Padding {
- fn from(p: [u16; 4]) -> Self {
- Padding {
- top: f32::from(p[0]),
- right: f32::from(p[1]),
- bottom: f32::from(p[2]),
- left: f32::from(p[3]),
- }
- }
-}
-
impl From<f32> for Padding {
fn from(p: f32) -> Self {
Padding {
@@ -144,17 +197,6 @@ impl From<[f32; 2]> for Padding {
}
}
-impl From<[f32; 4]> for Padding {
- fn from(p: [f32; 4]) -> Self {
- Padding {
- top: p[0],
- right: p[1],
- bottom: p[2],
- left: p[3],
- }
- }
-}
-
impl From<Padding> for Size {
fn from(padding: Padding) -> Self {
Self::new(padding.horizontal(), padding.vertical())
diff --git a/core/src/pixels.rs b/core/src/pixels.rs
index 425c0028..a1ea0f15 100644
--- a/core/src/pixels.rs
+++ b/core/src/pixels.rs
@@ -6,9 +6,14 @@
/// (e.g. `impl Into<Pixels>`) and, since `Pixels` implements `From` both for
/// `f32` and `u16`, you should be able to provide both integers and float
/// literals as needed.
-#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct Pixels(pub f32);
+impl Pixels {
+ /// Zero pixels.
+ pub const ZERO: Self = Self(0.0);
+}
+
impl From<f32> for Pixels {
fn from(amount: f32) -> Self {
Self(amount)
@@ -27,6 +32,30 @@ impl From<Pixels> for f32 {
}
}
+impl std::ops::Add for Pixels {
+ type Output = Pixels;
+
+ fn add(self, rhs: Self) -> Self {
+ Pixels(self.0 + rhs.0)
+ }
+}
+
+impl std::ops::Add<f32> for Pixels {
+ type Output = Pixels;
+
+ fn add(self, rhs: f32) -> Self {
+ Pixels(self.0 + rhs)
+ }
+}
+
+impl std::ops::Mul for Pixels {
+ type Output = Pixels;
+
+ fn mul(self, rhs: Self) -> Self {
+ Pixels(self.0 * rhs.0)
+ }
+}
+
impl std::ops::Mul<f32> for Pixels {
type Output = Pixels;
@@ -34,3 +63,19 @@ impl std::ops::Mul<f32> for Pixels {
Pixels(self.0 * rhs)
}
}
+
+impl std::ops::Div for Pixels {
+ type Output = Pixels;
+
+ fn div(self, rhs: Self) -> Self {
+ Pixels(self.0 / rhs.0)
+ }
+}
+
+impl std::ops::Div<f32> for Pixels {
+ type Output = Pixels;
+
+ fn div(self, rhs: f32) -> Self {
+ Pixels(self.0 / rhs)
+ }
+}
diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs
index 1556e072..cff33991 100644
--- a/core/src/rectangle.rs
+++ b/core/src/rectangle.rs
@@ -1,4 +1,4 @@
-use crate::{Point, Radians, Size, Vector};
+use crate::{Padding, Point, Radians, Size, Vector};
/// An axis-aligned rectangle.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@@ -47,6 +47,62 @@ impl Rectangle<f32> {
}
}
+ /// Creates a new square [`Rectangle`] with the center at the origin and
+ /// with the given radius.
+ pub fn with_radius(radius: f32) -> Self {
+ Self {
+ x: -radius,
+ y: -radius,
+ width: radius * 2.0,
+ height: radius * 2.0,
+ }
+ }
+
+ /// Creates a new axis-aligned [`Rectangle`] from the given vertices; returning the
+ /// rotation in [`Radians`] that must be applied to the axis-aligned [`Rectangle`]
+ /// to obtain the desired result.
+ pub fn with_vertices(
+ top_left: Point,
+ top_right: Point,
+ bottom_left: Point,
+ ) -> (Rectangle, Radians) {
+ let width = (top_right.x - top_left.x).hypot(top_right.y - top_left.y);
+
+ let height =
+ (bottom_left.x - top_left.x).hypot(bottom_left.y - top_left.y);
+
+ let rotation =
+ (top_right.y - top_left.y).atan2(top_right.x - top_left.x);
+
+ let rotation = if rotation < 0.0 {
+ 2.0 * std::f32::consts::PI + rotation
+ } else {
+ rotation
+ };
+
+ let position = {
+ let center = Point::new(
+ (top_right.x + bottom_left.x) / 2.0,
+ (top_right.y + bottom_left.y) / 2.0,
+ );
+
+ let rotation = -rotation - std::f32::consts::PI * 2.0;
+
+ Point::new(
+ center.x + (top_left.x - center.x) * rotation.cos()
+ - (top_left.y - center.y) * rotation.sin(),
+ center.y
+ + (top_left.x - center.x) * rotation.sin()
+ + (top_left.y - center.y) * rotation.cos(),
+ )
+ };
+
+ (
+ Rectangle::new(position, Size::new(width, height)),
+ Radians(rotation),
+ )
+ }
+
/// Returns the [`Point`] at the center of the [`Rectangle`].
pub fn center(&self) -> Point {
Point::new(self.center_x(), self.center_y())
@@ -164,12 +220,26 @@ impl Rectangle<f32> {
}
/// Expands the [`Rectangle`] a given amount.
- pub fn expand(self, amount: f32) -> Self {
+ pub fn expand(self, padding: impl Into<Padding>) -> Self {
+ let padding = padding.into();
+
+ Self {
+ x: self.x - padding.left,
+ y: self.y - padding.top,
+ width: self.width + padding.horizontal(),
+ height: self.height + padding.vertical(),
+ }
+ }
+
+ /// Shrinks the [`Rectangle`] a given amount.
+ pub fn shrink(self, padding: impl Into<Padding>) -> Self {
+ let padding = padding.into();
+
Self {
- x: self.x - amount,
- y: self.y - amount,
- width: self.width + amount * 2.0,
- height: self.height + amount * 2.0,
+ x: self.x + padding.left,
+ y: self.y + padding.top,
+ width: self.width - padding.horizontal(),
+ height: self.height - padding.vertical(),
}
}
diff --git a/core/src/renderer.rs b/core/src/renderer.rs
index a2785ae8..6684517f 100644
--- a/core/src/renderer.rs
+++ b/core/src/renderer.rs
@@ -69,7 +69,7 @@ pub struct Quad {
/// The bounds of the [`Quad`].
pub bounds: Rectangle,
- /// The [`Border`] of the [`Quad`].
+ /// The [`Border`] of the [`Quad`]. The border is drawn on the inside of the [`Quad`].
pub border: Border,
/// The [`Shadow`] of the [`Quad`].
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index e8709dbc..bbcdd8ff 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -1,11 +1,10 @@
use crate::alignment;
-use crate::image;
+use crate::image::{self, Image};
use crate::renderer::{self, Renderer};
use crate::svg;
use crate::text::{self, Text};
use crate::{
- Background, Color, Font, Pixels, Point, Radians, Rectangle, Size,
- Transformation,
+ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
impl Renderer for () {
@@ -77,9 +76,14 @@ impl text::Paragraph for () {
fn with_text(_text: Text<&str>) -> Self {}
+ fn with_spans<Link>(
+ _text: Text<&[text::Span<'_, Link, Self::Font>], Self::Font>,
+ ) -> Self {
+ }
+
fn resize(&mut self, _new_bounds: Size) {}
- fn compare(&self, _text: Text<&str>) -> text::Difference {
+ fn compare(&self, _text: Text<()>) -> text::Difference {
text::Difference::None
}
@@ -102,6 +106,14 @@ impl text::Paragraph for () {
fn hit_test(&self, _point: Point) -> Option<text::Hit> {
None
}
+
+ fn hit_span(&self, _point: Point) -> Option<usize> {
+ None
+ }
+
+ fn span_bounds(&self, _index: usize) -> Vec<Rectangle> {
+ vec![]
+ }
}
impl text::Editor for () {
@@ -109,6 +121,10 @@ impl text::Editor for () {
fn with_text(_text: &str) -> Self {}
+ fn is_empty(&self) -> bool {
+ true
+ }
+
fn cursor(&self) -> text::editor::Cursor {
text::editor::Cursor::Caret(Point::ORIGIN)
}
@@ -145,6 +161,7 @@ impl text::Editor for () {
_new_font: Self::Font,
_new_size: Pixels,
_new_line_height: text::LineHeight,
+ _new_wrapping: text::Wrapping,
_new_highlighter: &mut impl text::Highlighter,
) {
}
@@ -161,21 +178,13 @@ impl text::Editor for () {
}
impl image::Renderer for () {
- type Handle = ();
+ type Handle = image::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,
- ) {
- }
+ fn draw_image(&mut self, _image: Image, _bounds: Rectangle) {}
}
impl svg::Renderer for () {
@@ -183,13 +192,5 @@ impl svg::Renderer for () {
Size::default()
}
- fn draw_svg(
- &mut self,
- _handle: svg::Handle,
- _color: Option<Color>,
- _bounds: Rectangle,
- _rotation: Radians,
- _opacity: f32,
- ) {
- }
+ fn draw_svg(&mut self, _svg: svg::Svg, _bounds: Rectangle) {}
}
diff --git a/core/src/size.rs b/core/src/size.rs
index d7459355..95089236 100644
--- a/core/src/size.rs
+++ b/core/src/size.rs
@@ -99,6 +99,20 @@ impl<T> From<Size<T>> for Vector<T> {
}
}
+impl<T> std::ops::Add for Size<T>
+where
+ T: std::ops::Add<Output = T>,
+{
+ type Output = Size<T>;
+
+ fn add(self, rhs: Self) -> Self::Output {
+ Size {
+ width: self.width + rhs.width,
+ height: self.height + rhs.height,
+ }
+ }
+}
+
impl<T> std::ops::Sub for Size<T>
where
T: std::ops::Sub<Output = T>,
diff --git a/core/src/svg.rs b/core/src/svg.rs
index 946b8156..ac19b223 100644
--- a/core/src/svg.rs
+++ b/core/src/svg.rs
@@ -7,6 +7,66 @@ use std::hash::{Hash, Hasher as _};
use std::path::PathBuf;
use std::sync::Arc;
+/// A raster image that can be drawn.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Svg<H = Handle> {
+ /// The handle of the [`Svg`].
+ pub handle: H,
+
+ /// The [`Color`] filter to be applied to the [`Svg`].
+ ///
+ /// If some [`Color`] is set, the whole [`Svg`] will be
+ /// painted with it—ignoring any intrinsic colors.
+ ///
+ /// This can be useful for coloring icons programmatically
+ /// (e.g. with a theme).
+ pub color: Option<Color>,
+
+ /// The rotation to be applied to the image; on its center.
+ pub rotation: Radians,
+
+ /// The opacity of the [`Svg`].
+ ///
+ /// 0 means transparent. 1 means opaque.
+ pub opacity: f32,
+}
+
+impl Svg<Handle> {
+ /// Creates a new [`Svg`] with the given handle.
+ pub fn new(handle: impl Into<Handle>) -> Self {
+ Self {
+ handle: handle.into(),
+ color: None,
+ rotation: Radians(0.0),
+ opacity: 1.0,
+ }
+ }
+
+ /// Sets the [`Color`] filter of the [`Svg`].
+ pub fn color(mut self, color: impl Into<Color>) -> Self {
+ self.color = Some(color.into());
+ self
+ }
+
+ /// Sets the rotation of the [`Svg`].
+ pub fn rotation(mut self, rotation: impl Into<Radians>) -> Self {
+ self.rotation = rotation.into();
+ self
+ }
+
+ /// Sets the opacity of the [`Svg`].
+ pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
+ self.opacity = opacity.into();
+ self
+ }
+}
+
+impl From<&Handle> for Svg {
+ fn from(handle: &Handle) -> Self {
+ Svg::new(handle.clone())
+ }
+}
+
/// A handle of Svg data.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Handle {
@@ -95,12 +155,5 @@ pub trait Renderer: crate::Renderer {
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_svg(
- &mut self,
- handle: Handle,
- color: Option<Color>,
- bounds: Rectangle,
- rotation: Radians,
- opacity: f32,
- );
+ fn draw_svg(&mut self, svg: Svg, bounds: Rectangle);
}
diff --git a/core/src/text.rs b/core/src/text.rs
index b30feae0..d7b7fee4 100644
--- a/core/src/text.rs
+++ b/core/src/text.rs
@@ -1,16 +1,18 @@
//! Draw and interact with text.
-mod paragraph;
-
pub mod editor;
pub mod highlighter;
+pub mod paragraph;
pub use editor::Editor;
pub use highlighter::Highlighter;
pub use paragraph::Paragraph;
use crate::alignment;
-use crate::{Color, Pixels, Point, Rectangle, Size};
+use crate::{
+ Background, Border, Color, Padding, Pixels, Point, Rectangle, Size,
+};
+use std::borrow::Cow;
use std::hash::{Hash, Hasher};
/// A paragraph.
@@ -39,6 +41,9 @@ pub struct Text<Content = String, Font = crate::Font> {
/// The [`Shaping`] strategy of the [`Text`].
pub shaping: Shaping,
+
+ /// The [`Wrapping`] strategy of the [`Text`].
+ pub wrapping: Wrapping,
}
/// The shaping strategy of some text.
@@ -65,6 +70,22 @@ pub enum Shaping {
Advanced,
}
+/// The wrapping strategy of some text.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
+pub enum Wrapping {
+ /// No wrapping.
+ None,
+ /// Wraps at the word level.
+ ///
+ /// This is the default.
+ #[default]
+ Word,
+ /// Wraps at the glyph level.
+ Glyph,
+ /// Wraps at the word level, or fallback to glyph level if a word can't fit on a line by itself.
+ WordOrGlyph,
+}
+
/// The height of a line of text in a paragraph.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineHeight {
@@ -221,3 +242,294 @@ pub trait Renderer: crate::Renderer {
clip_bounds: Rectangle,
);
}
+
+/// A span of text.
+#[derive(Debug, Clone)]
+pub struct Span<'a, Link = (), Font = crate::Font> {
+ /// The [`Fragment`] of text.
+ pub text: Fragment<'a>,
+ /// The size of the [`Span`] in [`Pixels`].
+ pub size: Option<Pixels>,
+ /// The [`LineHeight`] of the [`Span`].
+ pub line_height: Option<LineHeight>,
+ /// The font of the [`Span`].
+ pub font: Option<Font>,
+ /// The [`Color`] of the [`Span`].
+ pub color: Option<Color>,
+ /// The link of the [`Span`].
+ pub link: Option<Link>,
+ /// The [`Highlight`] of the [`Span`].
+ pub highlight: Option<Highlight>,
+ /// The [`Padding`] of the [`Span`].
+ ///
+ /// Currently, it only affects the bounds of the [`Highlight`].
+ pub padding: Padding,
+ /// Whether the [`Span`] should be underlined or not.
+ pub underline: bool,
+ /// Whether the [`Span`] should be struck through or not.
+ pub strikethrough: bool,
+}
+
+/// A text highlight.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Highlight {
+ /// The [`Background`] of the highlight.
+ pub background: Background,
+ /// The [`Border`] of the highlight.
+ pub border: Border,
+}
+
+impl<'a, Link, Font> Span<'a, Link, Font> {
+ /// Creates a new [`Span`] of text with the given text fragment.
+ pub fn new(fragment: impl IntoFragment<'a>) -> Self {
+ Self {
+ text: fragment.into_fragment(),
+ size: None,
+ line_height: None,
+ font: None,
+ color: None,
+ highlight: None,
+ link: None,
+ padding: Padding::ZERO,
+ underline: false,
+ strikethrough: false,
+ }
+ }
+
+ /// Sets the size of the [`Span`].
+ pub fn size(mut self, size: impl Into<Pixels>) -> Self {
+ self.size = Some(size.into());
+ self
+ }
+
+ /// Sets the [`LineHeight`] of the [`Span`].
+ pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
+ self.line_height = Some(line_height.into());
+ self
+ }
+
+ /// Sets the font of the [`Span`].
+ pub fn font(mut self, font: impl Into<Font>) -> Self {
+ self.font = Some(font.into());
+ self
+ }
+
+ /// Sets the font of the [`Span`], if any.
+ pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
+ self.font = font.map(Into::into);
+ self
+ }
+
+ /// Sets the [`Color`] of the [`Span`].
+ pub fn color(mut self, color: impl Into<Color>) -> Self {
+ self.color = Some(color.into());
+ self
+ }
+
+ /// Sets the [`Color`] of the [`Span`], if any.
+ pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
+ self.color = color.map(Into::into);
+ self
+ }
+
+ /// Sets the link of the [`Span`].
+ pub fn link(mut self, link: impl Into<Link>) -> Self {
+ self.link = Some(link.into());
+ self
+ }
+
+ /// Sets the link of the [`Span`], if any.
+ pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
+ self.link = link.map(Into::into);
+ self
+ }
+
+ /// Sets the [`Background`] of the [`Span`].
+ pub fn background(self, background: impl Into<Background>) -> Self {
+ self.background_maybe(Some(background))
+ }
+
+ /// Sets the [`Background`] of the [`Span`], if any.
+ pub fn background_maybe(
+ mut self,
+ background: Option<impl Into<Background>>,
+ ) -> Self {
+ let Some(background) = background else {
+ return self;
+ };
+
+ match &mut self.highlight {
+ Some(highlight) => {
+ highlight.background = background.into();
+ }
+ None => {
+ self.highlight = Some(Highlight {
+ background: background.into(),
+ border: Border::default(),
+ });
+ }
+ }
+
+ self
+ }
+
+ /// Sets the [`Border`] of the [`Span`].
+ pub fn border(self, border: impl Into<Border>) -> Self {
+ self.border_maybe(Some(border))
+ }
+
+ /// Sets the [`Border`] of the [`Span`], if any.
+ pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
+ let Some(border) = border else {
+ return self;
+ };
+
+ match &mut self.highlight {
+ Some(highlight) => {
+ highlight.border = border.into();
+ }
+ None => {
+ self.highlight = Some(Highlight {
+ border: border.into(),
+ background: Background::Color(Color::TRANSPARENT),
+ });
+ }
+ }
+
+ self
+ }
+
+ /// Sets the [`Padding`] of the [`Span`].
+ ///
+ /// It only affects the [`background`] and [`border`] of the
+ /// [`Span`], currently.
+ ///
+ /// [`background`]: Self::background
+ /// [`border`]: Self::border
+ pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
+ self.padding = padding.into();
+ self
+ }
+
+ /// Sets whether the [`Span`] shoud be underlined or not.
+ pub fn underline(mut self, underline: bool) -> Self {
+ self.underline = underline;
+ self
+ }
+
+ /// Sets whether the [`Span`] shoud be struck through or not.
+ pub fn strikethrough(mut self, strikethrough: bool) -> Self {
+ self.strikethrough = strikethrough;
+ self
+ }
+
+ /// Turns the [`Span`] into a static one.
+ pub fn to_static(self) -> Span<'static, Link, Font> {
+ Span {
+ text: Cow::Owned(self.text.into_owned()),
+ size: self.size,
+ line_height: self.line_height,
+ font: self.font,
+ color: self.color,
+ link: self.link,
+ highlight: self.highlight,
+ padding: self.padding,
+ underline: self.underline,
+ strikethrough: self.strikethrough,
+ }
+ }
+}
+
+impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
+ fn from(value: &'a str) -> Self {
+ Span::new(value)
+ }
+}
+
+impl<'a, Link, Font: PartialEq> PartialEq for Span<'a, Link, Font> {
+ fn eq(&self, other: &Self) -> bool {
+ self.text == other.text
+ && self.size == other.size
+ && self.line_height == other.line_height
+ && self.font == other.font
+ && self.color == other.color
+ }
+}
+
+/// 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);
diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs
index fbf60696..cd30db3a 100644
--- a/core/src/text/editor.rs
+++ b/core/src/text/editor.rs
@@ -1,6 +1,6 @@
//! Edit text.
use crate::text::highlighter::{self, Highlighter};
-use crate::text::LineHeight;
+use crate::text::{LineHeight, Wrapping};
use crate::{Pixels, Point, Rectangle, Size};
use std::sync::Arc;
@@ -13,6 +13,9 @@ pub trait Editor: Sized + Default {
/// Creates a new [`Editor`] laid out with the given text.
fn with_text(text: &str) -> Self;
+ /// Returns true if the [`Editor`] has no contents.
+ fn is_empty(&self) -> bool;
+
/// Returns the current [`Cursor`] of the [`Editor`].
fn cursor(&self) -> Cursor;
@@ -47,6 +50,7 @@ pub trait Editor: Sized + Default {
new_font: Self::Font,
new_size: Pixels,
new_line_height: LineHeight,
+ new_wrapping: Wrapping,
new_highlighter: &mut impl Highlighter,
);
@@ -70,6 +74,8 @@ pub enum Action {
SelectWord,
/// Select the line at the current cursor.
SelectLine,
+ /// Select the entire buffer.
+ SelectAll,
/// Perform an [`Edit`].
Edit(Edit),
/// Click the [`Editor`] at the given [`Point`].
diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs
index 8ff04015..924276c3 100644
--- a/core/src/text/paragraph.rs
+++ b/core/src/text/paragraph.rs
@@ -1,6 +1,7 @@
+//! Draw paragraphs.
use crate::alignment;
-use crate::text::{Difference, Hit, Text};
-use crate::{Point, Size};
+use crate::text::{Difference, Hit, Span, Text};
+use crate::{Point, Rectangle, Size};
/// A text paragraph.
pub trait Paragraph: Sized + Default {
@@ -10,12 +11,17 @@ pub trait Paragraph: Sized + Default {
/// Creates a new [`Paragraph`] laid out with the given [`Text`].
fn with_text(text: Text<&str, Self::Font>) -> Self;
+ /// Creates a new [`Paragraph`] laid out with the given [`Text`].
+ fn with_spans<Link>(
+ text: Text<&[Span<'_, Link, Self::Font>], 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<&str, Self::Font>) -> Difference;
+ fn compare(&self, text: Text<(), Self::Font>) -> Difference;
/// Returns the horizontal alignment of the [`Paragraph`].
fn horizontal_alignment(&self) -> alignment::Horizontal;
@@ -31,29 +37,100 @@ pub trait Paragraph: Sized + Default {
/// [`Paragraph`], returning information about the nearest character.
fn hit_test(&self, point: Point) -> Option<Hit>;
+ /// Tests whether the provided point is within the boundaries of a
+ /// [`Span`] in the [`Paragraph`], returning the index of the [`Span`]
+ /// that was hit.
+ fn hit_span(&self, point: Point) -> Option<usize>;
+
+ /// Returns all bounds for the provided [`Span`] index of the [`Paragraph`].
+ /// A [`Span`] can have multiple bounds for each line it's on.
+ fn span_bounds(&self, index: usize) -> Vec<Rectangle>;
+
/// Returns the distance to the given grapheme index in the [`Paragraph`].
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<&str, Self::Font>) {
- match self.compare(text) {
+ /// Returns the minimum width that can fit the contents of the [`Paragraph`].
+ fn min_width(&self) -> f32 {
+ self.min_bounds().width
+ }
+
+ /// Returns the minimum height that can fit the contents of the [`Paragraph`].
+ fn min_height(&self) -> f32 {
+ self.min_bounds().height
+ }
+}
+
+/// A [`Paragraph`] of plain text.
+#[derive(Debug, Clone, Default)]
+pub struct Plain<P: Paragraph> {
+ raw: P,
+ content: String,
+}
+
+impl<P: Paragraph> Plain<P> {
+ /// Creates a new [`Plain`] paragraph.
+ pub fn new(text: Text<&str, P::Font>) -> Self {
+ let content = text.content.to_owned();
+
+ Self {
+ raw: P::with_text(text),
+ content,
+ }
+ }
+
+ /// Updates the plain [`Paragraph`] to match the given [`Text`], if needed.
+ pub fn update(&mut self, text: Text<&str, P::Font>) {
+ if self.content != text.content {
+ text.content.clone_into(&mut self.content);
+ self.raw = P::with_text(text);
+ return;
+ }
+
+ match self.raw.compare(Text {
+ content: (),
+ bounds: text.bounds,
+ size: text.size,
+ line_height: text.line_height,
+ font: text.font,
+ horizontal_alignment: text.horizontal_alignment,
+ vertical_alignment: text.vertical_alignment,
+ shaping: text.shaping,
+ wrapping: text.wrapping,
+ }) {
Difference::None => {}
Difference::Bounds => {
- self.resize(text.bounds);
+ self.raw.resize(text.bounds);
}
Difference::Shape => {
- *self = Self::with_text(text);
+ self.raw = P::with_text(text);
}
}
}
- /// Returns the minimum width that can fit the contents of the [`Paragraph`].
- fn min_width(&self) -> f32 {
- self.min_bounds().width
+ /// Returns the horizontal alignment of the [`Paragraph`].
+ pub fn horizontal_alignment(&self) -> alignment::Horizontal {
+ self.raw.horizontal_alignment()
}
- /// Returns the minimum height that can fit the contents of the [`Paragraph`].
- fn min_height(&self) -> f32 {
- self.min_bounds().height
+ /// Returns the vertical alignment of the [`Paragraph`].
+ pub fn vertical_alignment(&self) -> alignment::Vertical {
+ self.raw.vertical_alignment()
+ }
+
+ /// Returns the minimum boundaries that can fit the contents of the
+ /// [`Paragraph`].
+ pub fn min_bounds(&self) -> Size {
+ self.raw.min_bounds()
+ }
+
+ /// Returns the minimum width that can fit the contents of the
+ /// [`Paragraph`].
+ pub fn min_width(&self) -> f32 {
+ self.raw.min_width()
+ }
+
+ /// Returns the cached [`Paragraph`].
+ pub fn raw(&self) -> &P {
+ &self.raw
}
}
diff --git a/core/src/vector.rs b/core/src/vector.rs
index 049e648f..ff848c4f 100644
--- a/core/src/vector.rs
+++ b/core/src/vector.rs
@@ -18,9 +18,17 @@ impl<T> Vector<T> {
impl Vector {
/// The zero [`Vector`].
pub const ZERO: Self = Self::new(0.0, 0.0);
+}
+
+impl<T> std::ops::Neg for Vector<T>
+where
+ T: std::ops::Neg<Output = T>,
+{
+ type Output = Self;
- /// The unit [`Vector`].
- pub const UNIT: Self = Self::new(0.0, 0.0);
+ fn neg(self) -> Self::Output {
+ Self::new(-self.x, -self.y)
+ }
}
impl<T> std::ops::Add for Vector<T>
diff --git a/core/src/widget.rs b/core/src/widget.rs
index 0d12deba..c5beea54 100644
--- a/core/src/widget.rs
+++ b/core/src/widget.rs
@@ -27,11 +27,11 @@ use crate::{Clipboard, Length, Rectangle, Shell, Size, Vector};
/// widget:
///
/// - [`bezier_tool`], a Paint-like tool for drawing Bézier curves using
-/// [`lyon`].
+/// [`lyon`].
/// - [`custom_widget`], a demonstration of how to build a custom widget that
-/// draws a circle.
+/// draws a circle.
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
-/// `Mesh2D` primitive in [`iced_wgpu`].
+/// `Mesh2D` primitive in [`iced_wgpu`].
///
/// [examples]: https://github.com/iced-rs/iced/tree/0.12/examples
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.12/examples/bezier_tool
@@ -96,7 +96,7 @@ where
Vec::new()
}
- /// Reconciliates the [`Widget`] with the provided [`Tree`].
+ /// Reconciles the [`Widget`] with the provided [`Tree`].
fn diff(&self, _tree: &mut Tree) {}
/// Applies an [`Operation`] to the [`Widget`].
@@ -105,7 +105,7 @@ where
_state: &mut Tree,
_layout: Layout<'_>,
_renderer: &Renderer,
- _operation: &mut dyn Operation<()>,
+ _operation: &mut dyn Operation,
) {
}
diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs
index 3e4ed618..097c3601 100644
--- a/core/src/widget/operation.rs
+++ b/core/src/widget/operation.rs
@@ -12,11 +12,12 @@ use crate::{Rectangle, Vector};
use std::any::Any;
use std::fmt;
+use std::marker::PhantomData;
use std::sync::Arc;
/// A piece of logic that can traverse the widget tree of an application in
/// order to query or update some widget state.
-pub trait Operation<T>: Send {
+pub trait Operation<T = ()>: Send {
/// Operates on a widget that contains other widgets.
///
/// The `operate_on_children` function can be called to return control to
@@ -37,6 +38,7 @@ pub trait Operation<T>: Send {
_state: &mut dyn Scrollable,
_id: Option<&Id>,
_bounds: Rectangle,
+ _content_bounds: Rectangle,
_translation: Vector,
) {
}
@@ -53,6 +55,53 @@ pub trait Operation<T>: Send {
}
}
+impl<T, O> Operation<O> for Box<T>
+where
+ T: Operation<O> + ?Sized,
+{
+ fn container(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<O>),
+ ) {
+ self.as_mut().container(id, bounds, operate_on_children);
+ }
+
+ fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
+ self.as_mut().focusable(state, id);
+ }
+
+ fn scrollable(
+ &mut self,
+ state: &mut dyn Scrollable,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+ translation: Vector,
+ ) {
+ self.as_mut().scrollable(
+ state,
+ id,
+ bounds,
+ content_bounds,
+ translation,
+ );
+ }
+
+ fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ self.as_mut().text_input(state, id);
+ }
+
+ fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
+ self.as_mut().custom(state, id);
+ }
+
+ fn finish(&self) -> Outcome<O> {
+ self.as_ref().finish()
+ }
+}
+
/// The result of an [`Operation`].
pub enum Outcome<T> {
/// The [`Operation`] produced no result.
@@ -78,9 +127,69 @@ where
}
}
+/// Wraps the [`Operation`] in a black box, erasing its returning type.
+pub fn black_box<'a, T, O>(
+ operation: &'a mut dyn Operation<T>,
+) -> impl Operation<O> + 'a
+where
+ T: 'a,
+{
+ struct BlackBox<'a, T> {
+ operation: &'a mut dyn Operation<T>,
+ }
+
+ impl<'a, T, O> Operation<O> for BlackBox<'a, T> {
+ fn container(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<O>),
+ ) {
+ self.operation.container(id, bounds, &mut |operation| {
+ operate_on_children(&mut BlackBox { operation });
+ });
+ }
+
+ fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
+ self.operation.focusable(state, id);
+ }
+
+ fn scrollable(
+ &mut self,
+ state: &mut dyn Scrollable,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+ translation: Vector,
+ ) {
+ self.operation.scrollable(
+ state,
+ id,
+ bounds,
+ content_bounds,
+ translation,
+ );
+ }
+
+ fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ self.operation.text_input(state, id);
+ }
+
+ fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
+ self.operation.custom(state, id);
+ }
+
+ fn finish(&self) -> Outcome<O> {
+ Outcome::None
+ }
+ }
+
+ BlackBox { operation }
+}
+
/// Maps the output of an [`Operation`] using the given function.
pub fn map<A, B>(
- operation: Box<dyn Operation<A>>,
+ operation: impl Operation<A>,
f: impl Fn(A) -> B + Send + Sync + 'static,
) -> impl Operation<B>
where
@@ -88,13 +197,14 @@ where
B: 'static,
{
#[allow(missing_debug_implementations)]
- struct Map<A, B> {
- operation: Box<dyn Operation<A>>,
+ struct Map<O, A, B> {
+ operation: O,
f: Arc<dyn Fn(A) -> B + Send + Sync>,
}
- impl<A, B> Operation<B> for Map<A, B>
+ impl<O, A, B> Operation<B> for Map<O, A, B>
where
+ O: Operation<A>,
A: 'static,
B: 'static,
{
@@ -127,9 +237,16 @@ where
state: &mut dyn Scrollable,
id: Option<&Id>,
bounds: Rectangle,
+ content_bounds: Rectangle,
translation: Vector,
) {
- self.operation.scrollable(state, id, bounds, translation);
+ self.operation.scrollable(
+ state,
+ id,
+ bounds,
+ content_bounds,
+ translation,
+ );
}
fn focusable(
@@ -155,10 +272,7 @@ where
let Self { operation, .. } = self;
- MapRef {
- operation: operation.as_mut(),
- }
- .container(id, bounds, operate_on_children);
+ MapRef { operation }.container(id, bounds, operate_on_children);
}
fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
@@ -170,9 +284,16 @@ where
state: &mut dyn Scrollable,
id: Option<&Id>,
bounds: Rectangle,
+ content_bounds: Rectangle,
translation: Vector,
) {
- self.operation.scrollable(state, id, bounds, translation);
+ self.operation.scrollable(
+ state,
+ id,
+ bounds,
+ content_bounds,
+ translation,
+ );
}
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
@@ -201,6 +322,94 @@ where
}
}
+/// Chains the output of an [`Operation`] with the provided function to
+/// build a new [`Operation`].
+pub fn then<A, B, O>(
+ operation: impl Operation<A> + 'static,
+ f: fn(A) -> O,
+) -> impl Operation<B>
+where
+ A: 'static,
+ B: Send + 'static,
+ O: Operation<B> + 'static,
+{
+ struct Chain<T, O, A, B>
+ where
+ T: Operation<A>,
+ O: Operation<B>,
+ {
+ operation: T,
+ next: fn(A) -> O,
+ _result: PhantomData<B>,
+ }
+
+ impl<T, O, A, B> Operation<B> for Chain<T, O, A, B>
+ where
+ T: Operation<A> + 'static,
+ O: Operation<B> + 'static,
+ A: 'static,
+ B: Send + 'static,
+ {
+ fn container(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
+ ) {
+ self.operation.container(id, bounds, &mut |operation| {
+ operate_on_children(&mut black_box(operation));
+ });
+ }
+
+ fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
+ self.operation.focusable(state, id);
+ }
+
+ fn scrollable(
+ &mut self,
+ state: &mut dyn Scrollable,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+ translation: crate::Vector,
+ ) {
+ self.operation.scrollable(
+ state,
+ id,
+ bounds,
+ content_bounds,
+ translation,
+ );
+ }
+
+ fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ self.operation.text_input(state, id);
+ }
+
+ fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) {
+ self.operation.custom(state, id);
+ }
+
+ fn finish(&self) -> Outcome<B> {
+ match self.operation.finish() {
+ Outcome::None => Outcome::None,
+ Outcome::Some(value) => {
+ Outcome::Chain(Box::new((self.next)(value)))
+ }
+ Outcome::Chain(operation) => {
+ Outcome::Chain(Box::new(then(operation, self.next)))
+ }
+ }
+ }
+ }
+
+ Chain {
+ operation,
+ next: f,
+ _result: PhantomData,
+ }
+}
+
/// Produces an [`Operation`] that applies the given [`Operation`] to the
/// children of a container with the given [`Id`].
pub fn scope<T: 'static>(
diff --git a/core/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs
index 68c22faa..867c682e 100644
--- a/core/src/widget/operation/focusable.rs
+++ b/core/src/widget/operation/focusable.rs
@@ -1,5 +1,5 @@
//! Operate on widgets that can be focused.
-use crate::widget::operation::{Operation, Outcome};
+use crate::widget::operation::{self, Operation, Outcome};
use crate::widget::Id;
use crate::Rectangle;
@@ -58,19 +58,12 @@ pub fn focus<T>(target: Id) -> impl Operation<T> {
/// Produces an [`Operation`] that generates a [`Count`] and chains it with the
/// provided function to build a new [`Operation`].
-pub fn count<T, O>(f: fn(Count) -> O) -> impl Operation<T>
-where
- O: Operation<T> + 'static,
-{
- struct CountFocusable<O> {
+pub fn count() -> impl Operation<Count> {
+ struct CountFocusable {
count: Count,
- next: fn(Count) -> O,
}
- impl<T, O> Operation<T> for CountFocusable<O>
- where
- O: Operation<T> + 'static,
- {
+ impl Operation<Count> for CountFocusable {
fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) {
if state.is_focused() {
self.count.focused = Some(self.count.total);
@@ -83,26 +76,28 @@ where
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
- operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<Count>),
) {
operate_on_children(self);
}
- fn finish(&self) -> Outcome<T> {
- Outcome::Chain(Box::new((self.next)(self.count)))
+ fn finish(&self) -> Outcome<Count> {
+ Outcome::Some(self.count)
}
}
CountFocusable {
count: Count::default(),
- next: f,
}
}
/// Produces an [`Operation`] that searches for the current focused widget, and
/// - if found, focuses the previous focusable widget.
/// - if not found, focuses the last focusable widget.
-pub fn focus_previous<T>() -> impl Operation<T> {
+pub fn focus_previous<T>() -> impl Operation<T>
+where
+ T: Send + 'static,
+{
struct FocusPrevious {
count: Count,
current: usize,
@@ -136,13 +131,16 @@ pub fn focus_previous<T>() -> impl Operation<T> {
}
}
- count(|count| FocusPrevious { count, current: 0 })
+ operation::then(count(), |count| FocusPrevious { count, current: 0 })
}
/// Produces an [`Operation`] that searches for the current focused widget, and
/// - if found, focuses the next focusable widget.
/// - if not found, focuses the first focusable widget.
-pub fn focus_next<T>() -> impl Operation<T> {
+pub fn focus_next<T>() -> impl Operation<T>
+where
+ T: Send + 'static,
+{
struct FocusNext {
count: Count,
current: usize,
@@ -170,7 +168,7 @@ pub fn focus_next<T>() -> impl Operation<T> {
}
}
- count(|count| FocusNext { count, current: 0 })
+ operation::then(count(), |count| FocusNext { count, current: 0 })
}
/// Produces an [`Operation`] that searches for the current focused widget
diff --git a/core/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs
index 12161255..c2fecf56 100644
--- a/core/src/widget/operation/scrollable.rs
+++ b/core/src/widget/operation/scrollable.rs
@@ -9,6 +9,14 @@ pub trait Scrollable {
/// Scroll the widget to the given [`AbsoluteOffset`] along the horizontal & vertical axis.
fn scroll_to(&mut self, offset: AbsoluteOffset);
+
+ /// Scroll the widget by the given [`AbsoluteOffset`] along the horizontal & vertical axis.
+ fn scroll_by(
+ &mut self,
+ offset: AbsoluteOffset,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+ );
}
/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to
@@ -34,6 +42,7 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {
state: &mut dyn Scrollable,
id: Option<&Id>,
_bounds: Rectangle,
+ _content_bounds: Rectangle,
_translation: Vector,
) {
if Some(&self.target) == id {
@@ -68,6 +77,7 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
state: &mut dyn Scrollable,
id: Option<&Id>,
_bounds: Rectangle,
+ _content_bounds: Rectangle,
_translation: Vector,
) {
if Some(&self.target) == id {
@@ -79,6 +89,41 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
ScrollTo { target, offset }
}
+/// Produces an [`Operation`] that scrolls the widget with the given [`Id`] by
+/// the provided [`AbsoluteOffset`].
+pub fn scroll_by<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
+ struct ScrollBy {
+ target: Id,
+ offset: AbsoluteOffset,
+ }
+
+ impl<T> Operation<T> for ScrollBy {
+ fn container(
+ &mut self,
+ _id: Option<&Id>,
+ _bounds: Rectangle,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
+ ) {
+ operate_on_children(self);
+ }
+
+ fn scrollable(
+ &mut self,
+ state: &mut dyn Scrollable,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+ _translation: Vector,
+ ) {
+ if Some(&self.target) == id {
+ state.scroll_by(self.offset, bounds, content_bounds);
+ }
+ }
+ }
+
+ ScrollBy { target, offset }
+}
+
/// The amount of absolute offset in each direction of a [`Scrollable`].
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct AbsoluteOffset {
diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs
index f1f0b345..d8d6e4c6 100644
--- a/core/src/widget/text.rs
+++ b/core/src/widget/text.rs
@@ -3,16 +3,15 @@ use crate::alignment;
use crate::layout;
use crate::mouse;
use crate::renderer;
-use crate::text::{self, Paragraph};
+use crate::text;
+use crate::text::paragraph::{self, Paragraph};
use crate::widget::tree::{self, Tree};
use crate::{
Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Theme,
Widget,
};
-use std::borrow::Cow;
-
-pub use text::{LineHeight, Shaping};
+pub use text::{LineHeight, Shaping, Wrapping};
/// A paragraph of text.
#[allow(missing_debug_implementations)]
@@ -21,7 +20,7 @@ where
Theme: Catalog,
Renderer: text::Renderer,
{
- fragment: Fragment<'a>,
+ fragment: text::Fragment<'a>,
size: Option<Pixels>,
line_height: LineHeight,
width: Length,
@@ -30,6 +29,7 @@ where
vertical_alignment: alignment::Vertical,
font: Option<Renderer::Font>,
shaping: Shaping,
+ wrapping: Wrapping,
class: Theme::Class<'a>,
}
@@ -39,7 +39,7 @@ where
Renderer: text::Renderer,
{
/// Create a new fragment of [`Text`] with the given contents.
- pub fn new(fragment: impl IntoFragment<'a>) -> Self {
+ pub fn new(fragment: impl text::IntoFragment<'a>) -> Self {
Text {
fragment: fragment.into_fragment(),
size: None,
@@ -49,7 +49,8 @@ where
height: Length::Shrink,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
- shaping: Shaping::Basic,
+ shaping: Shaping::default(),
+ wrapping: Wrapping::default(),
class: Theme::default(),
}
}
@@ -86,21 +87,27 @@ where
self
}
+ /// Centers the [`Text`], both horizontally and vertically.
+ pub fn center(self) -> Self {
+ self.align_x(alignment::Horizontal::Center)
+ .align_y(alignment::Vertical::Center)
+ }
+
/// Sets the [`alignment::Horizontal`] of the [`Text`].
- pub fn horizontal_alignment(
+ pub fn align_x(
mut self,
- alignment: alignment::Horizontal,
+ alignment: impl Into<alignment::Horizontal>,
) -> Self {
- self.horizontal_alignment = alignment;
+ self.horizontal_alignment = alignment.into();
self
}
/// Sets the [`alignment::Vertical`] of the [`Text`].
- pub fn vertical_alignment(
+ pub fn align_y(
mut self,
- alignment: alignment::Vertical,
+ alignment: impl Into<alignment::Vertical>,
) -> Self {
- self.vertical_alignment = alignment;
+ self.vertical_alignment = alignment.into();
self
}
@@ -110,6 +117,12 @@ where
self
}
+ /// Sets the [`Wrapping`] strategy of the [`Text`].
+ pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
+ self.wrapping = wrapping;
+ self
+ }
+
/// Sets the style of the [`Text`].
#[must_use]
pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
@@ -149,7 +162,7 @@ where
/// The internal state of a [`Text`] widget.
#[derive(Debug, Default)]
-pub struct State<P: Paragraph>(P);
+pub struct State<P: Paragraph>(pub paragraph::Plain<P>);
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Text<'a, Theme, Renderer>
@@ -162,7 +175,9 @@ where
}
fn state(&self) -> tree::State {
- tree::State::new(State(Renderer::Paragraph::default()))
+ tree::State::new(State::<Renderer::Paragraph>(
+ paragraph::Plain::default(),
+ ))
}
fn size(&self) -> Size<Length> {
@@ -191,6 +206,7 @@ where
self.horizontal_alignment,
self.vertical_alignment,
self.shaping,
+ self.wrapping,
)
}
@@ -207,7 +223,7 @@ where
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
let style = theme.style(&self.class);
- draw(renderer, defaults, layout, state, style, viewport);
+ draw(renderer, defaults, layout, state.0.raw(), style, viewport);
}
}
@@ -225,6 +241,7 @@ pub fn layout<Renderer>(
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
shaping: Shaping,
+ wrapping: Wrapping,
) -> layout::Node
where
Renderer: text::Renderer,
@@ -246,6 +263,7 @@ where
horizontal_alignment,
vertical_alignment,
shaping,
+ wrapping,
});
paragraph.min_bounds()
@@ -266,13 +284,12 @@ pub fn draw<Renderer>(
renderer: &mut Renderer,
style: &renderer::Style,
layout: Layout<'_>,
- state: &State<Renderer::Paragraph>,
+ paragraph: &Renderer::Paragraph,
appearance: Style,
viewport: &Rectangle,
) where
Renderer: text::Renderer,
{
- let State(ref paragraph) = state;
let bounds = layout.bounds();
let x = match paragraph.horizontal_alignment() {
@@ -367,80 +384,42 @@ impl Catalog for Theme {
}
}
-/// 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>;
+/// The default text styling; color is inherited.
+pub fn default(_theme: &Theme) -> Style {
+ Style { color: None }
}
-impl<'a> IntoFragment<'a> for Fragment<'a> {
- fn into_fragment(self) -> Fragment<'a> {
- self
+/// Text with the default base color.
+pub fn base(theme: &Theme) -> Style {
+ Style {
+ color: Some(theme.palette().text),
}
}
-impl<'a, 'b> IntoFragment<'a> for &'a Fragment<'b> {
- fn into_fragment(self) -> Fragment<'a> {
- Fragment::Borrowed(self)
+/// Text conveying some important information, like an action.
+pub fn primary(theme: &Theme) -> Style {
+ Style {
+ color: Some(theme.palette().primary),
}
}
-impl<'a> IntoFragment<'a> for &'a str {
- fn into_fragment(self) -> Fragment<'a> {
- Fragment::Borrowed(self)
+/// Text conveying some secondary information, like a footnote.
+pub fn secondary(theme: &Theme) -> Style {
+ Style {
+ color: Some(theme.extended_palette().secondary.strong.color),
}
}
-impl<'a> IntoFragment<'a> for &'a String {
- fn into_fragment(self) -> Fragment<'a> {
- Fragment::Borrowed(self.as_str())
+/// Text conveying some positive information, like a successful event.
+pub fn success(theme: &Theme) -> Style {
+ Style {
+ color: Some(theme.palette().success),
}
}
-impl<'a> IntoFragment<'a> for String {
- fn into_fragment(self) -> Fragment<'a> {
- Fragment::Owned(self)
+/// Text conveying some negative information, like an error.
+pub fn danger(theme: &Theme) -> Style {
+ Style {
+ color: Some(theme.palette().danger),
}
}
-
-macro_rules! into_fragment {
- ($type:ty) => {
- impl<'a> IntoFragment<'a> for $type {
- fn into_fragment(self) -> Fragment<'a> {
- Fragment::Owned(self.to_string())
- }
- }
-
- impl<'a> IntoFragment<'a> for &$type {
- fn into_fragment(self) -> Fragment<'a> {
- Fragment::Owned(self.to_string())
- }
- }
- };
-}
-
-into_fragment!(char);
-into_fragment!(bool);
-
-into_fragment!(u8);
-into_fragment!(u16);
-into_fragment!(u32);
-into_fragment!(u64);
-into_fragment!(u128);
-into_fragment!(usize);
-
-into_fragment!(i8);
-into_fragment!(i16);
-into_fragment!(i32);
-into_fragment!(i64);
-into_fragment!(i128);
-into_fragment!(isize);
-
-into_fragment!(f32);
-into_fragment!(f64);
diff --git a/core/src/widget/tree.rs b/core/src/widget/tree.rs
index 6b1a1309..2600cfc6 100644
--- a/core/src/widget/tree.rs
+++ b/core/src/widget/tree.rs
@@ -46,7 +46,7 @@ impl Tree {
}
}
- /// Reconciliates the current tree with the provided [`Widget`].
+ /// Reconciles the current tree with the provided [`Widget`].
///
/// If the tag of the [`Widget`] matches the tag of the [`Tree`], then the
/// [`Widget`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called).
@@ -81,7 +81,7 @@ impl Tree {
);
}
- /// Reconciliates the children of the tree with the provided list of widgets using custom
+ /// Reconciles the children of the tree with the provided list of widgets using custom
/// logic both for diffing and creating new widget state.
pub fn diff_children_custom<T>(
&mut self,
@@ -107,7 +107,7 @@ impl Tree {
}
}
-/// Reconciliates the `current_children` with the provided list of widgets using
+/// Reconciles the `current_children` with the provided list of widgets using
/// custom logic both for diffing and creating new widget state.
///
/// The algorithm will try to minimize the impact of diffing by querying the
diff --git a/core/src/window/event.rs b/core/src/window/event.rs
index a14d127f..c9532e0d 100644
--- a/core/src/window/event.rs
+++ b/core/src/window/event.rs
@@ -23,20 +23,10 @@ pub enum Event {
Closed,
/// A window was moved.
- Moved {
- /// The new logical x location of the window
- x: i32,
- /// The new logical y location of the window
- y: i32,
- },
+ Moved(Point),
/// A window was resized.
- Resized {
- /// The new logical width of the window
- width: u32,
- /// The new logical height of the window
- height: u32,
- },
+ Resized(Size),
/// A window redraw was requested.
///
diff --git a/core/src/window/settings/linux.rs b/core/src/window/settings/linux.rs
index 009b9d9e..0a1e11cd 100644
--- a/core/src/window/settings/linux.rs
+++ b/core/src/window/settings/linux.rs
@@ -8,4 +8,10 @@ pub struct PlatformSpecific {
/// As a best practice, it is suggested to select an application id that match
/// the basename of the application’s .desktop file.
pub application_id: String,
+
+ /// Whether bypass the window manager mapping for x11 windows
+ ///
+ /// This flag is particularly useful for creating UI elements that need precise
+ /// positioning and immediate display without window manager interference.
+ pub override_redirect: bool,
}
diff --git a/core/src/window/settings/windows.rs b/core/src/window/settings/windows.rs
index 88fe2fbd..a47582a6 100644
--- a/core/src/window/settings/windows.rs
+++ b/core/src/window/settings/windows.rs
@@ -8,6 +8,12 @@ pub struct PlatformSpecific {
/// Whether show or hide the window icon in the taskbar.
pub skip_taskbar: bool,
+
+ /// Shows or hides the background drop shadow for undecorated windows.
+ ///
+ /// The shadow is hidden by default.
+ /// Enabling the shadow causes a thin 1px line to appear on the top of the window.
+ pub undecorated_shadow: bool,
}
impl Default for PlatformSpecific {
@@ -15,6 +21,7 @@ impl Default for PlatformSpecific {
Self {
drag_and_drop: true,
skip_taskbar: false,
+ undecorated_shadow: false,
}
}
}