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