Skip to content

Commit e8f8216

Browse files
authored
Merge pull request #2673 from iced-rs/feature/pin-widget
`pin` widget
2 parents 6ccc828 + a805177 commit e8f8216

File tree

4 files changed

+329
-2
lines changed

4 files changed

+329
-2
lines changed

examples/layout/src/main.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use iced::keyboard;
33
use iced::mouse;
44
use iced::widget::{
55
button, canvas, center, checkbox, column, container, horizontal_rule,
6-
horizontal_space, pick_list, row, scrollable, text, vertical_rule,
6+
horizontal_space, pick_list, pin, row, scrollable, stack, text,
7+
vertical_rule,
78
};
89
use iced::{
910
color, Center, Element, Fill, Font, Length, Point, Rectangle, Renderer,
@@ -151,6 +152,10 @@ impl Example {
151152
title: "Quotes",
152153
view: quotes,
153154
},
155+
Self {
156+
title: "Pinning",
157+
view: pinning,
158+
},
154159
];
155160

156161
fn is_first(self) -> bool {
@@ -309,6 +314,23 @@ fn quotes<'a>() -> Element<'a, Message> {
309314
.into()
310315
}
311316

317+
fn pinning<'a>() -> Element<'a, Message> {
318+
column![
319+
"The pin widget can be used to position a widget \
320+
at some fixed coordinates inside some other widget.",
321+
stack![
322+
container(pin("• (50, 50)").x(50).y(50))
323+
.width(500)
324+
.height(500)
325+
.style(container::bordered_box),
326+
pin("• (300, 300)").x(300).y(300),
327+
]
328+
]
329+
.align_x(Center)
330+
.spacing(10)
331+
.into()
332+
}
333+
312334
fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> {
313335
struct Square;
314336

widget/src/helpers.rs

Lines changed: 33 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, Row, Space, Stack, Themer};
27+
use crate::{Column, MouseArea, Pin, Row, Space, Stack, Themer};
2828

2929
use std::borrow::Borrow;
3030
use std::ops::RangeInclusive;
@@ -249,6 +249,38 @@ where
249249
container(content).center(Length::Fill)
250250
}
251251

252+
/// Creates a new [`Pin`] widget with the given content.
253+
///
254+
/// A [`Pin`] widget positions its contents at some fixed coordinates inside of its boundaries.
255+
///
256+
/// # Example
257+
/// ```no_run
258+
/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::core::Length::Fill; }
259+
/// # pub type State = ();
260+
/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
261+
/// use iced::widget::pin;
262+
/// use iced::Fill;
263+
///
264+
/// enum Message {
265+
/// // ...
266+
/// }
267+
///
268+
/// fn view(state: &State) -> Element<'_, Message> {
269+
/// pin("This text is displayed at coordinates (50, 50)!")
270+
/// .x(50)
271+
/// .y(50)
272+
/// .into()
273+
/// }
274+
/// ```
275+
pub fn pin<'a, Message, Theme, Renderer>(
276+
content: impl Into<Element<'a, Message, Theme, Renderer>>,
277+
) -> Pin<'a, Message, Theme, Renderer>
278+
where
279+
Renderer: core::Renderer,
280+
{
281+
Pin::new(content)
282+
}
283+
252284
/// Creates a new [`Column`] with the given children.
253285
///
254286
/// Columns distribute their children vertically.

widget/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub use iced_runtime::core;
1111
mod action;
1212
mod column;
1313
mod mouse_area;
14+
mod pin;
1415
mod row;
1516
mod space;
1617
mod stack;
@@ -63,6 +64,8 @@ pub use pane_grid::PaneGrid;
6364
#[doc(no_inline)]
6465
pub use pick_list::PickList;
6566
#[doc(no_inline)]
67+
pub use pin::Pin;
68+
#[doc(no_inline)]
6669
pub use progress_bar::ProgressBar;
6770
#[doc(no_inline)]
6871
pub use radio::Radio;

widget/src/pin.rs

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
//! A pin widget positions a widget at some fixed coordinates inside its boundaries.
2+
//!
3+
//! # Example
4+
//! ```no_run
5+
//! # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::core::Length::Fill; }
6+
//! # pub type State = ();
7+
//! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
8+
//! use iced::widget::pin;
9+
//! use iced::Fill;
10+
//!
11+
//! enum Message {
12+
//! // ...
13+
//! }
14+
//!
15+
//! fn view(state: &State) -> Element<'_, Message> {
16+
//! pin("This text is displayed at coordinates (50, 50)!")
17+
//! .x(50)
18+
//! .y(50)
19+
//! .into()
20+
//! }
21+
//! ```
22+
use crate::core::layout;
23+
use crate::core::mouse;
24+
use crate::core::overlay;
25+
use crate::core::renderer;
26+
use crate::core::widget;
27+
use crate::core::{
28+
self, Clipboard, Element, Event, Layout, Length, Pixels, Point, Rectangle,
29+
Shell, Size, Vector, Widget,
30+
};
31+
32+
/// A widget that positions its contents at some fixed coordinates inside of its boundaries.
33+
///
34+
/// By default, a [`Pin`] widget will try to fill its parent.
35+
///
36+
/// # Example
37+
/// ```no_run
38+
/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::core::Length::Fill; }
39+
/// # pub type State = ();
40+
/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
41+
/// use iced::widget::pin;
42+
/// use iced::Fill;
43+
///
44+
/// enum Message {
45+
/// // ...
46+
/// }
47+
///
48+
/// fn view(state: &State) -> Element<'_, Message> {
49+
/// pin("This text is displayed at coordinates (50, 50)!")
50+
/// .x(50)
51+
/// .y(50)
52+
/// .into()
53+
/// }
54+
/// ```
55+
#[allow(missing_debug_implementations)]
56+
pub struct Pin<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
57+
where
58+
Renderer: core::Renderer,
59+
{
60+
content: Element<'a, Message, Theme, Renderer>,
61+
width: Length,
62+
height: Length,
63+
position: Point,
64+
}
65+
66+
impl<'a, Message, Theme, Renderer> Pin<'a, Message, Theme, Renderer>
67+
where
68+
Renderer: core::Renderer,
69+
{
70+
/// Creates a [`Pin`] widget with the given content.
71+
pub fn new(
72+
content: impl Into<Element<'a, Message, Theme, Renderer>>,
73+
) -> Self {
74+
Self {
75+
content: content.into(),
76+
width: Length::Fill,
77+
height: Length::Fill,
78+
position: Point::ORIGIN,
79+
}
80+
}
81+
82+
/// Sets the width of the [`Pin`].
83+
pub fn width(mut self, width: impl Into<Length>) -> Self {
84+
self.width = width.into();
85+
self
86+
}
87+
88+
/// Sets the height of the [`Pin`].
89+
pub fn height(mut self, height: impl Into<Length>) -> Self {
90+
self.height = height.into();
91+
self
92+
}
93+
94+
/// Sets the position of the [`Pin`]; where the pinned widget will be displayed.
95+
pub fn position(mut self, position: impl Into<Point>) -> Self {
96+
self.position = position.into();
97+
self
98+
}
99+
100+
/// Sets the X coordinate of the [`Pin`].
101+
pub fn x(mut self, x: impl Into<Pixels>) -> Self {
102+
self.position.x = x.into().0;
103+
self
104+
}
105+
106+
/// Sets the Y coordinate of the [`Pin`].
107+
pub fn y(mut self, y: impl Into<Pixels>) -> Self {
108+
self.position.y = y.into().0;
109+
self
110+
}
111+
}
112+
113+
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
114+
for Pin<'a, Message, Theme, Renderer>
115+
where
116+
Renderer: core::Renderer,
117+
{
118+
fn tag(&self) -> widget::tree::Tag {
119+
self.content.as_widget().tag()
120+
}
121+
122+
fn state(&self) -> widget::tree::State {
123+
self.content.as_widget().state()
124+
}
125+
126+
fn children(&self) -> Vec<widget::Tree> {
127+
self.content.as_widget().children()
128+
}
129+
130+
fn diff(&self, tree: &mut widget::Tree) {
131+
self.content.as_widget().diff(tree);
132+
}
133+
134+
fn size(&self) -> Size<Length> {
135+
Size {
136+
width: self.width,
137+
height: self.height,
138+
}
139+
}
140+
141+
fn layout(
142+
&self,
143+
tree: &mut widget::Tree,
144+
renderer: &Renderer,
145+
limits: &layout::Limits,
146+
) -> layout::Node {
147+
let limits = limits.width(self.width).height(self.height);
148+
149+
let available =
150+
limits.max() - Size::new(self.position.x, self.position.y);
151+
152+
let node = self
153+
.content
154+
.as_widget()
155+
.layout(tree, renderer, &layout::Limits::new(Size::ZERO, available))
156+
.move_to(self.position);
157+
158+
let size = limits.resolve(self.width, self.height, node.size());
159+
layout::Node::with_children(size, vec![node])
160+
}
161+
162+
fn operate(
163+
&self,
164+
tree: &mut widget::Tree,
165+
layout: Layout<'_>,
166+
renderer: &Renderer,
167+
operation: &mut dyn widget::Operation,
168+
) {
169+
self.content.as_widget().operate(
170+
tree,
171+
layout.children().next().unwrap(),
172+
renderer,
173+
operation,
174+
);
175+
}
176+
177+
fn update(
178+
&mut self,
179+
tree: &mut widget::Tree,
180+
event: Event,
181+
layout: Layout<'_>,
182+
cursor: mouse::Cursor,
183+
renderer: &Renderer,
184+
clipboard: &mut dyn Clipboard,
185+
shell: &mut Shell<'_, Message>,
186+
viewport: &Rectangle,
187+
) {
188+
self.content.as_widget_mut().update(
189+
tree,
190+
event,
191+
layout.children().next().unwrap(),
192+
cursor,
193+
renderer,
194+
clipboard,
195+
shell,
196+
viewport,
197+
);
198+
}
199+
200+
fn mouse_interaction(
201+
&self,
202+
tree: &widget::Tree,
203+
layout: Layout<'_>,
204+
cursor: mouse::Cursor,
205+
viewport: &Rectangle,
206+
renderer: &Renderer,
207+
) -> mouse::Interaction {
208+
self.content.as_widget().mouse_interaction(
209+
tree,
210+
layout.children().next().unwrap(),
211+
cursor,
212+
viewport,
213+
renderer,
214+
)
215+
}
216+
217+
fn draw(
218+
&self,
219+
tree: &widget::Tree,
220+
renderer: &mut Renderer,
221+
theme: &Theme,
222+
style: &renderer::Style,
223+
layout: Layout<'_>,
224+
cursor: mouse::Cursor,
225+
viewport: &Rectangle,
226+
) {
227+
let bounds = layout.bounds();
228+
229+
if let Some(clipped_viewport) = bounds.intersection(viewport) {
230+
self.content.as_widget().draw(
231+
tree,
232+
renderer,
233+
theme,
234+
style,
235+
layout.children().next().unwrap(),
236+
cursor,
237+
&clipped_viewport,
238+
);
239+
}
240+
}
241+
242+
fn overlay<'b>(
243+
&'b mut self,
244+
tree: &'b mut widget::Tree,
245+
layout: Layout<'_>,
246+
renderer: &Renderer,
247+
translation: Vector,
248+
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
249+
self.content.as_widget_mut().overlay(
250+
tree,
251+
layout.children().next().unwrap(),
252+
renderer,
253+
translation,
254+
)
255+
}
256+
}
257+
258+
impl<'a, Message, Theme, Renderer> From<Pin<'a, Message, Theme, Renderer>>
259+
for Element<'a, Message, Theme, Renderer>
260+
where
261+
Message: 'a,
262+
Theme: 'a,
263+
Renderer: core::Renderer + 'a,
264+
{
265+
fn from(
266+
pin: Pin<'a, Message, Theme, Renderer>,
267+
) -> Element<'a, Message, Theme, Renderer> {
268+
Element::new(pin)
269+
}
270+
}

0 commit comments

Comments
 (0)