summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ROADMAP.md2
-rw-r--r--core/src/widget/operation/scrollable.rs41
-rw-r--r--examples/integration/src/main.rs3
-rw-r--r--examples/scrollable/src/main.rs6
-rw-r--r--wgpu/Cargo.toml6
-rw-r--r--wgpu/src/image/atlas.rs6
-rw-r--r--wgpu/src/window/compositor.rs3
-rw-r--r--widget/src/lazy/responsive.rs54
-rw-r--r--widget/src/scrollable.rs107
-rw-r--r--widget/src/slider.rs4
-rw-r--r--widget/src/text_input.rs7
-rw-r--r--widget/src/vertical_slider.rs4
12 files changed, 173 insertions, 70 deletions
diff --git a/ROADMAP.md b/ROADMAP.md
index c30c591b..f1893664 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -49,7 +49,7 @@ As a first approach, we could expose the underlying renderer directly here, and
In the long run, we could expose a renderer-agnostic abstraction to perform the drawing.
[#32]: https://github.com/iced-rs/iced/issues/32
-[#343] https://github.com/iced-rs/iced/issues/343
+[#343]: https://github.com/iced-rs/iced/issues/343
### Text shaping and font fallback ([#33])
[`wgpu_glyph`] uses [`glyph_brush`], which in turn uses [`rusttype`]. While the current implementation is able to layout text quite nicely, it does not perform any [text shaping].
diff --git a/core/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs
index 3b20631f..f947344d 100644
--- a/core/src/widget/operation/scrollable.rs
+++ b/core/src/widget/operation/scrollable.rs
@@ -5,6 +5,9 @@ use crate::widget::{Id, Operation};
pub trait Scrollable {
/// Snaps the scroll of the widget to the given `percentage` along the horizontal & vertical axis.
fn snap_to(&mut self, offset: RelativeOffset);
+
+ /// Scroll the widget to the given [`AbsoluteOffset`] along the horizontal & vertical axis.
+ fn scroll_to(&mut self, offset: AbsoluteOffset);
}
/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to
@@ -34,7 +37,43 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {
SnapTo { target, offset }
}
-/// The amount of offset in each direction of a [`Scrollable`].
+/// Produces an [`Operation`] that scrolls the widget with the given [`Id`] to
+/// the provided [`AbsoluteOffset`].
+pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
+ struct ScrollTo {
+ target: Id,
+ offset: AbsoluteOffset,
+ }
+
+ impl<T> Operation<T> for ScrollTo {
+ fn container(
+ &mut self,
+ _id: Option<&Id>,
+ 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>) {
+ if Some(&self.target) == id {
+ state.scroll_to(self.offset);
+ }
+ }
+ }
+
+ ScrollTo { target, offset }
+}
+
+/// The amount of absolute offset in each direction of a [`Scrollable`].
+#[derive(Debug, Clone, Copy, PartialEq, Default)]
+pub struct AbsoluteOffset {
+ /// The amount of horizontal offset
+ pub x: f32,
+ /// The amount of vertical offset
+ pub y: f32,
+}
+
+/// The amount of relative offset in each direction of a [`Scrollable`].
///
/// A value of `0.0` means start, while `1.0` means end.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs
index d9aae7b9..c935aca7 100644
--- a/examples/integration/src/main.rs
+++ b/examples/integration/src/main.rs
@@ -102,9 +102,8 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
capabilities
.formats
.iter()
- .filter(|format| format.describe().srgb)
.copied()
- .next()
+ .find(wgpu::TextureFormat::is_srgb)
.or_else(|| capabilities.formats.first().copied())
.expect("Get preferred format"),
adapter
diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs
index 2e99b1ac..97344c94 100644
--- a/examples/scrollable/src/main.rs
+++ b/examples/scrollable/src/main.rs
@@ -36,7 +36,7 @@ enum Message {
ScrollerWidthChanged(u16),
ScrollToBeginning,
ScrollToEnd,
- Scrolled(scrollable::RelativeOffset),
+ Scrolled(scrollable::Viewport),
}
impl Application for ScrollableDemo {
@@ -104,8 +104,8 @@ impl Application for ScrollableDemo {
self.current_scroll_offset,
)
}
- Message::Scrolled(offset) => {
- self.current_scroll_offset = offset;
+ Message::Scrolled(viewport) => {
+ self.current_scroll_offset = viewport.relative_offset();
Command::none()
}
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index 6934ae49..41eb4c23 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -13,7 +13,7 @@ image = ["iced_graphics/image"]
svg = ["resvg"]
[dependencies]
-wgpu = "0.15"
+wgpu = "0.16"
raw-window-handle = "0.5"
log = "0.4"
guillotiere = "0.6"
@@ -23,7 +23,7 @@ once_cell = "1.0"
rustc-hash = "1.1"
[target.'cfg(target_arch = "wasm32")'.dependencies]
-wgpu = { version = "0.15", features = ["webgl"] }
+wgpu = { version = "0.16", features = ["webgl"] }
[dependencies.twox-hash]
version = "1.6"
@@ -44,7 +44,7 @@ path = "../graphics"
[dependencies.glyphon]
version = "0.2"
git = "https://github.com/hecrj/glyphon.git"
-rev = "504aa8a9a1fb42726f02fa244b70119e7ca25933"
+rev = "f145067d292082abdd1f2b2481812d4a52c394ec"
[dependencies.encase]
version = "0.3.0"
diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs
index 39b6e5d2..366fe623 100644
--- a/wgpu/src/image/atlas.rs
+++ b/wgpu/src/image/atlas.rs
@@ -14,8 +14,6 @@ pub const SIZE: u32 = 2048;
use crate::core::Size;
-use std::num::NonZeroU32;
-
#[derive(Debug)]
pub struct Atlas {
texture: wgpu::Texture,
@@ -308,8 +306,8 @@ impl Atlas {
data,
wgpu::ImageDataLayout {
offset: offset as u64,
- bytes_per_row: NonZeroU32::new(4 * image_width + padding),
- rows_per_image: NonZeroU32::new(image_height),
+ bytes_per_row: Some(4 * image_width + padding),
+ rows_per_image: Some(image_height),
},
extent,
);
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index c55ffa46..500458e8 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -72,9 +72,8 @@ impl<Theme> Compositor<Theme> {
capabilities
.formats
.iter()
- .filter(|format| format.describe().srgb)
.copied()
- .next()
+ .find(wgpu::TextureFormat::is_srgb)
.or_else(|| {
log::warn!("No sRGB format found!");
diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs
index 7b2fc37c..b41d978b 100644
--- a/widget/src/lazy/responsive.rs
+++ b/widget/src/lazy/responsive.rs
@@ -42,7 +42,7 @@ where
view: Box::new(view),
content: RefCell::new(Content {
size: Size::ZERO,
- layout: layout::Node::new(Size::ZERO),
+ layout: None,
element: Element::new(horizontal_space(0)),
}),
}
@@ -51,7 +51,7 @@ where
struct Content<'a, Message, Renderer> {
size: Size,
- layout: layout::Node,
+ layout: Option<layout::Node>,
element: Element<'a, Message, Renderer>,
}
@@ -59,10 +59,19 @@ impl<'a, Message, Renderer> Content<'a, Message, Renderer>
where
Renderer: core::Renderer,
{
+ fn layout(&mut self, renderer: &Renderer) {
+ if self.layout.is_none() {
+ self.layout =
+ Some(self.element.as_widget().layout(
+ renderer,
+ &layout::Limits::new(Size::ZERO, self.size),
+ ));
+ }
+ }
+
fn update(
&mut self,
tree: &mut Tree,
- renderer: &Renderer,
new_size: Size,
view: &dyn Fn(Size) -> Element<'a, Message, Renderer>,
) {
@@ -74,11 +83,6 @@ where
self.size = new_size;
tree.diff(&self.element);
-
- self.layout = self
- .element
- .as_widget()
- .layout(renderer, &layout::Limits::new(Size::ZERO, self.size));
}
fn resolve<R, T>(
@@ -97,11 +101,12 @@ where
where
R: Deref<Target = Renderer>,
{
- self.update(tree, renderer.deref(), layout.bounds().size(), view);
+ self.update(tree, layout.bounds().size(), view);
+ self.layout(renderer.deref());
let content_layout = Layout::with_offset(
layout.position() - Point::ORIGIN,
- &self.layout,
+ self.layout.as_ref().unwrap(),
);
f(tree, renderer, content_layout, &mut self.element)
@@ -179,7 +184,10 @@ where
let state = tree.state.downcast_mut::<State>();
let mut content = self.content.borrow_mut();
- content.resolve(
+ let mut local_messages = vec![];
+ let mut local_shell = Shell::new(&mut local_messages);
+
+ let status = content.resolve(
&mut state.tree.borrow_mut(),
renderer,
layout,
@@ -192,10 +200,18 @@ where
cursor_position,
renderer,
clipboard,
- shell,
+ &mut local_shell,
)
},
- )
+ );
+
+ if local_shell.is_layout_invalid() {
+ content.layout = None;
+ }
+
+ shell.merge(local_shell, std::convert::identity);
+
+ status
}
fn draw(
@@ -274,22 +290,18 @@ where
types: PhantomData,
overlay_builder: |content: &mut RefMut<'_, Content<'_, _, _>>,
tree| {
- content.update(
- tree,
- renderer,
- layout.bounds().size(),
- &self.view,
- );
+ content.update(tree, layout.bounds().size(), &self.view);
+ content.layout(renderer);
let Content {
element,
- layout: content_layout,
+ layout: content_layout_node,
..
} = content.deref_mut();
let content_layout = Layout::with_offset(
layout.bounds().position() - Point::ORIGIN,
- content_layout,
+ content_layout_node.as_ref().unwrap(),
);
element
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index 161ae664..fd51a6a8 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -16,7 +16,7 @@ use crate::core::{
use crate::runtime::Command;
pub use crate::style::scrollable::{Scrollbar, Scroller, StyleSheet};
-pub use operation::scrollable::RelativeOffset;
+pub use operation::scrollable::{AbsoluteOffset, RelativeOffset};
/// A widget that can vertically display an infinite amount of content with a
/// scrollbar.
@@ -32,7 +32,7 @@ where
vertical: Properties,
horizontal: Option<Properties>,
content: Element<'a, Message, Renderer>,
- on_scroll: Option<Box<dyn Fn(RelativeOffset) -> Message + 'a>>,
+ on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@@ -87,12 +87,8 @@ where
/// Sets a function to call when the [`Scrollable`] is scrolled.
///
- /// The function takes the new relative x & y offset of the [`Scrollable`]
- /// (e.g. `0` means beginning, while `1` means end).
- pub fn on_scroll(
- mut self,
- f: impl Fn(RelativeOffset) -> Message + 'a,
- ) -> Self {
+ /// The function takes the [`Viewport`] of the [`Scrollable`]
+ pub fn on_scroll(mut self, f: impl Fn(Viewport) -> Message + 'a) -> Self {
self.on_scroll = Some(Box::new(f));
self
}
@@ -390,6 +386,15 @@ pub fn snap_to<Message: 'static>(
Command::widget(operation::scrollable::snap_to(id.0, offset))
}
+/// Produces a [`Command`] that scrolls the [`Scrollable`] with the given [`Id`]
+/// to the provided [`AbsoluteOffset`] along the x & y axis.
+pub fn scroll_to<Message: 'static>(
+ id: Id,
+ offset: AbsoluteOffset,
+) -> Command<Message> {
+ Command::widget(operation::scrollable::scroll_to(id.0, offset))
+}
+
/// Computes the layout of a [`Scrollable`].
pub fn layout<Renderer>(
renderer: &Renderer,
@@ -430,7 +435,7 @@ pub fn update<Message>(
shell: &mut Shell<'_, Message>,
vertical: &Properties,
horizontal: Option<&Properties>,
- on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>,
+ on_scroll: &Option<Box<dyn Fn(Viewport) -> Message + '_>>,
update_content: impl FnOnce(
Event,
Layout<'_>,
@@ -890,7 +895,7 @@ pub fn draw<Renderer>(
fn notify_on_scroll<Message>(
state: &mut State,
- on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>,
+ on_scroll: &Option<Box<dyn Fn(Viewport) -> Message + '_>>,
bounds: Rectangle,
content_bounds: Rectangle,
shell: &mut Shell<'_, Message>,
@@ -902,31 +907,36 @@ fn notify_on_scroll<Message>(
return;
}
- let x = state.offset_x.absolute(bounds.width, content_bounds.width)
- / (content_bounds.width - bounds.width);
+ let viewport = Viewport {
+ offset_x: state.offset_x,
+ offset_y: state.offset_y,
+ bounds,
+ content_bounds,
+ };
- let y = state
- .offset_y
- .absolute(bounds.height, content_bounds.height)
- / (content_bounds.height - bounds.height);
+ // Don't publish redundant viewports to shell
+ if let Some(last_notified) = state.last_notified {
+ let last_relative_offset = last_notified.relative_offset();
+ let current_relative_offset = viewport.relative_offset();
- let new_offset = RelativeOffset { x, y };
+ let last_absolute_offset = last_notified.absolute_offset();
+ let current_absolute_offset = viewport.absolute_offset();
- // Don't publish redundant offsets to shell
- if let Some(prev_offset) = state.last_notified {
let unchanged = |a: f32, b: f32| {
(a - b).abs() <= f32::EPSILON || (a.is_nan() && b.is_nan())
};
- if unchanged(prev_offset.x, new_offset.x)
- && unchanged(prev_offset.y, new_offset.y)
+ if unchanged(last_relative_offset.x, current_relative_offset.x)
+ && unchanged(last_relative_offset.y, current_relative_offset.y)
+ && unchanged(last_absolute_offset.x, current_absolute_offset.x)
+ && unchanged(last_absolute_offset.y, current_absolute_offset.y)
{
return;
}
}
- shell.publish(on_scroll(new_offset));
- state.last_notified = Some(new_offset);
+ shell.publish(on_scroll(viewport));
+ state.last_notified = Some(viewport);
}
}
@@ -939,7 +949,7 @@ pub struct State {
offset_x: Offset,
x_scroller_grabbed_at: Option<f32>,
keyboard_modifiers: keyboard::Modifiers,
- last_notified: Option<RelativeOffset>,
+ last_notified: Option<Viewport>,
}
impl Default for State {
@@ -960,6 +970,10 @@ impl operation::Scrollable for State {
fn snap_to(&mut self, offset: RelativeOffset) {
State::snap_to(self, offset);
}
+
+ fn scroll_to(&mut self, offset: AbsoluteOffset) {
+ State::scroll_to(self, offset)
+ }
}
#[derive(Debug, Clone, Copy)]
@@ -969,18 +983,51 @@ enum Offset {
}
impl Offset {
- fn absolute(self, window: f32, content: f32) -> f32 {
+ fn absolute(self, viewport: f32, content: f32) -> f32 {
match self {
Offset::Absolute(absolute) => {
- absolute.min((content - window).max(0.0))
+ absolute.min((content - viewport).max(0.0))
}
Offset::Relative(percentage) => {
- ((content - window) * percentage).max(0.0)
+ ((content - viewport) * percentage).max(0.0)
}
}
}
}
+/// The current [`Viewport`] of the [`Scrollable`].
+#[derive(Debug, Clone, Copy)]
+pub struct Viewport {
+ offset_x: Offset,
+ offset_y: Offset,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+}
+
+impl Viewport {
+ /// Returns the [`AbsoluteOffset`] of the current [`Viewport`].
+ pub fn absolute_offset(&self) -> AbsoluteOffset {
+ let x = self
+ .offset_x
+ .absolute(self.bounds.width, self.content_bounds.width);
+ let y = self
+ .offset_y
+ .absolute(self.bounds.height, self.content_bounds.height);
+
+ AbsoluteOffset { x, y }
+ }
+
+ /// Returns the [`RelativeOffset`] of the current [`Viewport`].
+ pub fn relative_offset(&self) -> RelativeOffset {
+ let AbsoluteOffset { x, y } = self.absolute_offset();
+
+ let x = x / (self.content_bounds.width - self.bounds.width);
+ let y = y / (self.content_bounds.height - self.bounds.height);
+
+ RelativeOffset { x, y }
+ }
+}
+
impl State {
/// Creates a new [`State`] with the scrollbar(s) at the beginning.
pub fn new() -> Self {
@@ -1046,6 +1093,12 @@ impl State {
self.offset_y = Offset::Relative(offset.y.clamp(0.0, 1.0));
}
+ /// Scroll to the provided [`AbsoluteOffset`].
+ pub fn scroll_to(&mut self, offset: AbsoluteOffset) {
+ self.offset_x = Offset::Absolute(offset.x.max(0.0));
+ self.offset_y = Offset::Absolute(offset.y.max(0.0));
+ }
+
/// Unsnaps the current scroll position, if snapped, given the bounds of the
/// [`Scrollable`] and its contents.
pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) {
diff --git a/widget/src/slider.rs b/widget/src/slider.rs
index 5a884e21..18a49665 100644
--- a/widget/src/slider.rs
+++ b/widget/src/slider.rs
@@ -389,7 +389,7 @@ pub fn draw<T, R>(
let offset = if range_start >= range_end {
0.0
} else {
- (bounds.width - handle_width / 2.0) * (value - range_start)
+ (bounds.width - handle_width) * (value - range_start)
/ (range_end - range_start)
};
@@ -415,7 +415,7 @@ pub fn draw<T, R>(
bounds: Rectangle {
x: bounds.x + offset + handle_width / 2.0,
y: rail_y - style.rail.width / 2.0,
- width: bounds.width - offset,
+ width: bounds.width - offset - handle_width / 2.0,
height: style.rail.width,
},
border_radius: Default::default(),
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index 32d0b1f8..364ec3cd 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -973,9 +973,12 @@ pub fn draw<Renderer>(
size: icon.size.unwrap_or_else(|| renderer.default_size()),
font: icon.font,
color: appearance.icon_color,
- bounds: icon_layout.bounds(),
+ bounds: Rectangle {
+ y: text_bounds.center_y(),
+ ..icon_layout.bounds()
+ },
horizontal_alignment: alignment::Horizontal::Left,
- vertical_alignment: alignment::Vertical::Top,
+ vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced,
});
}
diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs
index a7551aef..2635611d 100644
--- a/widget/src/vertical_slider.rs
+++ b/widget/src/vertical_slider.rs
@@ -387,7 +387,7 @@ pub fn draw<T, R>(
let offset = if range_start >= range_end {
0.0
} else {
- (bounds.height - handle_width / 2.0) * (value - range_end)
+ (bounds.height - handle_width) * (value - range_end)
/ (range_start - range_end)
};
@@ -414,7 +414,7 @@ pub fn draw<T, R>(
x: rail_x - style.rail.width / 2.0,
y: bounds.y + offset + handle_width / 2.0,
width: style.rail.width,
- height: bounds.height - offset,
+ height: bounds.height - offset - handle_width / 2.0,
},
border_radius: Default::default(),
border_width: 0.0,