Skip to main content

View

Struct View 

Source
pub struct View<Msg> {
Show 62 fields pub style: Style, pub fill: Option<AlphaColor<Srgb>>, pub hover_fill: Option<AlphaColor<Srgb>>, pub radius: f64, pub corner_radii: Option<RoundedRectRadii>, pub shadow: Option<Shadow>, pub fill_gradient: Option<Gradient>, pub border: Option<Border>, pub text: Option<TextSpec>, pub image: Option<ImageBrush>, pub image_fit: Option<ImageFit>, pub mask_image: Option<ImageBrush>, pub mask_placement: Option<MaskPlacement>, pub mask_extra: Vec<(ImageBrush, MaskCompose)>, pub painter: Option<Arc<dyn Fn(&mut Scene, &mut Typesetter, PaintRect) + Send + Sync>>, pub gpu_painter: Option<Arc<dyn Fn(&Device, &Queue, &mut CommandEncoder, &TextureView, PaintRect, (u32, u32)) + Send + Sync>>, pub over_painter: Option<Arc<dyn Fn(&mut Scene, &mut Typesetter, PaintRect) + Send + Sync>>, pub on_click: Option<Msg>, pub on_click_at: Option<Arc<dyn Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>, pub on_right_click: Option<Msg>, pub on_right_click_at: Option<Arc<dyn Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>, pub on_middle_click: Option<Msg>, pub drag: Option<Arc<dyn Fn(DragPhase, f32, f32) -> Option<Msg> + Send + Sync>>, pub drag_at: Option<Arc<dyn Fn(DragPhase, f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>, pub drag_velocity: Option<Arc<dyn Fn(DragPhase, f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>, pub drag_payload: Option<u64>, pub on_drop: Option<Arc<dyn Fn(u64) -> Option<Msg> + Send + Sync>>, pub drop_hover_fill: Option<AlphaColor<Srgb>>, pub clip: bool, pub clip_inset: Option<[f32; 4]>, pub clip_ellipse: Option<[f32; 14]>, pub clip_polygon: Option<(bool, Vec<[f32; 4]>)>, pub clip_path_svg: Option<(bool, String)>, pub clip_ref_inset: Option<[f32; 4]>, pub on_pointer_enter: Option<Msg>, pub on_pointer_leave: Option<Msg>, pub on_pointer_move_at: Option<Arc<dyn Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>, pub on_scroll: Option<Arc<dyn Fn(f32, f32) -> Option<Msg> + Send + Sync>>, pub on_scale: Option<Arc<dyn Fn(GesturePhase, f32, f32, f32) -> Option<Msg> + Send + Sync>>, pub on_rotate: Option<Arc<dyn Fn(GesturePhase, f32, f32, f32) -> Option<Msg> + Send + Sync>>, pub on_double_tap: Option<Msg>, pub on_double_tap_at: Option<Arc<dyn Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>, pub on_long_press: Option<Msg>, pub on_long_press_at: Option<Arc<dyn Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>, pub focusable: Option<u64>, pub text_select_key: Option<u64>, pub alpha: Option<f32>, pub anim: Option<Anim>, pub animated_size: Option<SizeAnim>, pub semantics: Option<SemanticsSpec>, pub hero: Option<Hero>, pub transform: Option<Affine>, pub transform_rel: Option<(f64, f64)>, pub transform_origin: Option<TransformPivot>, pub tooltip: Option<String>, pub cursor: Option<Cursor>, pub ripple: Option<Ripple>, pub layout_builder: Option<Arc<dyn Fn(Constraints) -> View<Msg> + Send + Sync>>, pub backdrop_blur: Option<f32>, pub filter: Vec<FilterOp>, pub blend: Option<BlendMode>, pub children: Vec<View<Msg>>,
}
Expand description

Nodo de la vista declarativa. Estilo de layout (taffy) + relleno opcional (vello) + texto opcional (skrifa+vello) + Msg al click opcional + hijos.

Fields§

§style: Style§fill: Option<AlphaColor<Srgb>>§hover_fill: Option<AlphaColor<Srgb>>

Relleno cuando el cursor está sobre este nodo. Sin valor (None) = no se reacciona al hover.

§radius: f64§corner_radii: Option<RoundedRectRadii>

Radio por esquina (top-left, top-right, bottom-right, bottom-left), que sobreescribe a radius cuando está presente. Permite cards con sólo las esquinas de arriba redondeadas, pestañas, bocadillos de chat, etc. (CSS border-radius con 4 valores). None = usar el radius uniforme. Ver View::radius_corners. La sombra sigue usando un radio escalar (el blur nativo de vello no acepta radios por esquina); el borde sí respeta las cuatro esquinas.

§shadow: Option<Shadow>

Sombra proyectada detrás del nodo (drop shadow). None = sin sombra (la mayoría de nodos). Ver Shadow.

§fill_gradient: Option<Gradient>

Relleno con gradiente, autoreado en el cuadrado unidad [0,1]² y mapeado al rect del nodo. Gana sobre fill como base; hover_fill (un color) lo sigue overrideando en hover. Ver View::fill_gradient.

§border: Option<Border>

Borde (stroke) sobre el contorno redondeado. Ver Border.

§text: Option<TextSpec>§image: Option<ImageBrush>

Imagen a pintar dentro del rect del nodo. Se centra y escala según Self::image_fit (default Contain = preservar aspect ratio cabiendo). El alfa por píxel de la imagen y el Image::alpha global se respetan; el fill (si lo hay) se pinta debajo como background. El clip al node_rrect respeta radius/corner_radii, así avatares y cards con esquinas redondeadas funcionan sin envolver en un padre clip(true).

§image_fit: Option<ImageFit>

Política de encaje de Self::image en el rect del nodo (CSS object-fit). None = Contain (el default histórico). Ver ImageFit y View::image_fit.

§mask_image: Option<ImageBrush>

Máscara de luminancia (CSS mask-image). Si está presente, el runtime aísla el subárbol del nodo en una capa y luego lo enmascara con la luminancia de esta imagen (push_luminance_mask_layer de vello): blanco = visible, negro = oculto, gris = semitransparente. El encaje lo fija Self::mask_placement (size/position/repeat); sin él la imagen se estira al border-box. None = sin máscara. Ver View::mask_image.

§mask_placement: Option<MaskPlacement>

Encaje de Self::mask_image (CSS mask-size/-position/-repeat). None = estirar al border-box (Fase 7.1226). Sólo se consulta si mask_image está presente. Ver MaskPlacement y View::mask_placement. Fase 7.1227.

§mask_extra: Vec<(ImageBrush, MaskCompose)>

Capas de máscara ADICIONALES (mask-image: url(a), url(b), …): cada una es (imagen, operador). Comparten Self::mask_placement con la capa 0 (Self::mask_image); se combinan con ella según el operador. Vacío = una sola capa. Ver View::mask_extra. Fase 7.1231.

§painter: Option<Arc<dyn Fn(&mut Scene, &mut Typesetter, PaintRect) + Send + Sync>>

Callback de pintura custom. Si está presente, el runtime lo invoca durante el paint del nodo con el Scene vivo + el rect absoluto. Pensado para “canvas elements” (dominium, pluma, cosmos) que pintan primitivas custom no expresables como una composición de Views.

§gpu_painter: Option<Arc<dyn Fn(&Device, &Queue, &mut CommandEncoder, &TextureView, PaintRect, (u32, u32)) + Send + Sync>>

Pintor GPU directo. Se invoca DESPUÉS de la pasada vello del frame; comparte tree y orden DFS con los demás. Ver GpuPaintFn.

§over_painter: Option<Arc<dyn Fn(&mut Scene, &mut Typesetter, PaintRect) + Send + Sync>>

Pintor vello “over”: closure que pinta DESPUÉS del pase GPU del frame, sobre una escena vello que el runtime compone con alpha encima de la intermedia. Sirve para sprites/texto AA encima de celdas instanciadas por GPU. Ver View::paint_over y OverPaintFn. Misma firma que PaintFn — sólo cambia cuándo corre (post-GPU). None = sin over-layer (coste cero).

§on_click: Option<Msg>§on_click_at: Option<Arc<dyn Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>

Handler de click que recibe la posición relativa al rect del nodo (esquina superior-izquierda del nodo = (0, 0)). Útil para canvas elements que quieren mapear el click a coordenadas de mundo. Si está presente, gana sobre on_click. Devolver None no dispara update.

§on_right_click: Option<Msg>

Equivalente a on_click pero para el botón derecho del ratón. Pensado para menús contextuales: el nodo declara qué Msg emitir cuando se le hace right-click, y la app abre el overlay con el menú.

§on_right_click_at: Option<Arc<dyn Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>

Variante posicional de Self::on_right_click. Útil para grillas que necesitan saber qué celda del rect recibió el click derecho (la celda no es un nodo aparte, sino una región dentro del nodo). Si está presente, gana sobre on_right_click.

§on_middle_click: Option<Msg>

Equivalente a on_click pero para el botón del medio del ratón (rueda presionada). Pensado para abrir en pestaña nueva — los browsers usan middle-click como atajo equivalente a Ctrl+Click.

§drag: Option<Arc<dyn Fn(DragPhase, f32, f32) -> Option<Msg> + Send + Sync>>

Handler de drag. Si está presente, este nodo arrastra (y NO emite on_click al presionar — un nodo es uno u otro).

§drag_at: Option<Arc<dyn Fn(DragPhase, f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>

Variante de drag que recibe la posición inicial del press relativa al rect del nodo. Gana sobre drag si ambos están presentes.

§drag_velocity: Option<Arc<dyn Fn(DragPhase, f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>

Variante de drag que recibe la velocidad al soltar (vx, vy en px/s) además del delta puntual. Gana sobre drag/drag_at cuando está presente — un nodo elige un único sabor de drag. Habilita fling-desde-drag (el caller arranca un ticker con esa velocidad y la decae con [fling_step]).

§drag_payload: Option<u64>

Payload u64 que viaja con el drag iniciado sobre este nodo. Lo recibe el handler Self::on_drop del drop target. Sin payload, el drag funciona igual pero ningún drop target reacciona.

§on_drop: Option<Arc<dyn Fn(u64) -> Option<Msg> + Send + Sync>>

Handler invocado al soltar un drag sobre este nodo (drop target).

§drop_hover_fill: Option<AlphaColor<Srgb>>

Color a pintar mientras un drag activo está hovereando este drop target. Sobrepone a fill/hover_fill cuando aplica.

§clip: bool

Si true, los descendientes se recortan al rect del nodo (vía scene.push_layer con Mix::Clip). El hit-test también respeta el recorte: clicks fuera del rect ignoran a los hijos.

§clip_inset: Option<[f32; 4]>

Si Some([top, right, bottom, left]), recorta los descendientes a un rect ENCOGIDO por esos insets (px) desde el rect del nodo — modela clip-path: inset(...). Implica clip aunque clip == false.

§clip_ellipse: Option<[f32; 14]>

Si Some(spec) (14 floats), recorta los descendientes a una ELIPSE — modela clip-path: circle()/ellipse(). El centro (4) se resuelve contra el rect: cx = cx_px + cx_pct/100·w, cy = cy_px + cy_pct/100·h. Cada radio (5: [px, pct_w, pct_h, pct_diag, side]) con side == 0 suma px + pct_w/100·w + pct_h/100·h + pct_diag/100·diag (diag = √(w²+h²)/√2); con side != 0 se computa desde la distancia del centro a los bordes (1/2 = closest/farthest sobre los 4 lados; 3/4 = ídem sobre el eje del radio). Layout: [cx×2, cy×2, rx×5, ry×5]. Implica clip aunque clip == false. Si conviven clip_inset y clip_ellipse, gana la elipse (una sola capa de recorte por nodo).

§clip_polygon: Option<(bool, Vec<[f32; 4]>)>

Si Some((evenodd, puntos)), recorta los descendientes a un POLÍGONO — modela clip-path: polygon(). Cada punto [x_px, x_pct, y_px, y_pct] resuelve (x_px + x_pct/100·w, y_px + y_pct/100·h) contra el rect. evenodd elige la regla de relleno. Implica clip aunque clip == false. Prioridad de recorte por nodo: polygon > elipse > inset > rect.

§clip_path_svg: Option<(bool, String)>

Si Some((evenodd, d)), recorta los descendientes a un PATH SVG — modela clip-path: path(). d es el string SVG crudo (user units px, relativos al origen del rect); el pintado lo parsea con BezPath::from_svg y lo traslada al origen del nodo. Si el parseo falla, no recorta. Implica clip aunque clip == false. Prioridad: path > polygon > elipse > inset > rect.

§clip_ref_inset: Option<[f32; 4]>

Si Some([t,r,b,l]), el clip-path se resuelve contra una caja de referencia (<geometry-box>) que es el rect del nodo ENCOGIDO por esos insets px (padding-box = border; content-box = border+padding). El pintado lo aplica ANTES de resolver la forma; sin forma, recorta a ese rect. None = referencia = border-box (rect completo). Fase 7.1225.

§on_pointer_enter: Option<Msg>

Msg a emitir cuando el cursor entra al rect del nodo (transición no-hover → hover). Útil para previews tipo “URL del link al pasar el mouse”.

§on_pointer_leave: Option<Msg>

Msg a emitir cuando el cursor sale del rect del nodo.

§on_pointer_move_at: Option<Arc<dyn Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>

Handler de movimiento del cursor sobre el nodo: recibe (local_x, local_y, rect_w, rect_h) en CADA CursorMoved mientras el cursor está encima (no sólo en la transición de entrada, a diferencia de Self::on_pointer_enter). Análogo posicional de hover, base de cosas como el thumbnail que sigue al cursor sobre un timeline o un drawer que reacciona a la posición. None no dispara update.

§on_scroll: Option<Arc<dyn Fn(f32, f32) -> Option<Msg> + Send + Sync>>

Handler de rueda local. Si está presente y el cursor cae sobre este nodo, el runtime lo invoca antes del App::on_wheel global; un Some(Msg) consume el evento. Base de las áreas de scroll autocontenidas. Ver ScrollFn.

§on_scale: Option<Arc<dyn Fn(GesturePhase, f32, f32, f32) -> Option<Msg> + Send + Sync>>

Handler de gesto de escala (pinch-to-zoom). Si está presente y el gesto cae sobre este nodo (Ctrl+rueda en desktop, pinch de trackpad en macOS), el runtime lo invoca con el factor incremental + el punto focal local. Base del zoom de canvases. Ver ScaleFn y View::on_scale.

§on_rotate: Option<Arc<dyn Fn(GesturePhase, f32, f32, f32) -> Option<Msg> + Send + Sync>>

Handler de gesto de rotación (dos dedos en trackpad, macOS). Si está presente y el gesto cae sobre este nodo, el runtime lo invoca con el delta de ángulo incremental (radianes) + el punto focal local. Ver RotateFn y View::on_rotate.

§on_double_tap: Option<Msg>

Msg a emitir en doble-tap (dos presses izquierdos sobre este nodo dentro de una ventana temporal corta y muy cerca). Es un evento aditivo: si el nodo también tiene on_click, éste igual dispara en cada press; el doble-tap llega además en el segundo. Para doble-tap exclusivo, poné el handler en un nodo sin on_click. Ver View::on_double_tap.

§on_double_tap_at: Option<Arc<dyn Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>

Variante posicional de Self::on_double_tap: recibe la posición del segundo tap relativa al rect del nodo (para zoom-to-point, etc.). Gana sobre on_double_tap si ambos están.

§on_long_press: Option<Msg>

Msg a emitir en long-press (mantener el botón izquierdo sobre este nodo ~500 ms sin moverse ni soltar). El runtime lo arbitra por tiempo: si el cursor se aleja (pasó a drag/scroll) o se suelta antes, se cancela. Evento aditivo (ver Self::on_double_tap); el caso limpio es un nodo con drag-to-pan + long-press y sin on_click (un canvas). Útil para menús contextuales táctiles / selección. Ver View::on_long_press.

§on_long_press_at: Option<Arc<dyn Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync>>

Variante posicional de Self::on_long_press: recibe la posición del press relativa al rect del nodo (para abrir el menú en el punto). Gana sobre on_long_press si ambos están.

§focusable: Option<u64>

Marca este nodo como enfocable con el id opaco u64. El runtime mantiene el foco (uno por ventana) y lo mueve con Tab/Shift+Tab en orden de árbol (pre-orden) y al clickear un nodo enfocable; notifica a la app vía App::on_focus para que pinte el ring y rutee el teclado. El id lo elige el caller (índice de campo, hash, etc.).

§text_select_key: Option<u64>

Marca este nodo de texto como seleccionable con el mouse fuera del editor (arrastrar resalta, Ctrl/Cmd+C copia). El u64 es una key estable entre rebuilds del View (los NodeId de taffy cambian cada frame, así que la selección retenida en el runtime se ancla a esta key, igual que animated). Sólo tiene efecto en nodos con text uniforme (no runs/spans). Ver View::selectable.

§alpha: Option<f32>

Opacidad multiplicada sobre TODO el subtree (este nodo + hijos), en [0.0, 1.0]. Se realiza con scene.push_layer(Mix::Normal, a, …) alrededor del rect del nodo: el subárbol se rasteriza en una capa intermedia y se compone al alfa indicado contra lo que ya hay detrás. None = sin capa (caso de la abrumadora mayoría de nodos). Útil para fade-in/out de overlays, ghosts mientras se arrastra, modales que aparecen, panels “vidrio”. Note que la composición tiene costo (allocate + blit), por lo que sólo poblar este slot cuando hace falta — no es un atributo gratis.

§anim: Option<Anim>

Animación implícita de las props de paint (fill/radius): cuando el valor cambia entre frames, el runtime interpola en vez de saltar. None = sin animación (la abrumadora mayoría). La key debe ser estable entre rebuilds. Ver Anim y View::animated. Lo consume el runtime vía AnimRegistry::reconcile (DESPUÉS de layout, ANTES de paint).

§animated_size: Option<SizeAnim>

Animación implícita de tamaño (Flutter AnimatedSize / Compose animateContentSize()). None = sin animación. La key debe ser estable entre rebuilds. A diferencia de Self::anim (props de paint, reconcilia DESPUÉS de layout), el tamaño tiene que estar firme antes del layout — siblings/hijos dependen del rect del nodo. El runtime llama reconcile_size_anim sobre el View tree antes de mount y parcha style.size con el valor interpolado. Sólo se activa si ambos style.size.width y style.size.height son Dimension::Length(_). Ver SizeAnim y View::animated_size.

§semantics: Option<SemanticsSpec>

Semántica accesible del nodo (rol, label, value, flags ARIA). El runtime la traduce a un árbol AccessKit por frame para alimentar lectores de pantalla (NVDA/VoiceOver/Orca/TalkBack). None = no declarada (el lector lee el texto plano si lo hay, sin rol específico). Ver SemanticsSpec.

§hero: Option<Hero>

Hero shared-element: marca este nodo como una identidad estable entre frames. Si la misma key aparece en otra posición en un frame siguiente, el runtime interpola transform para “volar” del rect anterior al actual durante la duration declarada. Ver Hero y HeroRegistry. None = sin hero (la abrumadora mayoría).

§transform: Option<Affine>

Transformación afín 2D aplicada a este nodo y todo su subtree alrededor del centro de su propio rect (convención CSS transform-origin: 50% 50%). El runtime resuelve el centro en paint (sólo entonces conoce el layout computado) y compone T(centro) · transform · T(-centro) sobre la transformación acumulada del padre, así nodos anidados transforman en el espacio ya transformado de su ancestro — igual que CSS. None = identidad (la abrumadora mayoría de nodos). Pensado para transform/ @keyframes CSS de puriy (rotate/scale/translate). El hit-test respeta el afín (un nodo transformado recibe clicks donde se ve pintado). Limitación restante: los painter/runs custom no heredan el afín, y la posición local que reciben los handlers *_at se reporta en espacio de pantalla, no en el espacio local del nodo.

§transform_rel: Option<(f64, f64)>

Traslación RELATIVA al tamaño del propio nodo, en fracciones de su rect computado: (fx, fy) ⇒ desplaza (fx · w, fy · h) px. Se resuelve en paint/hit_test (única instancia donde se conoce el tamaño usado) y se compone como el factor más externo del afín del nodo, ANTES del centrado por transform-origin. Pensado para el translate(<%>) de CSS (p. ej. el truco de centrado translate(-50%, -50%)(-0.5, -0.5)), que no es expresable como Affine fijo porque el % depende del layout. None = sin traslación relativa (la abrumadora mayoría). Compone con transform (afín fijo) si ambos están: T_rel · transform.

§transform_origin: Option<TransformPivot>

Punto de pivote de transform (CSS transform-origin). None ⇒ el default CSS 50% 50% (centro del rect) — el caso mayoritario. Ver TransformPivot y View::transform_origin.

§tooltip: Option<String>

Texto de tooltip: si está, el runtime/cliente puede mostrar un rótulo flotante cuando el cursor se posa sobre este nodo. Llimphi sólo transporta el dato hasta el MountedNode; quién lo pinta (un overlay del runtime, una surface popup del cliente) lo decide el consumidor. El hit-test de hover ya localiza el nodo bajo el cursor. None = sin tip.

§cursor: Option<Cursor>

Forma del puntero del mouse mientras está sobre este nodo (o un descendiente sin cursor propio — se hereda del ancestro más cercano que lo declare). El runtime lo resuelve en el hit-test de hover y lo aplica a la ventana. None = hereda (default flecha en la raíz). Ver Cursor y View::cursor. Llimphi-native (sin winit); el runtime lo mapea.

§ripple: Option<Ripple>

Feedback de tap ripple/InkWell: al presionar este nodo, el runtime emite una salpicadura Material (círculo que se expande desde el punto y se desvanece, recortado al contorno del nodo). Es puro feedback visual, aditivo al on_click; vive en el runtime (RippleRegistry), no en el Model. None = sin ripple. Ver View::ripple.

§layout_builder: Option<Arc<dyn Fn(Constraints) -> View<Msg> + Send + Sync>>

Constructor diferido sensible al tamaño (LayoutBuilder). Si está presente, este nodo NO usa sus children estáticos: el runtime resuelve su slot en una primera pasada de layout y luego invoca esta closure con las Constraints resueltas para producir el subárbol. None = nodo normal (la abrumadora mayoría). Ver View::layout_builder.

§backdrop_blur: Option<f32>

Backdrop blur sobre el contenido pintado debajo de este nodo. Ver View::backdrop_blur / MountedNode::backdrop_blur. v1: sólo se aplica a nodos top-level sin clip/alpha ancestral.

§filter: Vec<FilterOp>

Filtros CSS (filter: …) sobre el propio subárbol del nodo. Vacío = sin filtro. Ver View::filter / FilterOp. Fase 7.1232.

§blend: Option<BlendMode>

Modo de mezcla del nodo entero contra su backdrop (CSS mix-blend-mode). Some(bm) ⇒ el subárbol del nodo se rasteriza en una capa aislada (scene.push_layer(bm, …) alrededor del rect) y se mezcla con el modo bm contra todo lo pintado antes en el stacking context. None = source-over normal (la abrumadora mayoría). Ver View::blend. Fase 7.1237.

§children: Vec<View<Msg>>

Implementations§

Source§

impl<Msg> View<Msg>

Source

pub fn new(style: Style) -> View<Msg>

Examples found in repository?
examples/editor.rs (lines 83-90)
70    fn view(model: &Self::Model) -> View<Self::Msg> {
71        let body_text = if model.is_empty() {
72            "tipea algo · ctrl+L limpia · enter salto · backspace borra".to_string()
73        } else {
74            // Cursor visual al final del contenido.
75            format!("{model}\u{2588}")
76        };
77        let body_color = if model.is_empty() {
78            Color::from_rgba8(110, 130, 150, 255)
79        } else {
80            Color::from_rgba8(220, 230, 240, 255)
81        };
82
83        let body = View::new(Style {
84            size: Size {
85                width: percent(1.0_f32),
86                height: percent(1.0_f32),
87            },
88            flex_grow: 1.0,
89            ..Default::default()
90        })
91        .text_aligned(body_text, 22.0, body_color, Alignment::Start);
92
93        let status = View::new(Style {
94            size: Size {
95                width: percent(1.0_f32),
96                height: length(36.0_f32),
97            },
98            ..Default::default()
99        })
100        .fill(Color::from_rgba8(30, 36, 48, 255))
101        .text(
102            format!("{} chars", model.chars().count()),
103            16.0,
104            Color::from_rgba8(160, 180, 200, 255),
105        );
106
107        View::new(Style {
108            flex_direction: FlexDirection::Column,
109            size: Size {
110                width: percent(1.0_f32),
111                height: percent(1.0_f32),
112            },
113            gap: Size {
114                width: length(0.0_f32),
115                height: length(8.0_f32),
116            },
117            padding: llimphi_ui::llimphi_layout::taffy::Rect {
118                left: length(24.0_f32),
119                right: length(24.0_f32),
120                top: length(24.0_f32),
121                bottom: length(24.0_f32),
122            },
123            ..Default::default()
124        })
125        .fill(Color::from_rgba8(20, 24, 32, 255))
126        .children(vec![body, status])
127    }
More examples
Hide additional examples
examples/selectable_text.rs (lines 41-47)
40    fn view(_: &Self::Model) -> View<Self::Msg> {
41        let mut children: Vec<View<()>> = vec![View::new(Style {
42            size: Size {
43                width: percent(1.0_f32),
44                height: Dimension::auto(),
45            },
46            ..Default::default()
47        })
48        .text_aligned(
49            "Texto seleccionable (arrastrá + Ctrl/Cmd+C)",
50            22.0,
51            Color::from_rgba8(230, 240, 250, 255),
52            Alignment::Start,
53        )];
54
55        for (i, p) in PARRAFOS.iter().enumerate() {
56            children.push(
57                View::new(Style {
58                    size: Size {
59                        width: percent(1.0_f32),
60                        height: Dimension::auto(),
61                    },
62                    ..Default::default()
63                })
64                .text_aligned(
65                    *p,
66                    16.0,
67                    Color::from_rgba8(205, 214, 226, 255),
68                    Alignment::Start,
69                )
70                // La línea clave: cada párrafo es seleccionable con una key
71                // estable (su índice). El resaltado + copy los hace el runtime.
72                .selectable(i as u64),
73            );
74        }
75
76        View::new(Style {
77            flex_direction: FlexDirection::Column,
78            size: Size {
79                width: percent(1.0_f32),
80                height: percent(1.0_f32),
81            },
82            gap: Size {
83                width: length(0.0_f32),
84                height: length(20.0_f32),
85            },
86            padding: Rect {
87                left: length(40.0_f32),
88                right: length(40.0_f32),
89                top: length(36.0_f32),
90                bottom: length(36.0_f32),
91            },
92            align_items: Some(AlignItems::Start),
93            ..Default::default()
94        })
95        .fill(Color::from_rgba8(20, 24, 32, 255))
96        .children(children)
97    }
examples/gpu_paint_demo.rs (lines 55-63)
54    fn view(model: &Self::Model) -> View<Self::Msg> {
55        let title = View::new(Style {
56            size: Size {
57                width: percent(1.0_f32),
58                height: length(48.0_f32),
59            },
60            justify_content: Some(JustifyContent::Center),
61            align_items: Some(AlignItems::Center),
62            ..Default::default()
63        })
64        .text(
65            format!("gpu_paint_with — {POINTS} puntos GPU directo · seed {model}"),
66            22.0,
67            Color::from_rgba8(220, 230, 245, 255),
68        );
69
70        // Canvas central: vello pinta el fondo (fill + radius), GPU pinta
71        // la grilla de puntos encima vía gpu_paint_with. El seed del
72        // modelo se mete en el shader vía una rotación trivial — cada
73        // click cambia el patrón. El callback se invoca ya con el
74        // CommandEncoder del frame y la TextureView intermediate.
75        let seed = *model;
76        let canvas = View::new(Style {
77            size: Size {
78                width: percent(1.0_f32),
79                height: auto(),
80            },
81            flex_grow: 1.0,
82            ..Default::default()
83        })
84        .fill(Color::from_rgba8(14, 18, 28, 255))
85        .radius(8.0)
86        .gpu_paint_with(move |device, queue, encoder, view, rect, _viewport| {
87            draw_points(device, queue, encoder, view, rect, seed);
88        })
89        .on_click(Msg::Bump);
90
91        let footer = View::new(Style {
92            size: Size {
93                width: percent(1.0_f32),
94                height: length(28.0_f32),
95            },
96            justify_content: Some(JustifyContent::Center),
97            align_items: Some(AlignItems::Center),
98            ..Default::default()
99        })
100        .text(
101            "click sobre el canvas → rebobinar el seed",
102            14.0,
103            Color::from_rgba8(150, 165, 185, 255),
104        );
105
106        View::new(Style {
107            flex_direction: FlexDirection::Column,
108            size: Size {
109                width: percent(1.0_f32),
110                height: percent(1.0_f32),
111            },
112            gap: Size {
113                width: length(0.0_f32),
114                height: length(16.0_f32),
115            },
116            padding: TaffyRect {
117                left: length(24.0_f32),
118                right: length(24.0_f32),
119                top: length(16.0_f32),
120                bottom: length(16.0_f32),
121            },
122            ..Default::default()
123        })
124        .fill(Color::from_rgba8(24, 28, 38, 255))
125        .children(vec![title, canvas, footer])
126    }
examples/counter.rs (lines 44-53)
43    fn view(model: &Self::Model) -> View<Self::Msg> {
44        let number = View::new(Style {
45            size: Size {
46                width: percent(1.0_f32),
47                height: Dimension::auto(),
48            },
49            flex_grow: 1.0,
50            align_items: Some(AlignItems::Center),
51            justify_content: Some(JustifyContent::Center),
52            ..Default::default()
53        })
54        .text(model.to_string(), 160.0, Color::from_rgba8(230, 240, 250, 255));
55
56        let increment = View::new(Style {
57            size: Size {
58                width: length(160.0_f32),
59                height: length(56.0_f32),
60            },
61            align_items: Some(AlignItems::Center),
62            justify_content: Some(JustifyContent::Center),
63            ..Default::default()
64        })
65        .fill(Color::from_rgba8(60, 200, 130, 255))
66        .radius(12.0)
67        .text("+1", 28.0, Color::from_rgba8(10, 30, 20, 255))
68        .on_click(Msg::Increment);
69
70        let reset = View::new(Style {
71            size: Size {
72                width: length(120.0_f32),
73                height: length(56.0_f32),
74            },
75            align_items: Some(AlignItems::Center),
76            justify_content: Some(JustifyContent::Center),
77            ..Default::default()
78        })
79        .fill(Color::from_rgba8(220, 80, 80, 255))
80        .radius(12.0)
81        .text("reset", 22.0, Color::from_rgba8(30, 10, 10, 255))
82        .on_click(Msg::Reset);
83
84        let buttons = View::new(Style {
85            flex_direction: FlexDirection::Row,
86            size: Size {
87                width: percent(1.0_f32),
88                height: length(56.0_f32),
89            },
90            gap: Size {
91                width: length(16.0_f32),
92                height: length(0.0_f32),
93            },
94            justify_content: Some(JustifyContent::Center),
95            ..Default::default()
96        })
97        .children(vec![increment, reset]);
98
99        View::new(Style {
100            flex_direction: FlexDirection::Column,
101            size: Size {
102                width: percent(1.0_f32),
103                height: percent(1.0_f32),
104            },
105            gap: Size {
106                width: length(0.0_f32),
107                height: length(24.0_f32),
108            },
109            padding: llimphi_ui::llimphi_layout::taffy::Rect {
110                left: length(32.0_f32),
111                right: length(32.0_f32),
112                top: length(32.0_f32),
113                bottom: length(32.0_f32),
114            },
115            ..Default::default()
116        })
117        .fill(Color::from_rgba8(20, 24, 32, 255))
118        .children(vec![number, buttons])
119    }
examples/gestos.rs (lines 106-110)
101    fn view(model: &Self::Model) -> View<Self::Msg> {
102        let zoom = model.zoom;
103        let pan = model.pan;
104        let marks = model.marks.clone();
105
106        let canvas = View::new(Style {
107            size: Size { width: percent(1.0_f32), height: Dimension::auto() },
108            flex_grow: 1.0,
109            ..Default::default()
110        })
111        .fill(Color::from_rgba8(16, 18, 26, 255))
112        .clip(true)
113        .paint_with(move |scene, _ts, rect| {
114            // Grilla de mundo paso 40px, escalada por zoom y desplazada por pan.
115            let step = 40.0 * zoom as f64;
116            if step >= 4.0 {
117                let thin = Stroke::new(1.0);
118                let grid = Color::from_rgba8(40, 46, 60, 255);
119                // Offset del primer línea visible (pan módulo step).
120                let ox = (rect.x as f64) + (pan.0 as f64).rem_euclid(step);
121                let mut x = ox;
122                while x < (rect.x + rect.w) as f64 {
123                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
124                        &Line::new((x, rect.y as f64), (x, (rect.y + rect.h) as f64)));
125                    x += step;
126                }
127                let oy = (rect.y as f64) + (pan.1 as f64).rem_euclid(step);
128                let mut y = oy;
129                while y < (rect.y + rect.h) as f64 {
130                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
131                        &Line::new((rect.x as f64, y), ((rect.x + rect.w) as f64, y)));
132                    y += step;
133                }
134            }
135            // Marcas (coords de mundo → pantalla): pan + world·zoom.
136            let dot = Color::from_rgba8(90, 220, 150, 255);
137            let r = (6.0 * zoom as f64).clamp(3.0, 24.0);
138            for (wx, wy) in &marks {
139                let sx = rect.x as f64 + pan.0 as f64 + (*wx as f64) * zoom as f64;
140                let sy = rect.y as f64 + pan.1 as f64 + (*wy as f64) * zoom as f64;
141                scene.fill(Fill::NonZero, Affine::IDENTITY, dot, None, &Circle::new((sx, sy), r));
142            }
143        })
144        // Pinch-to-zoom (Ctrl+rueda / trackpad). El focal viene local al canvas.
145        .on_scale(|phase, factor, fx, fy| match phase {
146            GesturePhase::Update => Some(Msg::Zoom { factor, fx, fy }),
147            _ => None,
148        })
149        // Paneo por arrastre. El movimiento cancela un long-press en curso.
150        .draggable(|phase, dx, dy| match phase {
151            DragPhase::Move => Some(Msg::Pan { dx, dy }),
152            DragPhase::End => None,
153        })
154        // Doble-tap: reset. Long-press: marca en el punto.
155        .on_double_tap(Msg::Reset)
156        .on_long_press_at(|lx, ly, _w, _h| Some(Msg::Mark { lx, ly }));
157
158        let status = View::new(Style {
159            size: Size { width: percent(1.0_f32), height: length(40.0_f32) },
160            align_items: Some(AlignItems::Center),
161            justify_content: Some(JustifyContent::Center),
162            ..Default::default()
163        })
164        .fill(Color::from_rgba8(28, 32, 42, 255))
165        .text(
166            format!("{}   ·   ×{:.2}   ·   {} marcas", model.last, model.zoom, model.marks.len()),
167            18.0,
168            Color::from_rgba8(210, 220, 235, 255),
169        );
170
171        View::new(Style {
172            flex_direction: FlexDirection::Column,
173            size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
174            ..Default::default()
175        })
176        .fill(Color::from_rgba8(16, 18, 26, 255))
177        .children(vec![canvas, status])
178    }
Source

pub fn backdrop_blur(self, sigma: f32) -> View<Msg>

Aplica un backdrop blur Gaussiano al contenido pintado debajo de este nodo, restringido al rect del nodo (CSS backdrop-filter: blur(N) / Flutter BackdropFilter). El runtime descompone el árbol en “fondo + subárbol del nodo”, renderiza el fondo a la intermediate, borronea el rect con un Gauss separable, y compone el subárbol del nodo sobre el backdrop borroso vía un buffer secundario. Útil para chrome translúcido: sidebars/topbars con “vidrio esmerilado”.

sigma (pixels) controla el ancho del kernel — 4.0 “frosted glass” suave; 8.016.0 un blur fuerte; >20 se ve apagado. v1 capa el radius efectivo a 32 pixels (sigma > 10 empieza a clipear cola).

Limitación v1: sólo nodos top-level (children directos del root o de un agrupador sin clip/alpha) renderizan correctamente — un nodo dentro de una capa clippeada se pinta SIN clip en su pase, por el reset de layer-stack al cambiar de scene. Documentado en PARIDAD-FLUTTER.md Bloque 11.

Source

pub fn filter(self, ops: Vec<FilterOp>) -> View<Msg>

Aplica una lista de filtros CSS (filter: …) al propio subárbol del nodo (a diferencia de Self::backdrop_blur, que afecta lo pintado debajo). El runtime los recolecta con collect_filters y los aplica como post-pasada GPU sobre la intermediate, restringidos al rect del nodo, en el orden de la lista. Hoy sólo se modela blur (FilterOp); la lista crece por fase. Es ortogonal a clip/mask (un nodo puede llevar todos). Lista vacía = sin filtro. Fase 7.1232.

Limitación v1 (igual que backdrop_blur): la post-pasada opera sobre los píxeles finales del rect, así que no aísla el subárbol del fondo que asome detrás. Adecuado para nodos opacos.

Source

pub fn blend(self, bm: BlendMode) -> View<Msg>

Mezcla el nodo entero (su subárbol) contra su backdrop con el modo bm (CSS mix-blend-mode). El runtime abre una capa aislada (push_layer(bm, …)) alrededor del rect del nodo que envuelve fill + contenido + hijos; al cerrarla, el subárbol se compone contra todo lo pintado antes en el stacking context según bm (p. ej. Mix::Multiply). Es ortogonal a clip/mask/filter/alpha (un nodo puede llevar todos). Fase 7.1237.

Limitación v1 (igual que mask/filter): el backdrop es lo que ya está pintado en la escena, no un fondo aislado del subárbol — exacto cuando debajo hay contenido opaco, aproximado si la capa de abajo es el padre.

Source

pub fn layout_builder<F>(self, builder: F) -> View<Msg>
where F: Fn(Constraints) -> View<Msg> + Send + Sync + 'static,

Construye los hijos de este nodo de forma diferida, en función del tamaño del slot que el layout le asigne (Flutter LayoutBuilder). El runtime resuelve primero el rect del nodo (una pasada de layout con este nodo como hoja, sized por su Style/contexto flex) y recién entonces invoca builder(Constraints) para producir el subárbol — habilitando paneles responsive cuyo punto de quiebre depende del espacio local, no de la ventana (para eso alcanza on_resize + el Model).

El Style de este nodo define su tamaño (debe quedar acotado por el contexto: flex_grow, size definido o percent — no intrínseco a los hijos, que aún no existen). Cualquier children estático que se haya seteado se ignora: el builder es la fuente de los hijos.

Límite v1: sin anidamiento — un layout_builder dentro del subárbol que produce otro layout_builder no se resuelve (queda como hoja).

Source

pub fn ripple(self, key: u64, color: AlphaColor<Srgb>) -> View<Msg>

Marca este nodo para emitir un ripple/InkWell (la salpicadura de tap de Material) al recibir un press: un círculo que se expande desde el punto presionado y se desvanece, recortado al contorno del nodo. key debe ser estable entre rebuilds del View (índice/hash del item), igual que la key de Self::animated. color es el tinte de la onda — usá un color semitransparente (blanco a alpha ~0.25 sobre superficies oscuras, negro a alpha ~0.12 sobre claras); su alpha se atenúa con el fade. Es aditivo: convive con on_click/drag sin pisarlos. Duración por defecto 450 ms; para otra usar Self::ripple_styled.

Source

pub fn ripple_styled( self, key: u64, color: AlphaColor<Srgb>, duration: Duration, ) -> View<Msg>

Como Self::ripple pero con la duración explícita de la salpicadura.

Source

pub fn cursor(self, cursor: Cursor) -> View<Msg>

Fija la forma del puntero del mouse mientras el cursor está sobre este nodo (o un descendiente que no declare la suya — se hereda del ancestro más cercano que la tenga). El runtime la resuelve en el hit-test de hover y la aplica a la ventana. Ejemplos: .cursor(Cursor::Text) en un input, .cursor(Cursor::ColResize) en un divisor de splitter, .cursor(Cursor::Pointer) en un botón.

Source

pub fn tooltip(self, text: impl Into<String>) -> View<Msg>

Asocia un texto de tooltip a este nodo. Llimphi sólo lo transporta hasta el MountedNode; el consumidor decide cómo mostrarlo (un overlay del runtime, una surface popup del cliente) tras localizar el nodo bajo el cursor con el hit-test de hover.

Source

pub fn semantics(self, spec: SemanticsSpec) -> View<Msg>

Declara la semántica accesible completa del nodo de una vez. Usar cuando ya tenés un SemanticsSpec armado (p. ej. construido por un widget); para los casos puntuales preferí los atajos Self::role/Self::aria_label/etc.

Source

pub fn role(self, role: Role) -> View<Msg>

Fija el rol semántico del nodo. Si ya había semántica declarada, preserva label/value/flags y sólo sobreescribe el rol; si no, crea una SemanticsSpec con sólo el rol.

Source

pub fn aria_label(self, label: impl Into<Arc<str>>) -> View<Msg>

Fija el label accesible (“nombre” que el lector enuncia). Hace falta cuando el contenido visible del nodo no alcanza (p. ej. un botón con sólo un ícono). Preserva el resto de la SemanticsSpec si existía.

Source

pub fn aria_description(self, desc: impl Into<Arc<str>>) -> View<Msg>

Fija la descripción (contexto adicional que el lector enuncia tras el label, típicamente con un atajo). Para info que ayuda pero no es el nombre principal — no abusar (los lectores perciben ruido).

Source

pub fn aria_value(self, value: impl Into<Arc<str>>) -> View<Msg>

Fija el valor (texto del input, valor del slider/spinner). Lo que el lector lee después del label: “Volumen, 70”.

Source

pub fn aria_checked(self, v: bool) -> View<Msg>

Estado checked (checkbox/radio).

Source

pub fn aria_pressed(self, v: bool) -> View<Msg>

Estado pressed (toggle button).

Source

pub fn aria_expanded(self, v: bool) -> View<Msg>

Estado expanded (acordeón, menú abierto, tree row expandida).

Source

pub fn aria_disabled(self, v: bool) -> View<Msg>

Estado disabled — el control no responde a input.

Source

pub fn aria_readonly(self, v: bool) -> View<Msg>

Estado readonly — el control es visible/seleccionable pero no editable.

Source

pub fn aria_required(self, v: bool) -> View<Msg>

Estado required (campo de formulario obligatorio).

Source

pub fn on_scroll<F>(self, handler: F) -> View<Msg>
where F: Fn(f32, f32) -> Option<Msg> + Send + Sync + 'static,

Registra un handler de rueda local: si el cursor está sobre este nodo cuando la rueda gira, el runtime lo invoca con el delta (dx, dy) en líneas lógicas ANTES de caer al App::on_wheel global. Devolver Some(Msg) consume el evento. Es la base de las áreas de scroll autocontenidas (llimphi-widget-scroll).

Source

pub fn on_scale<F>(self, handler: F) -> View<Msg>
where F: Fn(GesturePhase, f32, f32, f32) -> Option<Msg> + Send + Sync + 'static,

Registra un handler de pinch-to-zoom (gesto de escala). El runtime lo invoca cuando el cursor está sobre este nodo y el usuario hace un gesto de escala: Ctrl + rueda en cualquier desktop (camino universal) o un pinch de trackpad en macOS. El handler recibe (phase, factor, focal_x, focal_y) — ver ScaleFn: factor es el cambio multiplicativo incremental (>1 agranda, <1 achica) y (focal_x, focal_y) es el punto bajo el cursor relativo al rect del nodo, para zoomear “hacia el cursor”. El típico patrón de canvas: Msg::Zoom { factor, fx, fy } que multiplica la escala del viewport y reajusta el pan para mantener el punto focal fijo. Devolver Some(Msg) consume el gesto (no cae al scroll/on_wheel).

Examples found in repository?
examples/gestos.rs (lines 145-148)
101    fn view(model: &Self::Model) -> View<Self::Msg> {
102        let zoom = model.zoom;
103        let pan = model.pan;
104        let marks = model.marks.clone();
105
106        let canvas = View::new(Style {
107            size: Size { width: percent(1.0_f32), height: Dimension::auto() },
108            flex_grow: 1.0,
109            ..Default::default()
110        })
111        .fill(Color::from_rgba8(16, 18, 26, 255))
112        .clip(true)
113        .paint_with(move |scene, _ts, rect| {
114            // Grilla de mundo paso 40px, escalada por zoom y desplazada por pan.
115            let step = 40.0 * zoom as f64;
116            if step >= 4.0 {
117                let thin = Stroke::new(1.0);
118                let grid = Color::from_rgba8(40, 46, 60, 255);
119                // Offset del primer línea visible (pan módulo step).
120                let ox = (rect.x as f64) + (pan.0 as f64).rem_euclid(step);
121                let mut x = ox;
122                while x < (rect.x + rect.w) as f64 {
123                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
124                        &Line::new((x, rect.y as f64), (x, (rect.y + rect.h) as f64)));
125                    x += step;
126                }
127                let oy = (rect.y as f64) + (pan.1 as f64).rem_euclid(step);
128                let mut y = oy;
129                while y < (rect.y + rect.h) as f64 {
130                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
131                        &Line::new((rect.x as f64, y), ((rect.x + rect.w) as f64, y)));
132                    y += step;
133                }
134            }
135            // Marcas (coords de mundo → pantalla): pan + world·zoom.
136            let dot = Color::from_rgba8(90, 220, 150, 255);
137            let r = (6.0 * zoom as f64).clamp(3.0, 24.0);
138            for (wx, wy) in &marks {
139                let sx = rect.x as f64 + pan.0 as f64 + (*wx as f64) * zoom as f64;
140                let sy = rect.y as f64 + pan.1 as f64 + (*wy as f64) * zoom as f64;
141                scene.fill(Fill::NonZero, Affine::IDENTITY, dot, None, &Circle::new((sx, sy), r));
142            }
143        })
144        // Pinch-to-zoom (Ctrl+rueda / trackpad). El focal viene local al canvas.
145        .on_scale(|phase, factor, fx, fy| match phase {
146            GesturePhase::Update => Some(Msg::Zoom { factor, fx, fy }),
147            _ => None,
148        })
149        // Paneo por arrastre. El movimiento cancela un long-press en curso.
150        .draggable(|phase, dx, dy| match phase {
151            DragPhase::Move => Some(Msg::Pan { dx, dy }),
152            DragPhase::End => None,
153        })
154        // Doble-tap: reset. Long-press: marca en el punto.
155        .on_double_tap(Msg::Reset)
156        .on_long_press_at(|lx, ly, _w, _h| Some(Msg::Mark { lx, ly }));
157
158        let status = View::new(Style {
159            size: Size { width: percent(1.0_f32), height: length(40.0_f32) },
160            align_items: Some(AlignItems::Center),
161            justify_content: Some(JustifyContent::Center),
162            ..Default::default()
163        })
164        .fill(Color::from_rgba8(28, 32, 42, 255))
165        .text(
166            format!("{}   ·   ×{:.2}   ·   {} marcas", model.last, model.zoom, model.marks.len()),
167            18.0,
168            Color::from_rgba8(210, 220, 235, 255),
169        );
170
171        View::new(Style {
172            flex_direction: FlexDirection::Column,
173            size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
174            ..Default::default()
175        })
176        .fill(Color::from_rgba8(16, 18, 26, 255))
177        .children(vec![canvas, status])
178    }
Source

pub fn on_rotate<F>(self, handler: F) -> View<Msg>
where F: Fn(GesturePhase, f32, f32, f32) -> Option<Msg> + Send + Sync + 'static,

Registra un handler de rotación con dos dedos (gesto de trackpad). El runtime lo invoca cuando el cursor está sobre este nodo y el usuario rota dos dedos en el trackpad (winit emite RotationGesture sólo en macOS). El handler recibe (phase, delta_radianes, focal_x, focal_y) — ver RotateFn: delta_radianes es el incremento angular (positivo = horario) y (focal_x, focal_y) el punto bajo el cursor relativo al rect del nodo, para rotar “alrededor del cursor”. Patrón típico de canvas/imagen: Msg::Rotate { delta, fx, fy } que acumula el ángulo del viewport. Devolver Some(Msg) consume el gesto.

Source

pub fn on_double_tap(self, msg: Msg) -> View<Msg>

Emite msg en doble-tap (dos clicks izquierdos rápidos y cercanos sobre este nodo). Aditivo respecto de on_click. Ver Self::on_double_tap (campo) para la semántica completa; para la posición del tap usar Self::on_double_tap_at.

Examples found in repository?
examples/gestos.rs (line 155)
101    fn view(model: &Self::Model) -> View<Self::Msg> {
102        let zoom = model.zoom;
103        let pan = model.pan;
104        let marks = model.marks.clone();
105
106        let canvas = View::new(Style {
107            size: Size { width: percent(1.0_f32), height: Dimension::auto() },
108            flex_grow: 1.0,
109            ..Default::default()
110        })
111        .fill(Color::from_rgba8(16, 18, 26, 255))
112        .clip(true)
113        .paint_with(move |scene, _ts, rect| {
114            // Grilla de mundo paso 40px, escalada por zoom y desplazada por pan.
115            let step = 40.0 * zoom as f64;
116            if step >= 4.0 {
117                let thin = Stroke::new(1.0);
118                let grid = Color::from_rgba8(40, 46, 60, 255);
119                // Offset del primer línea visible (pan módulo step).
120                let ox = (rect.x as f64) + (pan.0 as f64).rem_euclid(step);
121                let mut x = ox;
122                while x < (rect.x + rect.w) as f64 {
123                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
124                        &Line::new((x, rect.y as f64), (x, (rect.y + rect.h) as f64)));
125                    x += step;
126                }
127                let oy = (rect.y as f64) + (pan.1 as f64).rem_euclid(step);
128                let mut y = oy;
129                while y < (rect.y + rect.h) as f64 {
130                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
131                        &Line::new((rect.x as f64, y), ((rect.x + rect.w) as f64, y)));
132                    y += step;
133                }
134            }
135            // Marcas (coords de mundo → pantalla): pan + world·zoom.
136            let dot = Color::from_rgba8(90, 220, 150, 255);
137            let r = (6.0 * zoom as f64).clamp(3.0, 24.0);
138            for (wx, wy) in &marks {
139                let sx = rect.x as f64 + pan.0 as f64 + (*wx as f64) * zoom as f64;
140                let sy = rect.y as f64 + pan.1 as f64 + (*wy as f64) * zoom as f64;
141                scene.fill(Fill::NonZero, Affine::IDENTITY, dot, None, &Circle::new((sx, sy), r));
142            }
143        })
144        // Pinch-to-zoom (Ctrl+rueda / trackpad). El focal viene local al canvas.
145        .on_scale(|phase, factor, fx, fy| match phase {
146            GesturePhase::Update => Some(Msg::Zoom { factor, fx, fy }),
147            _ => None,
148        })
149        // Paneo por arrastre. El movimiento cancela un long-press en curso.
150        .draggable(|phase, dx, dy| match phase {
151            DragPhase::Move => Some(Msg::Pan { dx, dy }),
152            DragPhase::End => None,
153        })
154        // Doble-tap: reset. Long-press: marca en el punto.
155        .on_double_tap(Msg::Reset)
156        .on_long_press_at(|lx, ly, _w, _h| Some(Msg::Mark { lx, ly }));
157
158        let status = View::new(Style {
159            size: Size { width: percent(1.0_f32), height: length(40.0_f32) },
160            align_items: Some(AlignItems::Center),
161            justify_content: Some(JustifyContent::Center),
162            ..Default::default()
163        })
164        .fill(Color::from_rgba8(28, 32, 42, 255))
165        .text(
166            format!("{}   ·   ×{:.2}   ·   {} marcas", model.last, model.zoom, model.marks.len()),
167            18.0,
168            Color::from_rgba8(210, 220, 235, 255),
169        );
170
171        View::new(Style {
172            flex_direction: FlexDirection::Column,
173            size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
174            ..Default::default()
175        })
176        .fill(Color::from_rgba8(16, 18, 26, 255))
177        .children(vec![canvas, status])
178    }
Source

pub fn on_double_tap_at<F>(self, handler: F) -> View<Msg>
where F: Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync + 'static,

Como Self::on_double_tap pero el handler recibe la posición del segundo tap relativa al rect del nodo (lx, ly, w, h) — para zoom-to-point o seleccionar la entidad bajo el cursor. Gana sobre on_double_tap si ambos están.

Source

pub fn on_long_press(self, msg: Msg) -> View<Msg>

Emite msg en long-press (mantener el botón ~500 ms sin moverse). El runtime lo cancela si el cursor se aleja (pasó a drag) o se suelta antes. Aditivo respecto de on_click/drag. Ver Self::on_long_press (campo); para la posición usar Self::on_long_press_at.

Source

pub fn on_long_press_at<F>(self, handler: F) -> View<Msg>
where F: Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync + 'static,

Como Self::on_long_press pero el handler recibe la posición del press relativa al rect del nodo (lx, ly, w, h) — para abrir un menú contextual en el punto. Gana sobre on_long_press si ambos están.

Examples found in repository?
examples/gestos.rs (line 156)
101    fn view(model: &Self::Model) -> View<Self::Msg> {
102        let zoom = model.zoom;
103        let pan = model.pan;
104        let marks = model.marks.clone();
105
106        let canvas = View::new(Style {
107            size: Size { width: percent(1.0_f32), height: Dimension::auto() },
108            flex_grow: 1.0,
109            ..Default::default()
110        })
111        .fill(Color::from_rgba8(16, 18, 26, 255))
112        .clip(true)
113        .paint_with(move |scene, _ts, rect| {
114            // Grilla de mundo paso 40px, escalada por zoom y desplazada por pan.
115            let step = 40.0 * zoom as f64;
116            if step >= 4.0 {
117                let thin = Stroke::new(1.0);
118                let grid = Color::from_rgba8(40, 46, 60, 255);
119                // Offset del primer línea visible (pan módulo step).
120                let ox = (rect.x as f64) + (pan.0 as f64).rem_euclid(step);
121                let mut x = ox;
122                while x < (rect.x + rect.w) as f64 {
123                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
124                        &Line::new((x, rect.y as f64), (x, (rect.y + rect.h) as f64)));
125                    x += step;
126                }
127                let oy = (rect.y as f64) + (pan.1 as f64).rem_euclid(step);
128                let mut y = oy;
129                while y < (rect.y + rect.h) as f64 {
130                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
131                        &Line::new((rect.x as f64, y), ((rect.x + rect.w) as f64, y)));
132                    y += step;
133                }
134            }
135            // Marcas (coords de mundo → pantalla): pan + world·zoom.
136            let dot = Color::from_rgba8(90, 220, 150, 255);
137            let r = (6.0 * zoom as f64).clamp(3.0, 24.0);
138            for (wx, wy) in &marks {
139                let sx = rect.x as f64 + pan.0 as f64 + (*wx as f64) * zoom as f64;
140                let sy = rect.y as f64 + pan.1 as f64 + (*wy as f64) * zoom as f64;
141                scene.fill(Fill::NonZero, Affine::IDENTITY, dot, None, &Circle::new((sx, sy), r));
142            }
143        })
144        // Pinch-to-zoom (Ctrl+rueda / trackpad). El focal viene local al canvas.
145        .on_scale(|phase, factor, fx, fy| match phase {
146            GesturePhase::Update => Some(Msg::Zoom { factor, fx, fy }),
147            _ => None,
148        })
149        // Paneo por arrastre. El movimiento cancela un long-press en curso.
150        .draggable(|phase, dx, dy| match phase {
151            DragPhase::Move => Some(Msg::Pan { dx, dy }),
152            DragPhase::End => None,
153        })
154        // Doble-tap: reset. Long-press: marca en el punto.
155        .on_double_tap(Msg::Reset)
156        .on_long_press_at(|lx, ly, _w, _h| Some(Msg::Mark { lx, ly }));
157
158        let status = View::new(Style {
159            size: Size { width: percent(1.0_f32), height: length(40.0_f32) },
160            align_items: Some(AlignItems::Center),
161            justify_content: Some(JustifyContent::Center),
162            ..Default::default()
163        })
164        .fill(Color::from_rgba8(28, 32, 42, 255))
165        .text(
166            format!("{}   ·   ×{:.2}   ·   {} marcas", model.last, model.zoom, model.marks.len()),
167            18.0,
168            Color::from_rgba8(210, 220, 235, 255),
169        );
170
171        View::new(Style {
172            flex_direction: FlexDirection::Column,
173            size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
174            ..Default::default()
175        })
176        .fill(Color::from_rgba8(16, 18, 26, 255))
177        .children(vec![canvas, status])
178    }
Source

pub fn focusable(self, id: u64) -> View<Msg>

Marca este nodo como enfocable con el id opaco id. El runtime lo incluye en el orden de Tab (pre-orden del árbol) y le da foco al clickearlo; cada cambio de foco se notifica vía App::on_focus. El caller pinta el focus-ring comparando el id contra el foco que guardó en su Model.

Source

pub fn selectable(self, key: u64) -> View<Msg>

Marca este nodo de texto como seleccionable con el mouse fuera del editor: arrastrar sobre él resalta el rango y Ctrl/Cmd+C lo copia al portapapeles. key debe ser estable entre rebuilds del View (índice, hash del id) — la selección vive en el runtime anclada a esa key, no al NodeId (que cambia cada frame). Pensá en labels, párrafos, celdas de tabla, salidas de consola: cualquier texto que el usuario querría copiar sin un editor. Sólo aplica a texto uniforme (el de .text(...)/.text_aligned(...)); en nodos con runs/spans no tiene efecto (esos son del editor / RichText). Componer con el texto: View::new(style).text_aligned(s, 14.0, col, al).selectable(key).

Examples found in repository?
examples/selectable_text.rs (line 72)
40    fn view(_: &Self::Model) -> View<Self::Msg> {
41        let mut children: Vec<View<()>> = vec![View::new(Style {
42            size: Size {
43                width: percent(1.0_f32),
44                height: Dimension::auto(),
45            },
46            ..Default::default()
47        })
48        .text_aligned(
49            "Texto seleccionable (arrastrá + Ctrl/Cmd+C)",
50            22.0,
51            Color::from_rgba8(230, 240, 250, 255),
52            Alignment::Start,
53        )];
54
55        for (i, p) in PARRAFOS.iter().enumerate() {
56            children.push(
57                View::new(Style {
58                    size: Size {
59                        width: percent(1.0_f32),
60                        height: Dimension::auto(),
61                    },
62                    ..Default::default()
63                })
64                .text_aligned(
65                    *p,
66                    16.0,
67                    Color::from_rgba8(205, 214, 226, 255),
68                    Alignment::Start,
69                )
70                // La línea clave: cada párrafo es seleccionable con una key
71                // estable (su índice). El resaltado + copy los hace el runtime.
72                .selectable(i as u64),
73            );
74        }
75
76        View::new(Style {
77            flex_direction: FlexDirection::Column,
78            size: Size {
79                width: percent(1.0_f32),
80                height: percent(1.0_f32),
81            },
82            gap: Size {
83                width: length(0.0_f32),
84                height: length(20.0_f32),
85            },
86            padding: Rect {
87                left: length(40.0_f32),
88                right: length(40.0_f32),
89                top: length(36.0_f32),
90                bottom: length(36.0_f32),
91            },
92            align_items: Some(AlignItems::Start),
93            ..Default::default()
94        })
95        .fill(Color::from_rgba8(20, 24, 32, 255))
96        .children(children)
97    }
Source

pub fn hero(self, key: u64, duration: Duration) -> View<Msg>

Marca este nodo como hero shared-element con la key indicada. Cuando la misma key aparece en un rect distinto en el frame siguiente (entre rutas, paneles, layouts), el runtime interpola transform para “volar” del rect anterior al actual durante duration. La key debe ser estable y única dentro del frame; idéntica semántica a la key de Self::animated. Para easing distinto al ease-out cúbico default, ver Self::hero_curve.

Source

pub fn hero_curve( self, key: u64, duration: Duration, easing: fn(f32) -> f32, ) -> View<Msg>

Como Self::hero pero con easing explícito.

Source

pub fn transform(self, xf: Affine) -> View<Msg>

Aplica una transformación afín 2D a este nodo y todo su subtree, alrededor del centro de su rect (CSS transform-origin: 50% 50%). El centro se resuelve en paint contra el layout computado; el caller sólo provee el afín “local” (producto de sus rotate/scale/translate). Nodos anidados componen en el espacio ya transformado del padre. Pensado para transform y @keyframes CSS de puriy. Affine::IDENTITY equivale a no setear.

Source

pub fn transform_rel(self, frac: (f64, f64)) -> View<Msg>

Traslación relativa al tamaño del propio nodo: (fx, fy) desplaza (fx · w, fy · h) px, resueltos contra el rect computado en paint. Es el translate(<%>) de CSS que no cabe en un Affine fijo (p. ej. el centrado translate(-50%, -50%)transform_rel((-0.5, -0.5))). Compone con transform (si está) como factor más externo. Ver View::transform. (0.0, 0.0) equivale a no setear.

Source

pub fn transform_origin(self, pivot: TransformPivot) -> View<Msg>

Punto de pivote de transform (CSS transform-origin). Sin setear ⇒ centro del rect (50% 50%). Ver TransformPivot. Sólo tiene efecto junto con transform/transform_rel.

Source

pub fn fill(self, color: AlphaColor<Srgb>) -> View<Msg>

Examples found in repository?
examples/editor.rs (line 100)
70    fn view(model: &Self::Model) -> View<Self::Msg> {
71        let body_text = if model.is_empty() {
72            "tipea algo · ctrl+L limpia · enter salto · backspace borra".to_string()
73        } else {
74            // Cursor visual al final del contenido.
75            format!("{model}\u{2588}")
76        };
77        let body_color = if model.is_empty() {
78            Color::from_rgba8(110, 130, 150, 255)
79        } else {
80            Color::from_rgba8(220, 230, 240, 255)
81        };
82
83        let body = View::new(Style {
84            size: Size {
85                width: percent(1.0_f32),
86                height: percent(1.0_f32),
87            },
88            flex_grow: 1.0,
89            ..Default::default()
90        })
91        .text_aligned(body_text, 22.0, body_color, Alignment::Start);
92
93        let status = View::new(Style {
94            size: Size {
95                width: percent(1.0_f32),
96                height: length(36.0_f32),
97            },
98            ..Default::default()
99        })
100        .fill(Color::from_rgba8(30, 36, 48, 255))
101        .text(
102            format!("{} chars", model.chars().count()),
103            16.0,
104            Color::from_rgba8(160, 180, 200, 255),
105        );
106
107        View::new(Style {
108            flex_direction: FlexDirection::Column,
109            size: Size {
110                width: percent(1.0_f32),
111                height: percent(1.0_f32),
112            },
113            gap: Size {
114                width: length(0.0_f32),
115                height: length(8.0_f32),
116            },
117            padding: llimphi_ui::llimphi_layout::taffy::Rect {
118                left: length(24.0_f32),
119                right: length(24.0_f32),
120                top: length(24.0_f32),
121                bottom: length(24.0_f32),
122            },
123            ..Default::default()
124        })
125        .fill(Color::from_rgba8(20, 24, 32, 255))
126        .children(vec![body, status])
127    }
More examples
Hide additional examples
examples/selectable_text.rs (line 95)
40    fn view(_: &Self::Model) -> View<Self::Msg> {
41        let mut children: Vec<View<()>> = vec![View::new(Style {
42            size: Size {
43                width: percent(1.0_f32),
44                height: Dimension::auto(),
45            },
46            ..Default::default()
47        })
48        .text_aligned(
49            "Texto seleccionable (arrastrá + Ctrl/Cmd+C)",
50            22.0,
51            Color::from_rgba8(230, 240, 250, 255),
52            Alignment::Start,
53        )];
54
55        for (i, p) in PARRAFOS.iter().enumerate() {
56            children.push(
57                View::new(Style {
58                    size: Size {
59                        width: percent(1.0_f32),
60                        height: Dimension::auto(),
61                    },
62                    ..Default::default()
63                })
64                .text_aligned(
65                    *p,
66                    16.0,
67                    Color::from_rgba8(205, 214, 226, 255),
68                    Alignment::Start,
69                )
70                // La línea clave: cada párrafo es seleccionable con una key
71                // estable (su índice). El resaltado + copy los hace el runtime.
72                .selectable(i as u64),
73            );
74        }
75
76        View::new(Style {
77            flex_direction: FlexDirection::Column,
78            size: Size {
79                width: percent(1.0_f32),
80                height: percent(1.0_f32),
81            },
82            gap: Size {
83                width: length(0.0_f32),
84                height: length(20.0_f32),
85            },
86            padding: Rect {
87                left: length(40.0_f32),
88                right: length(40.0_f32),
89                top: length(36.0_f32),
90                bottom: length(36.0_f32),
91            },
92            align_items: Some(AlignItems::Start),
93            ..Default::default()
94        })
95        .fill(Color::from_rgba8(20, 24, 32, 255))
96        .children(children)
97    }
examples/gpu_paint_demo.rs (line 84)
54    fn view(model: &Self::Model) -> View<Self::Msg> {
55        let title = View::new(Style {
56            size: Size {
57                width: percent(1.0_f32),
58                height: length(48.0_f32),
59            },
60            justify_content: Some(JustifyContent::Center),
61            align_items: Some(AlignItems::Center),
62            ..Default::default()
63        })
64        .text(
65            format!("gpu_paint_with — {POINTS} puntos GPU directo · seed {model}"),
66            22.0,
67            Color::from_rgba8(220, 230, 245, 255),
68        );
69
70        // Canvas central: vello pinta el fondo (fill + radius), GPU pinta
71        // la grilla de puntos encima vía gpu_paint_with. El seed del
72        // modelo se mete en el shader vía una rotación trivial — cada
73        // click cambia el patrón. El callback se invoca ya con el
74        // CommandEncoder del frame y la TextureView intermediate.
75        let seed = *model;
76        let canvas = View::new(Style {
77            size: Size {
78                width: percent(1.0_f32),
79                height: auto(),
80            },
81            flex_grow: 1.0,
82            ..Default::default()
83        })
84        .fill(Color::from_rgba8(14, 18, 28, 255))
85        .radius(8.0)
86        .gpu_paint_with(move |device, queue, encoder, view, rect, _viewport| {
87            draw_points(device, queue, encoder, view, rect, seed);
88        })
89        .on_click(Msg::Bump);
90
91        let footer = View::new(Style {
92            size: Size {
93                width: percent(1.0_f32),
94                height: length(28.0_f32),
95            },
96            justify_content: Some(JustifyContent::Center),
97            align_items: Some(AlignItems::Center),
98            ..Default::default()
99        })
100        .text(
101            "click sobre el canvas → rebobinar el seed",
102            14.0,
103            Color::from_rgba8(150, 165, 185, 255),
104        );
105
106        View::new(Style {
107            flex_direction: FlexDirection::Column,
108            size: Size {
109                width: percent(1.0_f32),
110                height: percent(1.0_f32),
111            },
112            gap: Size {
113                width: length(0.0_f32),
114                height: length(16.0_f32),
115            },
116            padding: TaffyRect {
117                left: length(24.0_f32),
118                right: length(24.0_f32),
119                top: length(16.0_f32),
120                bottom: length(16.0_f32),
121            },
122            ..Default::default()
123        })
124        .fill(Color::from_rgba8(24, 28, 38, 255))
125        .children(vec![title, canvas, footer])
126    }
examples/counter.rs (line 65)
43    fn view(model: &Self::Model) -> View<Self::Msg> {
44        let number = View::new(Style {
45            size: Size {
46                width: percent(1.0_f32),
47                height: Dimension::auto(),
48            },
49            flex_grow: 1.0,
50            align_items: Some(AlignItems::Center),
51            justify_content: Some(JustifyContent::Center),
52            ..Default::default()
53        })
54        .text(model.to_string(), 160.0, Color::from_rgba8(230, 240, 250, 255));
55
56        let increment = View::new(Style {
57            size: Size {
58                width: length(160.0_f32),
59                height: length(56.0_f32),
60            },
61            align_items: Some(AlignItems::Center),
62            justify_content: Some(JustifyContent::Center),
63            ..Default::default()
64        })
65        .fill(Color::from_rgba8(60, 200, 130, 255))
66        .radius(12.0)
67        .text("+1", 28.0, Color::from_rgba8(10, 30, 20, 255))
68        .on_click(Msg::Increment);
69
70        let reset = View::new(Style {
71            size: Size {
72                width: length(120.0_f32),
73                height: length(56.0_f32),
74            },
75            align_items: Some(AlignItems::Center),
76            justify_content: Some(JustifyContent::Center),
77            ..Default::default()
78        })
79        .fill(Color::from_rgba8(220, 80, 80, 255))
80        .radius(12.0)
81        .text("reset", 22.0, Color::from_rgba8(30, 10, 10, 255))
82        .on_click(Msg::Reset);
83
84        let buttons = View::new(Style {
85            flex_direction: FlexDirection::Row,
86            size: Size {
87                width: percent(1.0_f32),
88                height: length(56.0_f32),
89            },
90            gap: Size {
91                width: length(16.0_f32),
92                height: length(0.0_f32),
93            },
94            justify_content: Some(JustifyContent::Center),
95            ..Default::default()
96        })
97        .children(vec![increment, reset]);
98
99        View::new(Style {
100            flex_direction: FlexDirection::Column,
101            size: Size {
102                width: percent(1.0_f32),
103                height: percent(1.0_f32),
104            },
105            gap: Size {
106                width: length(0.0_f32),
107                height: length(24.0_f32),
108            },
109            padding: llimphi_ui::llimphi_layout::taffy::Rect {
110                left: length(32.0_f32),
111                right: length(32.0_f32),
112                top: length(32.0_f32),
113                bottom: length(32.0_f32),
114            },
115            ..Default::default()
116        })
117        .fill(Color::from_rgba8(20, 24, 32, 255))
118        .children(vec![number, buttons])
119    }
examples/gestos.rs (line 111)
101    fn view(model: &Self::Model) -> View<Self::Msg> {
102        let zoom = model.zoom;
103        let pan = model.pan;
104        let marks = model.marks.clone();
105
106        let canvas = View::new(Style {
107            size: Size { width: percent(1.0_f32), height: Dimension::auto() },
108            flex_grow: 1.0,
109            ..Default::default()
110        })
111        .fill(Color::from_rgba8(16, 18, 26, 255))
112        .clip(true)
113        .paint_with(move |scene, _ts, rect| {
114            // Grilla de mundo paso 40px, escalada por zoom y desplazada por pan.
115            let step = 40.0 * zoom as f64;
116            if step >= 4.0 {
117                let thin = Stroke::new(1.0);
118                let grid = Color::from_rgba8(40, 46, 60, 255);
119                // Offset del primer línea visible (pan módulo step).
120                let ox = (rect.x as f64) + (pan.0 as f64).rem_euclid(step);
121                let mut x = ox;
122                while x < (rect.x + rect.w) as f64 {
123                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
124                        &Line::new((x, rect.y as f64), (x, (rect.y + rect.h) as f64)));
125                    x += step;
126                }
127                let oy = (rect.y as f64) + (pan.1 as f64).rem_euclid(step);
128                let mut y = oy;
129                while y < (rect.y + rect.h) as f64 {
130                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
131                        &Line::new((rect.x as f64, y), ((rect.x + rect.w) as f64, y)));
132                    y += step;
133                }
134            }
135            // Marcas (coords de mundo → pantalla): pan + world·zoom.
136            let dot = Color::from_rgba8(90, 220, 150, 255);
137            let r = (6.0 * zoom as f64).clamp(3.0, 24.0);
138            for (wx, wy) in &marks {
139                let sx = rect.x as f64 + pan.0 as f64 + (*wx as f64) * zoom as f64;
140                let sy = rect.y as f64 + pan.1 as f64 + (*wy as f64) * zoom as f64;
141                scene.fill(Fill::NonZero, Affine::IDENTITY, dot, None, &Circle::new((sx, sy), r));
142            }
143        })
144        // Pinch-to-zoom (Ctrl+rueda / trackpad). El focal viene local al canvas.
145        .on_scale(|phase, factor, fx, fy| match phase {
146            GesturePhase::Update => Some(Msg::Zoom { factor, fx, fy }),
147            _ => None,
148        })
149        // Paneo por arrastre. El movimiento cancela un long-press en curso.
150        .draggable(|phase, dx, dy| match phase {
151            DragPhase::Move => Some(Msg::Pan { dx, dy }),
152            DragPhase::End => None,
153        })
154        // Doble-tap: reset. Long-press: marca en el punto.
155        .on_double_tap(Msg::Reset)
156        .on_long_press_at(|lx, ly, _w, _h| Some(Msg::Mark { lx, ly }));
157
158        let status = View::new(Style {
159            size: Size { width: percent(1.0_f32), height: length(40.0_f32) },
160            align_items: Some(AlignItems::Center),
161            justify_content: Some(JustifyContent::Center),
162            ..Default::default()
163        })
164        .fill(Color::from_rgba8(28, 32, 42, 255))
165        .text(
166            format!("{}   ·   ×{:.2}   ·   {} marcas", model.last, model.zoom, model.marks.len()),
167            18.0,
168            Color::from_rgba8(210, 220, 235, 255),
169        );
170
171        View::new(Style {
172            flex_direction: FlexDirection::Column,
173            size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
174            ..Default::default()
175        })
176        .fill(Color::from_rgba8(16, 18, 26, 255))
177        .children(vec![canvas, status])
178    }
Source

pub fn alpha(self, a: f32) -> View<Msg>

Opacidad uniforme aplicada a este nodo y todos sus descendientes vía scene.push_layer(Mix::Normal, a, …). Pensado para fade-in/out de overlays, toasts y modales sin tener que tunear el alpha de cada color del subtree. Valores fuera de [0.0, 1.0] se clampean. Hace que el subtree se componga en una capa intermedia — usar sólo cuando sea necesario (no es gratuito).

Source

pub fn animated(self, key: u64, duration: Duration) -> View<Msg>

Anima de forma implícita las props de paint de este nodo (hoy fill y radius): cuando su valor cambia entre frames, el runtime interpola en duration con ease-out cúbico en vez de saltar (estilo Flutter AnimatedContainer). key debe ser estable entre rebuilds del View (índice de item, hash de id) — es lo que enlaza “el mismo nodo” entre frames; dos nodos distintos no deben compartir key. La primera aparición no anima; sólo los cambios posteriores. Para otra curva, Self::animated_curve.

Source

pub fn animated_size(self, key: u64, duration: Duration) -> View<Msg>

Anima de forma implícita el tamaño de este nodo (Flutter AnimatedSize / Compose animateContentSize()). Cuando style.size cambia entre frames, el runtime interpola en duration con ease-out cúbico en vez de saltar — siblings y hijos reflowean suave porque el reconciler parcha style.size antes del layout. key debe ser estable entre rebuilds. Para otra curva, Self::animated_size_curve. Bloque 15.

Límite v1: ambos style.size.width y style.size.height tienen que ser Dimension::Length(_). Si una es Percent/Auto, el nodo se monta tal cual sin animación (no hay valor en píxeles estable para interpolar). El caller que necesite animar un nodo flex puede envolver el contenido en un wrap con length(...) fijo y mover el flex al padre.

Source

pub fn animated_size_curve( self, key: u64, duration: Duration, easing: fn(f32) -> f32, ) -> View<Msg>

Como Self::animated_size pero con curva de easing custom.

Source

pub fn animated_enter(self, key: u64, duration: Duration) -> View<Msg>

Como Self::animated pero además anima la entrada: la primera vez que esta key aparece, su opacidad sube de 0 a su valor (alpha o 1.0) en duration — fade-in estilo AnimatedSwitcher/AnimatedVisibility. Útil para toasts, items de lista que aparecen, paneles que se montan, resultados que entran. Como toda animación implícita, depende de una key estable; reutilizar la key de un nodo que ya estaba NO refadea (sólo la primera aparición anima). Para animar también la salida, ver Self::animated_inout.

Source

pub fn animated_enter_from( self, key: u64, duration: Duration, from_xf: Affine, ) -> View<Msg>

Como Self::animated_enter pero además arranca la entrada desde una transformación afín específica hacia la del nodo (o la identidad si no setea .transform). Habilita scale-in / slide-in / rotate-in implícitos: el caller declara la pose inicial, el runtime interpola. Ejemplos:

  • Affine::scale(0.6) → “pop” (FAB de Material, modales).
  • Affine::translate((0.0, 60.0)) → slide-in vertical (snackbars).
  • Affine::translate((-w, 0.0)) → slide-in lateral (drawers). Combina con el fade-in de entrada (alpha 0 → opaque). Sin animación de salida; para entrada+salida con pose, ver Self::animated_inout_from.
Source

pub fn animated_pop_in(self, key: u64, duration: Duration) -> View<Msg>

Atajo Material: scale-in desde 0.6 (entrada “pop” del FAB). Combina con fade-in. Equivalente a .animated_enter_from(key, dur, Affine::scale(0.6)).

Source

pub fn animated_exit(self, key: u64, duration: Duration) -> View<Msg>

Anima la salida (fade-out): cuando esta key desaparece del árbol, el runtime retiene la última subescena que pintó y la reproduce con opacidad decreciente durante duration — estilo AnimatedSwitcher / AnimatedVisibility al ocultarse. No anima la entrada (para ambas, ver Self::animated_inout). Tiene coste por frame mientras el nodo vive (captura su subárbol); usar con moderación (toasts, modales, paneles).

Source

pub fn animated_inout(self, key: u64, duration: Duration) -> View<Msg>

Anima entrada y salida: fade-in en la primera aparición y fade-out al desmontarse, ambos en duration. La pieza completa de “animación de contenido” para un nodo que aparece y desaparece (un toast, un panel que se abre y cierra, un resultado que entra y se va).

Source

pub fn animated_inout_from( self, key: u64, duration: Duration, from_xf: Affine, ) -> View<Msg>

Como Self::animated_inout pero arranca la entrada desde la transformación afín from_xf (igual semántica que Self::animated_enter_from). La salida sigue siendo el fade-out estándar (la subescena retenida no transforma).

Source

pub fn animated_curve( self, key: u64, duration: Duration, easing: fn(f32) -> f32, ) -> View<Msg>

Como Self::animated pero con easing explícito (p. ej. llimphi_theme::motion::ease_in_out_cubic).

Source

pub fn animated_switch( self, key: u64, variant: u64, duration: Duration, ) -> View<Msg>

Cross-fade real entre variantes de contenido bajo la misma key (Flutter AnimatedSwitcher). variant identifica el contenido actual (índice de pestaña, hash del estado, discriminante de un enum…). Cuando variant cambia entre frames, el runtime desvanece la subescena vieja (fade-out, retenida del frame previo) mientras hace fade-in del subárbol nuevo, en el mismo rect — la transición real entre dos identidades, en vez de combinar animated_enter+animated_exit de dos keys distintas.

Envolvé el contenido conmutable en un nodo con esta marca; sus hijos son el contenido. La primera aparición no cruza (sólo fija la variante). Igual que exit, captura el subárbol por frame — usar en pocos nodos (un panel central, un visor que cambia de documento), no por fila.

Source

pub fn hover_fill(self, color: AlphaColor<Srgb>) -> View<Msg>

Color a usar cuando el cursor está sobre este nodo. Habilita el hit-test de hover sobre el nodo.

Source

pub fn draggable<F>(self, handler: F) -> View<Msg>
where F: Fn(DragPhase, f32, f32) -> Option<Msg> + Send + Sync + 'static,

Marca este nodo como draggable. Mientras el usuario sostenga el botón izquierdo sobre él, el runtime llama handler(Move, dx, dy) por cada CursorMoved (dx/dy = delta desde el evento anterior) y handler(End, 0, 0) al soltar. Sobreescribe on_click para este nodo: un nodo es draggable o clickable.

Examples found in repository?
examples/gestos.rs (lines 150-153)
101    fn view(model: &Self::Model) -> View<Self::Msg> {
102        let zoom = model.zoom;
103        let pan = model.pan;
104        let marks = model.marks.clone();
105
106        let canvas = View::new(Style {
107            size: Size { width: percent(1.0_f32), height: Dimension::auto() },
108            flex_grow: 1.0,
109            ..Default::default()
110        })
111        .fill(Color::from_rgba8(16, 18, 26, 255))
112        .clip(true)
113        .paint_with(move |scene, _ts, rect| {
114            // Grilla de mundo paso 40px, escalada por zoom y desplazada por pan.
115            let step = 40.0 * zoom as f64;
116            if step >= 4.0 {
117                let thin = Stroke::new(1.0);
118                let grid = Color::from_rgba8(40, 46, 60, 255);
119                // Offset del primer línea visible (pan módulo step).
120                let ox = (rect.x as f64) + (pan.0 as f64).rem_euclid(step);
121                let mut x = ox;
122                while x < (rect.x + rect.w) as f64 {
123                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
124                        &Line::new((x, rect.y as f64), (x, (rect.y + rect.h) as f64)));
125                    x += step;
126                }
127                let oy = (rect.y as f64) + (pan.1 as f64).rem_euclid(step);
128                let mut y = oy;
129                while y < (rect.y + rect.h) as f64 {
130                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
131                        &Line::new((rect.x as f64, y), ((rect.x + rect.w) as f64, y)));
132                    y += step;
133                }
134            }
135            // Marcas (coords de mundo → pantalla): pan + world·zoom.
136            let dot = Color::from_rgba8(90, 220, 150, 255);
137            let r = (6.0 * zoom as f64).clamp(3.0, 24.0);
138            for (wx, wy) in &marks {
139                let sx = rect.x as f64 + pan.0 as f64 + (*wx as f64) * zoom as f64;
140                let sy = rect.y as f64 + pan.1 as f64 + (*wy as f64) * zoom as f64;
141                scene.fill(Fill::NonZero, Affine::IDENTITY, dot, None, &Circle::new((sx, sy), r));
142            }
143        })
144        // Pinch-to-zoom (Ctrl+rueda / trackpad). El focal viene local al canvas.
145        .on_scale(|phase, factor, fx, fy| match phase {
146            GesturePhase::Update => Some(Msg::Zoom { factor, fx, fy }),
147            _ => None,
148        })
149        // Paneo por arrastre. El movimiento cancela un long-press en curso.
150        .draggable(|phase, dx, dy| match phase {
151            DragPhase::Move => Some(Msg::Pan { dx, dy }),
152            DragPhase::End => None,
153        })
154        // Doble-tap: reset. Long-press: marca en el punto.
155        .on_double_tap(Msg::Reset)
156        .on_long_press_at(|lx, ly, _w, _h| Some(Msg::Mark { lx, ly }));
157
158        let status = View::new(Style {
159            size: Size { width: percent(1.0_f32), height: length(40.0_f32) },
160            align_items: Some(AlignItems::Center),
161            justify_content: Some(JustifyContent::Center),
162            ..Default::default()
163        })
164        .fill(Color::from_rgba8(28, 32, 42, 255))
165        .text(
166            format!("{}   ·   ×{:.2}   ·   {} marcas", model.last, model.zoom, model.marks.len()),
167            18.0,
168            Color::from_rgba8(210, 220, 235, 255),
169        );
170
171        View::new(Style {
172            flex_direction: FlexDirection::Column,
173            size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
174            ..Default::default()
175        })
176        .fill(Color::from_rgba8(16, 18, 26, 255))
177        .children(vec![canvas, status])
178    }
Source

pub fn draggable_at<F>(self, handler: F) -> View<Msg>
where F: Fn(DragPhase, f32, f32, f32, f32) -> Option<Msg> + Send + Sync + 'static,

Como draggable, pero el handler también recibe la posición inicial del press relativa al rect del nodo (initial_lx, initial_ly). Útil cuando el caller necesita resolver qué entidad bajo el cursor inició el drag (Conceptos, lemmings, nodos de un grafo, etc.). Gana sobre draggable si ambos están.

Source

pub fn draggable_velocity<F>(self, handler: F) -> View<Msg>
where F: Fn(DragPhase, f32, f32, f32, f32) -> Option<Msg> + Send + Sync + 'static,

Como Self::draggable pero el handler recibe además la velocidad del drag al soltarlo (vx, vy en px/s) en la fase DragPhase::EndFn(DragPhase, dx, dy, vx, vy) -> Option<Msg>. El runtime mide el desplazamiento sobre los últimos ~100 ms y lo divide por el tiempo transcurrido. Durante DragPhase::Move, vx == vy == 0 (la velocidad sólo se calcula al final). Gana sobre draggable y draggable_at si conviven en el mismo nodo — un nodo elige un único sabor de drag. Habilita fling-desde-drag: el caller emite Msg::Fling { vx, vy } en End y arranca un ticker que decae la velocidad con [fling_step] hasta asentar.

Source

pub fn drag_payload(self, payload: u64) -> View<Msg>

Declara el payload u64 que viaja con el drag de este nodo. Los drop targets bajo cursor al soltar reciben este valor en su on_drop. Sin payload, los drop targets no reaccionan (útil para drags de “resize/scroll” que no representan transferencia).

Source

pub fn on_drop<F>(self, handler: F) -> View<Msg>
where F: Fn(u64) -> Option<Msg> + Send + Sync + 'static,

Marca este nodo como drop target. El runtime invoca handler(payload) cuando un drag termina sobre el rect de este nodo y el origen del drag declaró un payload. Si devuelve Some(Msg), se dispatchea al update antes del DragPhase::End del origen.

Source

pub fn drop_hover_fill(self, color: AlphaColor<Srgb>) -> View<Msg>

Color de relleno cuando un drag activo está hovereando este drop target. Análogo a hover_fill pero solo aplica mientras dura un drag. Útil para resaltar el destino válido.

Source

pub fn radius(self, r: f64) -> View<Msg>

Examples found in repository?
examples/gpu_paint_demo.rs (line 85)
54    fn view(model: &Self::Model) -> View<Self::Msg> {
55        let title = View::new(Style {
56            size: Size {
57                width: percent(1.0_f32),
58                height: length(48.0_f32),
59            },
60            justify_content: Some(JustifyContent::Center),
61            align_items: Some(AlignItems::Center),
62            ..Default::default()
63        })
64        .text(
65            format!("gpu_paint_with — {POINTS} puntos GPU directo · seed {model}"),
66            22.0,
67            Color::from_rgba8(220, 230, 245, 255),
68        );
69
70        // Canvas central: vello pinta el fondo (fill + radius), GPU pinta
71        // la grilla de puntos encima vía gpu_paint_with. El seed del
72        // modelo se mete en el shader vía una rotación trivial — cada
73        // click cambia el patrón. El callback se invoca ya con el
74        // CommandEncoder del frame y la TextureView intermediate.
75        let seed = *model;
76        let canvas = View::new(Style {
77            size: Size {
78                width: percent(1.0_f32),
79                height: auto(),
80            },
81            flex_grow: 1.0,
82            ..Default::default()
83        })
84        .fill(Color::from_rgba8(14, 18, 28, 255))
85        .radius(8.0)
86        .gpu_paint_with(move |device, queue, encoder, view, rect, _viewport| {
87            draw_points(device, queue, encoder, view, rect, seed);
88        })
89        .on_click(Msg::Bump);
90
91        let footer = View::new(Style {
92            size: Size {
93                width: percent(1.0_f32),
94                height: length(28.0_f32),
95            },
96            justify_content: Some(JustifyContent::Center),
97            align_items: Some(AlignItems::Center),
98            ..Default::default()
99        })
100        .text(
101            "click sobre el canvas → rebobinar el seed",
102            14.0,
103            Color::from_rgba8(150, 165, 185, 255),
104        );
105
106        View::new(Style {
107            flex_direction: FlexDirection::Column,
108            size: Size {
109                width: percent(1.0_f32),
110                height: percent(1.0_f32),
111            },
112            gap: Size {
113                width: length(0.0_f32),
114                height: length(16.0_f32),
115            },
116            padding: TaffyRect {
117                left: length(24.0_f32),
118                right: length(24.0_f32),
119                top: length(16.0_f32),
120                bottom: length(16.0_f32),
121            },
122            ..Default::default()
123        })
124        .fill(Color::from_rgba8(24, 28, 38, 255))
125        .children(vec![title, canvas, footer])
126    }
More examples
Hide additional examples
examples/counter.rs (line 66)
43    fn view(model: &Self::Model) -> View<Self::Msg> {
44        let number = View::new(Style {
45            size: Size {
46                width: percent(1.0_f32),
47                height: Dimension::auto(),
48            },
49            flex_grow: 1.0,
50            align_items: Some(AlignItems::Center),
51            justify_content: Some(JustifyContent::Center),
52            ..Default::default()
53        })
54        .text(model.to_string(), 160.0, Color::from_rgba8(230, 240, 250, 255));
55
56        let increment = View::new(Style {
57            size: Size {
58                width: length(160.0_f32),
59                height: length(56.0_f32),
60            },
61            align_items: Some(AlignItems::Center),
62            justify_content: Some(JustifyContent::Center),
63            ..Default::default()
64        })
65        .fill(Color::from_rgba8(60, 200, 130, 255))
66        .radius(12.0)
67        .text("+1", 28.0, Color::from_rgba8(10, 30, 20, 255))
68        .on_click(Msg::Increment);
69
70        let reset = View::new(Style {
71            size: Size {
72                width: length(120.0_f32),
73                height: length(56.0_f32),
74            },
75            align_items: Some(AlignItems::Center),
76            justify_content: Some(JustifyContent::Center),
77            ..Default::default()
78        })
79        .fill(Color::from_rgba8(220, 80, 80, 255))
80        .radius(12.0)
81        .text("reset", 22.0, Color::from_rgba8(30, 10, 10, 255))
82        .on_click(Msg::Reset);
83
84        let buttons = View::new(Style {
85            flex_direction: FlexDirection::Row,
86            size: Size {
87                width: percent(1.0_f32),
88                height: length(56.0_f32),
89            },
90            gap: Size {
91                width: length(16.0_f32),
92                height: length(0.0_f32),
93            },
94            justify_content: Some(JustifyContent::Center),
95            ..Default::default()
96        })
97        .children(vec![increment, reset]);
98
99        View::new(Style {
100            flex_direction: FlexDirection::Column,
101            size: Size {
102                width: percent(1.0_f32),
103                height: percent(1.0_f32),
104            },
105            gap: Size {
106                width: length(0.0_f32),
107                height: length(24.0_f32),
108            },
109            padding: llimphi_ui::llimphi_layout::taffy::Rect {
110                left: length(32.0_f32),
111                right: length(32.0_f32),
112                top: length(32.0_f32),
113                bottom: length(32.0_f32),
114            },
115            ..Default::default()
116        })
117        .fill(Color::from_rgba8(20, 24, 32, 255))
118        .children(vec![number, buttons])
119    }
Source

pub fn radius_corners(self, tl: f64, tr: f64, br: f64, bl: f64) -> View<Msg>

Radio por esquina (top-left, top-right, bottom-right, bottom-left, en sentido horario desde arriba-izquierda) — CSS border-radius con cuatro valores. Sobreescribe a Self::radius mientras esté presente. Para cards con sólo las esquinas de arriba redondeadas, pestañas, bocadillos de chat asimétricos, etc. El borde respeta las cuatro esquinas; la sombra sigue usando el radius escalar (el blur nativo de vello no acepta radios por esquina).

Source

pub fn shadow(self, shadow: Shadow) -> View<Msg>

Proyecta una sombra detrás del nodo (drop shadow), rasterizada con el blur gaussiano nativo de vello. Se pinta antes del relleno, así el fill opaco la tapa y la sombra asoma por el desenfoque/offset. El radio de la sombra sigue al del nodo (más el spread). Ver Shadow (Shadow::soft(alpha, blur) es el default tasteful).

Source

pub fn fill_gradient(self, gradient: Gradient) -> View<Msg>

Rellena el nodo con un gradiente en vez de un color sólido. El gradiente se autorea en el cuadrado unidad [0,1]² y el runtime lo mapea al rect del nodo (así no necesitás saber el tamaño al construir el View) — igual que Alignment relativo de Flutter.

use llimphi_ui::llimphi_raster::peniko::{Color, Gradient};
use llimphi_ui::llimphi_raster::kurbo::Point;
// vertical: arriba claro → abajo oscuro
let g = Gradient::new_linear(Point::new(0.0, 0.0), Point::new(0.0, 1.0))
    .with_stops([Color::from_rgba8(80,90,110,255), Color::from_rgba8(30,34,44,255)].as_slice());
view.fill_gradient(g)

Gana sobre fill como base; un hover_fill (color) lo sigue overrideando mientras el cursor está encima.

Source

pub fn border(self, width: f64, color: AlphaColor<Srgb>) -> View<Msg>

Dibuja un borde (stroke) sobre el contorno redondeado del nodo, inset media línea hacia adentro (el grosor queda dentro del rect). Reemplaza el viejo truco de envolver el nodo en un rect-padre del color del borde con padding de 1px.

Source

pub fn text( self, content: impl Into<String>, size_px: f32, color: AlphaColor<Srgb>, ) -> View<Msg>

Examples found in repository?
examples/editor.rs (lines 101-105)
70    fn view(model: &Self::Model) -> View<Self::Msg> {
71        let body_text = if model.is_empty() {
72            "tipea algo · ctrl+L limpia · enter salto · backspace borra".to_string()
73        } else {
74            // Cursor visual al final del contenido.
75            format!("{model}\u{2588}")
76        };
77        let body_color = if model.is_empty() {
78            Color::from_rgba8(110, 130, 150, 255)
79        } else {
80            Color::from_rgba8(220, 230, 240, 255)
81        };
82
83        let body = View::new(Style {
84            size: Size {
85                width: percent(1.0_f32),
86                height: percent(1.0_f32),
87            },
88            flex_grow: 1.0,
89            ..Default::default()
90        })
91        .text_aligned(body_text, 22.0, body_color, Alignment::Start);
92
93        let status = View::new(Style {
94            size: Size {
95                width: percent(1.0_f32),
96                height: length(36.0_f32),
97            },
98            ..Default::default()
99        })
100        .fill(Color::from_rgba8(30, 36, 48, 255))
101        .text(
102            format!("{} chars", model.chars().count()),
103            16.0,
104            Color::from_rgba8(160, 180, 200, 255),
105        );
106
107        View::new(Style {
108            flex_direction: FlexDirection::Column,
109            size: Size {
110                width: percent(1.0_f32),
111                height: percent(1.0_f32),
112            },
113            gap: Size {
114                width: length(0.0_f32),
115                height: length(8.0_f32),
116            },
117            padding: llimphi_ui::llimphi_layout::taffy::Rect {
118                left: length(24.0_f32),
119                right: length(24.0_f32),
120                top: length(24.0_f32),
121                bottom: length(24.0_f32),
122            },
123            ..Default::default()
124        })
125        .fill(Color::from_rgba8(20, 24, 32, 255))
126        .children(vec![body, status])
127    }
More examples
Hide additional examples
examples/gpu_paint_demo.rs (lines 64-68)
54    fn view(model: &Self::Model) -> View<Self::Msg> {
55        let title = View::new(Style {
56            size: Size {
57                width: percent(1.0_f32),
58                height: length(48.0_f32),
59            },
60            justify_content: Some(JustifyContent::Center),
61            align_items: Some(AlignItems::Center),
62            ..Default::default()
63        })
64        .text(
65            format!("gpu_paint_with — {POINTS} puntos GPU directo · seed {model}"),
66            22.0,
67            Color::from_rgba8(220, 230, 245, 255),
68        );
69
70        // Canvas central: vello pinta el fondo (fill + radius), GPU pinta
71        // la grilla de puntos encima vía gpu_paint_with. El seed del
72        // modelo se mete en el shader vía una rotación trivial — cada
73        // click cambia el patrón. El callback se invoca ya con el
74        // CommandEncoder del frame y la TextureView intermediate.
75        let seed = *model;
76        let canvas = View::new(Style {
77            size: Size {
78                width: percent(1.0_f32),
79                height: auto(),
80            },
81            flex_grow: 1.0,
82            ..Default::default()
83        })
84        .fill(Color::from_rgba8(14, 18, 28, 255))
85        .radius(8.0)
86        .gpu_paint_with(move |device, queue, encoder, view, rect, _viewport| {
87            draw_points(device, queue, encoder, view, rect, seed);
88        })
89        .on_click(Msg::Bump);
90
91        let footer = View::new(Style {
92            size: Size {
93                width: percent(1.0_f32),
94                height: length(28.0_f32),
95            },
96            justify_content: Some(JustifyContent::Center),
97            align_items: Some(AlignItems::Center),
98            ..Default::default()
99        })
100        .text(
101            "click sobre el canvas → rebobinar el seed",
102            14.0,
103            Color::from_rgba8(150, 165, 185, 255),
104        );
105
106        View::new(Style {
107            flex_direction: FlexDirection::Column,
108            size: Size {
109                width: percent(1.0_f32),
110                height: percent(1.0_f32),
111            },
112            gap: Size {
113                width: length(0.0_f32),
114                height: length(16.0_f32),
115            },
116            padding: TaffyRect {
117                left: length(24.0_f32),
118                right: length(24.0_f32),
119                top: length(16.0_f32),
120                bottom: length(16.0_f32),
121            },
122            ..Default::default()
123        })
124        .fill(Color::from_rgba8(24, 28, 38, 255))
125        .children(vec![title, canvas, footer])
126    }
examples/counter.rs (line 54)
43    fn view(model: &Self::Model) -> View<Self::Msg> {
44        let number = View::new(Style {
45            size: Size {
46                width: percent(1.0_f32),
47                height: Dimension::auto(),
48            },
49            flex_grow: 1.0,
50            align_items: Some(AlignItems::Center),
51            justify_content: Some(JustifyContent::Center),
52            ..Default::default()
53        })
54        .text(model.to_string(), 160.0, Color::from_rgba8(230, 240, 250, 255));
55
56        let increment = View::new(Style {
57            size: Size {
58                width: length(160.0_f32),
59                height: length(56.0_f32),
60            },
61            align_items: Some(AlignItems::Center),
62            justify_content: Some(JustifyContent::Center),
63            ..Default::default()
64        })
65        .fill(Color::from_rgba8(60, 200, 130, 255))
66        .radius(12.0)
67        .text("+1", 28.0, Color::from_rgba8(10, 30, 20, 255))
68        .on_click(Msg::Increment);
69
70        let reset = View::new(Style {
71            size: Size {
72                width: length(120.0_f32),
73                height: length(56.0_f32),
74            },
75            align_items: Some(AlignItems::Center),
76            justify_content: Some(JustifyContent::Center),
77            ..Default::default()
78        })
79        .fill(Color::from_rgba8(220, 80, 80, 255))
80        .radius(12.0)
81        .text("reset", 22.0, Color::from_rgba8(30, 10, 10, 255))
82        .on_click(Msg::Reset);
83
84        let buttons = View::new(Style {
85            flex_direction: FlexDirection::Row,
86            size: Size {
87                width: percent(1.0_f32),
88                height: length(56.0_f32),
89            },
90            gap: Size {
91                width: length(16.0_f32),
92                height: length(0.0_f32),
93            },
94            justify_content: Some(JustifyContent::Center),
95            ..Default::default()
96        })
97        .children(vec![increment, reset]);
98
99        View::new(Style {
100            flex_direction: FlexDirection::Column,
101            size: Size {
102                width: percent(1.0_f32),
103                height: percent(1.0_f32),
104            },
105            gap: Size {
106                width: length(0.0_f32),
107                height: length(24.0_f32),
108            },
109            padding: llimphi_ui::llimphi_layout::taffy::Rect {
110                left: length(32.0_f32),
111                right: length(32.0_f32),
112                top: length(32.0_f32),
113                bottom: length(32.0_f32),
114            },
115            ..Default::default()
116        })
117        .fill(Color::from_rgba8(20, 24, 32, 255))
118        .children(vec![number, buttons])
119    }
examples/gestos.rs (lines 165-169)
101    fn view(model: &Self::Model) -> View<Self::Msg> {
102        let zoom = model.zoom;
103        let pan = model.pan;
104        let marks = model.marks.clone();
105
106        let canvas = View::new(Style {
107            size: Size { width: percent(1.0_f32), height: Dimension::auto() },
108            flex_grow: 1.0,
109            ..Default::default()
110        })
111        .fill(Color::from_rgba8(16, 18, 26, 255))
112        .clip(true)
113        .paint_with(move |scene, _ts, rect| {
114            // Grilla de mundo paso 40px, escalada por zoom y desplazada por pan.
115            let step = 40.0 * zoom as f64;
116            if step >= 4.0 {
117                let thin = Stroke::new(1.0);
118                let grid = Color::from_rgba8(40, 46, 60, 255);
119                // Offset del primer línea visible (pan módulo step).
120                let ox = (rect.x as f64) + (pan.0 as f64).rem_euclid(step);
121                let mut x = ox;
122                while x < (rect.x + rect.w) as f64 {
123                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
124                        &Line::new((x, rect.y as f64), (x, (rect.y + rect.h) as f64)));
125                    x += step;
126                }
127                let oy = (rect.y as f64) + (pan.1 as f64).rem_euclid(step);
128                let mut y = oy;
129                while y < (rect.y + rect.h) as f64 {
130                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
131                        &Line::new((rect.x as f64, y), ((rect.x + rect.w) as f64, y)));
132                    y += step;
133                }
134            }
135            // Marcas (coords de mundo → pantalla): pan + world·zoom.
136            let dot = Color::from_rgba8(90, 220, 150, 255);
137            let r = (6.0 * zoom as f64).clamp(3.0, 24.0);
138            for (wx, wy) in &marks {
139                let sx = rect.x as f64 + pan.0 as f64 + (*wx as f64) * zoom as f64;
140                let sy = rect.y as f64 + pan.1 as f64 + (*wy as f64) * zoom as f64;
141                scene.fill(Fill::NonZero, Affine::IDENTITY, dot, None, &Circle::new((sx, sy), r));
142            }
143        })
144        // Pinch-to-zoom (Ctrl+rueda / trackpad). El focal viene local al canvas.
145        .on_scale(|phase, factor, fx, fy| match phase {
146            GesturePhase::Update => Some(Msg::Zoom { factor, fx, fy }),
147            _ => None,
148        })
149        // Paneo por arrastre. El movimiento cancela un long-press en curso.
150        .draggable(|phase, dx, dy| match phase {
151            DragPhase::Move => Some(Msg::Pan { dx, dy }),
152            DragPhase::End => None,
153        })
154        // Doble-tap: reset. Long-press: marca en el punto.
155        .on_double_tap(Msg::Reset)
156        .on_long_press_at(|lx, ly, _w, _h| Some(Msg::Mark { lx, ly }));
157
158        let status = View::new(Style {
159            size: Size { width: percent(1.0_f32), height: length(40.0_f32) },
160            align_items: Some(AlignItems::Center),
161            justify_content: Some(JustifyContent::Center),
162            ..Default::default()
163        })
164        .fill(Color::from_rgba8(28, 32, 42, 255))
165        .text(
166            format!("{}   ·   ×{:.2}   ·   {} marcas", model.last, model.zoom, model.marks.len()),
167            18.0,
168            Color::from_rgba8(210, 220, 235, 255),
169        );
170
171        View::new(Style {
172            flex_direction: FlexDirection::Column,
173            size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
174            ..Default::default()
175        })
176        .fill(Color::from_rgba8(16, 18, 26, 255))
177        .children(vec![canvas, status])
178    }
Source

pub fn text_aligned( self, content: impl Into<String>, size_px: f32, color: AlphaColor<Srgb>, alignment: Alignment, ) -> View<Msg>

Examples found in repository?
examples/editor.rs (line 91)
70    fn view(model: &Self::Model) -> View<Self::Msg> {
71        let body_text = if model.is_empty() {
72            "tipea algo · ctrl+L limpia · enter salto · backspace borra".to_string()
73        } else {
74            // Cursor visual al final del contenido.
75            format!("{model}\u{2588}")
76        };
77        let body_color = if model.is_empty() {
78            Color::from_rgba8(110, 130, 150, 255)
79        } else {
80            Color::from_rgba8(220, 230, 240, 255)
81        };
82
83        let body = View::new(Style {
84            size: Size {
85                width: percent(1.0_f32),
86                height: percent(1.0_f32),
87            },
88            flex_grow: 1.0,
89            ..Default::default()
90        })
91        .text_aligned(body_text, 22.0, body_color, Alignment::Start);
92
93        let status = View::new(Style {
94            size: Size {
95                width: percent(1.0_f32),
96                height: length(36.0_f32),
97            },
98            ..Default::default()
99        })
100        .fill(Color::from_rgba8(30, 36, 48, 255))
101        .text(
102            format!("{} chars", model.chars().count()),
103            16.0,
104            Color::from_rgba8(160, 180, 200, 255),
105        );
106
107        View::new(Style {
108            flex_direction: FlexDirection::Column,
109            size: Size {
110                width: percent(1.0_f32),
111                height: percent(1.0_f32),
112            },
113            gap: Size {
114                width: length(0.0_f32),
115                height: length(8.0_f32),
116            },
117            padding: llimphi_ui::llimphi_layout::taffy::Rect {
118                left: length(24.0_f32),
119                right: length(24.0_f32),
120                top: length(24.0_f32),
121                bottom: length(24.0_f32),
122            },
123            ..Default::default()
124        })
125        .fill(Color::from_rgba8(20, 24, 32, 255))
126        .children(vec![body, status])
127    }
More examples
Hide additional examples
examples/selectable_text.rs (lines 48-53)
40    fn view(_: &Self::Model) -> View<Self::Msg> {
41        let mut children: Vec<View<()>> = vec![View::new(Style {
42            size: Size {
43                width: percent(1.0_f32),
44                height: Dimension::auto(),
45            },
46            ..Default::default()
47        })
48        .text_aligned(
49            "Texto seleccionable (arrastrá + Ctrl/Cmd+C)",
50            22.0,
51            Color::from_rgba8(230, 240, 250, 255),
52            Alignment::Start,
53        )];
54
55        for (i, p) in PARRAFOS.iter().enumerate() {
56            children.push(
57                View::new(Style {
58                    size: Size {
59                        width: percent(1.0_f32),
60                        height: Dimension::auto(),
61                    },
62                    ..Default::default()
63                })
64                .text_aligned(
65                    *p,
66                    16.0,
67                    Color::from_rgba8(205, 214, 226, 255),
68                    Alignment::Start,
69                )
70                // La línea clave: cada párrafo es seleccionable con una key
71                // estable (su índice). El resaltado + copy los hace el runtime.
72                .selectable(i as u64),
73            );
74        }
75
76        View::new(Style {
77            flex_direction: FlexDirection::Column,
78            size: Size {
79                width: percent(1.0_f32),
80                height: percent(1.0_f32),
81            },
82            gap: Size {
83                width: length(0.0_f32),
84                height: length(20.0_f32),
85            },
86            padding: Rect {
87                left: length(40.0_f32),
88                right: length(40.0_f32),
89                top: length(36.0_f32),
90                bottom: length(36.0_f32),
91            },
92            align_items: Some(AlignItems::Start),
93            ..Default::default()
94        })
95        .fill(Color::from_rgba8(20, 24, 32, 255))
96        .children(children)
97    }
Source

pub fn text_aligned_italic( self, content: impl Into<String>, size_px: f32, color: AlphaColor<Srgb>, alignment: Alignment, italic: bool, ) -> View<Msg>

Como text_aligned pero con un flag italic. Si la fuente activa no tiene variante italic, parley aplica synthesizing.

Source

pub fn text_aligned_full( self, content: impl Into<String>, size_px: f32, color: AlphaColor<Srgb>, alignment: Alignment, italic: bool, font_family: Option<String>, ) -> View<Msg>

Como text_aligned_italic pero con font-family explícito. La cadena se pasa como parley::FontStack::Source (acepta listas CSS con fallbacks).

Source

pub fn text_runs( self, content: impl Into<String>, size_px: f32, default_color: AlphaColor<Srgb>, runs: Vec<(usize, usize, AlphaColor<Srgb>)>, alignment: Alignment, ) -> View<Msg>

Texto multicolor en una sola pasada de shaping: content se pinta con default_color y cada (start_byte, end_byte, color) de runs sobreescribe su rango (offsets en bytes). Pensado para syntax highlighting — un nodo por línea en vez de uno por token. Anclado arriba-izquierda (sin centrado vertical); el caller dimensiona el rect.

Source

pub fn text_spans( self, content: impl Into<String>, size_px: f32, default_color: AlphaColor<Srgb>, spans: Vec<TextSpan>, alignment: Alignment, ) -> View<Msg>

Texto RichText (Bloque 13 de PARIDAD-FLUTTER, cierra Tier 2): content se pinta con los defaults del bloque (size_px, default_color, alignment, weight 400, no italic, line-height 1.2, fuente default) y cada llimphi_text::TextSpan sobreescribe en su rango de bytes uno o más de size_px/weight/italic/font_family/color/underline/ strikethrough. Soporta wrap (el ancho lo fija el layout taffy del nodo); apto para párrafos con un <b>/<i>/<code>/<small> inline, links subrayados, headings dentro del mismo flujo, render barato de markdown.

Source

pub fn with_spans(self, spans: Vec<TextSpan>) -> View<Msg>

Adjunta o reemplaza los TextSpec::spans del texto ya seteado (RichText). Permite construir el texto con los builders uniformes (.text_aligned(...).bold().underline()) y luego inyectar overrides inline. No-op si el nodo no tiene texto.

Source

pub fn line_height(self, mult: f32) -> View<Msg>

Sobreescribe el múltiplo de interlínea del texto ya seteado (default 1.2). No-op si el nodo no tiene texto. Pensado para puriy, que pasa el line-height computado de CSS para que medición y pintado usen el mismo valor.

Source

pub fn text_weight(self, weight: f32) -> View<Msg>

Sobreescribe el peso de fuente del texto ya seteado (default 400 = normal). Convención CSS: 400 normal, 500 medium, 600 semibold, 700 bold. parley elige la variante más cercana de la familia activa o la sintetiza. No-op si el nodo no tiene texto. Afecta medida y pintado.

Source

pub fn bold(self) -> View<Msg>

Atajo de Self::text_weight a 700 (bold). No-op sin texto.

Source

pub fn mono(self) -> View<Msg>

Fija la familia de fuente del texto ya seteado a la monoespaciada embebida (llimphi_text::MONOSPACE) — ancho fijo garantizado para que ls, tablas y logs columneen. No-op si el nodo no tiene texto. Afecta medida y pintado.

Source

pub fn max_lines(self, n: usize) -> View<Msg>

Clampa el texto a n líneas sin glifo de ellipsis (corte seco del prefijo que cupo). CSS -webkit-line-clamp sin text-overflow. No-op sin texto. Para el corte con usar Self::ellipsis. Sólo trunca si hay envoltura (requiere ancho acotado por el layout).

Source

pub fn ellipsis(self, n: usize) -> View<Msg>

Clampa el texto a n líneas terminando la última en cuando excede (CSS text-overflow: ellipsis + -webkit-line-clamp: n). Lo más común para items de lista, celdas de tabla, breadcrumbs y labels en cajas dimensionadas. n = 1 es el clásico single-line ellipsis. No-op sin texto.

Source

pub fn letter_spacing(self, px: f32) -> View<Msg>

letter-spacing (CSS): px extra entre letras (0 = normal, negativo junta). Afecta medida y pintado. No-op sin texto. Sólo el camino uniforme; el RichText con spans lo ignora en v1.

Source

pub fn word_spacing(self, px: f32) -> View<Msg>

word-spacing (CSS): px extra entre palabras (0 = normal). Mismo régimen que Self::letter_spacing. No-op sin texto.

Source

pub fn no_wrap(self) -> View<Msg>

white-space: nowrap/pre (CSS): el texto no envuelve — se shapea en una sola línea (break_all_lines(None)) sin importar el ancho de la caja, y desborda (lo recorta overflow: hidden si lo hay). Afecta medida y pintado. No-op sin texto. Sólo el camino uniforme; el RichText con spans lo ignora en v1.

Source

pub fn overflow_wrap(self) -> View<Msg>

overflow-wrap: break-word/anywhere (CSS): una palabra más ancha que la caja se parte para que entre, en vez de desbordar. Afecta medida y pintado. No-op sin texto. Sólo el camino uniforme; el RichText con spans lo ignora en v1, igual que no_wrap.

Source

pub fn underline(self) -> View<Msg>

Activa subrayado del texto (CSS text-decoration: underline / Flutter TextDecoration.underline). parley registra la decoración por run y el runtime pinta la línea bajo la base usando underline_offset y underline_size del font metric — proporcional al tamaño de fuente elegido. No-op sin texto.

Source

pub fn strikethrough(self) -> View<Msg>

Activa tachado del texto (CSS text-decoration: line-through / Flutter TextDecoration.lineThrough). Mismo régimen que Self::underline pero usando el strikethrough metric. No-op sin texto.

Source

pub fn on_click(self, msg: Msg) -> View<Msg>

Examples found in repository?
examples/gpu_paint_demo.rs (line 89)
54    fn view(model: &Self::Model) -> View<Self::Msg> {
55        let title = View::new(Style {
56            size: Size {
57                width: percent(1.0_f32),
58                height: length(48.0_f32),
59            },
60            justify_content: Some(JustifyContent::Center),
61            align_items: Some(AlignItems::Center),
62            ..Default::default()
63        })
64        .text(
65            format!("gpu_paint_with — {POINTS} puntos GPU directo · seed {model}"),
66            22.0,
67            Color::from_rgba8(220, 230, 245, 255),
68        );
69
70        // Canvas central: vello pinta el fondo (fill + radius), GPU pinta
71        // la grilla de puntos encima vía gpu_paint_with. El seed del
72        // modelo se mete en el shader vía una rotación trivial — cada
73        // click cambia el patrón. El callback se invoca ya con el
74        // CommandEncoder del frame y la TextureView intermediate.
75        let seed = *model;
76        let canvas = View::new(Style {
77            size: Size {
78                width: percent(1.0_f32),
79                height: auto(),
80            },
81            flex_grow: 1.0,
82            ..Default::default()
83        })
84        .fill(Color::from_rgba8(14, 18, 28, 255))
85        .radius(8.0)
86        .gpu_paint_with(move |device, queue, encoder, view, rect, _viewport| {
87            draw_points(device, queue, encoder, view, rect, seed);
88        })
89        .on_click(Msg::Bump);
90
91        let footer = View::new(Style {
92            size: Size {
93                width: percent(1.0_f32),
94                height: length(28.0_f32),
95            },
96            justify_content: Some(JustifyContent::Center),
97            align_items: Some(AlignItems::Center),
98            ..Default::default()
99        })
100        .text(
101            "click sobre el canvas → rebobinar el seed",
102            14.0,
103            Color::from_rgba8(150, 165, 185, 255),
104        );
105
106        View::new(Style {
107            flex_direction: FlexDirection::Column,
108            size: Size {
109                width: percent(1.0_f32),
110                height: percent(1.0_f32),
111            },
112            gap: Size {
113                width: length(0.0_f32),
114                height: length(16.0_f32),
115            },
116            padding: TaffyRect {
117                left: length(24.0_f32),
118                right: length(24.0_f32),
119                top: length(16.0_f32),
120                bottom: length(16.0_f32),
121            },
122            ..Default::default()
123        })
124        .fill(Color::from_rgba8(24, 28, 38, 255))
125        .children(vec![title, canvas, footer])
126    }
More examples
Hide additional examples
examples/counter.rs (line 68)
43    fn view(model: &Self::Model) -> View<Self::Msg> {
44        let number = View::new(Style {
45            size: Size {
46                width: percent(1.0_f32),
47                height: Dimension::auto(),
48            },
49            flex_grow: 1.0,
50            align_items: Some(AlignItems::Center),
51            justify_content: Some(JustifyContent::Center),
52            ..Default::default()
53        })
54        .text(model.to_string(), 160.0, Color::from_rgba8(230, 240, 250, 255));
55
56        let increment = View::new(Style {
57            size: Size {
58                width: length(160.0_f32),
59                height: length(56.0_f32),
60            },
61            align_items: Some(AlignItems::Center),
62            justify_content: Some(JustifyContent::Center),
63            ..Default::default()
64        })
65        .fill(Color::from_rgba8(60, 200, 130, 255))
66        .radius(12.0)
67        .text("+1", 28.0, Color::from_rgba8(10, 30, 20, 255))
68        .on_click(Msg::Increment);
69
70        let reset = View::new(Style {
71            size: Size {
72                width: length(120.0_f32),
73                height: length(56.0_f32),
74            },
75            align_items: Some(AlignItems::Center),
76            justify_content: Some(JustifyContent::Center),
77            ..Default::default()
78        })
79        .fill(Color::from_rgba8(220, 80, 80, 255))
80        .radius(12.0)
81        .text("reset", 22.0, Color::from_rgba8(30, 10, 10, 255))
82        .on_click(Msg::Reset);
83
84        let buttons = View::new(Style {
85            flex_direction: FlexDirection::Row,
86            size: Size {
87                width: percent(1.0_f32),
88                height: length(56.0_f32),
89            },
90            gap: Size {
91                width: length(16.0_f32),
92                height: length(0.0_f32),
93            },
94            justify_content: Some(JustifyContent::Center),
95            ..Default::default()
96        })
97        .children(vec![increment, reset]);
98
99        View::new(Style {
100            flex_direction: FlexDirection::Column,
101            size: Size {
102                width: percent(1.0_f32),
103                height: percent(1.0_f32),
104            },
105            gap: Size {
106                width: length(0.0_f32),
107                height: length(24.0_f32),
108            },
109            padding: llimphi_ui::llimphi_layout::taffy::Rect {
110                left: length(32.0_f32),
111                right: length(32.0_f32),
112                top: length(32.0_f32),
113                bottom: length(32.0_f32),
114            },
115            ..Default::default()
116        })
117        .fill(Color::from_rgba8(20, 24, 32, 255))
118        .children(vec![number, buttons])
119    }
Source

pub fn on_pointer_enter(self, msg: Msg) -> View<Msg>

Dispatch msg cuando el cursor entra al rect del nodo (transición no-hover → hover). Sólo emite una vez por entrada — el runtime no repite el msg si el cursor se mueve dentro del rect.

Source

pub fn on_pointer_leave(self, msg: Msg) -> View<Msg>

Dispatch msg cuando el cursor sale del rect del nodo.

Source

pub fn on_pointer_move_at<F>(self, handler: F) -> View<Msg>
where F: Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync + 'static,

Handler de movimiento del cursor sobre el nodo. Recibe (local_x, local_y, rect_w, rect_h) (posición relativa al rect del nodo) en CADA CursorMoved mientras el cursor está encima — no sólo al entrar, a diferencia de Self::on_pointer_enter. Útil para seguir el cursor: thumbnail de hover sobre un timeline, drawer que reacciona a la posición. Devolver None no dispara update.

Source

pub fn on_click_at<F>(self, handler: F) -> View<Msg>
where F: Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync + 'static,

Como on_click, pero el handler recibe (local_x, local_y, rect_w, rect_h) — la posición del cursor relativa al rect del nodo más las dimensiones actuales del nodo. Útil para canvas elements que necesitan saber dónde fue el click para convertirlo a coordenadas de mundo. Sobrescribe on_click para este nodo si ambos están presentes.

Source

pub fn on_right_click(self, msg: Msg) -> View<Msg>

Declara el Msg a emitir cuando el usuario hace click derecho sobre este nodo. Para menús contextuales, conviene pasar un Msg::OpenMenu { ... } y dejar que el modelo guarde la posición; el overlay se abre vía [App::view_overlay].

Source

pub fn on_right_click_at<F>(self, handler: F) -> View<Msg>
where F: Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync + 'static,

Variante posicional de Self::on_right_click. El handler recibe (local_x, local_y, rect_w, rect_h) para que un nodo “grilla” pueda resolver internamente qué subcelda recibió el click. La posición está relativa al rect del nodo.

Source

pub fn on_middle_click(self, msg: Msg) -> View<Msg>

Declara el Msg a emitir cuando el usuario hace click con el botón del medio (rueda presionada). Usado típicamente para abrir links en pestaña nueva — igual que Ctrl+Click pero más rápido.

Source

pub fn image(self, image: ImageBrush) -> View<Msg>

Pinta image dentro del rect del nodo. El encaje default es ImageFit::Contain (preservar aspect ratio cabiendo); usar Self::image_fit para Cover/Fill/None. El clip respeta radius/corner_radii, así avatares y cards redondeadas funcionan sin envolver en clip(true). Re-exporta peniko::Image vía llimphi_raster::peniko::Image — el caller decodifica los bytes con el crate image (u otro) y construye el Image con Blob<u8> + ImageFormat::Rgba8.

Source

pub fn image_fit(self, fit: ImageFit) -> View<Msg>

Política de encaje de la imagen (CSS object-fit / Flutter BoxFit). Solo aplica si hay Self::image seteada. Ver ImageFit.

Source

pub fn mask_image(self, image: ImageBrush) -> View<Msg>

Aplica image como máscara de luminancia del subárbol del nodo (CSS mask-image). El paint aísla el subárbol en una capa y multiplica su alpha por la luminancia de la máscara (blanco = visible, negro = oculto). La imagen se estira al border-box del nodo. Ortogonal a Self::image (que pinta contenido) y a los clip_* (que recortan): un nodo puede llevar máscara y recorte a la vez.

Source

pub fn mask_placement(self, placement: MaskPlacement) -> View<Msg>

Fija el encaje de la máscara (CSS mask-size/-position/-repeat). Sólo surte efecto junto a Self::mask_image. Sin esto, la máscara se estira al border-box (Fase 7.1226). Ver MaskPlacement. Fase 7.1227.

Source

pub fn mask_extra(self, layers: Vec<(ImageBrush, MaskCompose)>) -> View<Msg>

Capas de máscara adicionales (imagen, operador) (CSS mask-image con lista). Comparten el Self::mask_placement con la capa 0 (Self::mask_image) y se combinan con ella según cada operador. Fase 7.1231.

Source

pub fn paint_with<F>(self, painter: F) -> View<Msg>
where F: Fn(&mut Scene, &mut Typesetter, PaintRect) + Send + Sync + 'static,

Registra una closure de pintura custom. El runtime la invoca con (&mut vello::Scene, &mut Typesetter, PaintRect) durante el paint del nodo. La closure es responsable de pintar primitivas custom dentro del rect; no debe dejar push_layer sin par. Soporte para canvas elements estilo dominium/pluma/cosmos.

Examples found in repository?
examples/gestos.rs (lines 113-143)
101    fn view(model: &Self::Model) -> View<Self::Msg> {
102        let zoom = model.zoom;
103        let pan = model.pan;
104        let marks = model.marks.clone();
105
106        let canvas = View::new(Style {
107            size: Size { width: percent(1.0_f32), height: Dimension::auto() },
108            flex_grow: 1.0,
109            ..Default::default()
110        })
111        .fill(Color::from_rgba8(16, 18, 26, 255))
112        .clip(true)
113        .paint_with(move |scene, _ts, rect| {
114            // Grilla de mundo paso 40px, escalada por zoom y desplazada por pan.
115            let step = 40.0 * zoom as f64;
116            if step >= 4.0 {
117                let thin = Stroke::new(1.0);
118                let grid = Color::from_rgba8(40, 46, 60, 255);
119                // Offset del primer línea visible (pan módulo step).
120                let ox = (rect.x as f64) + (pan.0 as f64).rem_euclid(step);
121                let mut x = ox;
122                while x < (rect.x + rect.w) as f64 {
123                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
124                        &Line::new((x, rect.y as f64), (x, (rect.y + rect.h) as f64)));
125                    x += step;
126                }
127                let oy = (rect.y as f64) + (pan.1 as f64).rem_euclid(step);
128                let mut y = oy;
129                while y < (rect.y + rect.h) as f64 {
130                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
131                        &Line::new((rect.x as f64, y), ((rect.x + rect.w) as f64, y)));
132                    y += step;
133                }
134            }
135            // Marcas (coords de mundo → pantalla): pan + world·zoom.
136            let dot = Color::from_rgba8(90, 220, 150, 255);
137            let r = (6.0 * zoom as f64).clamp(3.0, 24.0);
138            for (wx, wy) in &marks {
139                let sx = rect.x as f64 + pan.0 as f64 + (*wx as f64) * zoom as f64;
140                let sy = rect.y as f64 + pan.1 as f64 + (*wy as f64) * zoom as f64;
141                scene.fill(Fill::NonZero, Affine::IDENTITY, dot, None, &Circle::new((sx, sy), r));
142            }
143        })
144        // Pinch-to-zoom (Ctrl+rueda / trackpad). El focal viene local al canvas.
145        .on_scale(|phase, factor, fx, fy| match phase {
146            GesturePhase::Update => Some(Msg::Zoom { factor, fx, fy }),
147            _ => None,
148        })
149        // Paneo por arrastre. El movimiento cancela un long-press en curso.
150        .draggable(|phase, dx, dy| match phase {
151            DragPhase::Move => Some(Msg::Pan { dx, dy }),
152            DragPhase::End => None,
153        })
154        // Doble-tap: reset. Long-press: marca en el punto.
155        .on_double_tap(Msg::Reset)
156        .on_long_press_at(|lx, ly, _w, _h| Some(Msg::Mark { lx, ly }));
157
158        let status = View::new(Style {
159            size: Size { width: percent(1.0_f32), height: length(40.0_f32) },
160            align_items: Some(AlignItems::Center),
161            justify_content: Some(JustifyContent::Center),
162            ..Default::default()
163        })
164        .fill(Color::from_rgba8(28, 32, 42, 255))
165        .text(
166            format!("{}   ·   ×{:.2}   ·   {} marcas", model.last, model.zoom, model.marks.len()),
167            18.0,
168            Color::from_rgba8(210, 220, 235, 255),
169        );
170
171        View::new(Style {
172            flex_direction: FlexDirection::Column,
173            size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
174            ..Default::default()
175        })
176        .fill(Color::from_rgba8(16, 18, 26, 255))
177        .children(vec![canvas, status])
178    }
Source

pub fn gpu_paint_with<F>(self, painter: F) -> View<Msg>
where F: Fn(&Device, &Queue, &mut CommandEncoder, &TextureView, PaintRect, (u32, u32)) + Send + Sync + 'static,

Registra una closure de pintura GPU directo. La closure recibe (&Device, &Queue, &mut CommandEncoder, &TextureView, PaintRect, (viewport_w, viewport_h)) y debe escribir sobre el TextureView con LoadOp::Load (no clear) para preservar la pasada vello previa. El último argumento es el tamaño en pixels de la TextureView destino (la intermedia del frame) — necesario para calcular NDC sin asumir un viewport fijo. Ver GpuPaintFn para semántica completa, contexto y orden de pintura.

Examples found in repository?
examples/gpu_paint_demo.rs (lines 86-88)
54    fn view(model: &Self::Model) -> View<Self::Msg> {
55        let title = View::new(Style {
56            size: Size {
57                width: percent(1.0_f32),
58                height: length(48.0_f32),
59            },
60            justify_content: Some(JustifyContent::Center),
61            align_items: Some(AlignItems::Center),
62            ..Default::default()
63        })
64        .text(
65            format!("gpu_paint_with — {POINTS} puntos GPU directo · seed {model}"),
66            22.0,
67            Color::from_rgba8(220, 230, 245, 255),
68        );
69
70        // Canvas central: vello pinta el fondo (fill + radius), GPU pinta
71        // la grilla de puntos encima vía gpu_paint_with. El seed del
72        // modelo se mete en el shader vía una rotación trivial — cada
73        // click cambia el patrón. El callback se invoca ya con el
74        // CommandEncoder del frame y la TextureView intermediate.
75        let seed = *model;
76        let canvas = View::new(Style {
77            size: Size {
78                width: percent(1.0_f32),
79                height: auto(),
80            },
81            flex_grow: 1.0,
82            ..Default::default()
83        })
84        .fill(Color::from_rgba8(14, 18, 28, 255))
85        .radius(8.0)
86        .gpu_paint_with(move |device, queue, encoder, view, rect, _viewport| {
87            draw_points(device, queue, encoder, view, rect, seed);
88        })
89        .on_click(Msg::Bump);
90
91        let footer = View::new(Style {
92            size: Size {
93                width: percent(1.0_f32),
94                height: length(28.0_f32),
95            },
96            justify_content: Some(JustifyContent::Center),
97            align_items: Some(AlignItems::Center),
98            ..Default::default()
99        })
100        .text(
101            "click sobre el canvas → rebobinar el seed",
102            14.0,
103            Color::from_rgba8(150, 165, 185, 255),
104        );
105
106        View::new(Style {
107            flex_direction: FlexDirection::Column,
108            size: Size {
109                width: percent(1.0_f32),
110                height: percent(1.0_f32),
111            },
112            gap: Size {
113                width: length(0.0_f32),
114                height: length(16.0_f32),
115            },
116            padding: TaffyRect {
117                left: length(24.0_f32),
118                right: length(24.0_f32),
119                top: length(16.0_f32),
120                bottom: length(16.0_f32),
121            },
122            ..Default::default()
123        })
124        .fill(Color::from_rgba8(24, 28, 38, 255))
125        .children(vec![title, canvas, footer])
126    }
Source

pub fn paint_over<F>(self, painter: F) -> View<Msg>
where F: Fn(&mut Scene, &mut Typesetter, PaintRect) + Send + Sync + 'static,

Registra una closure de pintura vello “over” — misma firma que Self::paint_with (&mut Scene, &mut Typesetter, PaintRect), pero el runtime la ejecuta en una pasada vello FINAL después del pase GPU directo del frame, componiéndola con alpha sobre la intermedia. Es el complemento opt-in de Self::gpu_paint_with: permite pintar sprites/texto AA por vello ENCIMA de las celdas instanciadas por GPU del mismo (o de otro) nodo.

Orden resultante del frame: [vello base] → [gpu_paint] → [paint_over] → [overlay/menús]. Backward-compat total: si nadie usa paint_over, no se crea la pasada final (coste cero) y el resto del pipeline es idéntico. La closure no debe dejar push_layer sin par ni resetear la escena. Ver OverPaintFn.

Source

pub fn clip(self, enabled: bool) -> View<Msg>

Recorta los hijos al rect de este nodo (paint y hit-test). Útil para paneles con contenido virtualizado que no debe sangrar a vecinos (listas, scrollers, viewers).

Examples found in repository?
examples/gestos.rs (line 112)
101    fn view(model: &Self::Model) -> View<Self::Msg> {
102        let zoom = model.zoom;
103        let pan = model.pan;
104        let marks = model.marks.clone();
105
106        let canvas = View::new(Style {
107            size: Size { width: percent(1.0_f32), height: Dimension::auto() },
108            flex_grow: 1.0,
109            ..Default::default()
110        })
111        .fill(Color::from_rgba8(16, 18, 26, 255))
112        .clip(true)
113        .paint_with(move |scene, _ts, rect| {
114            // Grilla de mundo paso 40px, escalada por zoom y desplazada por pan.
115            let step = 40.0 * zoom as f64;
116            if step >= 4.0 {
117                let thin = Stroke::new(1.0);
118                let grid = Color::from_rgba8(40, 46, 60, 255);
119                // Offset del primer línea visible (pan módulo step).
120                let ox = (rect.x as f64) + (pan.0 as f64).rem_euclid(step);
121                let mut x = ox;
122                while x < (rect.x + rect.w) as f64 {
123                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
124                        &Line::new((x, rect.y as f64), (x, (rect.y + rect.h) as f64)));
125                    x += step;
126                }
127                let oy = (rect.y as f64) + (pan.1 as f64).rem_euclid(step);
128                let mut y = oy;
129                while y < (rect.y + rect.h) as f64 {
130                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
131                        &Line::new((rect.x as f64, y), ((rect.x + rect.w) as f64, y)));
132                    y += step;
133                }
134            }
135            // Marcas (coords de mundo → pantalla): pan + world·zoom.
136            let dot = Color::from_rgba8(90, 220, 150, 255);
137            let r = (6.0 * zoom as f64).clamp(3.0, 24.0);
138            for (wx, wy) in &marks {
139                let sx = rect.x as f64 + pan.0 as f64 + (*wx as f64) * zoom as f64;
140                let sy = rect.y as f64 + pan.1 as f64 + (*wy as f64) * zoom as f64;
141                scene.fill(Fill::NonZero, Affine::IDENTITY, dot, None, &Circle::new((sx, sy), r));
142            }
143        })
144        // Pinch-to-zoom (Ctrl+rueda / trackpad). El focal viene local al canvas.
145        .on_scale(|phase, factor, fx, fy| match phase {
146            GesturePhase::Update => Some(Msg::Zoom { factor, fx, fy }),
147            _ => None,
148        })
149        // Paneo por arrastre. El movimiento cancela un long-press en curso.
150        .draggable(|phase, dx, dy| match phase {
151            DragPhase::Move => Some(Msg::Pan { dx, dy }),
152            DragPhase::End => None,
153        })
154        // Doble-tap: reset. Long-press: marca en el punto.
155        .on_double_tap(Msg::Reset)
156        .on_long_press_at(|lx, ly, _w, _h| Some(Msg::Mark { lx, ly }));
157
158        let status = View::new(Style {
159            size: Size { width: percent(1.0_f32), height: length(40.0_f32) },
160            align_items: Some(AlignItems::Center),
161            justify_content: Some(JustifyContent::Center),
162            ..Default::default()
163        })
164        .fill(Color::from_rgba8(28, 32, 42, 255))
165        .text(
166            format!("{}   ·   ×{:.2}   ·   {} marcas", model.last, model.zoom, model.marks.len()),
167            18.0,
168            Color::from_rgba8(210, 220, 235, 255),
169        );
170
171        View::new(Style {
172            flex_direction: FlexDirection::Column,
173            size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
174            ..Default::default()
175        })
176        .fill(Color::from_rgba8(16, 18, 26, 255))
177        .children(vec![canvas, status])
178    }
Source

pub fn clip_inset(self, insets: [f32; 4]) -> View<Msg>

Recorta los descendientes a un rect encogido por insets px [top, right, bottom, left] desde el rect del nodo — modela clip-path: inset(...). Activa el recorte (paint + hit-test).

Source

pub fn clip_ellipse(self, spec: [f32; 14]) -> View<Msg>

Recorta los descendientes a una elipse — modela clip-path: circle()/ellipse(). spec es de 14 floats: centro [cx_px, cx_pct, cy_px, cy_pct] + dos radios [px, pct_w, pct_h, pct_diag, side], todos resueltos contra el rect del nodo en el pintado. Activa el recorte (paint; hit-test usa el rect completo).

Source

pub fn clip_polygon(self, evenodd: bool, points: Vec<[f32; 4]>) -> View<Msg>

Recorta los descendientes a un polígono — modela clip-path: polygon(). evenodd = regla de relleno; cada punto [x_px, x_pct, y_px, y_pct] resuelve contra el rect del nodo en el pintado. Activa el recorte (paint; hit-test usa el rect completo).

Source

pub fn clip_path_svg(self, evenodd: bool, d: impl Into<String>) -> View<Msg>

Recorta los descendientes a un path SVG — modela clip-path: path(). d es el string SVG crudo (user units px, relativos al origen del rect); el pintado lo parsea con BezPath::from_svg. evenodd = regla de relleno. Activa el recorte (paint; hit-test usa el rect completo).

Source

pub fn clip_ref_inset(self, insets: [f32; 4]) -> View<Msg>

Fija la caja de referencia del clip-path (<geometry-box>): el rect del nodo se encoge por insets px [top, right, bottom, left] antes de resolver la forma. Sin forma, recorta a ese rect. Activa el recorte.

Source

pub fn children(self, children: Vec<View<Msg>>) -> View<Msg>

Examples found in repository?
examples/editor.rs (line 126)
70    fn view(model: &Self::Model) -> View<Self::Msg> {
71        let body_text = if model.is_empty() {
72            "tipea algo · ctrl+L limpia · enter salto · backspace borra".to_string()
73        } else {
74            // Cursor visual al final del contenido.
75            format!("{model}\u{2588}")
76        };
77        let body_color = if model.is_empty() {
78            Color::from_rgba8(110, 130, 150, 255)
79        } else {
80            Color::from_rgba8(220, 230, 240, 255)
81        };
82
83        let body = View::new(Style {
84            size: Size {
85                width: percent(1.0_f32),
86                height: percent(1.0_f32),
87            },
88            flex_grow: 1.0,
89            ..Default::default()
90        })
91        .text_aligned(body_text, 22.0, body_color, Alignment::Start);
92
93        let status = View::new(Style {
94            size: Size {
95                width: percent(1.0_f32),
96                height: length(36.0_f32),
97            },
98            ..Default::default()
99        })
100        .fill(Color::from_rgba8(30, 36, 48, 255))
101        .text(
102            format!("{} chars", model.chars().count()),
103            16.0,
104            Color::from_rgba8(160, 180, 200, 255),
105        );
106
107        View::new(Style {
108            flex_direction: FlexDirection::Column,
109            size: Size {
110                width: percent(1.0_f32),
111                height: percent(1.0_f32),
112            },
113            gap: Size {
114                width: length(0.0_f32),
115                height: length(8.0_f32),
116            },
117            padding: llimphi_ui::llimphi_layout::taffy::Rect {
118                left: length(24.0_f32),
119                right: length(24.0_f32),
120                top: length(24.0_f32),
121                bottom: length(24.0_f32),
122            },
123            ..Default::default()
124        })
125        .fill(Color::from_rgba8(20, 24, 32, 255))
126        .children(vec![body, status])
127    }
More examples
Hide additional examples
examples/selectable_text.rs (line 96)
40    fn view(_: &Self::Model) -> View<Self::Msg> {
41        let mut children: Vec<View<()>> = vec![View::new(Style {
42            size: Size {
43                width: percent(1.0_f32),
44                height: Dimension::auto(),
45            },
46            ..Default::default()
47        })
48        .text_aligned(
49            "Texto seleccionable (arrastrá + Ctrl/Cmd+C)",
50            22.0,
51            Color::from_rgba8(230, 240, 250, 255),
52            Alignment::Start,
53        )];
54
55        for (i, p) in PARRAFOS.iter().enumerate() {
56            children.push(
57                View::new(Style {
58                    size: Size {
59                        width: percent(1.0_f32),
60                        height: Dimension::auto(),
61                    },
62                    ..Default::default()
63                })
64                .text_aligned(
65                    *p,
66                    16.0,
67                    Color::from_rgba8(205, 214, 226, 255),
68                    Alignment::Start,
69                )
70                // La línea clave: cada párrafo es seleccionable con una key
71                // estable (su índice). El resaltado + copy los hace el runtime.
72                .selectable(i as u64),
73            );
74        }
75
76        View::new(Style {
77            flex_direction: FlexDirection::Column,
78            size: Size {
79                width: percent(1.0_f32),
80                height: percent(1.0_f32),
81            },
82            gap: Size {
83                width: length(0.0_f32),
84                height: length(20.0_f32),
85            },
86            padding: Rect {
87                left: length(40.0_f32),
88                right: length(40.0_f32),
89                top: length(36.0_f32),
90                bottom: length(36.0_f32),
91            },
92            align_items: Some(AlignItems::Start),
93            ..Default::default()
94        })
95        .fill(Color::from_rgba8(20, 24, 32, 255))
96        .children(children)
97    }
examples/gpu_paint_demo.rs (line 125)
54    fn view(model: &Self::Model) -> View<Self::Msg> {
55        let title = View::new(Style {
56            size: Size {
57                width: percent(1.0_f32),
58                height: length(48.0_f32),
59            },
60            justify_content: Some(JustifyContent::Center),
61            align_items: Some(AlignItems::Center),
62            ..Default::default()
63        })
64        .text(
65            format!("gpu_paint_with — {POINTS} puntos GPU directo · seed {model}"),
66            22.0,
67            Color::from_rgba8(220, 230, 245, 255),
68        );
69
70        // Canvas central: vello pinta el fondo (fill + radius), GPU pinta
71        // la grilla de puntos encima vía gpu_paint_with. El seed del
72        // modelo se mete en el shader vía una rotación trivial — cada
73        // click cambia el patrón. El callback se invoca ya con el
74        // CommandEncoder del frame y la TextureView intermediate.
75        let seed = *model;
76        let canvas = View::new(Style {
77            size: Size {
78                width: percent(1.0_f32),
79                height: auto(),
80            },
81            flex_grow: 1.0,
82            ..Default::default()
83        })
84        .fill(Color::from_rgba8(14, 18, 28, 255))
85        .radius(8.0)
86        .gpu_paint_with(move |device, queue, encoder, view, rect, _viewport| {
87            draw_points(device, queue, encoder, view, rect, seed);
88        })
89        .on_click(Msg::Bump);
90
91        let footer = View::new(Style {
92            size: Size {
93                width: percent(1.0_f32),
94                height: length(28.0_f32),
95            },
96            justify_content: Some(JustifyContent::Center),
97            align_items: Some(AlignItems::Center),
98            ..Default::default()
99        })
100        .text(
101            "click sobre el canvas → rebobinar el seed",
102            14.0,
103            Color::from_rgba8(150, 165, 185, 255),
104        );
105
106        View::new(Style {
107            flex_direction: FlexDirection::Column,
108            size: Size {
109                width: percent(1.0_f32),
110                height: percent(1.0_f32),
111            },
112            gap: Size {
113                width: length(0.0_f32),
114                height: length(16.0_f32),
115            },
116            padding: TaffyRect {
117                left: length(24.0_f32),
118                right: length(24.0_f32),
119                top: length(16.0_f32),
120                bottom: length(16.0_f32),
121            },
122            ..Default::default()
123        })
124        .fill(Color::from_rgba8(24, 28, 38, 255))
125        .children(vec![title, canvas, footer])
126    }
examples/counter.rs (line 97)
43    fn view(model: &Self::Model) -> View<Self::Msg> {
44        let number = View::new(Style {
45            size: Size {
46                width: percent(1.0_f32),
47                height: Dimension::auto(),
48            },
49            flex_grow: 1.0,
50            align_items: Some(AlignItems::Center),
51            justify_content: Some(JustifyContent::Center),
52            ..Default::default()
53        })
54        .text(model.to_string(), 160.0, Color::from_rgba8(230, 240, 250, 255));
55
56        let increment = View::new(Style {
57            size: Size {
58                width: length(160.0_f32),
59                height: length(56.0_f32),
60            },
61            align_items: Some(AlignItems::Center),
62            justify_content: Some(JustifyContent::Center),
63            ..Default::default()
64        })
65        .fill(Color::from_rgba8(60, 200, 130, 255))
66        .radius(12.0)
67        .text("+1", 28.0, Color::from_rgba8(10, 30, 20, 255))
68        .on_click(Msg::Increment);
69
70        let reset = View::new(Style {
71            size: Size {
72                width: length(120.0_f32),
73                height: length(56.0_f32),
74            },
75            align_items: Some(AlignItems::Center),
76            justify_content: Some(JustifyContent::Center),
77            ..Default::default()
78        })
79        .fill(Color::from_rgba8(220, 80, 80, 255))
80        .radius(12.0)
81        .text("reset", 22.0, Color::from_rgba8(30, 10, 10, 255))
82        .on_click(Msg::Reset);
83
84        let buttons = View::new(Style {
85            flex_direction: FlexDirection::Row,
86            size: Size {
87                width: percent(1.0_f32),
88                height: length(56.0_f32),
89            },
90            gap: Size {
91                width: length(16.0_f32),
92                height: length(0.0_f32),
93            },
94            justify_content: Some(JustifyContent::Center),
95            ..Default::default()
96        })
97        .children(vec![increment, reset]);
98
99        View::new(Style {
100            flex_direction: FlexDirection::Column,
101            size: Size {
102                width: percent(1.0_f32),
103                height: percent(1.0_f32),
104            },
105            gap: Size {
106                width: length(0.0_f32),
107                height: length(24.0_f32),
108            },
109            padding: llimphi_ui::llimphi_layout::taffy::Rect {
110                left: length(32.0_f32),
111                right: length(32.0_f32),
112                top: length(32.0_f32),
113                bottom: length(32.0_f32),
114            },
115            ..Default::default()
116        })
117        .fill(Color::from_rgba8(20, 24, 32, 255))
118        .children(vec![number, buttons])
119    }
examples/gestos.rs (line 177)
101    fn view(model: &Self::Model) -> View<Self::Msg> {
102        let zoom = model.zoom;
103        let pan = model.pan;
104        let marks = model.marks.clone();
105
106        let canvas = View::new(Style {
107            size: Size { width: percent(1.0_f32), height: Dimension::auto() },
108            flex_grow: 1.0,
109            ..Default::default()
110        })
111        .fill(Color::from_rgba8(16, 18, 26, 255))
112        .clip(true)
113        .paint_with(move |scene, _ts, rect| {
114            // Grilla de mundo paso 40px, escalada por zoom y desplazada por pan.
115            let step = 40.0 * zoom as f64;
116            if step >= 4.0 {
117                let thin = Stroke::new(1.0);
118                let grid = Color::from_rgba8(40, 46, 60, 255);
119                // Offset del primer línea visible (pan módulo step).
120                let ox = (rect.x as f64) + (pan.0 as f64).rem_euclid(step);
121                let mut x = ox;
122                while x < (rect.x + rect.w) as f64 {
123                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
124                        &Line::new((x, rect.y as f64), (x, (rect.y + rect.h) as f64)));
125                    x += step;
126                }
127                let oy = (rect.y as f64) + (pan.1 as f64).rem_euclid(step);
128                let mut y = oy;
129                while y < (rect.y + rect.h) as f64 {
130                    scene.stroke(&thin, Affine::IDENTITY, grid, None,
131                        &Line::new((rect.x as f64, y), ((rect.x + rect.w) as f64, y)));
132                    y += step;
133                }
134            }
135            // Marcas (coords de mundo → pantalla): pan + world·zoom.
136            let dot = Color::from_rgba8(90, 220, 150, 255);
137            let r = (6.0 * zoom as f64).clamp(3.0, 24.0);
138            for (wx, wy) in &marks {
139                let sx = rect.x as f64 + pan.0 as f64 + (*wx as f64) * zoom as f64;
140                let sy = rect.y as f64 + pan.1 as f64 + (*wy as f64) * zoom as f64;
141                scene.fill(Fill::NonZero, Affine::IDENTITY, dot, None, &Circle::new((sx, sy), r));
142            }
143        })
144        // Pinch-to-zoom (Ctrl+rueda / trackpad). El focal viene local al canvas.
145        .on_scale(|phase, factor, fx, fy| match phase {
146            GesturePhase::Update => Some(Msg::Zoom { factor, fx, fy }),
147            _ => None,
148        })
149        // Paneo por arrastre. El movimiento cancela un long-press en curso.
150        .draggable(|phase, dx, dy| match phase {
151            DragPhase::Move => Some(Msg::Pan { dx, dy }),
152            DragPhase::End => None,
153        })
154        // Doble-tap: reset. Long-press: marca en el punto.
155        .on_double_tap(Msg::Reset)
156        .on_long_press_at(|lx, ly, _w, _h| Some(Msg::Mark { lx, ly }));
157
158        let status = View::new(Style {
159            size: Size { width: percent(1.0_f32), height: length(40.0_f32) },
160            align_items: Some(AlignItems::Center),
161            justify_content: Some(JustifyContent::Center),
162            ..Default::default()
163        })
164        .fill(Color::from_rgba8(28, 32, 42, 255))
165        .text(
166            format!("{}   ·   ×{:.2}   ·   {} marcas", model.last, model.zoom, model.marks.len()),
167            18.0,
168            Color::from_rgba8(210, 220, 235, 255),
169        );
170
171        View::new(Style {
172            flex_direction: FlexDirection::Column,
173            size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
174            ..Default::default()
175        })
176        .fill(Color::from_rgba8(16, 18, 26, 255))
177        .children(vec![canvas, status])
178    }
Source§

impl<Msg> View<Msg>
where Msg: 'static,

Source

pub fn map<Msg2, F>(self, f: F) -> View<Msg2>
where Msg2: 'static, F: Fn(Msg) -> Msg2 + Send + Sync + 'static,

Transforma el Msg de todo el árbol vía f, devolviendo View<Msg2>. Es la pieza que permite embeber el view de un sub-app en un host (junto con [crate::Handle::lift] para sus efectos): el host pinta sub_view.map(Msg::Sub) y los eventos del sub-árbol vuelven como su propio Msg. Patrón estándar de anidado Elm. f se comparte (Arc) entre todos los callbacks e hijos, así que debe ser Send + Sync.

Auto Trait Implementations§

§

impl<Msg> !RefUnwindSafe for View<Msg>

§

impl<Msg> !Send for View<Msg>

§

impl<Msg> !Sync for View<Msg>

§

impl<Msg> !UnwindSafe for View<Msg>

§

impl<Msg> Freeze for View<Msg>
where Msg: Freeze,

§

impl<Msg> Unpin for View<Msg>
where Msg: Unpin,

§

impl<Msg> UnsafeUnpin for View<Msg>
where Msg: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more