Merge branch 'master' into feat/multi-window-support
This commit is contained in:
commit
e09b4e24dd
331 changed files with 12085 additions and 3976 deletions
|
|
@ -1,32 +1,72 @@
|
|||
use crate::{Point, Rectangle, Vector};
|
||||
use std::f32::consts::PI;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
use std::f32::consts::{FRAC_PI_2, PI};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// Degrees
|
||||
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
|
||||
pub struct Degrees(pub f32);
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
/// Radians
|
||||
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
|
||||
pub struct Radians(pub f32);
|
||||
|
||||
impl Radians {
|
||||
/// The range of radians of a circle.
|
||||
pub const RANGE: RangeInclusive<Radians> = Radians(0.0)..=Radians(2.0 * PI);
|
||||
}
|
||||
|
||||
impl From<Degrees> for Radians {
|
||||
fn from(degrees: Degrees) -> Self {
|
||||
Radians(degrees.0 * PI / 180.0)
|
||||
Self(degrees.0 * PI / 180.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Radians {
|
||||
fn from(radians: f32) -> Self {
|
||||
Self(radians)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Radians {
|
||||
fn from(radians: u8) -> Self {
|
||||
Self(f32::from(radians))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Radians> for f64 {
|
||||
fn from(radians: Radians) -> Self {
|
||||
Self::from(radians.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl num_traits::FromPrimitive for Radians {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
impl Radians {
|
||||
/// Calculates the line in which the [`Angle`] intercepts the `bounds`.
|
||||
/// Calculates the line in which the angle intercepts the `bounds`.
|
||||
pub fn to_distance(&self, bounds: &Rectangle) -> (Point, Point) {
|
||||
let v1 = Vector::new(f32::cos(self.0), f32::sin(self.0));
|
||||
let angle = self.0 - FRAC_PI_2;
|
||||
let r = Vector::new(f32::cos(angle), f32::sin(angle));
|
||||
|
||||
let distance_to_rect = f32::min(
|
||||
f32::abs((bounds.y - bounds.center().y) / v1.y),
|
||||
f32::abs(((bounds.x + bounds.width) - bounds.center().x) / v1.x),
|
||||
let distance_to_rect = f32::max(
|
||||
f32::abs(r.x * bounds.width / 2.0),
|
||||
f32::abs(r.y * bounds.height / 2.0),
|
||||
);
|
||||
|
||||
let start = bounds.center() + v1 * distance_to_rect;
|
||||
let end = bounds.center() - v1 * distance_to_rect;
|
||||
let start = bounds.center() - r * distance_to_rect;
|
||||
let end = bounds.center() + r * distance_to_rect;
|
||||
|
||||
(start, end)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#[cfg(feature = "palette")]
|
||||
use palette::rgb::{Srgb, Srgba};
|
||||
|
||||
/// A color in the sRGB color space.
|
||||
/// A color in the `sRGB` color space.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub struct Color {
|
||||
/// Red component, 0.0 - 1.0
|
||||
|
|
@ -89,6 +89,26 @@ impl Color {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Color`] from its linear RGBA components.
|
||||
pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
// As described in:
|
||||
// https://en.wikipedia.org/wiki/SRGB
|
||||
fn gamma_component(u: f32) -> f32 {
|
||||
if u < 0.0031308 {
|
||||
12.92 * u
|
||||
} else {
|
||||
1.055 * u.powf(1.0 / 2.4) - 0.055
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
r: gamma_component(r),
|
||||
g: gamma_component(g),
|
||||
b: gamma_component(b),
|
||||
a,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the [`Color`] into its RGBA8 equivalent.
|
||||
#[must_use]
|
||||
pub fn into_rgba8(self) -> [u8; 4] {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ use crate::overlay;
|
|||
use crate::renderer;
|
||||
use crate::widget;
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{Clipboard, Color, Layout, Length, Rectangle, Shell, Widget};
|
||||
use crate::{
|
||||
Clipboard, Color, Layout, Length, Rectangle, Shell, Vector, Widget,
|
||||
};
|
||||
|
||||
use std::any::Any;
|
||||
use std::borrow::Borrow;
|
||||
|
|
@ -291,7 +293,7 @@ where
|
|||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
self.widget.diff(tree)
|
||||
self.widget.diff(tree);
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
|
|
@ -304,10 +306,11 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.widget.layout(renderer, limits)
|
||||
self.widget.layout(tree, renderer, limits)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
|
|
@ -325,11 +328,12 @@ where
|
|||
fn container(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(
|
||||
&mut dyn widget::Operation<T>,
|
||||
),
|
||||
) {
|
||||
self.operation.container(id, &mut |operation| {
|
||||
self.operation.container(id, bounds, &mut |operation| {
|
||||
operate_on_children(&mut MapOperation { operation });
|
||||
});
|
||||
}
|
||||
|
|
@ -346,8 +350,10 @@ where
|
|||
&mut self,
|
||||
state: &mut dyn widget::operation::Scrollable,
|
||||
id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
translation: Vector,
|
||||
) {
|
||||
self.operation.scrollable(state, id);
|
||||
self.operation.scrollable(state, id, bounds, translation);
|
||||
}
|
||||
|
||||
fn text_input(
|
||||
|
|
@ -380,6 +386,7 @@ where
|
|||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, B>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
let mut local_messages = Vec::new();
|
||||
let mut local_shell = Shell::new(&mut local_messages);
|
||||
|
|
@ -392,6 +399,7 @@ where
|
|||
renderer,
|
||||
clipboard,
|
||||
&mut local_shell,
|
||||
viewport,
|
||||
);
|
||||
|
||||
shell.merge(local_shell, &self.mapper);
|
||||
|
|
@ -410,7 +418,7 @@ where
|
|||
viewport: &Rectangle,
|
||||
) {
|
||||
self.widget
|
||||
.draw(tree, renderer, theme, style, layout, cursor, viewport)
|
||||
.draw(tree, renderer, theme, style, layout, cursor, viewport);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -484,10 +492,11 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.element.widget.layout(renderer, limits)
|
||||
self.element.widget.layout(tree, renderer, limits)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
|
|
@ -499,7 +508,7 @@ where
|
|||
) {
|
||||
self.element
|
||||
.widget
|
||||
.operate(state, layout, renderer, operation)
|
||||
.operate(state, layout, renderer, operation);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -511,10 +520,11 @@ where
|
|||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self.element
|
||||
.widget
|
||||
.on_event(state, event, layout, cursor, renderer, clipboard, shell)
|
||||
self.element.widget.on_event(
|
||||
state, event, layout, cursor, renderer, clipboard, shell, viewport,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ pub struct Font {
|
|||
pub weight: Weight,
|
||||
/// The [`Stretch`] of the [`Font`].
|
||||
pub stretch: Stretch,
|
||||
/// Whether if the [`Font`] is monospaced or not.
|
||||
pub monospaced: bool,
|
||||
/// The [`Style`] of the [`Font`].
|
||||
pub style: Style,
|
||||
}
|
||||
|
||||
impl Font {
|
||||
|
|
@ -20,13 +20,12 @@ impl Font {
|
|||
family: Family::SansSerif,
|
||||
weight: Weight::Normal,
|
||||
stretch: Stretch::Normal,
|
||||
monospaced: false,
|
||||
style: Style::Normal,
|
||||
};
|
||||
|
||||
/// A monospaced font with normal [`Weight`].
|
||||
pub const MONOSPACE: Font = Font {
|
||||
family: Family::Monospace,
|
||||
monospaced: true,
|
||||
..Self::DEFAULT
|
||||
};
|
||||
|
||||
|
|
@ -100,3 +99,13 @@ pub enum Stretch {
|
|||
ExtraExpanded,
|
||||
UltraExpanded,
|
||||
}
|
||||
|
||||
/// The style of some text.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
pub enum Style {
|
||||
#[default]
|
||||
Normal,
|
||||
Italic,
|
||||
Oblique,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@ use std::cmp::Ordering;
|
|||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD),
|
||||
/// or conically (TBD).
|
||||
///
|
||||
/// For a gradient which can be used as a fill on a canvas, see [`iced_graphics::Gradient`].
|
||||
pub enum Gradient {
|
||||
/// A linear gradient interpolates colors along a direction at a specific [`Angle`].
|
||||
/// A linear gradient interpolates colors along a direction at a specific angle.
|
||||
Linear(Linear),
|
||||
}
|
||||
|
||||
|
|
@ -96,8 +94,8 @@ impl Linear {
|
|||
mut self,
|
||||
stops: impl IntoIterator<Item = ColorStop>,
|
||||
) -> Self {
|
||||
for stop in stops.into_iter() {
|
||||
self = self.add_stop(stop.offset, stop.color)
|
||||
for stop in stops {
|
||||
self = self.add_stop(stop.offset, stop.color);
|
||||
}
|
||||
|
||||
self
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
/// The hasher used to compare layouts.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Hasher(twox_hash::XxHash64);
|
||||
#[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);
|
||||
|
||||
impl core::hash::Hasher for Hasher {
|
||||
fn write(&mut self, bytes: &[u8]) {
|
||||
self.0.write(bytes)
|
||||
self.0.write(bytes);
|
||||
}
|
||||
|
||||
fn finish(&self) -> u64 {
|
||||
|
|
|
|||
|
|
@ -164,6 +164,16 @@ impl std::fmt::Debug for Data {
|
|||
}
|
||||
}
|
||||
|
||||
/// Image filtering strategy.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
pub enum FilterMethod {
|
||||
/// Bilinear interpolation.
|
||||
#[default]
|
||||
Linear,
|
||||
/// Nearest neighbor.
|
||||
Nearest,
|
||||
}
|
||||
|
||||
/// A [`Renderer`] that can render raster graphics.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
|
|
@ -178,5 +188,10 @@ pub trait Renderer: crate::Renderer {
|
|||
|
||||
/// Draws an image with the given [`Handle`] and inside the provided
|
||||
/// `bounds`.
|
||||
fn draw(&mut self, handle: Self::Handle, bounds: Rectangle);
|
||||
fn draw(
|
||||
&mut self,
|
||||
handle: Self::Handle,
|
||||
filter_method: FilterMethod,
|
||||
bounds: Rectangle,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ pub mod flex;
|
|||
pub use limits::Limits;
|
||||
pub use node::Node;
|
||||
|
||||
use crate::{Point, Rectangle, Vector};
|
||||
use crate::{Point, Rectangle, Size, Vector};
|
||||
|
||||
/// The bounds of a [`Node`] and its children, using absolute coordinates.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -63,3 +63,36 @@ impl<'a> Layout<'a> {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a [`Node`] with two children nodes one right next to each other.
|
||||
pub fn next_to_each_other(
|
||||
limits: &Limits,
|
||||
spacing: f32,
|
||||
left: impl FnOnce(&Limits) -> Node,
|
||||
right: impl FnOnce(&Limits) -> Node,
|
||||
) -> Node {
|
||||
let mut left_node = left(limits);
|
||||
let left_size = left_node.size();
|
||||
|
||||
let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0));
|
||||
|
||||
let mut right_node = right(&right_limits);
|
||||
let right_size = right_node.size();
|
||||
|
||||
let (left_y, right_y) = if left_size.height > right_size.height {
|
||||
(0.0, (left_size.height - right_size.height) / 2.0)
|
||||
} else {
|
||||
((right_size.height - left_size.height) / 2.0, 0.0)
|
||||
};
|
||||
|
||||
left_node.move_to(Point::new(0.0, left_y));
|
||||
right_node.move_to(Point::new(left_size.width + spacing, right_y));
|
||||
|
||||
Node::with_children(
|
||||
Size::new(
|
||||
left_size.width + spacing + right_size.width,
|
||||
left_size.height.max(right_size.height),
|
||||
),
|
||||
vec![left_node, right_node],
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
use crate::Element;
|
||||
|
||||
use crate::layout::{Limits, Node};
|
||||
use crate::widget;
|
||||
use crate::{Alignment, Padding, Point, Size};
|
||||
|
||||
/// The main axis of a flex layout.
|
||||
|
|
@ -66,6 +67,7 @@ pub fn resolve<Message, Renderer>(
|
|||
spacing: f32,
|
||||
align_items: Alignment,
|
||||
items: &[Element<'_, Message, Renderer>],
|
||||
trees: &mut [widget::Tree],
|
||||
) -> Node
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
|
|
@ -81,7 +83,7 @@ where
|
|||
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
|
||||
nodes.resize(items.len(), Node::default());
|
||||
|
||||
for (i, child) in items.iter().enumerate() {
|
||||
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => child.as_widget().width(),
|
||||
Axis::Vertical => child.as_widget().height(),
|
||||
|
|
@ -94,7 +96,8 @@ where
|
|||
let child_limits =
|
||||
Limits::new(Size::ZERO, Size::new(max_width, max_height));
|
||||
|
||||
let layout = child.as_widget().layout(renderer, &child_limits);
|
||||
let layout =
|
||||
child.as_widget().layout(tree, renderer, &child_limits);
|
||||
let size = layout.size();
|
||||
|
||||
available -= axis.main(size);
|
||||
|
|
@ -108,7 +111,7 @@ where
|
|||
|
||||
let remaining = available.max(0.0);
|
||||
|
||||
for (i, child) in items.iter().enumerate() {
|
||||
for (i, (child, tree)) in items.iter().zip(trees).enumerate() {
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => child.as_widget().width(),
|
||||
Axis::Vertical => child.as_widget().height(),
|
||||
|
|
@ -133,7 +136,8 @@ where
|
|||
Size::new(max_width, max_height),
|
||||
);
|
||||
|
||||
let layout = child.as_widget().layout(renderer, &child_limits);
|
||||
let layout =
|
||||
child.as_widget().layout(tree, renderer, &child_limits);
|
||||
cross = cross.max(axis.cross(layout.size()));
|
||||
|
||||
nodes[i] = layout;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
use crate::{Length, Padding, Size};
|
||||
|
||||
/// A set of size constraints for layouting.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Limits {
|
||||
min: Size,
|
||||
max: Size,
|
||||
|
|
|
|||
|
|
@ -1,29 +1,21 @@
|
|||
//! The core library of [Iced].
|
||||
//!
|
||||
//! This library holds basic types that can be reused and re-exported in
|
||||
//! different runtime implementations. For instance, both [`iced_native`] and
|
||||
//! [`iced_web`] are built on top of `iced_core`.
|
||||
//! different runtime implementations.
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! [Iced]: https://github.com/iced-rs/iced
|
||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
|
||||
//! [`iced_web`]: https://github.com/iced-rs/iced_web
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
||||
)]
|
||||
#![forbid(unsafe_code, rust_2018_idioms)]
|
||||
#![deny(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
unused_results,
|
||||
clippy::extra_unused_lifetimes,
|
||||
clippy::from_over_into,
|
||||
clippy::needless_borrow,
|
||||
clippy::new_without_default,
|
||||
clippy::useless_conversion
|
||||
rustdoc::broken_intra_doc_links
|
||||
)]
|
||||
#![forbid(unsafe_code, rust_2018_idioms)]
|
||||
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
|
||||
pub mod alignment;
|
||||
pub mod clipboard;
|
||||
pub mod event;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub enum Kind {
|
|||
}
|
||||
|
||||
impl Kind {
|
||||
fn next(&self) -> Kind {
|
||||
fn next(self) -> Kind {
|
||||
match self {
|
||||
Kind::Single => Kind::Double,
|
||||
Kind::Double => Kind::Triple,
|
||||
|
|
@ -61,6 +61,11 @@ impl Click {
|
|||
self.kind
|
||||
}
|
||||
|
||||
/// Returns the position of the [`Click`].
|
||||
pub fn position(&self) -> Point {
|
||||
self.position
|
||||
}
|
||||
|
||||
fn is_consecutive(&self, new_position: Point, time: Instant) -> bool {
|
||||
let duration = if time > self.time {
|
||||
Some(time - self.time)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use crate::mouse;
|
|||
use crate::renderer;
|
||||
use crate::widget;
|
||||
use crate::widget::Tree;
|
||||
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
|
||||
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector};
|
||||
|
||||
/// An interactive component that can be displayed on top of other widgets.
|
||||
pub trait Overlay<Message, Renderer>
|
||||
|
|
@ -25,10 +25,11 @@ where
|
|||
///
|
||||
/// [`Node`]: layout::Node
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
renderer: &Renderer,
|
||||
bounds: Size,
|
||||
position: Point,
|
||||
translation: Vector,
|
||||
) -> layout::Node;
|
||||
|
||||
/// Draws the [`Overlay`] using the associated `Renderer`.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use std::any::Any;
|
|||
#[allow(missing_debug_implementations)]
|
||||
pub struct Element<'a, Message, Renderer> {
|
||||
position: Point,
|
||||
translation: Vector,
|
||||
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
|
||||
}
|
||||
|
||||
|
|
@ -25,7 +26,11 @@ where
|
|||
position: Point,
|
||||
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
|
||||
) -> Self {
|
||||
Self { position, overlay }
|
||||
Self {
|
||||
position,
|
||||
overlay,
|
||||
translation: Vector::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the position of the [`Element`].
|
||||
|
|
@ -36,6 +41,7 @@ where
|
|||
/// Translates the [`Element`].
|
||||
pub fn translate(mut self, translation: Vector) -> Self {
|
||||
self.position = self.position + translation;
|
||||
self.translation = self.translation + translation;
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -48,19 +54,24 @@ where
|
|||
{
|
||||
Element {
|
||||
position: self.position,
|
||||
translation: self.translation,
|
||||
overlay: Box::new(Map::new(self.overlay, f)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the layout of the [`Element`] in the given bounds.
|
||||
pub fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
renderer: &Renderer,
|
||||
bounds: Size,
|
||||
translation: Vector,
|
||||
) -> layout::Node {
|
||||
self.overlay
|
||||
.layout(renderer, bounds, self.position + translation)
|
||||
self.overlay.layout(
|
||||
renderer,
|
||||
bounds,
|
||||
self.position + translation,
|
||||
self.translation + translation,
|
||||
)
|
||||
}
|
||||
|
||||
/// Processes a runtime [`Event`].
|
||||
|
|
@ -98,7 +109,7 @@ where
|
|||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
) {
|
||||
self.overlay.draw(renderer, theme, style, layout, cursor)
|
||||
self.overlay.draw(renderer, theme, style, layout, cursor);
|
||||
}
|
||||
|
||||
/// Applies a [`widget::Operation`] to the [`Element`].
|
||||
|
|
@ -150,12 +161,13 @@ where
|
|||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
renderer: &Renderer,
|
||||
bounds: Size,
|
||||
position: Point,
|
||||
translation: Vector,
|
||||
) -> layout::Node {
|
||||
self.content.layout(renderer, bounds, position)
|
||||
self.content.layout(renderer, bounds, position, translation)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
|
|
@ -172,11 +184,12 @@ where
|
|||
fn container(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(
|
||||
&mut dyn widget::Operation<T>,
|
||||
),
|
||||
) {
|
||||
self.operation.container(id, &mut |operation| {
|
||||
self.operation.container(id, bounds, &mut |operation| {
|
||||
operate_on_children(&mut MapOperation { operation });
|
||||
});
|
||||
}
|
||||
|
|
@ -193,8 +206,10 @@ where
|
|||
&mut self,
|
||||
state: &mut dyn widget::operation::Scrollable,
|
||||
id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
translation: Vector,
|
||||
) {
|
||||
self.operation.scrollable(state, id);
|
||||
self.operation.scrollable(state, id, bounds, translation);
|
||||
}
|
||||
|
||||
fn text_input(
|
||||
|
|
@ -202,7 +217,7 @@ where
|
|||
state: &mut dyn widget::operation::TextInput,
|
||||
id: Option<&widget::Id>,
|
||||
) {
|
||||
self.operation.text_input(state, id)
|
||||
self.operation.text_input(state, id);
|
||||
}
|
||||
|
||||
fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) {
|
||||
|
|
@ -259,7 +274,7 @@ where
|
|||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
) {
|
||||
self.content.draw(renderer, theme, style, layout, cursor)
|
||||
self.content.draw(renderer, theme, style, layout, cursor);
|
||||
}
|
||||
|
||||
fn is_over(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ use crate::mouse;
|
|||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::widget;
|
||||
use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size};
|
||||
use crate::{
|
||||
Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size, Vector,
|
||||
};
|
||||
|
||||
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
|
||||
/// children.
|
||||
|
|
@ -61,17 +63,16 @@ where
|
|||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
renderer: &Renderer,
|
||||
bounds: Size,
|
||||
position: Point,
|
||||
_position: Point,
|
||||
translation: Vector,
|
||||
) -> layout::Node {
|
||||
let translation = position - Point::ORIGIN;
|
||||
|
||||
layout::Node::with_children(
|
||||
bounds,
|
||||
self.children
|
||||
.iter()
|
||||
.iter_mut()
|
||||
.map(|child| child.layout(renderer, bounds, translation))
|
||||
.collect(),
|
||||
)
|
||||
|
|
@ -138,12 +139,12 @@ where
|
|||
renderer: &Renderer,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
operation.container(None, &mut |operation| {
|
||||
operation.container(None, layout.bounds(), &mut |operation| {
|
||||
self.children.iter_mut().zip(layout.children()).for_each(
|
||||
|(child, layout)| {
|
||||
child.operate(layout, renderer, operation);
|
||||
},
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,9 +74,9 @@ impl Rectangle<f32> {
|
|||
/// Returns true if the given [`Point`] is contained in the [`Rectangle`].
|
||||
pub fn contains(&self, point: Point) -> bool {
|
||||
self.x <= point.x
|
||||
&& point.x <= self.x + self.width
|
||||
&& point.x < self.x + self.width
|
||||
&& self.y <= point.y
|
||||
&& point.y <= self.y + self.height
|
||||
&& point.y < self.y + self.height
|
||||
}
|
||||
|
||||
/// Returns true if the current [`Rectangle`] is completely within the given
|
||||
|
|
@ -197,3 +197,18 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Sub<Vector<T>> for Rectangle<T>
|
||||
where
|
||||
T: std::ops::Sub<Output = T>,
|
||||
{
|
||||
type Output = Rectangle<T>;
|
||||
|
||||
fn sub(self, translation: Vector<T>) -> Self {
|
||||
Rectangle {
|
||||
x: self.x - translation.x,
|
||||
y: self.y - translation.y,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,26 +5,13 @@ mod null;
|
|||
#[cfg(debug_assertions)]
|
||||
pub use null::Null;
|
||||
|
||||
use crate::layout;
|
||||
use crate::{Background, BorderRadius, Color, Element, Rectangle, Vector};
|
||||
use crate::{Background, BorderRadius, Color, Rectangle, Vector};
|
||||
|
||||
/// A component that can be used by widgets to draw themselves on a screen.
|
||||
pub trait Renderer: Sized {
|
||||
/// The supported theme of the [`Renderer`].
|
||||
type Theme;
|
||||
|
||||
/// Lays out the elements of a user interface.
|
||||
///
|
||||
/// You should override this if you need to perform any operations before or
|
||||
/// after layouting. For instance, trimming the measurements cache.
|
||||
fn layout<Message>(
|
||||
&mut self,
|
||||
element: &Element<'_, Message, Self>,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
element.as_widget().layout(self, limits)
|
||||
}
|
||||
|
||||
/// Draws the primitives recorded in the given closure in a new layer.
|
||||
///
|
||||
/// The layer will clip its contents to the provided `bounds`.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::alignment;
|
||||
use crate::renderer::{self, Renderer};
|
||||
use crate::text::{self, Text};
|
||||
use crate::{Background, Font, Point, Rectangle, Size, Vector};
|
||||
use crate::{Background, Color, Font, Pixels, Point, Rectangle, Size, Vector};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
|
|
@ -41,6 +42,8 @@ impl Renderer for Null {
|
|||
|
||||
impl text::Renderer for Null {
|
||||
type Font = Font;
|
||||
type Paragraph = ();
|
||||
type Editor = ();
|
||||
|
||||
const ICON_FONT: Font = Font::DEFAULT;
|
||||
const CHECKMARK_ICON: char = '0';
|
||||
|
|
@ -50,37 +53,117 @@ impl text::Renderer for Null {
|
|||
Font::default()
|
||||
}
|
||||
|
||||
fn default_size(&self) -> f32 {
|
||||
16.0
|
||||
fn default_size(&self) -> Pixels {
|
||||
Pixels(16.0)
|
||||
}
|
||||
|
||||
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
|
||||
|
||||
fn measure(
|
||||
&self,
|
||||
_content: &str,
|
||||
_size: f32,
|
||||
_line_height: text::LineHeight,
|
||||
_font: Font,
|
||||
_bounds: Size,
|
||||
_shaping: text::Shaping,
|
||||
) -> Size {
|
||||
Size::new(0.0, 20.0)
|
||||
fn fill_paragraph(
|
||||
&mut self,
|
||||
_paragraph: &Self::Paragraph,
|
||||
_position: Point,
|
||||
_color: Color,
|
||||
) {
|
||||
}
|
||||
|
||||
fn hit_test(
|
||||
&self,
|
||||
_contents: &str,
|
||||
_size: f32,
|
||||
_line_height: text::LineHeight,
|
||||
_font: Self::Font,
|
||||
_bounds: Size,
|
||||
_shaping: text::Shaping,
|
||||
_point: Point,
|
||||
_nearest_only: bool,
|
||||
) -> Option<text::Hit> {
|
||||
fn fill_editor(
|
||||
&mut self,
|
||||
_editor: &Self::Editor,
|
||||
_position: Point,
|
||||
_color: Color,
|
||||
) {
|
||||
}
|
||||
|
||||
fn fill_text(
|
||||
&mut self,
|
||||
_paragraph: Text<'_, Self::Font>,
|
||||
_position: Point,
|
||||
_color: Color,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl text::Paragraph for () {
|
||||
type Font = Font;
|
||||
|
||||
fn with_text(_text: Text<'_, Self::Font>) -> Self {}
|
||||
|
||||
fn resize(&mut self, _new_bounds: Size) {}
|
||||
|
||||
fn compare(&self, _text: Text<'_, Self::Font>) -> text::Difference {
|
||||
text::Difference::None
|
||||
}
|
||||
|
||||
fn horizontal_alignment(&self) -> alignment::Horizontal {
|
||||
alignment::Horizontal::Left
|
||||
}
|
||||
|
||||
fn vertical_alignment(&self) -> alignment::Vertical {
|
||||
alignment::Vertical::Top
|
||||
}
|
||||
|
||||
fn grapheme_position(&self, _line: usize, _index: usize) -> Option<Point> {
|
||||
None
|
||||
}
|
||||
|
||||
fn fill_text(&mut self, _text: Text<'_, Self::Font>) {}
|
||||
fn min_bounds(&self) -> Size {
|
||||
Size::ZERO
|
||||
}
|
||||
|
||||
fn hit_test(&self, _point: Point) -> Option<text::Hit> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl text::Editor for () {
|
||||
type Font = Font;
|
||||
|
||||
fn with_text(_text: &str) -> Self {}
|
||||
|
||||
fn cursor(&self) -> text::editor::Cursor {
|
||||
text::editor::Cursor::Caret(Point::ORIGIN)
|
||||
}
|
||||
|
||||
fn cursor_position(&self) -> (usize, usize) {
|
||||
(0, 0)
|
||||
}
|
||||
|
||||
fn selection(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn line(&self, _index: usize) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn line_count(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn perform(&mut self, _action: text::editor::Action) {}
|
||||
|
||||
fn bounds(&self) -> Size {
|
||||
Size::ZERO
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
_new_bounds: Size,
|
||||
_new_font: Self::Font,
|
||||
_new_size: Pixels,
|
||||
_new_line_height: text::LineHeight,
|
||||
_new_highlighter: &mut impl text::Highlighter,
|
||||
) {
|
||||
}
|
||||
|
||||
fn highlight<H: text::Highlighter>(
|
||||
&mut self,
|
||||
_font: Self::Font,
|
||||
_highlighter: &mut H,
|
||||
_format_highlight: impl Fn(
|
||||
&H::Highlight,
|
||||
) -> text::highlighter::Format<Self::Font>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ impl<'a, Message> Shell<'a, Message> {
|
|||
self.messages.push(message);
|
||||
}
|
||||
|
||||
/// Requests a new frame to be drawn at the given [`Instant`].
|
||||
/// Requests a new frame to be drawn.
|
||||
pub fn request_redraw(&mut self, request: window::RedrawRequest) {
|
||||
match self.redraw_request {
|
||||
None => {
|
||||
|
|
@ -48,7 +48,7 @@ impl<'a, Message> Shell<'a, Message> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the requested [`Instant`] a redraw should happen, if any.
|
||||
/// Returns the request a redraw should happen, if any.
|
||||
pub fn redraw_request(&self) -> Option<window::RedrawRequest> {
|
||||
self.redraw_request
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ impl<'a, Message> Shell<'a, Message> {
|
|||
if self.is_layout_invalid {
|
||||
self.is_layout_invalid = false;
|
||||
|
||||
f()
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
134
core/src/text.rs
134
core/src/text.rs
|
|
@ -1,6 +1,15 @@
|
|||
//! Draw and interact with text.
|
||||
mod paragraph;
|
||||
|
||||
pub mod editor;
|
||||
pub mod highlighter;
|
||||
|
||||
pub use editor::Editor;
|
||||
pub use highlighter::Highlighter;
|
||||
pub use paragraph::Paragraph;
|
||||
|
||||
use crate::alignment;
|
||||
use crate::{Color, Pixels, Point, Rectangle, Size};
|
||||
use crate::{Color, Pixels, Point, Size};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
|
@ -12,17 +21,14 @@ pub struct Text<'a, Font> {
|
|||
pub content: &'a str,
|
||||
|
||||
/// The bounds of the paragraph.
|
||||
pub bounds: Rectangle,
|
||||
pub bounds: Size,
|
||||
|
||||
/// The size of the [`Text`] in logical pixels.
|
||||
pub size: f32,
|
||||
pub size: Pixels,
|
||||
|
||||
/// The line height of the [`Text`].
|
||||
pub line_height: LineHeight,
|
||||
|
||||
/// The color of the [`Text`].
|
||||
pub color: Color,
|
||||
|
||||
/// The font of the [`Text`].
|
||||
pub font: Font,
|
||||
|
||||
|
|
@ -129,10 +135,43 @@ impl Hit {
|
|||
}
|
||||
}
|
||||
|
||||
/// The difference detected in some text.
|
||||
///
|
||||
/// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some
|
||||
/// [`Text`].
|
||||
///
|
||||
/// [`compare`]: Paragraph::compare
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Difference {
|
||||
/// No difference.
|
||||
///
|
||||
/// The text can be reused as it is!
|
||||
None,
|
||||
|
||||
/// A bounds difference.
|
||||
///
|
||||
/// This normally means a relayout is necessary, but the shape of the text can
|
||||
/// be reused.
|
||||
Bounds,
|
||||
|
||||
/// A shape difference.
|
||||
///
|
||||
/// The contents, alignment, sizes, fonts, or any other essential attributes
|
||||
/// of the shape of the text have changed. A complete reshape and relayout of
|
||||
/// the text is necessary.
|
||||
Shape,
|
||||
}
|
||||
|
||||
/// A renderer capable of measuring and drawing [`Text`].
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// The font type used.
|
||||
type Font: Copy;
|
||||
type Font: Copy + PartialEq;
|
||||
|
||||
/// The [`Paragraph`] of this [`Renderer`].
|
||||
type Paragraph: Paragraph<Font = Self::Font> + 'static;
|
||||
|
||||
/// The [`Editor`] of this [`Renderer`].
|
||||
type Editor: Editor<Font = Self::Font> + 'static;
|
||||
|
||||
/// The icon font of the backend.
|
||||
const ICON_FONT: Self::Font;
|
||||
|
|
@ -151,62 +190,35 @@ pub trait Renderer: crate::Renderer {
|
|||
fn default_font(&self) -> Self::Font;
|
||||
|
||||
/// Returns the default size of [`Text`].
|
||||
fn default_size(&self) -> f32;
|
||||
|
||||
/// Measures the text in the given bounds and returns the minimum boundaries
|
||||
/// that can fit the contents.
|
||||
fn measure(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
line_height: LineHeight,
|
||||
font: Self::Font,
|
||||
bounds: Size,
|
||||
shaping: Shaping,
|
||||
) -> Size;
|
||||
|
||||
/// Measures the width of the text as if it were laid out in a single line.
|
||||
fn measure_width(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
font: Self::Font,
|
||||
shaping: Shaping,
|
||||
) -> f32 {
|
||||
let bounds = self.measure(
|
||||
content,
|
||||
size,
|
||||
LineHeight::Absolute(Pixels(size)),
|
||||
font,
|
||||
Size::INFINITY,
|
||||
shaping,
|
||||
);
|
||||
|
||||
bounds.width
|
||||
}
|
||||
|
||||
/// Tests whether the provided point is within the boundaries of text
|
||||
/// laid out with the given parameters, returning information about
|
||||
/// the nearest character.
|
||||
///
|
||||
/// If `nearest_only` is true, the hit test does not consider whether the
|
||||
/// the point is interior to any glyph bounds, returning only the character
|
||||
/// with the nearest centeroid.
|
||||
fn hit_test(
|
||||
&self,
|
||||
contents: &str,
|
||||
size: f32,
|
||||
line_height: LineHeight,
|
||||
font: Self::Font,
|
||||
bounds: Size,
|
||||
shaping: Shaping,
|
||||
point: Point,
|
||||
nearest_only: bool,
|
||||
) -> Option<Hit>;
|
||||
fn default_size(&self) -> Pixels;
|
||||
|
||||
/// Loads a [`Self::Font`] from its bytes.
|
||||
fn load_font(&mut self, font: Cow<'static, [u8]>);
|
||||
|
||||
/// Draws the given [`Text`].
|
||||
fn fill_text(&mut self, text: Text<'_, Self::Font>);
|
||||
/// Draws the given [`Paragraph`] at the given position and with the given
|
||||
/// [`Color`].
|
||||
fn fill_paragraph(
|
||||
&mut self,
|
||||
text: &Self::Paragraph,
|
||||
position: Point,
|
||||
color: Color,
|
||||
);
|
||||
|
||||
/// Draws the given [`Editor`] at the given position and with the given
|
||||
/// [`Color`].
|
||||
fn fill_editor(
|
||||
&mut self,
|
||||
editor: &Self::Editor,
|
||||
position: Point,
|
||||
color: Color,
|
||||
);
|
||||
|
||||
/// Draws the given [`Text`] at the given position and with the given
|
||||
/// [`Color`].
|
||||
fn fill_text(
|
||||
&mut self,
|
||||
text: Text<'_, Self::Font>,
|
||||
position: Point,
|
||||
color: Color,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
181
core/src/text/editor.rs
Normal file
181
core/src/text/editor.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
//! Edit text.
|
||||
use crate::text::highlighter::{self, Highlighter};
|
||||
use crate::text::LineHeight;
|
||||
use crate::{Pixels, Point, Rectangle, Size};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A component that can be used by widgets to edit multi-line text.
|
||||
pub trait Editor: Sized + Default {
|
||||
/// The font of the [`Editor`].
|
||||
type Font: Copy + PartialEq + Default;
|
||||
|
||||
/// Creates a new [`Editor`] laid out with the given text.
|
||||
fn with_text(text: &str) -> Self;
|
||||
|
||||
/// Returns the current [`Cursor`] of the [`Editor`].
|
||||
fn cursor(&self) -> Cursor;
|
||||
|
||||
/// Returns the current cursor position of the [`Editor`].
|
||||
///
|
||||
/// Line and column, respectively.
|
||||
fn cursor_position(&self) -> (usize, usize);
|
||||
|
||||
/// Returns the current selected text of the [`Editor`].
|
||||
fn selection(&self) -> Option<String>;
|
||||
|
||||
/// Returns the text of the given line in the [`Editor`], if it exists.
|
||||
fn line(&self, index: usize) -> Option<&str>;
|
||||
|
||||
/// Returns the amount of lines in the [`Editor`].
|
||||
fn line_count(&self) -> usize;
|
||||
|
||||
/// Performs an [`Action`] on the [`Editor`].
|
||||
fn perform(&mut self, action: Action);
|
||||
|
||||
/// Returns the current boundaries of the [`Editor`].
|
||||
fn bounds(&self) -> Size;
|
||||
|
||||
/// Updates the [`Editor`] with some new attributes.
|
||||
fn update(
|
||||
&mut self,
|
||||
new_bounds: Size,
|
||||
new_font: Self::Font,
|
||||
new_size: Pixels,
|
||||
new_line_height: LineHeight,
|
||||
new_highlighter: &mut impl Highlighter,
|
||||
);
|
||||
|
||||
/// Runs a text [`Highlighter`] in the [`Editor`].
|
||||
fn highlight<H: Highlighter>(
|
||||
&mut self,
|
||||
font: Self::Font,
|
||||
highlighter: &mut H,
|
||||
format_highlight: impl Fn(&H::Highlight) -> highlighter::Format<Self::Font>,
|
||||
);
|
||||
}
|
||||
|
||||
/// An interaction with an [`Editor`].
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Action {
|
||||
/// Apply a [`Motion`].
|
||||
Move(Motion),
|
||||
/// Select text with a given [`Motion`].
|
||||
Select(Motion),
|
||||
/// Select the word at the current cursor.
|
||||
SelectWord,
|
||||
/// Select the line at the current cursor.
|
||||
SelectLine,
|
||||
/// Perform an [`Edit`].
|
||||
Edit(Edit),
|
||||
/// Click the [`Editor`] at the given [`Point`].
|
||||
Click(Point),
|
||||
/// Drag the mouse on the [`Editor`] to the given [`Point`].
|
||||
Drag(Point),
|
||||
/// Scroll the [`Editor`] a certain amount of lines.
|
||||
Scroll {
|
||||
/// The amount of lines to scroll.
|
||||
lines: i32,
|
||||
},
|
||||
}
|
||||
|
||||
impl Action {
|
||||
/// Returns whether the [`Action`] is an editing action.
|
||||
pub fn is_edit(&self) -> bool {
|
||||
matches!(self, Self::Edit(_))
|
||||
}
|
||||
}
|
||||
|
||||
/// An action that edits text.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Edit {
|
||||
/// Insert the given character.
|
||||
Insert(char),
|
||||
/// Paste the given text.
|
||||
Paste(Arc<String>),
|
||||
/// Break the current line.
|
||||
Enter,
|
||||
/// Delete the previous character.
|
||||
Backspace,
|
||||
/// Delete the next character.
|
||||
Delete,
|
||||
}
|
||||
|
||||
/// A cursor movement.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Motion {
|
||||
/// Move left.
|
||||
Left,
|
||||
/// Move right.
|
||||
Right,
|
||||
/// Move up.
|
||||
Up,
|
||||
/// Move down.
|
||||
Down,
|
||||
/// Move to the left boundary of a word.
|
||||
WordLeft,
|
||||
/// Move to the right boundary of a word.
|
||||
WordRight,
|
||||
/// Move to the start of the line.
|
||||
Home,
|
||||
/// Move to the end of the line.
|
||||
End,
|
||||
/// Move to the start of the previous window.
|
||||
PageUp,
|
||||
/// Move to the start of the next window.
|
||||
PageDown,
|
||||
/// Move to the start of the text.
|
||||
DocumentStart,
|
||||
/// Move to the end of the text.
|
||||
DocumentEnd,
|
||||
}
|
||||
|
||||
impl Motion {
|
||||
/// Widens the [`Motion`], if possible.
|
||||
pub fn widen(self) -> Self {
|
||||
match self {
|
||||
Self::Left => Self::WordLeft,
|
||||
Self::Right => Self::WordRight,
|
||||
Self::Home => Self::DocumentStart,
|
||||
Self::End => Self::DocumentEnd,
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`Direction`] of the [`Motion`].
|
||||
pub fn direction(&self) -> Direction {
|
||||
match self {
|
||||
Self::Left
|
||||
| Self::Up
|
||||
| Self::WordLeft
|
||||
| Self::Home
|
||||
| Self::PageUp
|
||||
| Self::DocumentStart => Direction::Left,
|
||||
Self::Right
|
||||
| Self::Down
|
||||
| Self::WordRight
|
||||
| Self::End
|
||||
| Self::PageDown
|
||||
| Self::DocumentEnd => Direction::Right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A direction in some text.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Direction {
|
||||
/// <-
|
||||
Left,
|
||||
/// ->
|
||||
Right,
|
||||
}
|
||||
|
||||
/// The cursor of an [`Editor`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Cursor {
|
||||
/// Cursor without a selection
|
||||
Caret(Point),
|
||||
|
||||
/// Cursor selecting a range of text
|
||||
Selection(Vec<Rectangle>),
|
||||
}
|
||||
88
core/src/text/highlighter.rs
Normal file
88
core/src/text/highlighter.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//! Highlight text.
|
||||
use crate::Color;
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
/// A type capable of highlighting text.
|
||||
///
|
||||
/// A [`Highlighter`] highlights lines in sequence. When a line changes,
|
||||
/// it must be notified and the lines after the changed one must be fed
|
||||
/// again to the [`Highlighter`].
|
||||
pub trait Highlighter: 'static {
|
||||
/// The settings to configure the [`Highlighter`].
|
||||
type Settings: PartialEq + Clone;
|
||||
|
||||
/// The output of the [`Highlighter`].
|
||||
type Highlight;
|
||||
|
||||
/// The highlight iterator type.
|
||||
type Iterator<'a>: Iterator<Item = (Range<usize>, Self::Highlight)>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
/// Creates a new [`Highlighter`] from its [`Self::Settings`].
|
||||
fn new(settings: &Self::Settings) -> Self;
|
||||
|
||||
/// Updates the [`Highlighter`] with some new [`Self::Settings`].
|
||||
fn update(&mut self, new_settings: &Self::Settings);
|
||||
|
||||
/// Notifies the [`Highlighter`] that the line at the given index has changed.
|
||||
fn change_line(&mut self, line: usize);
|
||||
|
||||
/// Highlights the given line.
|
||||
///
|
||||
/// If a line changed prior to this, the first line provided here will be the
|
||||
/// line that changed.
|
||||
fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_>;
|
||||
|
||||
/// Returns the current line of the [`Highlighter`].
|
||||
///
|
||||
/// If `change_line` has been called, this will normally be the least index
|
||||
/// that changed.
|
||||
fn current_line(&self) -> usize;
|
||||
}
|
||||
|
||||
/// A highlighter that highlights nothing.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PlainText;
|
||||
|
||||
impl Highlighter for PlainText {
|
||||
type Settings = ();
|
||||
type Highlight = ();
|
||||
|
||||
type Iterator<'a> = std::iter::Empty<(Range<usize>, Self::Highlight)>;
|
||||
|
||||
fn new(_settings: &Self::Settings) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn update(&mut self, _new_settings: &Self::Settings) {}
|
||||
|
||||
fn change_line(&mut self, _line: usize) {}
|
||||
|
||||
fn highlight_line(&mut self, _line: &str) -> Self::Iterator<'_> {
|
||||
std::iter::empty()
|
||||
}
|
||||
|
||||
fn current_line(&self) -> usize {
|
||||
usize::MAX
|
||||
}
|
||||
}
|
||||
|
||||
/// The format of some text.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Format<Font> {
|
||||
/// The [`Color`] of the text.
|
||||
pub color: Option<Color>,
|
||||
/// The `Font` of the text.
|
||||
pub font: Option<Font>,
|
||||
}
|
||||
|
||||
impl<Font> Default for Format<Font> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
color: None,
|
||||
font: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
59
core/src/text/paragraph.rs
Normal file
59
core/src/text/paragraph.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use crate::alignment;
|
||||
use crate::text::{Difference, Hit, Text};
|
||||
use crate::{Point, Size};
|
||||
|
||||
/// A text paragraph.
|
||||
pub trait Paragraph: Sized + Default {
|
||||
/// The font of this [`Paragraph`].
|
||||
type Font: Copy + PartialEq;
|
||||
|
||||
/// Creates a new [`Paragraph`] laid out with the given [`Text`].
|
||||
fn with_text(text: Text<'_, 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;
|
||||
|
||||
/// Returns the horizontal alignment of the [`Paragraph`].
|
||||
fn horizontal_alignment(&self) -> alignment::Horizontal;
|
||||
|
||||
/// Returns the vertical alignment of the [`Paragraph`].
|
||||
fn vertical_alignment(&self) -> alignment::Vertical;
|
||||
|
||||
/// Returns the minimum boundaries that can fit the contents of the
|
||||
/// [`Paragraph`].
|
||||
fn min_bounds(&self) -> Size;
|
||||
|
||||
/// Tests whether the provided point is within the boundaries of the
|
||||
/// [`Paragraph`], returning information about the nearest character.
|
||||
fn hit_test(&self, point: Point) -> Option<Hit>;
|
||||
|
||||
/// 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<'_, Self::Font>) {
|
||||
match self.compare(text) {
|
||||
Difference::None => {}
|
||||
Difference::Bounds => {
|
||||
self.resize(text.bounds);
|
||||
}
|
||||
Difference::Shape => {
|
||||
*self = Self::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 minimum height that can fit the contents of the [`Paragraph`].
|
||||
fn min_height(&self) -> f32 {
|
||||
self.min_bounds().height
|
||||
}
|
||||
}
|
||||
|
|
@ -33,12 +33,12 @@ use crate::{Clipboard, Length, Rectangle, Shell};
|
|||
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
|
||||
/// `Mesh2D` primitive in [`iced_wgpu`].
|
||||
///
|
||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.9/examples
|
||||
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.9/examples/bezier_tool
|
||||
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.9/examples/custom_widget
|
||||
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.9/examples/geometry
|
||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.10/examples
|
||||
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.10/examples/bezier_tool
|
||||
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.10/examples/custom_widget
|
||||
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.10/examples/geometry
|
||||
/// [`lyon`]: https://github.com/nical/lyon
|
||||
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/wgpu
|
||||
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.10/wgpu
|
||||
pub trait Widget<Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
|
|
@ -55,6 +55,7 @@ where
|
|||
/// user interface.
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node;
|
||||
|
|
@ -62,7 +63,7 @@ where
|
|||
/// Draws the [`Widget`] using the associated `Renderer`.
|
||||
fn draw(
|
||||
&self,
|
||||
state: &Tree,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
@ -115,6 +116,7 @@ where
|
|||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
_shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ pub use scrollable::Scrollable;
|
|||
pub use text_input::TextInput;
|
||||
|
||||
use crate::widget::Id;
|
||||
use crate::{Rectangle, Vector};
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
|
|
@ -23,6 +24,7 @@ pub trait Operation<T> {
|
|||
fn container(
|
||||
&mut self,
|
||||
id: Option<&Id>,
|
||||
bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
);
|
||||
|
||||
|
|
@ -30,7 +32,14 @@ pub trait Operation<T> {
|
|||
fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {}
|
||||
|
||||
/// Operates on a widget that can be scrolled.
|
||||
fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {}
|
||||
fn scrollable(
|
||||
&mut self,
|
||||
_state: &mut dyn Scrollable,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
_translation: Vector,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Operates on a widget that has text input.
|
||||
fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {}
|
||||
|
|
@ -92,6 +101,7 @@ where
|
|||
fn container(
|
||||
&mut self,
|
||||
id: Option<&Id>,
|
||||
bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
|
||||
) {
|
||||
struct MapRef<'a, A> {
|
||||
|
|
@ -102,11 +112,12 @@ where
|
|||
fn container(
|
||||
&mut self,
|
||||
id: Option<&Id>,
|
||||
bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
|
||||
) {
|
||||
let Self { operation, .. } = self;
|
||||
|
||||
operation.container(id, &mut |operation| {
|
||||
operation.container(id, bounds, &mut |operation| {
|
||||
operate_on_children(&mut MapRef { operation });
|
||||
});
|
||||
}
|
||||
|
|
@ -115,8 +126,10 @@ where
|
|||
&mut self,
|
||||
state: &mut dyn Scrollable,
|
||||
id: Option<&Id>,
|
||||
bounds: Rectangle,
|
||||
translation: Vector,
|
||||
) {
|
||||
self.operation.scrollable(state, id);
|
||||
self.operation.scrollable(state, id, bounds, translation);
|
||||
}
|
||||
|
||||
fn focusable(
|
||||
|
|
@ -145,15 +158,21 @@ where
|
|||
MapRef {
|
||||
operation: operation.as_mut(),
|
||||
}
|
||||
.container(id, operate_on_children);
|
||||
.container(id, bounds, operate_on_children);
|
||||
}
|
||||
|
||||
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>) {
|
||||
self.operation.scrollable(state, id);
|
||||
fn scrollable(
|
||||
&mut self,
|
||||
state: &mut dyn Scrollable,
|
||||
id: Option<&Id>,
|
||||
bounds: Rectangle,
|
||||
translation: Vector,
|
||||
) {
|
||||
self.operation.scrollable(state, id, bounds, translation);
|
||||
}
|
||||
|
||||
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
|
||||
|
|
@ -197,6 +216,7 @@ pub fn scope<T: 'static>(
|
|||
fn container(
|
||||
&mut self,
|
||||
id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>),
|
||||
) {
|
||||
if id == Some(&self.target) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! Operate on widgets that can be focused.
|
||||
use crate::widget::operation::{Operation, Outcome};
|
||||
use crate::widget::Id;
|
||||
use crate::Rectangle;
|
||||
|
||||
/// The internal state of a widget that can be focused.
|
||||
pub trait Focusable {
|
||||
|
|
@ -45,9 +46,10 @@ pub fn focus<T>(target: Id) -> impl Operation<T> {
|
|||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
operate_on_children(self);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,9 +82,10 @@ where
|
|||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
operate_on_children(self);
|
||||
}
|
||||
|
||||
fn finish(&self) -> Outcome<T> {
|
||||
|
|
@ -126,9 +129,10 @@ pub fn focus_previous<T>() -> impl Operation<T> {
|
|||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
operate_on_children(self);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,9 +163,10 @@ pub fn focus_next<T>() -> impl Operation<T> {
|
|||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
operate_on_children(self);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -185,9 +190,10 @@ pub fn find_focused() -> impl Operation<Id> {
|
|||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
operate_on_children(self);
|
||||
}
|
||||
|
||||
fn finish(&self) -> Outcome<Id> {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//! Operate on widgets that can be scrolled.
|
||||
use crate::widget::{Id, Operation};
|
||||
use crate::{Rectangle, Vector};
|
||||
|
||||
/// The internal state of a widget that can be scrolled.
|
||||
pub trait Scrollable {
|
||||
|
|
@ -22,12 +23,19 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {
|
|||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
operate_on_children(self);
|
||||
}
|
||||
|
||||
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
|
||||
fn scrollable(
|
||||
&mut self,
|
||||
state: &mut dyn Scrollable,
|
||||
id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
_translation: Vector,
|
||||
) {
|
||||
if Some(&self.target) == id {
|
||||
state.snap_to(self.offset);
|
||||
}
|
||||
|
|
@ -49,12 +57,19 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
|
|||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
operate_on_children(self);
|
||||
}
|
||||
|
||||
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
|
||||
fn scrollable(
|
||||
&mut self,
|
||||
state: &mut dyn Scrollable,
|
||||
id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
_translation: Vector,
|
||||
) {
|
||||
if Some(&self.target) == id {
|
||||
state.scroll_to(self.offset);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! Operate on widgets that have text input.
|
||||
use crate::widget::operation::Operation;
|
||||
use crate::widget::Id;
|
||||
use crate::Rectangle;
|
||||
|
||||
/// The internal state of a widget that has text input.
|
||||
pub trait TextInput {
|
||||
|
|
@ -34,9 +35,10 @@ pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> {
|
|||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
operate_on_children(self);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,9 +65,10 @@ pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> {
|
|||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
operate_on_children(self);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -93,9 +96,10 @@ pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> {
|
|||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
operate_on_children(self);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,9 +125,10 @@ pub fn select_all<T>(target: Id) -> impl Operation<T> {
|
|||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
operate_on_children(self);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ use crate::alignment;
|
|||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::renderer;
|
||||
use crate::text;
|
||||
use crate::widget::Tree;
|
||||
use crate::{Color, Element, Layout, Length, Pixels, Rectangle, Widget};
|
||||
use crate::text::{self, Paragraph};
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{Color, Element, Layout, Length, Pixels, Point, Rectangle, Widget};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ where
|
|||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
content: Cow<'a, str>,
|
||||
size: Option<f32>,
|
||||
size: Option<Pixels>,
|
||||
line_height: LineHeight,
|
||||
width: Length,
|
||||
height: Length,
|
||||
|
|
@ -53,7 +53,7 @@ where
|
|||
|
||||
/// Sets the size of the [`Text`].
|
||||
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
|
||||
self.size = Some(size.into().0);
|
||||
self.size = Some(size.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -117,11 +117,23 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The internal state of a [`Text`] widget.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct State<P: Paragraph>(P);
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer>
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State<Renderer::Paragraph>>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State(Renderer::Paragraph::default()))
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
|
@ -132,30 +144,29 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits = limits.width(self.width).height(self.height);
|
||||
|
||||
let size = self.size.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
let bounds = renderer.measure(
|
||||
layout(
|
||||
tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
self.height,
|
||||
&self.content,
|
||||
size,
|
||||
self.line_height,
|
||||
self.font.unwrap_or_else(|| renderer.default_font()),
|
||||
limits.max(),
|
||||
self.size,
|
||||
self.font,
|
||||
self.horizontal_alignment,
|
||||
self.vertical_alignment,
|
||||
self.shaping,
|
||||
);
|
||||
|
||||
let size = limits.resolve(bounds);
|
||||
|
||||
layout::Node::new(size)
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
@ -163,22 +174,60 @@ where
|
|||
_cursor_position: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
||||
|
||||
draw(
|
||||
renderer,
|
||||
style,
|
||||
layout,
|
||||
&self.content,
|
||||
self.size,
|
||||
self.line_height,
|
||||
self.font,
|
||||
state,
|
||||
theme.appearance(self.style.clone()),
|
||||
self.horizontal_alignment,
|
||||
self.vertical_alignment,
|
||||
self.shaping,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces the [`layout::Node`] of a [`Text`] widget.
|
||||
pub fn layout<Renderer>(
|
||||
state: &mut State<Renderer::Paragraph>,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
width: Length,
|
||||
height: Length,
|
||||
content: &str,
|
||||
line_height: LineHeight,
|
||||
size: Option<Pixels>,
|
||||
font: Option<Renderer::Font>,
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
vertical_alignment: alignment::Vertical,
|
||||
shaping: Shaping,
|
||||
) -> layout::Node
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
let limits = limits.width(width).height(height);
|
||||
let bounds = limits.max();
|
||||
|
||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||
|
||||
let State(ref mut paragraph) = state;
|
||||
|
||||
paragraph.update(text::Text {
|
||||
content,
|
||||
bounds,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
horizontal_alignment,
|
||||
vertical_alignment,
|
||||
shaping,
|
||||
});
|
||||
|
||||
let size = limits.resolve(paragraph.min_bounds());
|
||||
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
/// Draws text using the same logic as the [`Text`] widget.
|
||||
///
|
||||
/// Specifically:
|
||||
|
|
@ -193,44 +242,31 @@ pub fn draw<Renderer>(
|
|||
renderer: &mut Renderer,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
content: &str,
|
||||
size: Option<f32>,
|
||||
line_height: LineHeight,
|
||||
font: Option<Renderer::Font>,
|
||||
state: &State<Renderer::Paragraph>,
|
||||
appearance: Appearance,
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
vertical_alignment: alignment::Vertical,
|
||||
shaping: Shaping,
|
||||
) where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
let State(ref paragraph) = state;
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let x = match horizontal_alignment {
|
||||
let x = match paragraph.horizontal_alignment() {
|
||||
alignment::Horizontal::Left => bounds.x,
|
||||
alignment::Horizontal::Center => bounds.center_x(),
|
||||
alignment::Horizontal::Right => bounds.x + bounds.width,
|
||||
};
|
||||
|
||||
let y = match vertical_alignment {
|
||||
let y = match paragraph.vertical_alignment() {
|
||||
alignment::Vertical::Top => bounds.y,
|
||||
alignment::Vertical::Center => bounds.center_y(),
|
||||
alignment::Vertical::Bottom => bounds.y + bounds.height,
|
||||
};
|
||||
|
||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
renderer.fill_text(crate::Text {
|
||||
content,
|
||||
size,
|
||||
line_height,
|
||||
bounds: Rectangle { x, y, ..bounds },
|
||||
color: appearance.color.unwrap_or(style.text_color),
|
||||
font: font.unwrap_or_else(|| renderer.default_font()),
|
||||
horizontal_alignment,
|
||||
vertical_alignment,
|
||||
shaping,
|
||||
});
|
||||
renderer.fill_paragraph(
|
||||
paragraph,
|
||||
Point::new(x, y),
|
||||
appearance.color.unwrap_or(style.text_color),
|
||||
);
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Text<'a, Renderer>>
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ impl Tree {
|
|||
Renderer: crate::Renderer,
|
||||
{
|
||||
if self.tag == new.borrow().tag() {
|
||||
new.borrow().diff(self)
|
||||
new.borrow().diff(self);
|
||||
} else {
|
||||
*self = Self::new(new);
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@ impl Tree {
|
|||
new_children,
|
||||
|tree, widget| tree.diff(widget.borrow()),
|
||||
|widget| Self::new(widget.borrow()),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// Reconciliates the children of the tree with the provided list of widgets using custom
|
||||
|
|
@ -107,6 +107,88 @@ impl Tree {
|
|||
}
|
||||
}
|
||||
|
||||
/// Reconciliates 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
|
||||
/// `maybe_changed` closure.
|
||||
pub fn diff_children_custom_with_search<T>(
|
||||
current_children: &mut Vec<Tree>,
|
||||
new_children: &[T],
|
||||
diff: impl Fn(&mut Tree, &T),
|
||||
maybe_changed: impl Fn(usize) -> bool,
|
||||
new_state: impl Fn(&T) -> Tree,
|
||||
) {
|
||||
if new_children.is_empty() {
|
||||
current_children.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if current_children.is_empty() {
|
||||
current_children.extend(new_children.iter().map(new_state));
|
||||
return;
|
||||
}
|
||||
|
||||
let first_maybe_changed = maybe_changed(0);
|
||||
let last_maybe_changed = maybe_changed(current_children.len() - 1);
|
||||
|
||||
if current_children.len() > new_children.len() {
|
||||
if !first_maybe_changed && last_maybe_changed {
|
||||
current_children.truncate(new_children.len());
|
||||
} else {
|
||||
let difference_index = if first_maybe_changed {
|
||||
0
|
||||
} else {
|
||||
(1..current_children.len())
|
||||
.find(|&i| maybe_changed(i))
|
||||
.unwrap_or(0)
|
||||
};
|
||||
|
||||
let _ = current_children.splice(
|
||||
difference_index
|
||||
..difference_index
|
||||
+ (current_children.len() - new_children.len()),
|
||||
std::iter::empty(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if current_children.len() < new_children.len() {
|
||||
let first_maybe_changed = maybe_changed(0);
|
||||
let last_maybe_changed = maybe_changed(current_children.len() - 1);
|
||||
|
||||
if !first_maybe_changed && last_maybe_changed {
|
||||
current_children.extend(
|
||||
new_children[current_children.len()..].iter().map(new_state),
|
||||
);
|
||||
} else {
|
||||
let difference_index = if first_maybe_changed {
|
||||
0
|
||||
} else {
|
||||
(1..current_children.len())
|
||||
.find(|&i| maybe_changed(i))
|
||||
.unwrap_or(0)
|
||||
};
|
||||
|
||||
let _ = current_children.splice(
|
||||
difference_index..difference_index,
|
||||
new_children[difference_index
|
||||
..difference_index
|
||||
+ (new_children.len() - current_children.len())]
|
||||
.iter()
|
||||
.map(new_state),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Merge loop with extend logic (?)
|
||||
for (child_state, new) in
|
||||
current_children.iter_mut().zip(new_children.iter())
|
||||
{
|
||||
diff(child_state, new);
|
||||
}
|
||||
}
|
||||
|
||||
/// The identifier of some widget state.
|
||||
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
||||
pub struct Tag(any::TypeId);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//! Build window-based GUI applications.
|
||||
pub mod icon;
|
||||
pub mod settings;
|
||||
|
||||
mod event;
|
||||
mod id;
|
||||
|
|
@ -7,7 +8,6 @@ mod level;
|
|||
mod mode;
|
||||
mod position;
|
||||
mod redraw_request;
|
||||
mod settings;
|
||||
mod user_attention;
|
||||
|
||||
pub use event::Event;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::Size;
|
|||
|
||||
use std::mem;
|
||||
|
||||
/// Builds an [`Icon`] from its RGBA pixels in the sRGB color space.
|
||||
/// Builds an [`Icon`] from its RGBA pixels in the `sRGB` color space.
|
||||
pub fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u32,
|
||||
|
|
@ -49,7 +49,7 @@ impl Icon {
|
|||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
/// An error produced when using [`Icon::from_rgba`] with invalid arguments.
|
||||
/// An error produced when using [`from_rgba`] with invalid arguments.
|
||||
pub enum Error {
|
||||
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
||||
/// safely interpreted as 32bpp RGBA pixels.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ pub enum RedrawRequest {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::time::{Duration, Instant};
|
||||
use crate::time::{Duration, Instant};
|
||||
|
||||
#[test]
|
||||
fn ordering() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use crate::window::{Icon, Level, Position};
|
||||
|
||||
//! Configure your windows.
|
||||
#[cfg(target_os = "windows")]
|
||||
#[path = "settings/windows.rs"]
|
||||
mod platform;
|
||||
|
|
@ -8,6 +7,10 @@ mod platform;
|
|||
#[path = "settings/macos.rs"]
|
||||
mod platform;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[path = "settings/linux.rs"]
|
||||
mod platform;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[path = "settings/wasm.rs"]
|
||||
mod platform;
|
||||
|
|
@ -15,13 +18,15 @@ mod platform;
|
|||
#[cfg(not(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "linux",
|
||||
target_arch = "wasm32"
|
||||
)))]
|
||||
#[path = "settings/other.rs"]
|
||||
mod platform;
|
||||
|
||||
pub use platform::PlatformSpecific;
|
||||
use crate::window::{Icon, Level, Position};
|
||||
|
||||
pub use platform::PlatformSpecific;
|
||||
/// The window settings of an application.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Settings {
|
||||
|
|
@ -70,8 +75,8 @@ pub struct Settings {
|
|||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: (1024, 768),
|
||||
position: Position::default(),
|
||||
min_size: None,
|
||||
|
|
@ -82,8 +87,8 @@ impl Default for Settings {
|
|||
transparent: false,
|
||||
level: Level::default(),
|
||||
icon: None,
|
||||
platform_specific: Default::default(),
|
||||
exit_on_close_request: true,
|
||||
platform_specific: PlatformSpecific::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
core/src/window/settings/linux.rs
Normal file
11
core/src/window/settings/linux.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
//! Platform specific settings for Linux.
|
||||
|
||||
/// The platform specific window settings of an application.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct PlatformSpecific {
|
||||
/// Sets the application id of the window.
|
||||
///
|
||||
/// 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,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue