1use crate::{
25 button::{ButtonBuilder, ButtonMessage},
26 core::{
27 pool::Handle, reflect::prelude::*, reflect::FieldValue, type_traits::prelude::*,
28 visitor::prelude::*, ImmutableString, PhantomDataSendSync,
29 },
30 define_constructor, define_widget_deref,
31 draw::DrawingContext,
32 grid::{Column, GridBuilder, Row},
33 image::ImageBuilder,
34 inspector::{
35 editors::{
36 PropertyEditorBuildContext, PropertyEditorDefinition, PropertyEditorInstance,
37 PropertyEditorMessageContext, PropertyEditorTranslationContext,
38 },
39 FieldKind, InspectorError, PropertyChanged,
40 },
41 list_view::{ListViewBuilder, ListViewMessage},
42 message::{OsEvent, UiMessage},
43 resources::BIND_ICON,
44 stack_panel::StackPanelBuilder,
45 style::{
46 resource::{StyleResource, StyleResourceExt},
47 Style, StyledProperty,
48 },
49 utils::{make_dropdown_list_option, make_simple_tooltip},
50 widget::WidgetBuilder,
51 window::{Window, WindowBuilder, WindowMessage, WindowTitle},
52 BuildContext, Control, HorizontalAlignment, MessageDirection, Orientation, Thickness, UiNode,
53 UserInterface, VerticalAlignment, Widget,
54};
55use fyrox_core::algebra::{Matrix3, Vector2};
56use fyrox_graph::BaseSceneGraph;
57use std::{
58 any::{Any, TypeId},
59 fmt::{Debug, Formatter},
60 ops::{Deref, DerefMut},
61 sync::mpsc::Sender,
62};
63
64#[derive(Debug, Clone, PartialEq)]
65pub enum StyledPropertySelectorMessage {
66 PropertyName(ImmutableString),
67}
68
69impl StyledPropertySelectorMessage {
70 define_constructor!(StyledPropertySelectorMessage:PropertyName => fn property_name(ImmutableString), layout: false);
71}
72
73#[derive(Debug, Clone, PartialEq)]
74pub enum StyledPropertyEditorMessage {
75 BindProperty(ImmutableString),
76}
77
78impl StyledPropertyEditorMessage {
79 define_constructor!(StyledPropertyEditorMessage:BindProperty => fn bind_property(ImmutableString), layout: false);
80}
81
82#[derive(Debug, Clone, Visit, Reflect, ComponentProvider, TypeUuidProvider)]
83#[type_uuid(id = "3a863a0f-7414-44f5-a7aa-7a6668a6d406")]
84#[reflect(derived_type = "UiNode")]
85pub struct StyledPropertySelector {
86 window: Window,
87 properties: Handle<UiNode>,
88 property_list: Vec<ImmutableString>,
89 ok: Handle<UiNode>,
90 cancel: Handle<UiNode>,
91 style_property_name: ImmutableString,
92}
93
94impl Deref for StyledPropertySelector {
95 type Target = Widget;
96
97 fn deref(&self) -> &Self::Target {
98 &self.window.widget
99 }
100}
101
102impl DerefMut for StyledPropertySelector {
103 fn deref_mut(&mut self) -> &mut Self::Target {
104 &mut self.window.widget
105 }
106}
107
108impl Control for StyledPropertySelector {
109 fn on_remove(&self, sender: &Sender<UiMessage>) {
110 self.window.on_remove(sender)
111 }
112
113 fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
114 self.window.measure_override(ui, available_size)
115 }
116
117 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
118 self.window.arrange_override(ui, final_size)
119 }
120
121 fn draw(&self, drawing_context: &mut DrawingContext) {
122 self.window.draw(drawing_context)
123 }
124
125 fn on_visual_transform_changed(
126 &self,
127 old_transform: &Matrix3<f32>,
128 new_transform: &Matrix3<f32>,
129 ) {
130 self.window
131 .on_visual_transform_changed(old_transform, new_transform)
132 }
133
134 fn post_draw(&self, drawing_context: &mut DrawingContext) {
135 self.window.post_draw(drawing_context)
136 }
137
138 fn update(&mut self, dt: f32, ui: &mut UserInterface) {
139 self.window.update(dt, ui)
140 }
141
142 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
143 self.window.handle_routed_message(ui, message);
144
145 if let Some(ListViewMessage::SelectionChanged(selected)) = message.data() {
146 if let Some(selected_index) = selected.first() {
147 if message.destination() == self.properties {
148 self.style_property_name = self.property_list[*selected_index].clone();
149 }
150 }
151 } else if let Some(ButtonMessage::Click) = message.data() {
152 if message.destination() == self.ok {
153 ui.send_message(StyledPropertySelectorMessage::property_name(
154 self.handle,
155 MessageDirection::FromWidget,
156 self.style_property_name.clone(),
157 ));
158 ui.send_message(WindowMessage::close(
159 self.handle,
160 MessageDirection::ToWidget,
161 ));
162 } else if message.destination() == self.cancel {
163 ui.send_message(WindowMessage::close(
164 self.handle,
165 MessageDirection::ToWidget,
166 ));
167 }
168 }
169 }
170
171 fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
172 self.window.preview_message(ui, message)
173 }
174
175 fn handle_os_event(
176 &mut self,
177 self_handle: Handle<UiNode>,
178 ui: &mut UserInterface,
179 event: &OsEvent,
180 ) {
181 self.window.handle_os_event(self_handle, ui, event)
182 }
183
184 fn accepts_drop(&self, widget: Handle<UiNode>, ui: &UserInterface) -> bool {
185 self.window.accepts_drop(widget, ui)
186 }
187}
188
189pub struct StyledPropertySelectorBuilder {
190 window_builder: WindowBuilder,
191}
192
193impl StyledPropertySelectorBuilder {
194 pub fn new(window_builder: WindowBuilder) -> Self {
195 Self { window_builder }
196 }
197
198 pub fn build(
199 self,
200 target_style: &StyleResource,
201 target_type: TypeId,
202 style_property_name: ImmutableString,
203 ctx: &mut BuildContext,
204 ) -> Handle<UiNode> {
205 let style_data = target_style.data_ref();
206 let (items, property_list): (Vec<_>, Vec<_>) = style_data
207 .all_properties()
208 .iter()
209 .filter_map(|(name, value)| {
210 if value.value_type_id() == target_type {
211 Some((make_dropdown_list_option(ctx, name), name.clone()))
212 } else {
213 None
214 }
215 })
216 .unzip();
217 let selection = property_list
218 .iter()
219 .position(|name| name == &style_property_name);
220 let selected_item = selection.and_then(|i| items.get(i).cloned());
221 let properties = ListViewBuilder::new(
222 WidgetBuilder::new()
223 .on_row(0)
224 .on_column(0)
225 .with_margin(Thickness::uniform(1.0)),
226 )
227 .with_selection(selection.map(|i| vec![i]).unwrap_or_default())
228 .with_items(items)
229 .build(ctx);
230
231 let ok;
232 let cancel;
233 let buttons = StackPanelBuilder::new(
234 WidgetBuilder::new()
235 .on_column(0)
236 .on_row(1)
237 .with_horizontal_alignment(HorizontalAlignment::Right)
238 .with_child({
239 ok = ButtonBuilder::new(
240 WidgetBuilder::new()
241 .with_margin(Thickness::uniform(1.0))
242 .with_width(100.0),
243 )
244 .with_text("OK")
245 .build(ctx);
246 ok
247 })
248 .with_child({
249 cancel = ButtonBuilder::new(
250 WidgetBuilder::new()
251 .with_margin(Thickness::uniform(1.0))
252 .with_width(100.0),
253 )
254 .with_text("Cancel")
255 .build(ctx);
256 cancel
257 }),
258 )
259 .with_orientation(Orientation::Horizontal)
260 .build(ctx);
261
262 let content = GridBuilder::new(
263 WidgetBuilder::new()
264 .with_child(properties)
265 .with_child(buttons),
266 )
267 .add_row(Row::stretch())
268 .add_row(Row::auto())
269 .add_column(Column::stretch())
270 .build(ctx);
271
272 let selector = StyledPropertySelector {
273 window: self.window_builder.with_content(content).build_window(ctx),
274 properties,
275 property_list,
276 ok,
277 cancel,
278 style_property_name,
279 };
280
281 if let Some(selected_item) = selected_item {
282 ctx.send_message(ListViewMessage::bring_item_into_view(
283 properties,
284 MessageDirection::ToWidget,
285 selected_item,
286 ));
287 }
288
289 ctx.add_node(UiNode::new(selector))
290 }
291
292 pub fn build_and_open_window(
293 target_style: &StyleResource,
294 target_type: TypeId,
295 style_property_name: ImmutableString,
296 ctx: &mut BuildContext,
297 ) -> Handle<UiNode> {
298 let window = StyledPropertySelectorBuilder::new(
299 WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(200.0))
300 .with_title(WindowTitle::text("Select A Style Property"))
301 .with_remove_on_close(true)
302 .open(false),
303 )
304 .build(target_style, target_type, style_property_name, ctx);
305
306 ctx.send_message(WindowMessage::open_modal(
307 window,
308 MessageDirection::ToWidget,
309 true,
310 true,
311 ));
312
313 window
314 }
315}
316
317#[derive(Debug, Clone, Visit, Reflect, ComponentProvider, TypeUuidProvider)]
318#[type_uuid(id = "1b8fb74a-3911-4b44-bb71-1a0382ebb9a7")]
319#[reflect(derived_type = "UiNode")]
320pub struct StyledPropertyEditor {
321 widget: Widget,
322 bind: Handle<UiNode>,
323 inner_editor: Handle<UiNode>,
324 selector: Handle<UiNode>,
325 target_style: Option<StyleResource>,
326 style_property_name: ImmutableString,
327 #[visit(skip)]
328 #[reflect(hidden)]
329 target_type_id: TypeId,
330}
331
332define_widget_deref!(StyledPropertyEditor);
333
334impl Control for StyledPropertyEditor {
335 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
336 self.widget.handle_routed_message(ui, message);
337
338 if let Some(target_style) = self.target_style.as_ref() {
339 if let Some(ButtonMessage::Click) = message.data() {
340 if message.destination() == self.bind {
341 self.selector = StyledPropertySelectorBuilder::build_and_open_window(
342 target_style,
343 self.target_type_id,
344 self.style_property_name.clone(),
345 &mut ui.build_ctx(),
346 );
347 }
348 }
349 }
350
351 if message.destination() == self.inner_editor
357 && message.direction == MessageDirection::FromWidget
358 {
359 let mut clone = message.clone();
360 clone.destination = self.handle;
361 ui.send_message(clone);
362 }
363 }
364
365 fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
366 if message.destination() == self.selector
367 && message.direction() == MessageDirection::FromWidget
368 {
369 if let Some(StyledPropertySelectorMessage::PropertyName(name)) = message.data() {
370 ui.send_message(StyledPropertyEditorMessage::bind_property(
371 self.handle,
372 MessageDirection::FromWidget,
373 name.clone(),
374 ))
375 }
376 }
377 }
378}
379
380struct StyledPropertyEditorBuilder {
381 widget_builder: WidgetBuilder,
382 inner_editor: Handle<UiNode>,
383 container: Handle<UiNode>,
384 style_property_name: ImmutableString,
385 target_style: Option<StyleResource>,
386 target_type_id: TypeId,
387}
388
389impl StyledPropertyEditorBuilder {
390 pub fn new(widget_builder: WidgetBuilder) -> Self {
391 Self {
392 widget_builder,
393 inner_editor: Handle::NONE,
394 container: Handle::NONE,
395 style_property_name: Default::default(),
396 target_style: None,
397 target_type_id: ().type_id(),
398 }
399 }
400
401 pub fn with_inner_editor(mut self, inner_editor: Handle<UiNode>) -> Self {
402 self.inner_editor = inner_editor;
403 self
404 }
405
406 pub fn with_container(mut self, container: Handle<UiNode>) -> Self {
407 self.container = container;
408 self
409 }
410
411 pub fn with_style_property_name(mut self, style_property_name: ImmutableString) -> Self {
412 self.style_property_name = style_property_name;
413 self
414 }
415
416 pub fn with_style_resource(mut self, target_style: Option<StyleResource>) -> Self {
417 self.target_style = target_style;
418 self
419 }
420
421 pub fn with_target_type_id(mut self, type_id: TypeId) -> Self {
422 self.target_type_id = type_id;
423 self
424 }
425
426 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
427 let is_bound = !self.style_property_name.is_empty();
428 let brush = if is_bound {
429 ctx.style.property(Style::BRUSH_BRIGHT_BLUE)
430 } else {
431 ctx.style.property(Style::BRUSH_BRIGHTEST)
432 };
433
434 let tooltip = if is_bound {
435 &format!("Bound To `{}` Property", self.style_property_name)
436 } else {
437 "Bind To Style Property"
438 };
439
440 let bind;
441 let grid = GridBuilder::new(WidgetBuilder::new().with_child(self.container).with_child({
442 bind = ButtonBuilder::new(
443 WidgetBuilder::new()
444 .on_column(1)
445 .with_tooltip(make_simple_tooltip(ctx, tooltip))
446 .with_margin(Thickness::uniform(1.0))
447 .with_vertical_alignment(VerticalAlignment::Top)
448 .with_width(18.0)
449 .with_height(18.0),
450 )
451 .with_content(
452 ImageBuilder::new(
453 WidgetBuilder::new()
454 .with_background(brush)
455 .with_margin(Thickness::uniform(2.0))
456 .with_width(16.0)
457 .with_height(16.0),
458 )
459 .with_opt_texture(BIND_ICON.clone())
460 .build(ctx),
461 )
462 .build(ctx);
463 bind
464 }))
465 .add_row(Row::auto())
466 .add_column(Column::stretch())
467 .add_column(Column::auto())
468 .build(ctx);
469
470 ctx.add_node(UiNode::new(StyledPropertyEditor {
471 widget: self
472 .widget_builder
473 .with_preview_messages(true)
474 .with_child(grid)
475 .build(ctx),
476 bind,
477 inner_editor: self.inner_editor,
478 selector: Default::default(),
479 target_style: self.target_style,
480 style_property_name: self.style_property_name,
481 target_type_id: self.target_type_id,
482 }))
483 }
484}
485
486pub struct StyledPropertyEditorDefinition<T>
487where
488 T: FieldValue,
489{
490 #[allow(dead_code)]
491 phantom: PhantomDataSendSync<T>,
492}
493
494impl<T> StyledPropertyEditorDefinition<T>
495where
496 T: FieldValue,
497{
498 pub fn new() -> Self {
499 Self {
500 phantom: Default::default(),
501 }
502 }
503}
504
505impl<T> Debug for StyledPropertyEditorDefinition<T>
506where
507 T: Reflect + FieldValue,
508{
509 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
510 writeln!(f, "StyledPropertyEditorDefinition")
511 }
512}
513
514impl<T> PropertyEditorDefinition for StyledPropertyEditorDefinition<T>
515where
516 T: Reflect + FieldValue,
517{
518 fn value_type_id(&self) -> TypeId {
519 TypeId::of::<StyledProperty<T>>()
520 }
521
522 fn create_instance(
523 &self,
524 ctx: PropertyEditorBuildContext,
525 ) -> Result<PropertyEditorInstance, InspectorError> {
526 let value = ctx.property_info.cast_value::<StyledProperty<T>>()?;
527 if let Some(definition) = ctx
528 .definition_container
529 .definitions()
530 .get(&TypeId::of::<T>())
531 {
532 let property_info = ctx.property_info;
533
534 let proxy_property_info = FieldRef {
535 metadata: &FieldMetadata {
536 name: property_info.name,
537 display_name: property_info.display_name,
538 read_only: property_info.read_only,
539 immutable_collection: property_info.immutable_collection,
540 min_value: property_info.min_value,
541 max_value: property_info.max_value,
542 step: property_info.step,
543 precision: property_info.precision,
544 tag: property_info.tag,
545 doc: property_info.doc,
546 },
547 value: &**value,
548 };
549
550 let instance =
551 definition
552 .property_editor
553 .create_instance(PropertyEditorBuildContext {
554 build_context: ctx.build_context,
555 property_info: &proxy_property_info,
556 environment: ctx.environment.clone(),
557 definition_container: ctx.definition_container.clone(),
558 sync_flag: ctx.sync_flag,
559 layer_index: ctx.layer_index,
560 generate_property_string_values: ctx.generate_property_string_values,
561 filter: ctx.filter,
562 name_column_width: ctx.name_column_width,
563 base_path: ctx.base_path.clone(),
564 has_parent_object: ctx.has_parent_object,
565 })?;
566
567 let wrapper = StyledPropertyEditorBuilder::new(WidgetBuilder::new())
568 .with_container(match instance {
569 PropertyEditorInstance::Simple { editor } => editor,
570 PropertyEditorInstance::Custom { container, .. } => container,
571 })
572 .with_inner_editor(match instance {
573 PropertyEditorInstance::Simple { editor } => editor,
574 PropertyEditorInstance::Custom { editor, .. } => editor,
575 })
576 .with_target_type_id(TypeId::of::<T>())
577 .with_style_resource(ctx.environment.as_ref().and_then(|env| {
578 (&**env as &dyn ComponentProvider)
579 .component_ref::<Option<StyleResource>>()
580 .cloned()
581 .flatten()
582 }))
583 .with_style_property_name(value.name.clone())
584 .build(ctx.build_context);
585
586 Ok(match instance {
587 PropertyEditorInstance::Simple { .. } => {
588 PropertyEditorInstance::Simple { editor: wrapper }
589 }
590 PropertyEditorInstance::Custom { .. } => PropertyEditorInstance::Custom {
591 container: wrapper,
592 editor: wrapper,
593 },
594 })
595 } else {
596 Err(InspectorError::Custom("No editor!".to_string()))
597 }
598 }
599
600 fn create_message(
601 &self,
602 ctx: PropertyEditorMessageContext,
603 ) -> Result<Option<UiMessage>, InspectorError> {
604 if let Some(definition) = ctx
605 .definition_container
606 .definitions()
607 .get(&TypeId::of::<T>())
608 {
609 let instance = ctx
610 .ui
611 .node(ctx.instance)
612 .cast::<StyledPropertyEditor>()
613 .unwrap();
614
615 let property_info = ctx.property_info;
616
617 let value = property_info.cast_value::<StyledProperty<T>>()?;
618
619 let proxy_property_info = FieldRef {
620 metadata: &FieldMetadata {
621 name: property_info.name,
622 display_name: property_info.display_name,
623 read_only: property_info.read_only,
624 immutable_collection: property_info.immutable_collection,
625 min_value: property_info.min_value,
626 max_value: property_info.max_value,
627 step: property_info.step,
628 precision: property_info.precision,
629 tag: property_info.tag,
630 doc: property_info.doc,
631 },
632 value: &**value,
633 };
634
635 return definition
636 .property_editor
637 .create_message(PropertyEditorMessageContext {
638 property_info: &proxy_property_info,
639 environment: ctx.environment.clone(),
640 definition_container: ctx.definition_container.clone(),
641 sync_flag: ctx.sync_flag,
642 instance: instance.inner_editor,
643 layer_index: ctx.layer_index,
644 ui: ctx.ui,
645 generate_property_string_values: ctx.generate_property_string_values,
646 filter: ctx.filter,
647 name_column_width: ctx.name_column_width,
648 base_path: ctx.base_path.clone(),
649 has_parent_object: ctx.has_parent_object,
650 });
651 }
652
653 Err(InspectorError::Custom("No editor!".to_string()))
654 }
655
656 fn translate_message(&self, ctx: PropertyEditorTranslationContext) -> Option<PropertyChanged> {
657 if let Some(StyledPropertyEditorMessage::BindProperty(name)) = ctx.message.data() {
658 return Some(PropertyChanged {
659 name: format!("{}.{}", ctx.name, StyledProperty::<T>::NAME),
660 value: FieldKind::object(name.clone()),
661 });
662 }
663
664 if let Some(definition) = ctx
666 .definition_container
667 .definitions()
668 .get(&TypeId::of::<T>())
669 {
670 let mut property_change =
671 definition
672 .property_editor
673 .translate_message(PropertyEditorTranslationContext {
674 environment: ctx.environment.clone(),
675 name: ctx.name,
676 message: ctx.message,
677 definition_container: ctx.definition_container.clone(),
678 })?;
679
680 property_change.name += ".";
681 property_change.name += StyledProperty::<T>::PROPERTY;
682
683 return Some(property_change);
684 }
685
686 None
687 }
688}