Skip to main content

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::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        // Re-cast messages from inner editor as message from this editor.
346        // If anything is listening to messages from this editor, let them hear the messages from the inner
347        // editor as if they were coming from this editor, but *do not* re-cast messages to the inner editor
348        // to this editor. Particularly, when the inner editor is made invisible, that does not mean that
349        // this editor should be invisible.
350        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        // Try to translate other messages using inner property editor.
653        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}