1use 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 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}