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};
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#[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 #[default]
62 Stretch,
63 Tile,
65}
66
67#[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 NinePatchMessage:LeftMargin => fn left_margin(u32), layout: false
83 );
84 define_constructor!(
85 NinePatchMessage:RightMargin => fn right_margin(u32), layout: false
87 );
88 define_constructor!(
89 NinePatchMessage:TopMargin => fn top_margin(u32), layout: false
91 );
92 define_constructor!(
93 NinePatchMessage:BottomMargin => fn bottom_margin(u32), layout: false
95 );
96 define_constructor!(
97 NinePatchMessage:TextureRegion => fn texture_region(Rect<u32>), layout: false
99 );
100 define_constructor!(
101 NinePatchMessage:Texture => fn texture(Option<TextureResource>), layout: false
103 );
104 define_constructor!(
105 NinePatchMessage:DrawCenter => fn draw_center(bool), layout: false
107 );
108}
109
110#[derive(Default, Clone, Visit, Reflect, Debug, PartialEq)]
113pub struct TextureSlice {
114 pub texture_source: Option<TextureResource>,
117 pub bottom_margin: InheritableVariable<u32>,
119 pub left_margin: InheritableVariable<u32>,
121 pub right_margin: InheritableVariable<u32>,
123 pub top_margin: InheritableVariable<u32>,
125 pub texture_region: InheritableVariable<Rect<u32>>,
127}
128
129impl TextureSlice {
130 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 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#[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 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 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 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 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 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 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 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 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 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 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 }
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
582pub 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}