teksilo_widgets/primitives/
padding.rs1use teksilo_canvas::{Canvas, Point, Rect, Size, SizeProposal};
35
36use teksilo_core::WidgetId;
37use teksilo_core::accessibility::AccessNodeBuilder;
38use teksilo_core::signal::Prop;
39use teksilo_core::widget::{LayoutContext, PaintContext, PendingChild, Widget, WidgetPlacement};
40
41#[derive(Debug)]
48pub struct Padding {
49 top: Prop<f32>,
50 trailing: Prop<f32>,
51 bottom: Prop<f32>,
52 leading: Prop<f32>,
53 child_id: Option<WidgetId>,
54 pending_child: Option<PendingChild>,
55}
56
57impl Padding {
58 pub fn new(
64 top: impl Into<Prop<f32>>,
65 trailing: impl Into<Prop<f32>>,
66 bottom: impl Into<Prop<f32>>,
67 leading: impl Into<Prop<f32>>,
68 ) -> Self {
69 Self {
70 top: top.into(),
71 trailing: trailing.into(),
72 bottom: bottom.into(),
73 leading: leading.into(),
74 child_id: None,
75 pending_child: None,
76 }
77 }
78
79 pub fn uniform(amount: impl Into<Prop<f32>>) -> Self {
81 let amount = amount.into();
82 Self {
83 top: amount.clone(),
84 trailing: amount.clone(),
85 bottom: amount.clone(),
86 leading: amount,
87 child_id: None,
88 pending_child: None,
89 }
90 }
91
92 pub fn symmetric(vertical: impl Into<Prop<f32>>, horizontal: impl Into<Prop<f32>>) -> Self {
97 let vertical = vertical.into();
98 let horizontal = horizontal.into();
99 Self {
100 top: vertical.clone(),
101 trailing: horizontal.clone(),
102 bottom: vertical,
103 leading: horizontal,
104 child_id: None,
105 pending_child: None,
106 }
107 }
108
109 pub fn child_id(mut self, id: WidgetId) -> Self {
111 self.pending_child = Some(PendingChild::Id(id));
112 self
113 }
114
115 pub fn child(mut self, widget: impl Widget + 'static) -> Self {
117 self.pending_child = Some(PendingChild::Deferred(Box::new(widget)));
118 self
119 }
120
121 fn horizontal_inset(&self) -> f32 {
122 self.leading.get() + self.trailing.get()
123 }
124
125 fn vertical_inset(&self) -> f32 {
126 self.top.get() + self.bottom.get()
127 }
128}
129
130impl Widget for Padding {
131 fn build(&mut self, ctx: &mut teksilo_core::build_context::BuildContext) -> Vec<WidgetId> {
132 if let Some(pending) = self.pending_child.take() {
133 self.child_id = Some(match pending {
134 PendingChild::Id(id) => id,
135 PendingChild::Deferred(w) => ctx.add_boxed(w),
136 });
137 }
138 let self_id = ctx.self_id();
141 let registry = ctx.binding_registry();
142 self.top.register_if_bound(
143 self_id,
144 registry,
145 teksilo_core::binding::BindingLevel::Relayout,
146 );
147 self.trailing.register_if_bound(
148 self_id,
149 registry,
150 teksilo_core::binding::BindingLevel::Relayout,
151 );
152 self.bottom.register_if_bound(
153 self_id,
154 registry,
155 teksilo_core::binding::BindingLevel::Relayout,
156 );
157 self.leading.register_if_bound(
158 self_id,
159 registry,
160 teksilo_core::binding::BindingLevel::Relayout,
161 );
162 self.child_id.into_iter().collect()
163 }
164
165 fn layout_response(
166 &self,
167 proposal: SizeProposal,
168 ctx: &LayoutContext,
169 ) -> teksilo_core::widget::LayoutResponse {
170 let h_inset = self.horizontal_inset();
171 let v_inset = self.vertical_inset();
172
173 if let Some(child_id) = self.child_id {
177 let inner_proposal = SizeProposal {
178 width: proposal.width.map(|w| (w - h_inset).max(0.0)),
179 height: proposal.height.map(|h| (h - v_inset).max(0.0)),
180 };
181 if let Some(r) = ctx.child_layout_response(child_id, inner_proposal) {
182 let size = Size::new(r.size.width + h_inset, r.size.height + v_inset);
183 let min = Size::new(r.min.width + h_inset, r.min.height + v_inset);
184 return teksilo_core::widget::LayoutResponse::flexible(size, r.flex)
185 .with_shrink(r.shrink)
186 .with_min(min);
187 }
188 }
189
190 let size = proposal.resolve(h_inset, v_inset);
191 Size::new(size.width.max(h_inset), size.height.max(v_inset)).into()
192 }
193
194 fn place_children(
195 &self,
196 bounds: Rect,
197 _proposal: SizeProposal,
198 children: &mut [WidgetPlacement],
199 ctx: &LayoutContext,
200 ) {
201 let top = self.top.get();
202 let h_inset = self.horizontal_inset();
203 let v_inset = self.vertical_inset();
204 let phys_left = if ctx.is_rtl() {
206 self.trailing.get()
207 } else {
208 self.leading.get()
209 };
210 for child in children.iter_mut() {
211 child.origin = Point::new(bounds.x + phys_left, bounds.y + top);
212 child.size = Size::new(
213 (bounds.width - h_inset).max(0.0),
214 (bounds.height - v_inset).max(0.0),
215 );
216 }
217 }
218
219 fn paint(&self, _bounds: Rect, _canvas: &mut Canvas, _ctx: &PaintContext) {}
220
221 fn accessibility(&self, _builder: &mut AccessNodeBuilder) {}
222
223 fn children(&self) -> Vec<WidgetId> {
224 self.child_id.into_iter().collect()
225 }
226}