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