1use iced_native::alignment::{self, Alignment};
3use iced_native::color;
4use iced_native::event::{self, Event};
5use iced_native::layout;
6use iced_native::mouse;
7use iced_native::overlay;
8use iced_native::renderer;
9use iced_native::widget::{self, Operation, Tree};
10use iced_native::{
11 Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle,
12 Shell, Widget,
13};
14
15use crate::widget::StyleType;
16
17pub use iced_style::container::{Appearance, StyleSheet};
18
19#[allow(missing_debug_implementations)]
23pub struct Container<'a, Message, Renderer>
24where
25 Renderer: iced_native::Renderer,
26 Renderer::Theme: StyleSheet,
27{
28 id: Option<Id>,
29 padding: Padding,
30 width: Length,
31 height: Length,
32 max_width: f32,
33 max_height: f32,
34 horizontal_alignment: alignment::Horizontal,
35 vertical_alignment: alignment::Vertical,
36 style: StyleType<<Renderer::Theme as StyleSheet>::Style>,
37 content: Element<'a, Message, Renderer>,
38}
39
40impl<'a, Message, Renderer> Container<'a, Message, Renderer>
41where
42 Renderer: iced_native::Renderer,
43 Renderer::Theme: StyleSheet,
44{
45 pub fn new<T>(content: T) -> Self
47 where
48 T: Into<Element<'a, Message, Renderer>>,
49 {
50 Container {
51 id: None,
52 padding: Padding::ZERO,
53 width: Length::Shrink,
54 height: Length::Shrink,
55 max_width: f32::INFINITY,
56 max_height: f32::INFINITY,
57 horizontal_alignment: alignment::Horizontal::Left,
58 vertical_alignment: alignment::Vertical::Top,
59 style: StyleType::Static(Default::default()),
60 content: content.into(),
61 }
62 }
63
64 pub fn id(mut self, id: Id) -> Self {
66 self.id = Some(id);
67 self
68 }
69
70 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
72 self.padding = padding.into();
73 self
74 }
75
76 pub fn width(mut self, width: impl Into<Length>) -> Self {
78 self.width = width.into();
79 self
80 }
81
82 pub fn height(mut self, height: impl Into<Length>) -> Self {
84 self.height = height.into();
85 self
86 }
87
88 pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
90 self.max_width = max_width.into().0;
91 self
92 }
93
94 pub fn max_height(mut self, max_height: impl Into<Pixels>) -> Self {
96 self.max_height = max_height.into().0;
97 self
98 }
99
100 pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self {
102 self.horizontal_alignment = alignment;
103 self
104 }
105
106 pub fn align_y(mut self, alignment: alignment::Vertical) -> Self {
108 self.vertical_alignment = alignment;
109 self
110 }
111
112 pub fn center_x(mut self) -> Self {
114 self.horizontal_alignment = alignment::Horizontal::Center;
115 self
116 }
117
118 pub fn center_y(mut self) -> Self {
120 self.vertical_alignment = alignment::Vertical::Center;
121 self
122 }
123
124 pub fn style(mut self, style: impl Into<<Renderer::Theme as StyleSheet>::Style>) -> Self {
126 self.style = StyleType::Static(style.into());
127 self
128 }
129
130 pub fn blend_style(
132 mut self,
133 style1: <Renderer::Theme as StyleSheet>::Style,
134 style2: <Renderer::Theme as StyleSheet>::Style,
135 percent: f32,
136 ) -> Self {
137 self.style = StyleType::Blend(style1, style2, percent);
138 self
139 }
140}
141
142impl<'a, Message, Renderer> Widget<Message, Renderer> for Container<'a, Message, Renderer>
143where
144 Renderer: iced_native::Renderer,
145 Renderer::Theme: StyleSheet,
146{
147 fn children(&self) -> Vec<Tree> {
148 vec![Tree::new(&self.content)]
149 }
150
151 fn diff(&self, tree: &mut Tree) {
152 tree.diff_children(std::slice::from_ref(&self.content))
153 }
154
155 fn width(&self) -> Length {
156 self.width
157 }
158
159 fn height(&self) -> Length {
160 self.height
161 }
162
163 fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
164 layout(
165 renderer,
166 limits,
167 self.width,
168 self.height,
169 self.max_width,
170 self.max_height,
171 self.padding,
172 self.horizontal_alignment,
173 self.vertical_alignment,
174 |renderer, limits| self.content.as_widget().layout(renderer, limits),
175 )
176 }
177
178 fn operate(
179 &self,
180 tree: &mut Tree,
181 layout: Layout<'_>,
182 renderer: &Renderer,
183 operation: &mut dyn Operation<Message>,
184 ) {
185 operation.container(self.id.as_ref().map(|id| &id.0), &mut |operation| {
186 self.content.as_widget().operate(
187 &mut tree.children[0],
188 layout.children().next().unwrap(),
189 renderer,
190 operation,
191 );
192 });
193 }
194
195 fn on_event(
196 &mut self,
197 tree: &mut Tree,
198 event: Event,
199 layout: Layout<'_>,
200 cursor_position: Point,
201 renderer: &Renderer,
202 clipboard: &mut dyn Clipboard,
203 shell: &mut Shell<'_, Message>,
204 ) -> event::Status {
205 self.content.as_widget_mut().on_event(
206 &mut tree.children[0],
207 event,
208 layout.children().next().unwrap(),
209 cursor_position,
210 renderer,
211 clipboard,
212 shell,
213 )
214 }
215
216 fn mouse_interaction(
217 &self,
218 tree: &Tree,
219 layout: Layout<'_>,
220 cursor_position: Point,
221 viewport: &Rectangle,
222 renderer: &Renderer,
223 ) -> mouse::Interaction {
224 self.content.as_widget().mouse_interaction(
225 &tree.children[0],
226 layout.children().next().unwrap(),
227 cursor_position,
228 viewport,
229 renderer,
230 )
231 }
232
233 fn draw(
234 &self,
235 tree: &Tree,
236 renderer: &mut Renderer,
237 theme: &Renderer::Theme,
238 renderer_style: &renderer::Style,
239 layout: Layout<'_>,
240 cursor_position: Point,
241 viewport: &Rectangle,
242 ) {
243 let style = match &self.style {
244 StyleType::Static(style) => theme.appearance(style),
245 StyleType::Blend(one, two, percent) => {
246 blend_appearances(theme.appearance(one), theme.appearance(two), *percent)
247 }
248 };
249
250 draw_background(renderer, &style, layout.bounds());
251
252 self.content.as_widget().draw(
253 &tree.children[0],
254 renderer,
255 theme,
256 &renderer::Style {
257 text_color: style.text_color.unwrap_or(renderer_style.text_color),
258 },
259 layout.children().next().unwrap(),
260 cursor_position,
261 viewport,
262 );
263 }
264
265 fn overlay<'b>(
266 &'b mut self,
267 tree: &'b mut Tree,
268 layout: Layout<'_>,
269 renderer: &Renderer,
270 ) -> Option<overlay::Element<'b, Message, Renderer>> {
271 self.content.as_widget_mut().overlay(
272 &mut tree.children[0],
273 layout.children().next().unwrap(),
274 renderer,
275 )
276 }
277}
278
279impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
280 for Element<'a, Message, Renderer>
281where
282 Message: 'a,
283 Renderer: 'a + iced_native::Renderer,
284 Renderer::Theme: StyleSheet,
285{
286 fn from(column: Container<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
287 Element::new(column)
288 }
289}
290
291pub fn layout<Renderer>(
293 renderer: &Renderer,
294 limits: &layout::Limits,
295 width: Length,
296 height: Length,
297 max_width: f32,
298 max_height: f32,
299 padding: Padding,
300 horizontal_alignment: alignment::Horizontal,
301 vertical_alignment: alignment::Vertical,
302 layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
303) -> layout::Node {
304 let limits = limits
305 .loose()
306 .max_width(max_width)
307 .max_height(max_height)
308 .width(width)
309 .height(height);
310
311 let mut content = layout_content(renderer, &limits.pad(padding).loose());
312 let padding = padding.fit(content.size(), limits.max());
313 let size = limits.pad(padding).resolve(content.size());
314
315 content.move_to(Point::new(padding.left, padding.top));
316 content.align(
317 Alignment::from(horizontal_alignment),
318 Alignment::from(vertical_alignment),
319 size,
320 );
321
322 layout::Node::with_children(size.pad(padding), vec![content])
323}
324
325pub fn draw_background<Renderer>(
327 renderer: &mut Renderer,
328 appearance: &Appearance,
329 bounds: Rectangle,
330) where
331 Renderer: iced_native::Renderer,
332{
333 if appearance.background.is_some() || appearance.border_width > 0.0 {
334 renderer.fill_quad(
335 renderer::Quad {
336 bounds,
337 border_radius: appearance.border_radius.into(),
338 border_width: appearance.border_width,
339 border_color: appearance.border_color,
340 },
341 appearance
342 .background
343 .unwrap_or(Background::Color(Color::TRANSPARENT)),
344 );
345 }
346}
347
348#[derive(Debug, Clone, PartialEq, Eq, Hash)]
350pub struct Id(widget::Id);
351
352impl Id {
353 pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
355 Self(widget::Id::new(id))
356 }
357
358 pub fn unique() -> Self {
362 Self(widget::Id::unique())
363 }
364}
365
366impl From<Id> for widget::Id {
367 fn from(id: Id) -> Self {
368 id.0
369 }
370}
371
372fn blend_appearances(
373 one: iced_style::container::Appearance,
374 mut two: iced_style::container::Appearance,
375 percent: f32,
376) -> iced_style::container::Appearance {
377 use crate::lerp;
378
379 let background_one: Color = one
381 .background
382 .map(|b| match b {
383 Background::Color(c) => c,
384 })
385 .unwrap_or(color!(0, 0, 0));
386 let background_two: Color = two
387 .background
388 .map(|b| match b {
389 Background::Color(c) => c,
390 })
391 .unwrap_or(color!(0, 0, 0));
392 let background: [f32; 4] = background_one
393 .into_linear()
394 .iter()
395 .zip(background_two.into_linear().iter())
396 .map(|(o, t)| lerp(*o, *t, percent))
397 .collect::<Vec<f32>>()
398 .try_into()
399 .unwrap();
400 let background: Color = background.into();
401
402 let border_color: [f32; 4] = one
404 .border_color
405 .into_linear()
406 .iter()
407 .zip(two.border_color.into_linear().iter())
408 .map(|(o, t)| lerp(*o, *t, percent))
409 .collect::<Vec<f32>>()
410 .try_into()
411 .unwrap();
412
413 let text = one
415 .text_color
416 .map(|t| {
417 let ret: [f32; 4] = t
418 .into_linear()
419 .iter()
420 .zip(two.text_color.unwrap_or(t).into_linear().iter())
421 .map(|(o, t)| lerp(*o, *t, percent))
422 .collect::<Vec<f32>>()
423 .try_into()
424 .unwrap();
425 ret
426 })
427 .map(Into::<Color>::into);
428
429 two.background = Some(background.into());
430 two.border_radius = lerp(one.border_radius, two.border_radius, percent);
431 two.border_width = lerp(one.border_width, two.border_width, percent);
432 two.border_color = border_color.into();
433 two.text_color = text;
434 two
435}