fyrox_ui/inspector/editors/
style.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Property editor for [`StyledProperty`]. It acts like a proxy to inner property, but also
22//! adds a special "bind" button used to change style binding of the property.
23
24use 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        // Re-cast messages from inner editor as message from this editor.
352        // If anything is listening to messages from this editor, let them hear the messages from the inner
353        // editor as if they were coming from this editor, but *do not* re-cast messages to the inner editor
354        // to this editor. Particularly, when the inner editor is made invisible, that does not mean that
355        // this editor should be invisible.
356        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        // Try to translate other messages using inner property editor.
665        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}