ailloli_ui_runtime/layout/
layout_ctx.rs

1//! Mutable layout context shared while traversing retained nodes.
2
3#[cfg(feature = "devtools")]
4use ailloli_ui_core::geometry::Size;
5use ailloli_ui_core::geometry::{Constraints, Rect};
6use ailloli_ui_core::ids::ElementId;
7use ailloli_ui_core::math::Scale;
8use ailloli_ui_text::TextSystem;
9use std::cell::RefCell;
10use std::collections::{HashMap, HashSet};
11use std::rc::Rc;
12
13use super::layout_attempt::{LayoutAttempt, LayoutAttemptToken};
14use super::layout_engine::LayoutEngine;
15#[cfg(feature = "devtools")]
16use super::layout_result::LayoutDebugInfo;
17use super::layout_result::LayoutResult;
18use crate::component::reactive::{ReactiveDependencyBatchResult, ReactiveDependencyUpdate};
19
20/// Erased runtime callback used to atomically publish committed layout reads.
21type ReactiveLayoutPublisher =
22    Rc<dyn Fn(&[ReactiveDependencyUpdate]) -> ReactiveDependencyBatchResult>;
23
24/// Erased runtime callback used when a layout attempt observes stale input.
25type ReactiveLayoutRetry = Rc<dyn Fn(ElementId)>;
26
27/// Erased runtime callback used when a whole staging overlay is discarded.
28type ReactiveLayoutAbandon = Rc<dyn Fn()>;
29
30/// Authority of the geometry produced by the current layout traversal.
31///
32/// Measurement is speculative and must not commit persistent state derived
33/// from the temporary geometry. Commit passes use the allocation retained by
34/// the parent and may evaluate geometry-dependent effects. A measurement pass
35/// is sticky: descendants cannot regain commit authority locally.
36///
37/// # Examples
38///
39/// ```
40/// use ailloli_ui_runtime::layout::LayoutPass;
41///
42/// assert!(LayoutPass::Measure.is_measure());
43/// assert!(LayoutPass::Commit.is_committed());
44/// ```
45#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
46pub enum LayoutPass {
47    /// Speculative intrinsic-size or allocation probe.
48    Measure,
49    /// Authoritative retained allocation.
50    #[default]
51    Commit,
52}
53
54/// Provides phase predicates and sticky descendant composition.
55impl LayoutPass {
56    /// Returns `true` for speculative measurement geometry.
57    pub const fn is_measure(self) -> bool {
58        matches!(self, Self::Measure)
59    }
60
61    /// Returns `true` for authoritative committed geometry.
62    pub const fn is_committed(self) -> bool {
63        matches!(self, Self::Commit)
64    }
65
66    /// Combines an ancestor pass with a locally requested child pass.
67    const fn descend(self, requested: Self) -> Self {
68        if self.is_measure() || requested.is_measure() {
69            Self::Measure
70        } else {
71            Self::Commit
72        }
73    }
74}
75
76/// Mutable services and scoped hints available during one layout traversal.
77///
78/// Geometry is expressed in logical pixels. The context borrows its optional
79/// text system exclusively, so it is local to the current thread and layout
80/// pass rather than a shareable global service.
81///
82/// # Examples
83///
84/// ```
85/// use ailloli_ui_core::Scale;
86/// use ailloli_ui_runtime::layout::LayoutContext;
87///
88/// let ctx = LayoutContext::new(Scale::new(2.0));
89/// assert_eq!(ctx.scale.dpr, 2.0);
90/// assert!(ctx.text_system.is_none());
91/// ```
92pub struct LayoutContext<'a> {
93    /// Device-pixel ratio used for snapping and layout-cache identity.
94    pub scale: Scale,
95    /// Shared text layout engine (optional for text-free tests).
96    pub text_system: Option<&'a mut TextSystem>,
97    /// Optional content-local viewport propagated by a virtualizing ancestor.
98    virtual_viewport: Option<VirtualViewport>,
99    /// Current geometry authority, inherited monotonically by descendants.
100    layout_pass: LayoutPass,
101    /// Explicit speculative branches adopted by the current authoritative attempt.
102    measure_branches: Rc<RefCell<MeasureBranchState>>,
103    /// Innermost explicit speculative branch, if one is active.
104    current_measure_branch: Option<u64>,
105    /// Runtime-owned writes staged by the current outer layout call.
106    layout_attempt: Option<LayoutAttempt>,
107    /// Exact successful attempt scoped only while post-layout hooks execute.
108    committed_layout_attempt: Option<LayoutAttemptToken>,
109    /// Optional retained dependency publisher installed by `Runtime`.
110    reactive_layout_publisher: Option<ReactiveLayoutPublisher>,
111    /// Optional targeted retry scheduler installed by `Runtime`.
112    reactive_layout_retry: Option<ReactiveLayoutRetry>,
113    /// Optional internal diagnostic callback for discarded staging overlays.
114    reactive_layout_abandon: Option<ReactiveLayoutAbandon>,
115    #[cfg(feature = "devtools")]
116    /// Latest developer-tooling layout record for each element in this context.
117    pub debug_layouts: HashMap<ElementId, LayoutDebugInfo>,
118}
119
120/// Bookkeeping shared with explicit measurement tokens.
121#[derive(Default)]
122struct MeasureBranchState {
123    /// Last allocated nonzero branch identity.
124    next_id: u64,
125    /// Direct parent of every branch allocated by the active attempt.
126    parents: HashMap<u64, Option<u64>>,
127    /// Branches explicitly accepted by their owner widget.
128    adopted: HashSet<u64>,
129}
130
131/// Result of an explicit speculative layout branch.
132///
133/// Dropping this token abandons every measurement performed by the branch.
134/// Call [`Self::adopt`] only when the result contributes to the authoritative
135/// geometry being returned by the widget. Existing widgets that use
136/// [`LayoutCtx::with_layout_pass`] retain their historical auto-adopt behavior.
137#[doc(hidden)]
138#[must_use = "dropping a measurement branch abandons its reactive dependencies"]
139pub struct LayoutMeasurement<T> {
140    /// Branch result, taken exactly once by `adopt`.
141    value: Option<T>,
142    /// Nonzero identity recorded by staged measurement entries.
143    branch_id: u64,
144    /// Shared acceptance set owned by the layout context.
145    state: Rc<RefCell<MeasureBranchState>>,
146}
147
148impl<T> LayoutMeasurement<T> {
149    /// Borrows the speculative result before deciding whether to adopt it.
150    #[doc(hidden)]
151    pub fn result(&self) -> &T {
152        self.value
153            .as_ref()
154            .expect("layout measurement result was already adopted")
155    }
156
157    /// Accepts this branch and returns its result.
158    ///
159    /// The authoritative layout attempt will publish the branch's reactive
160    /// reads only if that complete attempt also succeeds and remains current.
161    #[doc(hidden)]
162    pub fn adopt(mut self) -> T {
163        self.state.borrow_mut().adopted.insert(self.branch_id);
164        self.value
165            .take()
166            .expect("layout measurement result was already adopted")
167    }
168}
169
170/// Viewport made available to widgets whose content is larger than the
171/// visible scroll container. Coordinates are local to the content.
172///
173/// Values use logical pixels and are stored verbatim. In particular, negative
174/// or non-finite overscan is not clamped here; consumers must define how such
175/// values affect virtualization.
176///
177/// # Examples
178///
179/// ```
180/// use ailloli_ui_core::Rect;
181/// use ailloli_ui_runtime::layout::VirtualViewport;
182///
183/// let viewport = VirtualViewport::new(Rect::new(0.0, 120.0, 640.0, 480.0), 32.0);
184/// assert_eq!(viewport.overscan, 32.0);
185/// ```
186#[derive(Debug, Clone, Copy, PartialEq)]
187pub struct VirtualViewport {
188    /// Visible rectangle in content-local logical pixels.
189    pub rect: Rect,
190    /// Extra logical pixels requested around every visible edge.
191    pub overscan: f32,
192}
193
194/// Provides the operations defined for VirtualViewport.
195impl VirtualViewport {
196    /// Creates a viewport without validating its rectangle or overscan.
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// use ailloli_ui_core::Rect;
202    /// use ailloli_ui_runtime::layout::VirtualViewport;
203    ///
204    /// let viewport = VirtualViewport::new(Rect::new(10.0, 20.0, 30.0, 40.0), 8.0);
205    /// assert_eq!(viewport.rect.y, 20.0);
206    /// ```
207    pub const fn new(rect: Rect, overscan: f32) -> Self {
208        Self { rect, overscan }
209    }
210}
211
212/// Short alias for [`LayoutContext`].
213///
214/// # Examples
215///
216/// ```
217/// use ailloli_ui_core::Scale;
218/// use ailloli_ui_runtime::layout::LayoutCtx;
219///
220/// let ctx = LayoutCtx::new(Scale::new(1.0));
221/// assert_eq!(ctx.scale.dpr, 1.0);
222/// ```
223pub type LayoutCtx<'a> = LayoutContext<'a>;
224
225/// Provides the operations defined for `LayoutContext<'a>`.
226impl<'a> LayoutContext<'a> {
227    /// Creates a context without a text system or virtual viewport.
228    ///
229    /// With `devtools`, the debug-record map also starts empty.
230    ///
231    /// # Examples
232    ///
233    /// ```
234    /// use ailloli_ui_core::Scale;
235    /// use ailloli_ui_runtime::layout::LayoutContext;
236    ///
237    /// let ctx = LayoutContext::new(Scale::new(1.25));
238    /// assert!(ctx.virtual_viewport().is_none());
239    /// ```
240    pub fn new(scale: Scale) -> Self {
241        Self {
242            scale,
243            text_system: None,
244            virtual_viewport: None,
245            layout_pass: LayoutPass::Commit,
246            measure_branches: Rc::new(RefCell::new(MeasureBranchState::default())),
247            current_measure_branch: None,
248            layout_attempt: None,
249            committed_layout_attempt: None,
250            reactive_layout_publisher: None,
251            reactive_layout_retry: None,
252            reactive_layout_abandon: None,
253            #[cfg(feature = "devtools")]
254            debug_layouts: HashMap::new(),
255        }
256    }
257
258    /// Creates a context borrowing `text_system` for this layout pass.
259    ///
260    /// The virtual viewport starts as `None`; with `devtools`, debug records
261    /// start empty.
262    ///
263    /// # Examples
264    ///
265    /// ```
266    /// use ailloli_ui_core::Scale;
267    /// use ailloli_ui_runtime::layout::LayoutContext;
268    /// use ailloli_ui_text::TextSystem;
269    ///
270    /// let mut text = TextSystem::new();
271    /// let ctx = LayoutContext::with_text_system(Scale::new(1.0), &mut text);
272    /// assert!(ctx.text_system.is_some());
273    /// ```
274    pub fn with_text_system(scale: Scale, text_system: &'a mut TextSystem) -> Self {
275        Self {
276            scale,
277            text_system: Some(text_system),
278            virtual_viewport: None,
279            layout_pass: LayoutPass::Commit,
280            measure_branches: Rc::new(RefCell::new(MeasureBranchState::default())),
281            current_measure_branch: None,
282            layout_attempt: None,
283            committed_layout_attempt: None,
284            reactive_layout_publisher: None,
285            reactive_layout_retry: None,
286            reactive_layout_abandon: None,
287            #[cfg(feature = "devtools")]
288            debug_layouts: HashMap::new(),
289        }
290    }
291
292    /// Returns the currently scoped content viewport.
293    ///
294    /// `None` means widgets should lay out without a virtualization hint.
295    ///
296    /// # Examples
297    ///
298    /// ```
299    /// use ailloli_ui_core::{Rect, Scale};
300    /// use ailloli_ui_runtime::layout::{LayoutCtx, VirtualViewport};
301    ///
302    /// let mut ctx = LayoutCtx::new(Scale::new(1.0));
303    /// ctx.replace_virtual_viewport(Some(VirtualViewport::new(
304    ///     Rect::new(0.0, 50.0, 200.0, 100.0), 12.0,
305    /// )));
306    /// assert_eq!(ctx.virtual_viewport().unwrap().rect.y, 50.0);
307    /// ```
308    pub const fn virtual_viewport(&self) -> Option<VirtualViewport> {
309        self.virtual_viewport
310    }
311
312    /// Returns the authority of geometry produced by the current traversal.
313    ///
314    /// Widgets may always compute geometry, but must not persist effects based
315    /// on it while this returns [`LayoutPass::Measure`].
316    pub const fn layout_pass(&self) -> LayoutPass {
317        self.layout_pass
318    }
319
320    /// Returns the exact outer attempt owning the current layout callback.
321    ///
322    /// During `layout`, this identifies the active staging overlay. During
323    /// `layout_committed`, it identifies the validated overlay whose geometry
324    /// has just been published. Outside those callbacks it returns `None`.
325    #[doc(hidden)]
326    pub fn layout_attempt_token(&self) -> Option<LayoutAttemptToken> {
327        self.layout_attempt
328            .as_ref()
329            .map(LayoutAttempt::token)
330            .or(self.committed_layout_attempt)
331    }
332
333    /// Runs a descendant traversal under a sticky requested layout pass.
334    ///
335    /// Requesting [`LayoutPass::Commit`] beneath an existing measurement keeps
336    /// the descendant in [`LayoutPass::Measure`]. The previous pass is restored
337    /// after `layout` returns.
338    ///
339    /// # Examples
340    ///
341    /// ```
342    /// use ailloli_ui_core::Scale;
343    /// use ailloli_ui_runtime::layout::{LayoutCtx, LayoutPass};
344    ///
345    /// let mut ctx = LayoutCtx::new(Scale::new(1.0));
346    /// ctx.with_layout_pass(LayoutPass::Measure, |ctx| {
347    ///     assert!(ctx.layout_pass().is_measure());
348    ///     ctx.with_layout_pass(LayoutPass::Commit, |ctx| {
349    ///         assert!(ctx.layout_pass().is_measure());
350    ///     });
351    /// });
352    /// assert!(ctx.layout_pass().is_committed());
353    /// ```
354    pub fn with_layout_pass<R>(
355        &mut self,
356        requested: LayoutPass,
357        layout: impl FnOnce(&mut Self) -> R,
358    ) -> R {
359        let previous = self.layout_pass;
360        self.layout_pass = previous.descend(requested);
361        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| layout(self)));
362        self.layout_pass = previous;
363        match result {
364            Ok(result) => result,
365            Err(payload) => std::panic::resume_unwind(payload),
366        }
367    }
368
369    /// Runs an explicitly adoptable speculative measurement branch.
370    ///
371    /// The returned token abandons the branch by default. This hidden
372    /// cross-crate API lets layout widgets distinguish probes that contribute
373    /// to their final allocation from alternatives that were only evaluated.
374    #[doc(hidden)]
375    pub fn measure_branch<R>(
376        &mut self,
377        layout: impl FnOnce(&mut Self) -> R,
378    ) -> LayoutMeasurement<R> {
379        let parent_branch = self.current_measure_branch;
380        let branch_id = {
381            let mut state = self.measure_branches.borrow_mut();
382            state.next_id = state
383                .next_id
384                .checked_add(1)
385                .expect("layout measurement branch identity exhausted");
386            let branch_id = state.next_id;
387            state.parents.insert(branch_id, parent_branch);
388            branch_id
389        };
390        let previous_pass = self.layout_pass;
391        self.layout_pass = previous_pass.descend(LayoutPass::Measure);
392        self.current_measure_branch = Some(branch_id);
393        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| layout(self)));
394        self.current_measure_branch = parent_branch;
395        self.layout_pass = previous_pass;
396        match result {
397            Ok(value) => LayoutMeasurement {
398                value: Some(value),
399                branch_id,
400                state: self.measure_branches.clone(),
401            },
402            Err(payload) => std::panic::resume_unwind(payload),
403        }
404    }
405
406    /// Returns the explicit branch currently owning speculative work.
407    pub(crate) const fn current_measure_branch(&self) -> Option<u64> {
408        self.current_measure_branch
409    }
410
411    /// Returns whether a branch and every enclosing explicit branch were accepted.
412    pub(crate) fn measure_branch_is_adopted(&self, branch_id: u64) -> bool {
413        let state = self.measure_branches.borrow();
414        let mut branch = Some(branch_id);
415        while let Some(branch_id) = branch {
416            if !state.adopted.contains(&branch_id) {
417                return false;
418            }
419            let Some(parent) = state.parents.get(&branch_id) else {
420                return false;
421            };
422            branch = *parent;
423        }
424        true
425    }
426
427    /// Clears adoption records after the outer authoritative attempt finishes.
428    pub(crate) fn clear_measure_branch_adoptions(&mut self) {
429        let mut state = self.measure_branches.borrow_mut();
430        state.adopted.clear();
431        state.parents.clear();
432    }
433
434    /// Returns whether an outer layout call currently owns the staging overlay.
435    pub(crate) const fn has_layout_attempt(&self) -> bool {
436        self.layout_attempt.is_some()
437    }
438
439    /// Starts an empty staging overlay for one outer layout call.
440    pub(crate) fn begin_layout_attempt(&mut self) {
441        assert!(
442            self.layout_attempt.is_none(),
443            "nested layout attempts must reuse the active overlay"
444        );
445        self.clear_measure_branch_adoptions();
446        self.layout_attempt = Some(LayoutAttempt::new());
447    }
448
449    /// Borrows the active staging overlay.
450    pub(crate) fn layout_attempt(&self) -> &LayoutAttempt {
451        self.layout_attempt
452            .as_ref()
453            .expect("layout attempt is not active")
454    }
455
456    /// Mutably borrows the active staging overlay.
457    pub(crate) fn layout_attempt_mut(&mut self) -> &mut LayoutAttempt {
458        self.layout_attempt
459            .as_mut()
460            .expect("layout attempt is not active")
461    }
462
463    /// Removes the active overlay for validation, commit, or abandonment.
464    pub(crate) fn take_layout_attempt(&mut self) -> LayoutAttempt {
465        self.layout_attempt
466            .take()
467            .expect("layout attempt is not active")
468    }
469
470    /// Scopes one validated attempt identity to a post-layout hook.
471    pub(crate) fn with_committed_layout_attempt<R>(
472        &mut self,
473        token: Option<LayoutAttemptToken>,
474        callback: impl FnOnce(&mut Self) -> R,
475    ) -> R {
476        let previous = std::mem::replace(&mut self.committed_layout_attempt, token);
477        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(self)));
478        self.committed_layout_attempt = previous;
479        match result {
480            Ok(result) => result,
481            Err(payload) => std::panic::resume_unwind(payload),
482        }
483    }
484
485    /// Installs runtime-owned dependency publication and retry callbacks.
486    pub(crate) fn set_reactive_layout_callbacks(
487        &mut self,
488        publisher: ReactiveLayoutPublisher,
489        retry: ReactiveLayoutRetry,
490    ) {
491        self.reactive_layout_publisher = Some(publisher);
492        self.reactive_layout_retry = Some(retry);
493    }
494
495    /// Installs the runtime-owned discarded-attempt diagnostic callback.
496    pub(crate) fn set_reactive_layout_abandon_callback(&mut self, abandon: ReactiveLayoutAbandon) {
497        self.reactive_layout_abandon = Some(abandon);
498    }
499
500    /// Publishes a prevalidated batch when this context belongs to a runtime.
501    pub(crate) fn publish_reactive_layout(
502        &self,
503        updates: &[ReactiveDependencyUpdate],
504    ) -> ReactiveDependencyBatchResult {
505        if let Some(publisher) = &self.reactive_layout_publisher {
506            publisher(updates)
507        } else {
508            ReactiveDependencyBatchResult::Accepted { renewed: false }
509        }
510    }
511
512    /// Schedules another targeted layout after a superseded attempt.
513    pub(crate) fn request_reactive_layout_retry(&self, element_id: ElementId) {
514        if let Some(retry) = &self.reactive_layout_retry {
515            retry(element_id);
516        }
517    }
518
519    /// Records one outer staging overlay discarded before publication.
520    pub(crate) fn record_reactive_layout_abandon(&self) {
521        if let Some(abandon) = &self.reactive_layout_abandon {
522            abandon();
523        }
524    }
525
526    /// Replaces the current content viewport and returns the previous value.
527    /// Layout containers use this to scope the hint to their child traversal.
528    /// Passing `None` clears the hint. The caller should restore the returned
529    /// value after laying out a scoped child.
530    ///
531    /// # Examples
532    ///
533    /// ```
534    /// use ailloli_ui_core::{Rect, Scale};
535    /// use ailloli_ui_runtime::layout::{LayoutCtx, VirtualViewport};
536    ///
537    /// let mut ctx = LayoutCtx::new(Scale::new(1.0));
538    /// let viewport = VirtualViewport::new(Rect::new(0.0, 0.0, 80.0, 60.0), 4.0);
539    /// assert!(ctx.replace_virtual_viewport(Some(viewport)).is_none());
540    /// assert_eq!(ctx.replace_virtual_viewport(None), Some(viewport));
541    /// ```
542    pub fn replace_virtual_viewport(
543        &mut self,
544        viewport: Option<VirtualViewport>,
545    ) -> Option<VirtualViewport> {
546        std::mem::replace(&mut self.virtual_viewport, viewport)
547    }
548
549    #[cfg(feature = "devtools")]
550    /// Records the latest layout and returns the stored debug snapshot.
551    ///
552    /// The first call for an element fixes `constraints_in`. Every call sets
553    /// `constraints_final` to `Some(constraints)` and replaces `layout_size`.
554    /// The map grows by unique element ID for the lifetime of this context.
555    ///
556    /// # Examples
557    ///
558    /// ```
559    /// use ailloli_ui_core::{Constraints, ElementId, Scale, Size};
560    /// use ailloli_ui_runtime::layout::LayoutCtx;
561    ///
562    /// let mut ctx = LayoutCtx::new(Scale::new(1.0));
563    /// let info = ctx.record_debug_layout(
564    ///     ElementId(7), Constraints::tight(20.0, 10.0), Size::new(20.0, 10.0),
565    /// );
566    /// assert_eq!(info.layout_size, Size::new(20.0, 10.0));
567    /// assert!(info.constraints_final.is_some());
568    /// ```
569    pub fn record_debug_layout(
570        &mut self,
571        element_id: ElementId,
572        constraints: Constraints,
573        size: Size,
574    ) -> LayoutDebugInfo {
575        let entry = self
576            .debug_layouts
577            .entry(element_id)
578            .or_insert_with(|| LayoutDebugInfo {
579                constraints_in: constraints,
580                constraints_final: None,
581                layout_size: size,
582            });
583        entry.constraints_final = Some(constraints);
584        entry.layout_size = size;
585        entry.clone()
586    }
587}
588
589/// Handle used by widgets to lay out one retained direct child.
590///
591/// # Examples
592///
593/// ```
594/// use ailloli_ui_core::ElementId;
595/// use ailloli_ui_runtime::layout::LayoutChild;
596///
597/// let child = LayoutChild { element_id: ElementId(3) };
598/// assert_eq!(child.element_id, ElementId(3));
599/// ```
600pub struct LayoutChild {
601    /// Retained-tree identity delegated to [`LayoutEngine`].
602    pub element_id: ElementId,
603}
604
605/// Provides the operations defined for LayoutChild.
606impl LayoutChild {
607    /// Lays out this child with the supplied logical-pixel constraints.
608    ///
609    /// A stale or unknown `element_id` produces [`LayoutResult::zero`] and no
610    /// diagnostic cache event. Other behavior, including widget panics, is
611    /// delegated to [`LayoutEngine::layout_element`].
612    ///
613    /// # Examples
614    ///
615    /// ```
616    /// use ailloli_ui_core::{Constraints, ElementId, Scale, Size};
617    /// use ailloli_ui_runtime::element::ElementTree;
618    /// use ailloli_ui_runtime::layout::{LayoutChild, LayoutCtx, LayoutEngine};
619    ///
620    /// let mut tree = ElementTree::<()>::new();
621    /// let mut engine = LayoutEngine::new(&mut tree);
622    /// let mut ctx = LayoutCtx::new(Scale::new(1.0));
623    /// let mut child = LayoutChild { element_id: ElementId(99) };
624    /// assert_eq!(
625    ///     child.layout(&mut engine, &mut ctx, Constraints::tight(10.0, 20.0)).size,
626    ///     Size::default(),
627    /// );
628    /// ```
629    pub fn layout<A: 'static>(
630        &mut self,
631        engine: &mut LayoutEngine<'_, A>,
632        ctx: &mut LayoutCtx<'_>,
633        constraints: Constraints,
634    ) -> LayoutResult {
635        engine.layout_element(ctx, self.element_id, constraints)
636    }
637}