Skip to content

Commit e90ff41

Browse files
committed
Implement pop widget 🎉
1 parent 3428a3d commit e90ff41

File tree

4 files changed

+264
-1
lines changed

4 files changed

+264
-1
lines changed

core/src/rectangle.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,20 @@ impl Rectangle<f32> {
143143
&& point.y < self.y + self.height
144144
}
145145

146+
/// Returns the minimum distance from the given [`Point`] to any of the edges
147+
/// of the [`Rectangle`].
148+
pub fn distance(&self, point: Point) -> f32 {
149+
let center = self.center();
150+
151+
let distance_x =
152+
((point.x - center.x).abs() - self.width / 2.0).max(0.0);
153+
154+
let distance_y =
155+
((point.y - center.y).abs() - self.height / 2.0).max(0.0);
156+
157+
distance_x.hypot(distance_y)
158+
}
159+
146160
/// Returns true if the current [`Rectangle`] is completely within the given
147161
/// `container`.
148162
pub fn is_within(&self, container: &Rectangle) -> bool {

widget/src/helpers.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::text_input::{self, TextInput};
2424
use crate::toggler::{self, Toggler};
2525
use crate::tooltip::{self, Tooltip};
2626
use crate::vertical_slider::{self, VerticalSlider};
27-
use crate::{Column, MouseArea, Pin, Row, Space, Stack, Themer};
27+
use crate::{Column, MouseArea, Pin, Pop, Row, Space, Stack, Themer};
2828

2929
use std::borrow::Borrow;
3030
use std::ops::RangeInclusive;
@@ -970,6 +970,20 @@ where
970970
})
971971
}
972972

973+
/// Creates a new [`Pop`] widget.
974+
///
975+
/// A [`Pop`] widget can generate messages when it pops in and out of view.
976+
/// It can even notify you with anticipation at a given distance!
977+
pub fn pop<'a, Message, Theme, Renderer>(
978+
content: impl Into<Element<'a, Message, Theme, Renderer>>,
979+
) -> Pop<'a, Message, Theme, Renderer>
980+
where
981+
Renderer: core::Renderer,
982+
Message: Clone,
983+
{
984+
Pop::new(content)
985+
}
986+
973987
/// Creates a new [`Scrollable`] with the provided content.
974988
///
975989
/// Scrollables let users navigate an endless amount of content with a scrollbar.

widget/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub mod keyed;
2525
pub mod overlay;
2626
pub mod pane_grid;
2727
pub mod pick_list;
28+
pub mod pop;
2829
pub mod progress_bar;
2930
pub mod radio;
3031
pub mod rule;
@@ -66,6 +67,8 @@ pub use pick_list::PickList;
6667
#[doc(no_inline)]
6768
pub use pin::Pin;
6869
#[doc(no_inline)]
70+
pub use pop::Pop;
71+
#[doc(no_inline)]
6972
pub use progress_bar::ProgressBar;
7073
#[doc(no_inline)]
7174
pub use radio::Radio;

widget/src/pop.rs

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
//! Generate messages when content pops in and out of view.
2+
use crate::core::layout;
3+
use crate::core::mouse;
4+
use crate::core::overlay;
5+
use crate::core::renderer;
6+
use crate::core::widget;
7+
use crate::core::widget::tree::{self, Tree};
8+
use crate::core::window;
9+
use crate::core::{
10+
self, Clipboard, Element, Event, Layout, Length, Pixels, Rectangle, Shell,
11+
Size, Vector, Widget,
12+
};
13+
14+
/// A widget that can generate messages when its content pops in and out of view.
15+
///
16+
/// It can even notify you with anticipation at a given distance!
17+
#[allow(missing_debug_implementations)]
18+
pub struct Pop<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> {
19+
content: Element<'a, Message, Theme, Renderer>,
20+
on_show: Option<Message>,
21+
on_hide: Option<Message>,
22+
anticipate: Pixels,
23+
}
24+
25+
impl<'a, Message, Theme, Renderer> Pop<'a, Message, Theme, Renderer>
26+
where
27+
Renderer: core::Renderer,
28+
Message: Clone,
29+
{
30+
/// Creates a new [`Pop`] widget with the given content.
31+
pub fn new(
32+
content: impl Into<Element<'a, Message, Theme, Renderer>>,
33+
) -> Self {
34+
Self {
35+
content: content.into(),
36+
on_show: None,
37+
on_hide: None,
38+
anticipate: Pixels::ZERO,
39+
}
40+
}
41+
42+
/// Sets the message to be produced when the content pops into view.
43+
pub fn on_show(mut self, on_show: Message) -> Self {
44+
self.on_show = Some(on_show);
45+
self
46+
}
47+
48+
/// Sets the message to be produced when the content pops out of view.
49+
pub fn on_hide(mut self, on_hide: Message) -> Self {
50+
self.on_hide = Some(on_hide);
51+
self
52+
}
53+
54+
/// Sets the distance in [`Pixels`] to use in anticipation of the
55+
/// content popping into view.
56+
///
57+
/// This can be quite useful to lazily load items in a long scrollable
58+
/// behind the scenes before the user can notice it!
59+
pub fn anticipate(mut self, distance: impl Into<Pixels>) -> Self {
60+
self.anticipate = distance.into();
61+
self
62+
}
63+
}
64+
65+
#[derive(Debug, Clone, Copy, Default)]
66+
struct State {
67+
has_popped_in: bool,
68+
}
69+
70+
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
71+
for Pop<'_, Message, Theme, Renderer>
72+
where
73+
Message: Clone,
74+
Renderer: core::Renderer,
75+
{
76+
fn tag(&self) -> tree::Tag {
77+
tree::Tag::of::<State>()
78+
}
79+
80+
fn state(&self) -> tree::State {
81+
tree::State::new(State::default())
82+
}
83+
84+
fn children(&self) -> Vec<Tree> {
85+
vec![Tree::new(&self.content)]
86+
}
87+
88+
fn diff(&self, tree: &mut Tree) {
89+
tree.diff_children(&[&self.content]);
90+
}
91+
92+
fn update(
93+
&mut self,
94+
tree: &mut Tree,
95+
event: Event,
96+
layout: Layout<'_>,
97+
cursor: mouse::Cursor,
98+
renderer: &Renderer,
99+
clipboard: &mut dyn Clipboard,
100+
shell: &mut Shell<'_, Message>,
101+
viewport: &Rectangle,
102+
) {
103+
if let Event::Window(window::Event::RedrawRequested(_)) = &event {
104+
let state = tree.state.downcast_mut::<State>();
105+
let bounds = layout.bounds();
106+
107+
let top_left_distance = viewport.distance(bounds.position());
108+
109+
let bottom_right_distance = viewport
110+
.distance(bounds.position() + Vector::from(bounds.size()));
111+
112+
let distance = top_left_distance.min(bottom_right_distance);
113+
114+
if state.has_popped_in {
115+
if let Some(on_hide) = &self.on_hide {
116+
if distance > self.anticipate.0 {
117+
state.has_popped_in = false;
118+
shell.publish(on_hide.clone());
119+
}
120+
}
121+
} else if let Some(on_show) = &self.on_show {
122+
if distance <= self.anticipate.0 {
123+
state.has_popped_in = true;
124+
shell.publish(on_show.clone());
125+
}
126+
}
127+
}
128+
129+
self.content.as_widget_mut().update(
130+
tree, event, layout, cursor, renderer, clipboard, shell, viewport,
131+
);
132+
}
133+
134+
fn size(&self) -> Size<Length> {
135+
self.content.as_widget().size()
136+
}
137+
138+
fn size_hint(&self) -> Size<Length> {
139+
self.content.as_widget().size_hint()
140+
}
141+
142+
fn layout(
143+
&self,
144+
tree: &mut Tree,
145+
renderer: &Renderer,
146+
limits: &layout::Limits,
147+
) -> layout::Node {
148+
self.content
149+
.as_widget()
150+
.layout(&mut tree.children[0], renderer, limits)
151+
}
152+
153+
fn draw(
154+
&self,
155+
tree: &Tree,
156+
renderer: &mut Renderer,
157+
theme: &Theme,
158+
style: &renderer::Style,
159+
layout: layout::Layout<'_>,
160+
cursor: mouse::Cursor,
161+
viewport: &Rectangle,
162+
) {
163+
self.content.as_widget().draw(
164+
&tree.children[0],
165+
renderer,
166+
theme,
167+
style,
168+
layout,
169+
cursor,
170+
viewport,
171+
);
172+
}
173+
174+
fn operate(
175+
&self,
176+
tree: &mut Tree,
177+
layout: core::Layout<'_>,
178+
renderer: &Renderer,
179+
operation: &mut dyn widget::Operation,
180+
) {
181+
self.content.as_widget().operate(
182+
&mut tree.children[0],
183+
layout,
184+
renderer,
185+
operation,
186+
);
187+
}
188+
189+
fn mouse_interaction(
190+
&self,
191+
tree: &Tree,
192+
layout: core::Layout<'_>,
193+
cursor: mouse::Cursor,
194+
viewport: &Rectangle,
195+
renderer: &Renderer,
196+
) -> mouse::Interaction {
197+
self.content.as_widget().mouse_interaction(
198+
&tree.children[0],
199+
layout,
200+
cursor,
201+
viewport,
202+
renderer,
203+
)
204+
}
205+
206+
fn overlay<'b>(
207+
&'b mut self,
208+
tree: &'b mut Tree,
209+
layout: core::Layout<'_>,
210+
renderer: &Renderer,
211+
translation: core::Vector,
212+
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
213+
self.content.as_widget_mut().overlay(
214+
&mut tree.children[0],
215+
layout,
216+
renderer,
217+
translation,
218+
)
219+
}
220+
}
221+
222+
impl<'a, Message, Theme, Renderer> From<Pop<'a, Message, Theme, Renderer>>
223+
for Element<'a, Message, Theme, Renderer>
224+
where
225+
Renderer: core::Renderer + 'a,
226+
Theme: 'a,
227+
Message: Clone + 'a,
228+
{
229+
fn from(pop: Pop<'a, Message, Theme, Renderer>) -> Self {
230+
Element::new(pop)
231+
}
232+
}

0 commit comments

Comments
 (0)