1use crate::{
22 border::BorderBuilder,
23 brush::Brush,
24 button::{ButtonBuilder, ButtonMessage},
25 control_trait_proxy_impls,
26 core::{
27 algebra::{Matrix3, Vector2},
28 color::Color,
29 math::Rect,
30 pool::Handle,
31 reflect::prelude::*,
32 some_or_return,
33 type_traits::prelude::*,
34 visitor::prelude::*,
35 },
36 decorator::DecoratorBuilder,
37 define_widget_deref,
38 draw::{CommandTexture, Draw, DrawingContext},
39 grid::{Column, GridBuilder, Row},
40 inspector::{
41 editors::{
42 PropertyEditorBuildContext, PropertyEditorDefinition, PropertyEditorInstance,
43 PropertyEditorMessageContext, PropertyEditorTranslationContext,
44 },
45 FieldKind, InspectorError, PropertyChanged,
46 },
47 message::{CursorIcon, MessageDirection, UiMessage},
48 nine_patch::TextureSlice,
49 numeric::{NumericUpDownBuilder, NumericUpDownMessage},
50 rect::{RectEditorBuilder, RectEditorMessage},
51 scroll_viewer::ScrollViewerBuilder,
52 stack_panel::StackPanelBuilder,
53 text::TextBuilder,
54 thumb::{ThumbBuilder, ThumbMessage},
55 widget::{Widget, WidgetBuilder, WidgetMessage},
56 window::{Window, WindowBuilder, WindowMessage, WindowTitle},
57 BuildContext, Control, Thickness, UiNode, UserInterface, VerticalAlignment,
58};
59
60use crate::button::Button;
61use crate::message::MessageData;
62use crate::numeric::NumericUpDown;
63use crate::rect::RectEditor;
64use crate::thumb::Thumb;
65use crate::window::WindowAlignment;
66use fyrox_texture::TextureKind;
67use std::{
68 any::TypeId,
69 ops::{Deref, DerefMut},
70};
71
72#[derive(Debug, Clone, PartialEq)]
73pub enum TextureSliceEditorMessage {
74 Slice(TextureSlice),
75}
76impl MessageData for TextureSliceEditorMessage {}
77
78#[derive(Debug, Clone, PartialEq)]
79struct DragContext {
80 initial_position: Vector2<f32>,
81 bottom_margin: u32,
82 left_margin: u32,
83 right_margin: u32,
84 top_margin: u32,
85 texture_region: Rect<u32>,
86}
87
88#[derive(Clone, Reflect, Visit, TypeUuidProvider, ComponentProvider, Debug)]
89#[type_uuid(id = "bd89b59f-13be-4804-bd9c-ed40cfd48b92")]
90#[reflect(derived_type = "UiNode")]
91pub struct TextureSliceEditor {
92 widget: Widget,
93 slice: TextureSlice,
94 handle_size: f32,
95 region_min_thumb: Handle<Thumb>,
96 region_max_thumb: Handle<Thumb>,
97 slice_min_thumb: Handle<Thumb>,
98 slice_max_thumb: Handle<Thumb>,
99 #[reflect(hidden)]
100 #[visit(skip)]
101 drag_context: Option<DragContext>,
102 #[reflect(hidden)]
103 #[visit(skip)]
104 scale: f32,
105}
106
107impl TextureSliceEditor {
108 fn sync_thumbs(&self, ui: &UserInterface) {
109 for (thumb, position) in [
110 (self.region_min_thumb, self.slice.texture_region.position),
111 (
112 self.region_max_thumb,
113 self.slice.texture_region.right_bottom_corner(),
114 ),
115 (self.slice_min_thumb, self.slice.margin_min()),
116 (self.slice_max_thumb, self.slice.margin_max()),
117 ] {
118 ui.send(
119 thumb,
120 WidgetMessage::DesiredPosition(position.cast::<f32>()),
121 )
122 }
123 }
124
125 fn on_thumb_dragged(&mut self, thumb: Handle<UiNode>, offset: Vector2<f32>) {
126 let ctx = some_or_return!(self.drag_context.as_ref());
127 let texture = some_or_return!(self.slice.texture_source.clone());
128 let texture_state = texture.state();
129 let texture_data = some_or_return!(texture_state.data_ref());
130 let TextureKind::Rectangle { width, height } = texture_data.kind() else {
131 return;
132 };
133
134 let offset = Vector2::new(offset.x as i32, offset.y as i32);
135
136 let margin_min = self.slice.margin_min();
137 let margin_max = self.slice.margin_max();
138 let initial_region = ctx.texture_region;
139 let region = self.slice.texture_region.deref_mut();
140
141 if thumb == self.slice_min_thumb {
142 let top_margin = ctx.top_margin.saturating_add_signed(offset.y);
143 if top_margin + region.position.y <= margin_max.y {
144 *self.slice.top_margin = top_margin;
145 } else {
146 *self.slice.top_margin = margin_max.y - region.position.y;
147 }
148
149 let left_margin = ctx.left_margin.saturating_add_signed(offset.x);
150 if left_margin + region.position.x <= margin_max.x {
151 *self.slice.left_margin = left_margin;
152 } else {
153 *self.slice.left_margin = margin_max.x - region.position.x;
154 }
155 } else if thumb == self.slice_max_thumb {
156 let bottom_margin = ctx.bottom_margin.saturating_add_signed(-offset.y);
157 if (region.position.y + region.size.y).saturating_sub(bottom_margin) >= margin_min.y {
158 *self.slice.bottom_margin = bottom_margin;
159 } else {
160 *self.slice.bottom_margin = region.position.y + region.size.y - margin_min.y;
161 }
162
163 let right_margin = ctx.right_margin.saturating_add_signed(-offset.x);
164 if (region.position.x + region.size.x).saturating_sub(right_margin) >= margin_min.x {
165 *self.slice.right_margin = right_margin;
166 } else {
167 *self.slice.right_margin = region.position.x + region.size.x - margin_min.x;
168 }
169 } else if thumb == self.region_min_thumb {
170 let x = initial_region.position.x.saturating_add_signed(offset.x);
171 let max_x = initial_region.position.x + initial_region.size.x;
172 region.position.x = x.min(max_x);
173
174 let y = initial_region.position.y.saturating_add_signed(offset.y);
175 let max_y = initial_region.position.y + initial_region.size.y;
176 region.position.y = y.min(max_y);
177
178 region.size.x = ctx
179 .texture_region
180 .size
181 .x
182 .saturating_add_signed(-offset.x)
183 .min(initial_region.position.x + initial_region.size.x);
184 region.size.y = ctx
185 .texture_region
186 .size
187 .y
188 .saturating_add_signed(-offset.y)
189 .min(initial_region.position.y + initial_region.size.y);
190 } else if thumb == self.region_max_thumb {
191 region.size.x = ctx
192 .texture_region
193 .size
194 .x
195 .saturating_add_signed(offset.x)
196 .min(width);
197 region.size.y = ctx
198 .texture_region
199 .size
200 .y
201 .saturating_add_signed(offset.y)
202 .min(height);
203 }
204 }
205}
206
207define_widget_deref!(TextureSliceEditor);
208
209impl Control for TextureSliceEditor {
210 fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
211 let mut size: Vector2<f32> = self.widget.measure_override(ui, available_size);
212
213 if let Some(texture) = self.slice.texture_source.as_ref() {
214 let state = texture.state();
215 if let Some(data) = state.data_ref() {
216 if let TextureKind::Rectangle { width, height } = data.kind() {
217 let width = width as f32;
218 let height = height as f32;
219 if size.x < width {
220 size.x = width;
221 }
222 if size.y < height {
223 size.y = height;
224 }
225 }
226 }
227 }
228
229 size
230 }
231
232 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
233 for &child_handle in self.widget.children() {
234 let child = ui.nodes.borrow(child_handle);
235 ui.arrange_node(
236 child_handle,
237 &Rect::new(
238 child.desired_local_position().x,
239 child.desired_local_position().y,
240 child.desired_size().x,
241 child.desired_size().y,
242 ),
243 );
244 }
245
246 final_size
247 }
248
249 fn draw(&self, drawing_context: &mut DrawingContext) {
250 let texture = some_or_return!(self.slice.texture_source.clone());
251
252 let state = texture.state();
253 let texture_data = some_or_return!(state.data_ref());
254
255 let TextureKind::Rectangle { width, height } = texture_data.kind() else {
257 return;
258 };
259
260 let texture_width = width as f32;
261 let texture_height = height as f32;
262
263 drawing_context.push_rect_filled(&Rect::new(0.0, 0.0, texture_width, texture_height), None);
264 drawing_context.commit(
265 self.clip_bounds(),
266 self.background(),
267 CommandTexture::Texture(texture.clone()),
268 &self.material,
269 None,
270 );
271
272 let mut bounds = Rect {
273 position: self.slice.texture_region.position.cast::<f32>(),
274 size: self.slice.texture_region.size.cast::<f32>(),
275 };
276
277 if bounds.size.x == 0.0 && bounds.size.y == 0.0 {
278 bounds.size.x = texture_width;
279 bounds.size.y = texture_height;
280 }
281
282 drawing_context.push_rect(&bounds, 1.0);
283 drawing_context.commit(
284 self.clip_bounds(),
285 self.foreground(),
286 CommandTexture::Texture(texture.clone()),
287 &self.material,
288 None,
289 );
290
291 let left_margin = *self.slice.left_margin as f32;
292 let right_margin = *self.slice.right_margin as f32;
293 let top_margin = *self.slice.top_margin as f32;
294 let bottom_margin = *self.slice.bottom_margin as f32;
295 let thickness = 1.0 / self.scale;
296
297 drawing_context.push_line(
299 Vector2::new(bounds.position.x + left_margin, bounds.position.y),
300 Vector2::new(
301 bounds.position.x + left_margin,
302 bounds.position.y + bounds.size.y,
303 ),
304 thickness,
305 );
306 drawing_context.push_line(
307 Vector2::new(
308 bounds.position.x + bounds.size.x - right_margin,
309 bounds.position.y,
310 ),
311 Vector2::new(
312 bounds.position.x + bounds.size.x - right_margin,
313 bounds.position.y + bounds.size.y,
314 ),
315 thickness,
316 );
317 drawing_context.push_line(
318 Vector2::new(bounds.position.x, bounds.position.y + top_margin),
319 Vector2::new(
320 bounds.position.x + bounds.size.x,
321 bounds.position.y + top_margin,
322 ),
323 thickness,
324 );
325 drawing_context.push_line(
326 Vector2::new(
327 bounds.position.x,
328 bounds.position.y + bounds.size.y - bottom_margin,
329 ),
330 Vector2::new(
331 bounds.position.x + bounds.size.x,
332 bounds.position.y + bounds.size.y - bottom_margin,
333 ),
334 thickness,
335 );
336 drawing_context.commit(
337 self.clip_bounds(),
338 self.foreground(),
339 CommandTexture::None,
340 &self.material,
341 None,
342 );
343 }
344
345 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
346 self.widget.handle_routed_message(ui, message);
347
348 if let Some(TextureSliceEditorMessage::Slice(slice)) = message.data_for(self.handle()) {
349 self.slice = slice.clone();
350 self.sync_thumbs(ui);
351 } else if let Some(msg) = message.data::<ThumbMessage>() {
352 match msg {
353 ThumbMessage::DragStarted { position } => {
354 self.drag_context = Some(DragContext {
355 initial_position: *position,
356 bottom_margin: *self.slice.bottom_margin,
357 left_margin: *self.slice.left_margin,
358 right_margin: *self.slice.right_margin,
359 top_margin: *self.slice.top_margin,
360 texture_region: *self.slice.texture_region,
361 });
362 }
363 ThumbMessage::DragDelta { offset } => {
364 self.on_thumb_dragged(message.destination(), *offset);
365 self.sync_thumbs(ui);
366 }
367 ThumbMessage::DragCompleted { .. } => {
368 self.drag_context = None;
369 ui.post(
370 self.handle(),
371 TextureSliceEditorMessage::Slice(self.slice.clone()),
372 );
373 }
374 }
375 } else if let Some(WidgetMessage::MouseWheel { amount, .. }) = message.data() {
376 self.scale = (self.scale + 0.1 * *amount).clamp(1.0, 10.0);
377
378 ui.send(
379 self.handle,
380 WidgetMessage::LayoutTransform(Matrix3::new_scaling(self.scale)),
381 );
382
383 for thumb in [
384 self.slice_min_thumb,
385 self.slice_max_thumb,
386 self.region_min_thumb,
387 self.region_max_thumb,
388 ] {
389 ui.send(thumb, WidgetMessage::Width(self.handle_size / self.scale));
390 ui.send(thumb, WidgetMessage::Height(self.handle_size / self.scale));
391 }
392 }
393 }
394}
395
396pub struct TextureSliceEditorBuilder {
397 widget_builder: WidgetBuilder,
398 slice: TextureSlice,
399 handle_size: f32,
400}
401
402fn make_thumb(position: Vector2<u32>, handle_size: f32, ctx: &mut BuildContext) -> Handle<Thumb> {
403 ThumbBuilder::new(
404 WidgetBuilder::new()
405 .with_desired_position(position.cast::<f32>())
406 .with_child(
407 DecoratorBuilder::new(BorderBuilder::new(
408 WidgetBuilder::new()
409 .with_width(handle_size)
410 .with_height(handle_size)
411 .with_cursor(Some(CursorIcon::Grab))
412 .with_foreground(Brush::Solid(Color::opaque(0, 150, 0)).into()),
413 ))
414 .with_pressable(false)
415 .with_selected(false)
416 .with_normal_brush(Brush::Solid(Color::opaque(0, 150, 0)).into())
417 .with_hover_brush(Brush::Solid(Color::opaque(0, 255, 0)).into())
418 .build(ctx),
419 ),
420 )
421 .build(ctx)
422}
423
424impl TextureSliceEditorBuilder {
425 pub fn new(widget_builder: WidgetBuilder) -> Self {
426 Self {
427 widget_builder,
428 slice: Default::default(),
429 handle_size: 8.0,
430 }
431 }
432
433 pub fn with_texture_slice(mut self, slice: TextureSlice) -> Self {
434 self.slice = slice;
435 self
436 }
437
438 pub fn with_handle_size(mut self, size: f32) -> Self {
439 self.handle_size = size;
440 self
441 }
442
443 pub fn build(self, ctx: &mut BuildContext) -> Handle<TextureSliceEditor> {
444 let region_min_thumb =
445 make_thumb(self.slice.texture_region.position, self.handle_size, ctx);
446 let region_max_thumb = make_thumb(
447 self.slice.texture_region.right_bottom_corner(),
448 self.handle_size,
449 ctx,
450 );
451 let slice_min_thumb = make_thumb(self.slice.margin_min(), self.handle_size, ctx);
452 let slice_max_thumb = make_thumb(self.slice.margin_max(), self.handle_size, ctx);
453
454 ctx.add(TextureSliceEditor {
455 widget: self
456 .widget_builder
457 .with_child(region_min_thumb)
458 .with_child(region_max_thumb)
459 .with_child(slice_min_thumb)
460 .with_child(slice_max_thumb)
461 .build(ctx),
462 slice: self.slice,
463 handle_size: self.handle_size,
464 region_min_thumb,
465 region_max_thumb,
466 slice_min_thumb,
467 slice_max_thumb,
468 drag_context: None,
469 scale: 1.0,
470 })
471 }
472}
473
474#[derive(Clone, Reflect, Visit, TypeUuidProvider, ComponentProvider, Debug)]
475#[type_uuid(id = "0293081d-55fd-4aa2-a06e-d53fba1a2617")]
476#[reflect(derived_type = "UiNode")]
477pub struct TextureSliceEditorWindow {
478 window: Window,
479 parent_editor: Handle<UiNode>,
480 slice_editor: Handle<TextureSliceEditor>,
481 texture_slice: TextureSlice,
482 left_margin: Handle<NumericUpDown<u32>>,
483 right_margin: Handle<NumericUpDown<u32>>,
484 top_margin: Handle<NumericUpDown<u32>>,
485 bottom_margin: Handle<NumericUpDown<u32>>,
486 region: Handle<RectEditor<u32>>,
487}
488
489impl TextureSliceEditorWindow {
490 fn on_slice_changed(&self, ui: &UserInterface) {
491 ui.send(
492 self.region,
493 RectEditorMessage::Value(*self.texture_slice.texture_region),
494 );
495
496 for (widget, value) in [
497 (self.left_margin, &self.texture_slice.left_margin),
498 (self.right_margin, &self.texture_slice.right_margin),
499 (self.top_margin, &self.texture_slice.top_margin),
500 (self.bottom_margin, &self.texture_slice.bottom_margin),
501 ] {
502 ui.send(widget, NumericUpDownMessage::Value(**value));
503 }
504
505 ui.send(
507 self.parent_editor,
508 TextureSliceEditorMessage::Slice(self.texture_slice.clone()),
509 );
510 }
511}
512
513impl Deref for TextureSliceEditorWindow {
514 type Target = Widget;
515
516 fn deref(&self) -> &Self::Target {
517 &self.window.widget
518 }
519}
520
521impl DerefMut for TextureSliceEditorWindow {
522 fn deref_mut(&mut self) -> &mut Self::Target {
523 &mut self.window.widget
524 }
525}
526
527impl Control for TextureSliceEditorWindow {
528 control_trait_proxy_impls!(window);
529
530 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
531 self.window.handle_routed_message(ui, message);
532 if let Some(TextureSliceEditorMessage::Slice(slice)) = message.data() {
533 if message.is_from(self.slice_editor) {
534 self.texture_slice = slice.clone();
535 self.on_slice_changed(ui);
536 }
537
538 if message.is_for(self.handle()) && &self.texture_slice != slice {
539 self.texture_slice = slice.clone();
540
541 ui.send(
542 self.slice_editor,
543 TextureSliceEditorMessage::Slice(self.texture_slice.clone()),
544 );
545
546 self.on_slice_changed(ui);
547 }
548 } else if let Some(NumericUpDownMessage::Value(value)) =
549 message.data::<NumericUpDownMessage<u32>>()
550 {
551 if message.direction() == MessageDirection::FromWidget {
552 let mut slice = self.texture_slice.clone();
553 let mut target = None;
554 for (widget, margin) in [
555 (self.left_margin, &mut slice.left_margin),
556 (self.right_margin, &mut slice.right_margin),
557 (self.top_margin, &mut slice.top_margin),
558 (self.bottom_margin, &mut slice.bottom_margin),
559 ] {
560 if message.destination() == widget {
561 margin.set_value_and_mark_modified(*value);
562 target = Some(widget);
563 break;
564 }
565 }
566 if target.is_some() {
567 ui.send(self.handle, TextureSliceEditorMessage::Slice(slice));
568 }
569 }
570 } else if let Some(RectEditorMessage::Value(value)) =
571 message.data_from::<RectEditorMessage<u32>>(self.region)
572 {
573 let mut slice = self.texture_slice.clone();
574 slice.texture_region.set_value_and_mark_modified(*value);
575 ui.send(self.handle, TextureSliceEditorMessage::Slice(slice));
576 }
577 }
578}
579
580pub struct TextureSliceEditorWindowBuilder {
581 window_builder: WindowBuilder,
582 texture_slice: TextureSlice,
583}
584
585impl TextureSliceEditorWindowBuilder {
586 pub fn new(window_builder: WindowBuilder) -> Self {
587 Self {
588 window_builder,
589 texture_slice: Default::default(),
590 }
591 }
592
593 pub fn with_texture_slice(mut self, slice: TextureSlice) -> Self {
594 self.texture_slice = slice;
595 self
596 }
597
598 pub fn build(
599 self,
600 parent_editor: Handle<UiNode>,
601 ctx: &mut BuildContext,
602 ) -> Handle<TextureSliceEditorWindow> {
603 let region_text = TextBuilder::new(WidgetBuilder::new())
604 .with_text("Texture Region")
605 .build(ctx);
606 let region =
607 RectEditorBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(1.0)))
608 .with_value(*self.texture_slice.texture_region)
609 .build(ctx);
610 let left_margin_text = TextBuilder::new(WidgetBuilder::new())
611 .with_text("Left Margin")
612 .build(ctx);
613 let left_margin =
614 NumericUpDownBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(1.0)))
615 .with_value(*self.texture_slice.left_margin)
616 .build(ctx);
617 let right_margin_text = TextBuilder::new(WidgetBuilder::new())
618 .with_text("Right Margin")
619 .build(ctx);
620 let right_margin =
621 NumericUpDownBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(1.0)))
622 .with_value(*self.texture_slice.right_margin)
623 .build(ctx);
624 let top_margin_text = TextBuilder::new(WidgetBuilder::new())
625 .with_text("Top Margin")
626 .build(ctx);
627 let top_margin =
628 NumericUpDownBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(1.0)))
629 .with_value(*self.texture_slice.top_margin)
630 .build(ctx);
631 let bottom_margin_text = TextBuilder::new(WidgetBuilder::new())
632 .with_text("Bottom Margin")
633 .build(ctx);
634 let bottom_margin =
635 NumericUpDownBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(1.0)))
636 .with_value(*self.texture_slice.bottom_margin)
637 .build(ctx);
638
639 let toolbar = StackPanelBuilder::new(
640 WidgetBuilder::new()
641 .with_child(region_text)
642 .with_child(region)
643 .with_child(left_margin_text)
644 .with_child(left_margin)
645 .with_child(right_margin_text)
646 .with_child(right_margin)
647 .with_child(top_margin_text)
648 .with_child(top_margin)
649 .with_child(bottom_margin_text)
650 .with_child(bottom_margin)
651 .on_column(0),
652 )
653 .build(ctx);
654
655 let slice_editor = TextureSliceEditorBuilder::new(
656 WidgetBuilder::new()
657 .with_clip_to_bounds(false)
658 .with_background(Brush::Solid(Color::WHITE).into())
659 .with_foreground(Brush::Solid(Color::GREEN).into())
660 .with_margin(Thickness::uniform(3.0)),
661 )
662 .with_texture_slice(self.texture_slice.clone())
663 .build(ctx);
664 let scroll_viewer = ScrollViewerBuilder::new(WidgetBuilder::new().on_column(1))
665 .with_horizontal_scroll_allowed(true)
666 .with_vertical_scroll_allowed(true)
667 .with_content(slice_editor)
668 .with_h_scroll_speed(0.0)
670 .with_v_scroll_speed(0.0)
671 .build(ctx);
672 let content = GridBuilder::new(
673 WidgetBuilder::new()
674 .with_child(toolbar)
675 .with_child(scroll_viewer),
676 )
677 .add_column(Column::strict(200.0))
678 .add_column(Column::stretch())
679 .add_row(Row::stretch())
680 .build(ctx);
681
682 let node = TextureSliceEditorWindow {
683 window: self.window_builder.with_content(content).build_window(ctx),
684 parent_editor,
685 slice_editor,
686 texture_slice: self.texture_slice,
687 left_margin,
688 right_margin,
689 top_margin,
690 bottom_margin,
691 region,
692 };
693
694 ctx.add(node)
695 }
696}
697
698#[derive(Clone, Reflect, Visit, TypeUuidProvider, ComponentProvider, Debug)]
699#[type_uuid(id = "024f3a3a-6784-4675-bd99-a4c6c19a8d91")]
700#[reflect(derived_type = "UiNode")]
701pub struct TextureSliceFieldEditor {
702 widget: Widget,
703 texture_slice: TextureSlice,
704 edit: Handle<Button>,
705 editor: Handle<TextureSliceEditorWindow>,
706}
707
708define_widget_deref!(TextureSliceFieldEditor);
709
710impl Control for TextureSliceFieldEditor {
711 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
712 self.widget.handle_routed_message(ui, message);
713
714 if let Some(ButtonMessage::Click) = message.data() {
715 if message.destination() == self.edit {
716 self.editor = TextureSliceEditorWindowBuilder::new(
717 WindowBuilder::new(WidgetBuilder::new().with_width(700.0).with_height(500.0))
718 .with_title(WindowTitle::text("Texture Slice Editor"))
719 .open(false)
720 .with_remove_on_close(true),
721 )
722 .with_texture_slice(self.texture_slice.clone())
723 .build(self.handle, &mut ui.build_ctx());
724
725 ui.send(
726 self.editor,
727 WindowMessage::Open {
728 alignment: WindowAlignment::Center,
729 modal: true,
730 focus_content: true,
731 },
732 );
733 }
734 } else if let Some(TextureSliceEditorMessage::Slice(slice)) = message.data_for(self.handle)
735 {
736 if &self.texture_slice != slice {
737 self.texture_slice = slice.clone();
738 ui.send_message(message.reverse());
739 ui.send(
740 self.editor,
741 TextureSliceEditorMessage::Slice(self.texture_slice.clone()),
742 );
743 }
744 }
745 }
746}
747
748pub struct TextureSliceFieldEditorBuilder {
749 widget_builder: WidgetBuilder,
750 texture_slice: TextureSlice,
751}
752
753impl TextureSliceFieldEditorBuilder {
754 pub fn new(widget_builder: WidgetBuilder) -> Self {
755 Self {
756 widget_builder,
757 texture_slice: Default::default(),
758 }
759 }
760
761 pub fn with_texture_slice(mut self, slice: TextureSlice) -> Self {
762 self.texture_slice = slice;
763 self
764 }
765
766 pub fn build(self, ctx: &mut BuildContext) -> Handle<TextureSliceFieldEditor> {
767 let edit = ButtonBuilder::new(WidgetBuilder::new())
768 .with_text("Edit...")
769 .build(ctx);
770
771 let node = TextureSliceFieldEditor {
772 widget: self.widget_builder.with_child(edit).build(ctx),
773 texture_slice: self.texture_slice,
774 edit,
775 editor: Default::default(),
776 };
777 ctx.add(node)
778 }
779}
780
781#[derive(Debug)]
782pub struct TextureSlicePropertyEditorDefinition;
783
784impl PropertyEditorDefinition for TextureSlicePropertyEditorDefinition {
785 fn value_type_id(&self) -> TypeId {
786 TypeId::of::<TextureSlice>()
787 }
788
789 fn create_instance(
790 &self,
791 ctx: PropertyEditorBuildContext,
792 ) -> Result<PropertyEditorInstance, InspectorError> {
793 let value = ctx.property_info.cast_value::<TextureSlice>()?;
794 Ok(PropertyEditorInstance::simple(
795 TextureSliceFieldEditorBuilder::new(
796 WidgetBuilder::new()
797 .with_margin(Thickness::top_bottom(1.0))
798 .with_vertical_alignment(VerticalAlignment::Center),
799 )
800 .with_texture_slice(value.clone())
801 .build(ctx.build_context),
802 ))
803 }
804
805 fn create_message(
806 &self,
807 ctx: PropertyEditorMessageContext,
808 ) -> Result<Option<UiMessage>, InspectorError> {
809 let value = ctx.property_info.cast_value::<TextureSlice>()?;
810 Ok(Some(
811 UiMessage::with_data(TextureSliceEditorMessage::Slice(value.clone()))
812 .with_destination(ctx.instance),
813 ))
814 }
815
816 fn translate_message(&self, ctx: PropertyEditorTranslationContext) -> Option<PropertyChanged> {
817 if ctx.message.direction() == MessageDirection::FromWidget {
818 if let Some(TextureSliceEditorMessage::Slice(value)) = ctx.message.data() {
819 return Some(PropertyChanged {
820 name: ctx.name.to_string(),
821
822 value: FieldKind::object(value.clone()),
823 });
824 }
825 }
826 None
827 }
828}