Merge branch 'master' into advanced-text
This commit is contained in:
commit
8e8808f0e1
12 changed files with 173 additions and 70 deletions
|
|
@ -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].
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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!");
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue