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(mut self, widget: impl teksilo_core::IntoTeksiChild) -> Self {
111 self.pending_child = Some(teksilo_core::IntoTeksiChild::into_pending(widget));
112 self
113 }
114 pub fn child_opt(self, widget: Option<impl teksilo_core::IntoTeksiChild>) -> Self {
121 match widget {
122 Some(w) => self.child(w),
123 None => self,
124 }
125 }
126
127 fn horizontal_inset(&self) -> f32 {
128 self.leading.get() + self.trailing.get()
129 }
130
131 fn vertical_inset(&self) -> f32 {
132 self.top.get() + self.bottom.get()
133 }
134}
135
136impl Widget for Padding {
137 fn build(&mut self, ctx: &mut teksilo_core::build_context::BuildContext) -> Vec<WidgetId> {
138 if let Some(pending) = self.pending_child.take() {
139 self.child_id = Some(match pending {
140 PendingChild::Id(id) => id,
141 PendingChild::Deferred(w) => ctx.add_boxed(w),
142 });
143 }
144 let self_id = ctx.self_id();
147 let registry = ctx.binding_registry();
148 self.top.register_if_bound(
149 self_id,
150 registry,
151 teksilo_core::binding::BindingLevel::Relayout,
152 );
153 self.trailing.register_if_bound(
154 self_id,
155 registry,
156 teksilo_core::binding::BindingLevel::Relayout,
157 );
158 self.bottom.register_if_bound(
159 self_id,
160 registry,
161 teksilo_core::binding::BindingLevel::Relayout,
162 );
163 self.leading.register_if_bound(
164 self_id,
165 registry,
166 teksilo_core::binding::BindingLevel::Relayout,
167 );
168 self.child_id.into_iter().collect()
169 }
170
171 fn layout_response(
172 &self,
173 proposal: SizeProposal,
174 ctx: &LayoutContext,
175 ) -> teksilo_core::widget::LayoutResponse {
176 let h_inset = self.horizontal_inset();
177 let v_inset = self.vertical_inset();
178
179 if let Some(child_id) = self.child_id {
183 let inner_proposal = SizeProposal {
184 width: proposal.width.map(|w| (w - h_inset).max(0.0)),
185 height: proposal.height.map(|h| (h - v_inset).max(0.0)),
186 };
187 if let Some(r) = ctx.child_layout_response(child_id, inner_proposal) {
188 let size = Size::new(r.size.width + h_inset, r.size.height + v_inset);
189 let min = Size::new(r.min.width + h_inset, r.min.height + v_inset);
190 return teksilo_core::widget::LayoutResponse::flexible(size, r.flex)
191 .with_shrink(r.shrink)
192 .with_min(min);
193 }
194 }
195
196 let size = proposal.resolve(h_inset, v_inset);
197 Size::new(size.width.max(h_inset), size.height.max(v_inset)).into()
198 }
199
200 fn place_children(
201 &self,
202 bounds: Rect,
203 _proposal: SizeProposal,
204 children: &mut [WidgetPlacement],
205 ctx: &LayoutContext,
206 ) {
207 let top = self.top.get();
208 let h_inset = self.horizontal_inset();
209 let v_inset = self.vertical_inset();
210 let phys_left = if ctx.is_rtl() {
212 self.trailing.get()
213 } else {
214 self.leading.get()
215 };
216 for child in children.iter_mut() {
217 child.origin = Point::new(bounds.x + phys_left, bounds.y + top);
218 child.size = Size::new(
219 (bounds.width - h_inset).max(0.0),
220 (bounds.height - v_inset).max(0.0),
221 );
222 }
223 }
224
225 fn paint(&self, _bounds: Rect, _canvas: &mut Canvas, _ctx: &PaintContext) {}
226
227 fn accessibility(&self, _builder: &mut AccessNodeBuilder) {}
228
229 fn children(&self) -> Vec<WidgetId> {
230 self.child_id.into_iter().collect()
231 }
232}