fyroxed_base/world/graph/
item.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
21use crate::{
22    fyrox::{
23        core::{
24            algebra::Vector2, color::Color, pool::ErasedHandle, pool::Handle, reflect::prelude::*,
25            type_traits::prelude::*, uuid_provider, visitor::prelude::*,
26        },
27        graph::BaseSceneGraph,
28        gui::{
29            brush::Brush,
30            define_constructor,
31            draw::{CommandTexture, Draw, DrawingContext},
32            grid::{Column, GridBuilder, Row},
33            image::ImageBuilder,
34            message::{MessageDirection, OsEvent, UiMessage},
35            style::{resource::StyleResourceExt, Style, StyledProperty},
36            text::{TextBuilder, TextMessage},
37            tree::{Tree, TreeBuilder},
38            utils::make_simple_tooltip,
39            widget::{Widget, WidgetBuilder, WidgetMessage},
40            BuildContext, Control, Thickness, UiNode, UserInterface, VerticalAlignment,
41        },
42        resource::texture::TextureResource,
43    },
44    load_image,
45    message::MessageSender,
46    utils::make_node_name,
47    Message,
48};
49use std::{
50    fmt::{Debug, Formatter},
51    ops::{Deref, DerefMut},
52};
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum SceneItemMessage {
56    Name(String),
57    Validate(Result<(), String>),
58}
59
60impl SceneItemMessage {
61    define_constructor!(SceneItemMessage:Name => fn name(String), layout: false);
62    define_constructor!(SceneItemMessage:Validate => fn validate(Result<(), String>), layout: false);
63}
64
65#[derive(Copy, Clone)]
66pub enum DropAnchor {
67    Side {
68        visual_offset: f32,
69        index_offset: isize,
70    },
71    OnTop,
72}
73
74#[derive(Visit, Reflect, ComponentProvider)]
75pub struct SceneItem {
76    #[component(include)]
77    pub tree: Tree,
78    text_name: Handle<UiNode>,
79    name_value: String,
80    grid: Handle<UiNode>,
81    pub entity_handle: ErasedHandle,
82    // Can be unassigned if there's no warning.
83    pub warning_icon: Handle<UiNode>,
84    #[reflect(hidden)]
85    #[visit(skip)]
86    sender: MessageSender,
87    #[reflect(hidden)]
88    #[visit(skip)]
89    pub drop_anchor: DropAnchor,
90}
91
92impl SceneItem {
93    pub fn name(&self) -> &str {
94        &self.name_value
95    }
96}
97
98impl Clone for SceneItem {
99    fn clone(&self) -> Self {
100        Self {
101            tree: self.tree.clone(),
102            text_name: self.text_name,
103            name_value: self.name_value.clone(),
104            grid: self.grid,
105            entity_handle: self.entity_handle,
106            warning_icon: self.warning_icon,
107            sender: self.sender.clone(),
108            drop_anchor: self.drop_anchor,
109        }
110    }
111}
112
113impl Debug for SceneItem {
114    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
115        write!(f, "SceneItem")
116    }
117}
118
119impl Deref for SceneItem {
120    type Target = Widget;
121
122    fn deref(&self) -> &Self::Target {
123        &self.tree
124    }
125}
126
127impl DerefMut for SceneItem {
128    fn deref_mut(&mut self) -> &mut Self::Target {
129        &mut self.tree
130    }
131}
132
133uuid_provider!(SceneItem = "16f35257-a250-413b-ab51-b1ad086a3a9c");
134
135impl Control for SceneItem {
136    fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
137        self.tree.measure_override(ui, available_size)
138    }
139
140    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
141        self.tree.arrange_override(ui, final_size)
142    }
143
144    fn post_draw(&self, drawing_context: &mut DrawingContext) {
145        self.tree.draw(drawing_context);
146
147        let width = self.screen_bounds().w();
148        match self.drop_anchor {
149            DropAnchor::Side { visual_offset, .. } => {
150                drawing_context.push_line(
151                    Vector2::new(0.0, visual_offset),
152                    Vector2::new(width, visual_offset),
153                    2.0,
154                );
155            }
156            DropAnchor::OnTop => {}
157        }
158        drawing_context.commit(
159            self.clip_bounds().inflate(0.0, 2.0),
160            Brush::Solid(Color::CORN_FLOWER_BLUE),
161            CommandTexture::None,
162            None,
163        );
164    }
165
166    fn update(&mut self, dt: f32, ui: &mut UserInterface) {
167        self.tree.update(dt, ui);
168    }
169
170    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
171        self.tree.handle_routed_message(ui, message);
172
173        if let Some(SceneItemMessage::Name(name)) = message.data() {
174            if message.destination() == self.handle() {
175                self.name_value = make_node_name(name, self.entity_handle);
176
177                ui.send_message(TextMessage::text(
178                    self.text_name,
179                    MessageDirection::ToWidget,
180                    self.name_value.clone(),
181                ));
182            }
183        } else if let Some(SceneItemMessage::Validate(result)) = message.data() {
184            if message.destination() == self.handle() {
185                match result {
186                    Ok(_) => {
187                        ui.send_message(WidgetMessage::remove(
188                            self.warning_icon,
189                            MessageDirection::ToWidget,
190                        ));
191                        self.warning_icon = Handle::NONE;
192                    }
193                    Err(msg) => {
194                        self.warning_icon = ImageBuilder::new(
195                            WidgetBuilder::new()
196                                .with_width(20.0)
197                                .with_height(20.0)
198                                .with_tooltip(make_simple_tooltip(&mut ui.build_ctx(), msg))
199                                .with_margin(Thickness::uniform(1.0))
200                                .on_row(0)
201                                .on_column(2),
202                        )
203                        .with_opt_texture(load_image!("../../../resources/warning.png"))
204                        .build(&mut ui.build_ctx());
205
206                        ui.send_message(WidgetMessage::link(
207                            self.warning_icon,
208                            MessageDirection::ToWidget,
209                            self.grid,
210                        ));
211                    }
212                }
213            }
214        } else if let Some(WidgetMessage::DoubleClick { .. }) = message.data() {
215            let flag = 0b0010;
216            if message.flags & flag != flag {
217                self.sender
218                    .send(Message::FocusObject(self.entity_handle.into()));
219                message.set_handled(true);
220                message.flags |= flag;
221            }
222        } else if let Some(msg) = message.data::<WidgetMessage>() {
223            match msg {
224                WidgetMessage::DragOver(_) => {
225                    if let Some(background) = ui.try_get(self.tree.background) {
226                        let cursor_pos = ui.cursor_position();
227                        let bounds = background.screen_bounds();
228                        let deflated_bounds = bounds.deflate(0.0, 5.0);
229                        if bounds.contains(cursor_pos) {
230                            if cursor_pos.y < deflated_bounds.y() {
231                                self.drop_anchor = DropAnchor::Side {
232                                    visual_offset: 0.0,
233                                    index_offset: 0,
234                                };
235                            } else if deflated_bounds.contains(cursor_pos) {
236                                self.drop_anchor = DropAnchor::OnTop;
237                            } else if cursor_pos.y > deflated_bounds.y() + deflated_bounds.h() {
238                                self.drop_anchor = DropAnchor::Side {
239                                    visual_offset: bounds.h() - 1.0,
240                                    index_offset: 0,
241                                };
242                            }
243                        } else {
244                            self.drop_anchor = DropAnchor::OnTop;
245                        }
246                    }
247                }
248                WidgetMessage::MouseLeave => {
249                    self.drop_anchor = DropAnchor::OnTop;
250                }
251                _ => (),
252            }
253        }
254    }
255
256    fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
257        self.tree.preview_message(ui, message);
258    }
259
260    fn handle_os_event(
261        &mut self,
262        self_handle: Handle<UiNode>,
263        ui: &mut UserInterface,
264        event: &OsEvent,
265    ) {
266        self.tree.handle_os_event(self_handle, ui, event);
267    }
268}
269
270pub struct SceneItemBuilder {
271    tree_builder: TreeBuilder,
272    entity_handle: ErasedHandle,
273    name: String,
274    icon: Option<TextureResource>,
275    text_brush: Option<StyledProperty<Brush>>,
276}
277
278impl SceneItemBuilder {
279    pub fn new(tree_builder: TreeBuilder) -> Self {
280        Self {
281            tree_builder,
282            entity_handle: Default::default(),
283            name: Default::default(),
284            icon: None,
285            text_brush: None,
286        }
287    }
288
289    pub fn with_entity_handle(mut self, entity_handle: ErasedHandle) -> Self {
290        self.entity_handle = entity_handle;
291        self
292    }
293
294    pub fn with_name(mut self, name: String) -> Self {
295        self.name = name;
296        self
297    }
298
299    pub fn with_icon(mut self, icon: Option<TextureResource>) -> Self {
300        self.icon = icon;
301        self
302    }
303
304    pub fn with_text_brush(mut self, brush: StyledProperty<Brush>) -> Self {
305        self.text_brush = Some(brush);
306        self
307    }
308
309    pub fn build(self, ctx: &mut BuildContext, sender: MessageSender) -> Handle<UiNode> {
310        let text_name;
311        let content = GridBuilder::new(
312            WidgetBuilder::new()
313                .with_child(
314                    ImageBuilder::new(
315                        WidgetBuilder::new()
316                            .with_width(16.0)
317                            .with_height(16.0)
318                            .on_column(0)
319                            .with_margin(Thickness::left_right(1.0))
320                            .with_visibility(self.icon.is_some()),
321                    )
322                    .with_opt_texture(self.icon)
323                    .build(ctx),
324                )
325                .with_child({
326                    text_name = TextBuilder::new(
327                        WidgetBuilder::new()
328                            .with_foreground(
329                                self.text_brush
330                                    .unwrap_or(ctx.style.property(Style::BRUSH_TEXT)),
331                            )
332                            .with_margin(Thickness::left(1.0))
333                            .on_column(1)
334                            .with_vertical_alignment(VerticalAlignment::Center),
335                    )
336                    .with_text(format!(
337                        "{} ({}:{})",
338                        self.name,
339                        self.entity_handle.index(),
340                        self.entity_handle.generation()
341                    ))
342                    .build(ctx);
343                    text_name
344                }),
345        )
346        .add_row(Row::stretch())
347        .add_column(Column::auto())
348        .add_column(Column::stretch())
349        .add_column(Column::auto())
350        .build(ctx);
351
352        let tree = self.tree_builder.with_content(content).build_tree(ctx);
353
354        let item = SceneItem {
355            tree,
356            entity_handle: self.entity_handle,
357            name_value: self.name,
358            text_name,
359            grid: content,
360            warning_icon: Default::default(),
361            sender,
362            drop_anchor: DropAnchor::OnTop,
363        };
364
365        ctx.add_node(UiNode::new(item))
366    }
367}
368
369#[cfg(test)]
370mod test {
371    use crate::world::graph::item::SceneItemBuilder;
372    use fyrox::gui::tree::TreeBuilder;
373    use fyrox::{gui::test::test_widget_deletion, gui::widget::WidgetBuilder};
374
375    #[test]
376    fn test_deletion() {
377        test_widget_deletion(|ctx| {
378            SceneItemBuilder::new(TreeBuilder::new(WidgetBuilder::new()))
379                .build(ctx, Default::default())
380        });
381    }
382}