fyrox_ui/
nine_patch.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use crate::{
22    brush::Brush,
23    core::{
24        algebra::Vector2, color::Color, math::Rect, pool::Handle, reflect::prelude::*,
25        some_or_return, type_traits::prelude::*, variable::InheritableVariable,
26        visitor::prelude::*,
27    },
28    define_constructor,
29    draw::{CommandTexture, Draw, DrawingContext},
30    message::{compare_and_set, MessageDirection, UiMessage},
31    widget::{Widget, WidgetBuilder},
32    BuildContext, Control, UiNode, UserInterface,
33};
34
35use fyrox_graph::{
36    constructor::{ConstructorProvider, GraphNodeConstructor},
37    BaseSceneGraph,
38};
39use fyrox_material::MaterialResource;
40use fyrox_texture::{TextureKind, TextureResource};
41use std::ops::{Deref, DerefMut};
42use strum_macros::{AsRefStr, EnumString, VariantNames};
43
44/// Stretch mode for the middle sections of [`NinePatch`] widget.
45#[derive(
46    Debug,
47    Default,
48    Copy,
49    Clone,
50    Hash,
51    PartialEq,
52    Eq,
53    Reflect,
54    Visit,
55    AsRefStr,
56    EnumString,
57    VariantNames,
58    TypeUuidProvider,
59)]
60#[type_uuid(id = "c5bb0a5c-6581-45f7-899c-78aa1da8b659")]
61pub enum StretchMode {
62    /// Stretches middle sections of the widget. Could lead to distorted image.
63    #[default]
64    Stretch,
65    /// Tiles middle sections of the widget. Prevents distortion of the image.
66    Tile,
67}
68
69/// A set of possible nine patch messages.
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub enum NinePatchMessage {
72    LeftMargin(u32),
73    RightMargin(u32),
74    TopMargin(u32),
75    BottomMargin(u32),
76    TextureRegion(Rect<u32>),
77    Texture(Option<TextureResource>),
78    DrawCenter(bool),
79}
80
81impl NinePatchMessage {
82    define_constructor!(
83        /// Creates [`NinePatchMessage::LeftMargin`] message.
84        NinePatchMessage:LeftMargin => fn left_margin(u32), layout: false
85    );
86    define_constructor!(
87        /// Creates [`NinePatchMessage::RightMargin`] message.
88        NinePatchMessage:RightMargin => fn right_margin(u32), layout: false
89    );
90    define_constructor!(
91        /// Creates [`NinePatchMessage::TopMargin`] message.
92        NinePatchMessage:TopMargin => fn top_margin(u32), layout: false
93    );
94    define_constructor!(
95        /// Creates [`NinePatchMessage::BottomMargin`] message.
96        NinePatchMessage:BottomMargin => fn bottom_margin(u32), layout: false
97    );
98    define_constructor!(
99        /// Creates [`NinePatchMessage::TextureRegion`] message.
100        NinePatchMessage:TextureRegion => fn texture_region(Rect<u32>), layout: false
101    );
102    define_constructor!(
103        /// Creates [`NinePatchMessage::Texture`] message.
104        NinePatchMessage:Texture => fn texture(Option<TextureResource>), layout: false
105    );
106    define_constructor!(
107        /// Creates [`NinePatchMessage::DrawCenter`] message.
108        NinePatchMessage:DrawCenter => fn draw_center(bool), layout: false
109    );
110}
111
112/// A texture slice that defines a region in a texture and margins that will be used to split the
113/// section in nine pieces.
114#[derive(Default, Clone, Visit, Reflect, Debug, PartialEq)]
115pub struct TextureSlice {
116    /// Texture of the slice. This field is used only for editing purposes in the UI. Can be [`None`]
117    /// if no editing is needed.
118    pub texture_source: Option<TextureResource>,
119    /// Offset from the bottom side of the texture region.
120    pub bottom_margin: InheritableVariable<u32>,
121    /// Offset from the left side of the texture region.
122    pub left_margin: InheritableVariable<u32>,
123    /// Offset from the right side of the texture region.
124    pub right_margin: InheritableVariable<u32>,
125    /// Offset from the top of the texture region.
126    pub top_margin: InheritableVariable<u32>,
127    /// Region in the texture. Default is all zeros, which means that the entire texture is used.
128    pub texture_region: InheritableVariable<Rect<u32>>,
129}
130
131impl TextureSlice {
132    /// Returns the top left point.
133    pub fn margin_min(&self) -> Vector2<u32> {
134        Vector2::new(
135            self.texture_region.position.x + *self.left_margin,
136            self.texture_region.position.y + *self.top_margin,
137        )
138    }
139
140    /// Returns the bottom right point.
141    pub fn margin_max(&self) -> Vector2<u32> {
142        Vector2::new(
143            self.texture_region.position.x
144                + self
145                    .texture_region
146                    .size
147                    .x
148                    .saturating_sub(*self.right_margin),
149            self.texture_region.position.y
150                + self
151                    .texture_region
152                    .size
153                    .y
154                    .saturating_sub(*self.bottom_margin),
155        )
156    }
157}
158
159/// `NinePatch` widget is used to split an image in nine sections, where each corner section will
160/// remain the same, while the middle parts between each corner will be used to evenly fill the
161/// space. This widget is primarily used in the UI to create resizable frames, buttons, windows, etc.
162///
163/// ## Example
164///
165/// The following examples shows how to create a nine-patch widget with a texture and some margins.
166///
167/// ```rust
168/// # use fyrox_ui::{
169/// #     core::{math::Rect, pool::Handle},
170/// #     nine_patch::NinePatchBuilder,
171/// #     widget::WidgetBuilder,
172/// #     UiNode, UserInterface,
173/// # };
174/// # use fyrox_texture::TextureResource;
175/// #
176/// fn create_nine_patch(texture: TextureResource, ui: &mut UserInterface) -> Handle<UiNode> {
177///     NinePatchBuilder::new(WidgetBuilder::new())
178///         // Specify margins for each side in pixels.
179///         .with_left_margin(50)
180///         .with_right_margin(50)
181///         .with_top_margin(40)
182///         .with_bottom_margin(40)
183///         .with_texture(texture)
184///         // Optionally, you can also specify a region in a texture to use. It is useful if you
185///         // have a texture atlas where most of the UI elements are packed.
186///         .with_texture_region(Rect::new(200, 200, 400, 400))
187///         .build(&mut ui.build_ctx())
188/// }
189/// ```
190#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider, TypeUuidProvider)]
191#[type_uuid(id = "c345033e-8c10-4186-b101-43f73b85981d")]
192#[reflect(derived_type = "UiNode")]
193pub struct NinePatch {
194    pub widget: Widget,
195    pub texture_slice: TextureSlice,
196    pub draw_center: InheritableVariable<bool>,
197    #[reflect(setter = "set_texture")]
198    pub texture: InheritableVariable<Option<TextureResource>>,
199    pub stretch_mode: InheritableVariable<StretchMode>,
200}
201
202impl NinePatch {
203    pub fn set_texture(&mut self, texture: Option<TextureResource>) {
204        self.texture.set_value_and_mark_modified(texture.clone());
205        self.texture_slice.texture_source = texture;
206    }
207}
208
209impl ConstructorProvider<UiNode, UserInterface> for NinePatch {
210    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
211        GraphNodeConstructor::new::<Self>()
212            .with_variant("Nine Patch", |ui| {
213                NinePatchBuilder::new(
214                    WidgetBuilder::new()
215                        .with_name("Nine Patch")
216                        .with_width(200.0)
217                        .with_height(200.0),
218                )
219                .build(&mut ui.build_ctx())
220                .into()
221            })
222            .with_group("Visual")
223    }
224}
225
226crate::define_widget_deref!(NinePatch);
227
228fn draw_image(
229    image: &TextureResource,
230    bounds: Rect<f32>,
231    tex_coords: &[Vector2<f32>; 4],
232    clip_bounds: Rect<f32>,
233    background: Brush,
234    material: &MaterialResource,
235    drawing_context: &mut DrawingContext,
236) {
237    drawing_context.push_rect_filled(&bounds, Some(tex_coords));
238    let texture = CommandTexture::Texture(image.clone());
239    drawing_context.commit(clip_bounds, background, texture, material, None);
240}
241
242fn draw_tiled_image(
243    image: &TextureResource,
244    texture_width: f32,
245    texture_height: f32,
246    bounds: Rect<f32>,
247    tex_coords: &[Vector2<f32>; 4],
248    clip_bounds: Rect<f32>,
249    background: Brush,
250    material: &MaterialResource,
251    drawing_context: &mut DrawingContext,
252) {
253    let region_bounds = Rect::new(
254        tex_coords[0].x * texture_width,
255        tex_coords[0].y * texture_height,
256        (tex_coords[1].x - tex_coords[0].x) * texture_width,
257        (tex_coords[2].y - tex_coords[0].y) * texture_height,
258    );
259
260    let nx = (bounds.size.x / region_bounds.size.x).ceil() as usize;
261    let ny = (bounds.size.y / region_bounds.size.y).ceil() as usize;
262
263    for y in 0..ny {
264        for x in 0..nx {
265            let tile_bounds = Rect::new(
266                bounds.position.x + x as f32 * region_bounds.size.x,
267                bounds.position.y + y as f32 * region_bounds.size.y,
268                region_bounds.size.x,
269                region_bounds.size.y,
270            );
271
272            drawing_context.push_rect_filled(&tile_bounds, Some(tex_coords));
273        }
274    }
275
276    drawing_context.commit(
277        clip_bounds,
278        background,
279        CommandTexture::Texture(image.clone()),
280        material,
281        None,
282    );
283}
284
285impl Control for NinePatch {
286    fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
287        let mut size: Vector2<f32> = available_size;
288
289        let column1_width_pixels = *self.texture_slice.left_margin as f32;
290        let column3_width_pixels = *self.texture_slice.right_margin as f32;
291
292        let row1_height_pixels = *self.texture_slice.top_margin as f32;
293        let row3_height_pixels = *self.texture_slice.bottom_margin as f32;
294
295        let x_overflow = column1_width_pixels + column3_width_pixels;
296        let y_overflow = row1_height_pixels + row3_height_pixels;
297
298        let center_size =
299            Vector2::new(available_size.x - x_overflow, available_size.y - y_overflow);
300
301        for &child in self.children.iter() {
302            ui.measure_node(child, center_size);
303            let desired_size = ui.node(child).desired_size();
304            size.x = size.x.max(desired_size.x.ceil());
305            size.y = size.y.max(desired_size.y.ceil());
306        }
307        size
308    }
309
310    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
311        let column1_width_pixels = *self.texture_slice.left_margin as f32;
312        let column3_width_pixels = *self.texture_slice.right_margin as f32;
313
314        let row1_height_pixels = *self.texture_slice.top_margin as f32;
315        let row3_height_pixels = *self.texture_slice.bottom_margin as f32;
316
317        let x_overflow = column1_width_pixels + column3_width_pixels;
318        let y_overflow = row1_height_pixels + row3_height_pixels;
319
320        let final_rect = Rect::new(
321            column1_width_pixels,
322            row1_height_pixels,
323            final_size.x - x_overflow,
324            final_size.y - y_overflow,
325        );
326
327        for &child in self.children.iter() {
328            ui.arrange_node(child, &final_rect);
329        }
330
331        final_size
332    }
333
334    fn draw(&self, drawing_context: &mut DrawingContext) {
335        let texture = some_or_return!(self.texture.as_ref());
336
337        let texture_state = texture.state();
338        let texture_state = some_or_return!(texture_state.data_ref());
339
340        // Only 2D textures can be used with nine-patch.
341        let TextureKind::Rectangle { width, height } = texture_state.kind() else {
342            return;
343        };
344
345        let texture_width = width as f32;
346        let texture_height = height as f32;
347
348        let patch_bounds = self.widget.bounding_rect();
349
350        let left_margin = *self.texture_slice.left_margin as f32;
351        let right_margin = *self.texture_slice.right_margin as f32;
352        let top_margin = *self.texture_slice.top_margin as f32;
353        let bottom_margin = *self.texture_slice.bottom_margin as f32;
354
355        let mut region = Rect {
356            position: self.texture_slice.texture_region.position.cast::<f32>(),
357            size: self.texture_slice.texture_region.size.cast::<f32>(),
358        };
359
360        if region.size.x == 0.0 && region.size.y == 0.0 {
361            region.size.x = texture_width;
362            region.size.y = texture_height;
363        }
364
365        let center_uv_x_min = (region.position.x + left_margin) / texture_width;
366        let center_uv_x_max = (region.position.x + region.size.x - right_margin) / texture_width;
367        let center_uv_y_min = (region.position.y + top_margin) / texture_height;
368        let center_uv_y_max = (region.position.y + region.size.y - bottom_margin) / texture_height;
369        let uv_x_min = region.position.x / texture_width;
370        let uv_x_max = (region.position.x + region.size.x) / texture_width;
371        let uv_y_min = region.position.y / texture_height;
372        let uv_y_max = (region.position.y + region.size.y) / texture_height;
373
374        let x_overflow = left_margin + right_margin;
375        let y_overflow = top_margin + bottom_margin;
376
377        let stretch_mode = *self.stretch_mode;
378        let mut draw_piece = |bounds: Rect<f32>, tex_coords: &[Vector2<f32>; 4]| match stretch_mode
379        {
380            StretchMode::Stretch => {
381                draw_image(
382                    texture,
383                    bounds,
384                    tex_coords,
385                    self.clip_bounds(),
386                    self.widget.background(),
387                    &self.material,
388                    drawing_context,
389                );
390            }
391            StretchMode::Tile => draw_tiled_image(
392                texture,
393                texture_width,
394                texture_height,
395                bounds,
396                tex_coords,
397                self.clip_bounds(),
398                self.widget.background(),
399                &self.material,
400                drawing_context,
401            ),
402        };
403
404        //top left
405        let bounds = Rect {
406            position: patch_bounds.position,
407            size: Vector2::new(left_margin, top_margin),
408        };
409        let tex_coords = [
410            Vector2::new(uv_x_min, uv_y_min),
411            Vector2::new(center_uv_x_min, uv_y_min),
412            Vector2::new(center_uv_x_min, center_uv_y_min),
413            Vector2::new(uv_x_min, center_uv_y_min),
414        ];
415        draw_piece(bounds, &tex_coords);
416
417        //top center
418        let bounds = Rect {
419            position: Vector2::new(
420                patch_bounds.position.x + left_margin,
421                patch_bounds.position.y,
422            ),
423            size: Vector2::new(patch_bounds.size.x - x_overflow, top_margin),
424        };
425        let tex_coords = [
426            Vector2::new(center_uv_x_min, uv_y_min),
427            Vector2::new(center_uv_x_max, uv_y_min),
428            Vector2::new(center_uv_x_max, center_uv_y_min),
429            Vector2::new(center_uv_x_min, center_uv_y_min),
430        ];
431        draw_piece(bounds, &tex_coords);
432
433        //top right
434        let bounds = Rect {
435            position: Vector2::new(
436                (patch_bounds.position.x + patch_bounds.size.x) - right_margin,
437                patch_bounds.position.y,
438            ),
439            size: Vector2::new(right_margin, top_margin),
440        };
441        let tex_coords = [
442            Vector2::new(center_uv_x_max, uv_y_min),
443            Vector2::new(uv_x_max, uv_y_min),
444            Vector2::new(uv_x_max, center_uv_y_min),
445            Vector2::new(center_uv_x_max, center_uv_y_min),
446        ];
447        draw_piece(bounds, &tex_coords);
448        ////////////////////////////////////////////////////////////////////////////////
449        //middle left
450        let bounds = Rect {
451            position: Vector2::new(
452                patch_bounds.position.x,
453                patch_bounds.position.y + top_margin,
454            ),
455            size: Vector2::new(left_margin, patch_bounds.size.y - y_overflow),
456        };
457        let tex_coords = [
458            Vector2::new(uv_x_min, center_uv_y_min),
459            Vector2::new(center_uv_x_min, center_uv_y_min),
460            Vector2::new(center_uv_x_min, center_uv_y_max),
461            Vector2::new(uv_x_min, center_uv_y_max),
462        ];
463        draw_piece(bounds, &tex_coords);
464
465        if *self.draw_center {
466            //middle center
467            let bounds = Rect {
468                position: Vector2::new(
469                    patch_bounds.position.x + left_margin,
470                    patch_bounds.position.y + top_margin,
471                ),
472                size: Vector2::new(
473                    patch_bounds.size.x - x_overflow,
474                    patch_bounds.size.y - y_overflow,
475                ),
476            };
477            let tex_coords = [
478                Vector2::new(center_uv_x_min, center_uv_y_min),
479                Vector2::new(center_uv_x_max, center_uv_y_min),
480                Vector2::new(center_uv_x_max, center_uv_y_max),
481                Vector2::new(center_uv_x_min, center_uv_y_max),
482            ];
483            draw_piece(bounds, &tex_coords);
484        }
485
486        //middle right
487        let bounds = Rect {
488            position: Vector2::new(
489                (patch_bounds.position.x + patch_bounds.size.x) - right_margin,
490                patch_bounds.position.y + top_margin,
491            ),
492            size: Vector2::new(right_margin, patch_bounds.size.y - y_overflow),
493        };
494        let tex_coords = [
495            Vector2::new(center_uv_x_max, center_uv_y_min),
496            Vector2::new(uv_x_max, center_uv_y_min),
497            Vector2::new(uv_x_max, center_uv_y_max),
498            Vector2::new(center_uv_x_max, center_uv_y_max),
499        ];
500        draw_piece(bounds, &tex_coords);
501
502        ////////////////////////////////////////////////////////////////////////////////
503        //bottom left
504        let bounds = Rect {
505            position: Vector2::new(
506                patch_bounds.position.x,
507                (patch_bounds.position.y + patch_bounds.size.y) - bottom_margin,
508            ),
509            size: Vector2::new(left_margin, bottom_margin),
510        };
511        let tex_coords = [
512            Vector2::new(uv_x_min, center_uv_y_max),
513            Vector2::new(center_uv_x_min, center_uv_y_max),
514            Vector2::new(center_uv_x_min, uv_y_max),
515            Vector2::new(uv_x_min, uv_y_max),
516        ];
517        draw_piece(bounds, &tex_coords);
518
519        //bottom center
520        let bounds = Rect {
521            position: Vector2::new(
522                patch_bounds.position.x + left_margin,
523                (patch_bounds.position.y + patch_bounds.size.y) - bottom_margin,
524            ),
525            size: Vector2::new(patch_bounds.size.x - x_overflow, bottom_margin),
526        };
527        let tex_coords = [
528            Vector2::new(center_uv_x_min, center_uv_y_max),
529            Vector2::new(center_uv_x_max, center_uv_y_max),
530            Vector2::new(center_uv_x_max, uv_y_max),
531            Vector2::new(center_uv_x_min, uv_y_max),
532        ];
533        draw_piece(bounds, &tex_coords);
534
535        //bottom right
536        let bounds = Rect {
537            position: Vector2::new(
538                (patch_bounds.position.x + patch_bounds.size.x) - right_margin,
539                (patch_bounds.position.y + patch_bounds.size.y) - bottom_margin,
540            ),
541            size: Vector2::new(right_margin, bottom_margin),
542        };
543        let tex_coords = [
544            Vector2::new(center_uv_x_max, center_uv_y_max),
545            Vector2::new(uv_x_max, center_uv_y_max),
546            Vector2::new(uv_x_max, uv_y_max),
547            Vector2::new(center_uv_x_max, uv_y_max),
548        ];
549        draw_piece(bounds, &tex_coords);
550
551        //end drawing
552    }
553
554    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
555        self.widget.handle_routed_message(ui, message);
556
557        if let Some(msg) = message.data::<NinePatchMessage>() {
558            if message.destination() == self.handle()
559                && message.direction() == MessageDirection::ToWidget
560            {
561                let slice = &mut self.texture_slice;
562                match msg {
563                    NinePatchMessage::LeftMargin(margin) => {
564                        compare_and_set(slice.left_margin.deref_mut(), margin, message, ui);
565                    }
566                    NinePatchMessage::RightMargin(margin) => {
567                        compare_and_set(slice.right_margin.deref_mut(), margin, message, ui);
568                    }
569                    NinePatchMessage::TopMargin(margin) => {
570                        compare_and_set(slice.top_margin.deref_mut(), margin, message, ui);
571                    }
572                    NinePatchMessage::BottomMargin(margin) => {
573                        compare_and_set(slice.bottom_margin.deref_mut(), margin, message, ui);
574                    }
575                    NinePatchMessage::TextureRegion(region) => {
576                        compare_and_set(slice.texture_region.deref_mut(), region, message, ui);
577                    }
578                    NinePatchMessage::Texture(texture) => {
579                        compare_and_set(&mut slice.texture_source, texture, message, ui);
580                    }
581                    NinePatchMessage::DrawCenter(draw_center) => {
582                        compare_and_set(self.draw_center.deref_mut(), draw_center, message, ui);
583                    }
584                }
585            }
586        }
587    }
588}
589
590/// Creates instances of [`NinePatch`] widget.
591pub struct NinePatchBuilder {
592    pub widget_builder: WidgetBuilder,
593    pub texture: Option<TextureResource>,
594    pub bottom_margin: u32,
595    pub left_margin: u32,
596    pub right_margin: u32,
597    pub top_margin: u32,
598    pub texture_region: Rect<u32>,
599    pub draw_center: bool,
600    pub stretch_mode: StretchMode,
601}
602
603impl NinePatchBuilder {
604    pub fn new(widget_builder: WidgetBuilder) -> Self {
605        Self {
606            widget_builder,
607            texture: None,
608            bottom_margin: 20,
609            left_margin: 20,
610            right_margin: 20,
611            top_margin: 20,
612            texture_region: Rect::new(0, 0, 200, 200),
613            draw_center: true,
614            stretch_mode: Default::default(),
615        }
616    }
617
618    pub fn with_texture(mut self, texture: TextureResource) -> Self {
619        self.texture = Some(texture);
620        self
621    }
622
623    pub fn with_bottom_margin(mut self, margin: u32) -> Self {
624        self.bottom_margin = margin;
625        self
626    }
627
628    pub fn with_left_margin(mut self, margin: u32) -> Self {
629        self.left_margin = margin;
630        self
631    }
632
633    pub fn with_right_margin(mut self, margin: u32) -> Self {
634        self.right_margin = margin;
635        self
636    }
637
638    pub fn with_top_margin(mut self, margin: u32) -> Self {
639        self.top_margin = margin;
640        self
641    }
642
643    pub fn with_texture_region(mut self, rect: Rect<u32>) -> Self {
644        self.texture_region = rect;
645        self
646    }
647
648    pub fn with_draw_center(mut self, draw_center: bool) -> Self {
649        self.draw_center = draw_center;
650        self
651    }
652
653    pub fn with_stretch_mode(mut self, stretch: StretchMode) -> Self {
654        self.stretch_mode = stretch;
655        self
656    }
657
658    pub fn build(mut self, ctx: &mut BuildContext) -> Handle<UiNode> {
659        if self.widget_builder.background.is_none() {
660            self.widget_builder.background = Some(Brush::Solid(Color::WHITE).into())
661        }
662
663        ctx.add_node(UiNode::new(NinePatch {
664            widget: self.widget_builder.build(ctx),
665            texture_slice: TextureSlice {
666                texture_source: self.texture.clone(),
667                bottom_margin: self.bottom_margin.into(),
668                left_margin: self.left_margin.into(),
669                right_margin: self.right_margin.into(),
670                top_margin: self.top_margin.into(),
671                texture_region: self.texture_region.into(),
672            },
673            draw_center: self.draw_center.into(),
674            texture: self.texture.into(),
675            stretch_mode: self.stretch_mode.into(),
676        }))
677    }
678}
679
680#[cfg(test)]
681mod test {
682    use crate::nine_patch::NinePatchBuilder;
683    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
684
685    #[test]
686    fn test_deletion() {
687        test_widget_deletion(|ctx| NinePatchBuilder::new(WidgetBuilder::new()).build(ctx));
688    }
689}