1use 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#[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 #[default]
64 Stretch,
65 Tile,
67}
68
69#[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 NinePatchMessage:LeftMargin => fn left_margin(u32), layout: false
85 );
86 define_constructor!(
87 NinePatchMessage:RightMargin => fn right_margin(u32), layout: false
89 );
90 define_constructor!(
91 NinePatchMessage:TopMargin => fn top_margin(u32), layout: false
93 );
94 define_constructor!(
95 NinePatchMessage:BottomMargin => fn bottom_margin(u32), layout: false
97 );
98 define_constructor!(
99 NinePatchMessage:TextureRegion => fn texture_region(Rect<u32>), layout: false
101 );
102 define_constructor!(
103 NinePatchMessage:Texture => fn texture(Option<TextureResource>), layout: false
105 );
106 define_constructor!(
107 NinePatchMessage:DrawCenter => fn draw_center(bool), layout: false
109 );
110}
111
112#[derive(Default, Clone, Visit, Reflect, Debug, PartialEq)]
115pub struct TextureSlice {
116 pub texture_source: Option<TextureResource>,
119 pub bottom_margin: InheritableVariable<u32>,
121 pub left_margin: InheritableVariable<u32>,
123 pub right_margin: InheritableVariable<u32>,
125 pub top_margin: InheritableVariable<u32>,
127 pub texture_region: InheritableVariable<Rect<u32>>,
129}
130
131impl TextureSlice {
132 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 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#[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 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 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 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 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 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 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 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 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 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 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 }
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
590pub 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}