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