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