1use std::fmt;
4
5use iced_widget::core::widget::{self, Tree, tree};
6use iced_widget::core::{
7 Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
8 alignment, layout, mouse, overlay, renderer,
9};
10
11use crate::Theme;
12
13pub struct Viewport<'a, Message, Renderer = iced_widget::Renderer> {
19 content: Element<'a, Message, Theme, Renderer>,
20 width: Length,
21 height: Length,
22 layout_width: Option<f32>,
23 layout_height: Option<f32>,
24 align_y: alignment::Vertical,
25}
26
27impl<'a, Message, Renderer> Viewport<'a, Message, Renderer>
28where
29 Message: 'a,
30 Renderer: iced_widget::core::Renderer + 'a,
31{
32 pub fn new(content: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
33 let content = content.into();
34 let size = content.as_widget().size();
35
36 Self {
37 content,
38 width: size.width,
39 height: size.height,
40 layout_width: None,
41 layout_height: None,
42 align_y: alignment::Vertical::Top,
43 }
44 }
45
46 pub fn fixed_height(
47 content: impl Into<Element<'a, Message, Theme, Renderer>>,
48 visible_height: f32,
49 layout_height: f32,
50 ) -> Self {
51 Self::new(content)
52 .height(Length::Fixed(visible_height))
53 .layout_height(layout_height)
54 }
55
56 pub fn width(mut self, width: impl Into<Length>) -> Self {
57 self.width = width.into();
58 self
59 }
60
61 pub fn height(mut self, height: impl Into<Length>) -> Self {
62 self.height = height.into();
63 self
64 }
65
66 pub fn layout_width(mut self, layout_width: f32) -> Self {
67 self.layout_width = Some(layout_width);
68 self
69 }
70
71 pub fn layout_height(mut self, layout_height: f32) -> Self {
72 self.layout_height = Some(layout_height);
73 self
74 }
75
76 pub fn align_y(mut self, alignment: alignment::Vertical) -> Self {
77 self.align_y = alignment;
78 self
79 }
80}
81
82pub fn fixed_height<'a, Message, Renderer>(
85 content: impl Into<Element<'a, Message, Theme, Renderer>>,
86 visible_height: f32,
87 layout_height: f32,
88) -> Element<'a, Message, Theme, Renderer>
89where
90 Message: 'a,
91 Renderer: iced_widget::core::Renderer + 'a,
92{
93 Viewport::fixed_height(content, visible_height, layout_height).into()
94}
95
96impl<Message, Renderer> fmt::Debug for Viewport<'_, Message, Renderer> {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 f.debug_struct("Viewport")
99 .field("width", &self.width)
100 .field("height", &self.height)
101 .field("layout_width", &self.layout_width)
102 .field("layout_height", &self.layout_height)
103 .finish_non_exhaustive()
104 }
105}
106
107impl<Message, Renderer> Widget<Message, Theme, Renderer> for Viewport<'_, Message, Renderer>
108where
109 Renderer: iced_widget::core::Renderer,
110{
111 fn tag(&self) -> tree::Tag {
112 self.content.as_widget().tag()
113 }
114
115 fn state(&self) -> tree::State {
116 self.content.as_widget().state()
117 }
118
119 fn children(&self) -> Vec<Tree> {
120 self.content.as_widget().children()
121 }
122
123 fn diff(&self, tree: &mut Tree) {
124 self.content.as_widget().diff(tree);
125 }
126
127 fn size(&self) -> Size<Length> {
128 Size {
129 width: self.width,
130 height: self.height,
131 }
132 }
133
134 fn size_hint(&self) -> Size<Length> {
135 self.size()
136 }
137
138 fn layout(
139 &mut self,
140 tree: &mut Tree,
141 renderer: &Renderer,
142 limits: &layout::Limits,
143 ) -> layout::Node {
144 let size = limits.resolve(self.width, self.height, Size::ZERO);
145 let content_size = Size::new(
146 self.layout_width.unwrap_or(size.width).max(size.width),
147 self.layout_height.unwrap_or(size.height).max(size.height),
148 );
149 let content_limits = layout::Limits::new(content_size, content_size);
150 let content = self
151 .content
152 .as_widget_mut()
153 .layout(tree, renderer, &content_limits)
154 .move_to(Point::new(
155 0.0,
156 match self.align_y {
157 alignment::Vertical::Top => 0.0,
158 alignment::Vertical::Center => (size.height - content_size.height) / 2.0,
159 alignment::Vertical::Bottom => size.height - content_size.height,
160 },
161 ));
162
163 layout::Node::with_children(size, vec![content])
164 }
165
166 fn operate(
167 &mut self,
168 tree: &mut Tree,
169 layout: Layout<'_>,
170 renderer: &Renderer,
171 operation: &mut dyn widget::Operation,
172 ) {
173 if let Some(content_layout) = layout.children().next() {
174 self.content
175 .as_widget_mut()
176 .operate(tree, content_layout, renderer, operation);
177 }
178 }
179
180 fn update(
181 &mut self,
182 tree: &mut Tree,
183 event: &Event,
184 layout: Layout<'_>,
185 cursor: mouse::Cursor,
186 renderer: &Renderer,
187 clipboard: &mut dyn Clipboard,
188 shell: &mut Shell<'_, Message>,
189 viewport: &Rectangle,
190 ) {
191 let Some(visible_viewport) = layout.bounds().intersection(viewport) else {
192 return;
193 };
194 let Some(content_layout) = layout.children().next() else {
195 return;
196 };
197 let cursor = if cursor.is_over(visible_viewport) {
198 cursor
199 } else {
200 mouse::Cursor::Unavailable
201 };
202
203 self.content.as_widget_mut().update(
204 tree,
205 event,
206 content_layout,
207 cursor,
208 renderer,
209 clipboard,
210 shell,
211 &visible_viewport,
212 );
213 }
214
215 fn mouse_interaction(
216 &self,
217 tree: &Tree,
218 layout: Layout<'_>,
219 cursor: mouse::Cursor,
220 viewport: &Rectangle,
221 renderer: &Renderer,
222 ) -> mouse::Interaction {
223 let Some(visible_viewport) = layout.bounds().intersection(viewport) else {
224 return mouse::Interaction::None;
225 };
226 let Some(content_layout) = layout.children().next() else {
227 return mouse::Interaction::None;
228 };
229
230 if !cursor.is_over(visible_viewport) {
231 return mouse::Interaction::None;
232 }
233
234 self.content.as_widget().mouse_interaction(
235 tree,
236 content_layout,
237 cursor,
238 &visible_viewport,
239 renderer,
240 )
241 }
242
243 fn draw(
244 &self,
245 tree: &Tree,
246 renderer: &mut Renderer,
247 theme: &Theme,
248 style: &renderer::Style,
249 layout: Layout<'_>,
250 cursor: mouse::Cursor,
251 viewport: &Rectangle,
252 ) {
253 let Some(visible_viewport) = layout.bounds().intersection(viewport) else {
254 return;
255 };
256 let Some(content_layout) = layout.children().next() else {
257 return;
258 };
259
260 renderer.with_layer(visible_viewport, |renderer| {
261 self.content.as_widget().draw(
262 tree,
263 renderer,
264 theme,
265 style,
266 content_layout,
267 cursor,
268 &visible_viewport,
269 );
270 });
271 }
272
273 fn overlay<'b>(
274 &'b mut self,
275 tree: &'b mut Tree,
276 layout: Layout<'b>,
277 renderer: &Renderer,
278 viewport: &Rectangle,
279 translation: Vector,
280 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
281 let visible_viewport = layout.bounds().intersection(viewport)?;
282 let content_layout = layout.children().next()?;
283
284 self.content.as_widget_mut().overlay(
285 tree,
286 content_layout,
287 renderer,
288 &visible_viewport,
289 translation,
290 )
291 }
292}
293
294impl<'a, Message, Renderer> From<Viewport<'a, Message, Renderer>>
295 for Element<'a, Message, Theme, Renderer>
296where
297 Message: 'a,
298 Renderer: iced_widget::core::Renderer + 'a,
299{
300 fn from(viewport: Viewport<'a, Message, Renderer>) -> Self {
301 Element::new(viewport)
302 }
303}
304
305#[cfg(test)]
306#[path = "../../../tests/widget/component/viewport.rs"]
307mod tests;