1use crate::{
22 asset::{
23 item::{AssetItem, AssetItemMessage},
24 preview::cache::IconRequest,
25 selector::AssetSelectorMixin,
26 },
27 fyrox::{
28 asset::{core::pool::Handle, manager::ResourceManager, state::ResourceState},
29 core::{
30 color::Color, log::Log, parking_lot::Mutex, reflect::prelude::*,
31 type_traits::prelude::*, uuid_provider, visitor::prelude::*, SafeLock,
32 },
33 graph::SceneGraph,
34 gui::{
35 brush::Brush,
36 button::{Button, ButtonBuilder, ButtonMessage},
37 draw::{CommandTexture, Draw, DrawingContext},
38 grid::{Column, GridBuilder, Row},
39 image::{Image, ImageBuilder, ImageMessage},
40 inspector::{
41 editors::{
42 PropertyEditorBuildContext, PropertyEditorDefinition, PropertyEditorInstance,
43 PropertyEditorMessageContext, PropertyEditorTranslationContext,
44 },
45 FieldAction, InspectorError, PropertyChanged,
46 },
47 message::{MessageData, UiMessage},
48 text::{Text, TextBuilder, TextMessage},
49 utils::{make_asset_preview_tooltip, make_simple_tooltip, ImageButtonBuilder},
50 widget::{Widget, WidgetBuilder, WidgetMessage},
51 BuildContext, Control, Thickness, UiNode, UserInterface, VerticalAlignment,
52 },
53 material::{Material, MaterialResource, MaterialResourceExtension},
54 },
55 load_image,
56 message::MessageSender,
57 plugins::inspector::EditorEnvironment,
58 utils::make_pick_button,
59 Message, MessageDirection,
60};
61use std::{
62 any::TypeId,
63 fmt::{Debug, Formatter},
64 ops::{Deref, DerefMut},
65 sync::mpsc::Sender,
66};
67
68#[derive(Debug, Clone, PartialEq)]
69pub enum MaterialFieldMessage {
70 Material(MaterialResource),
71}
72impl MessageData for MaterialFieldMessage {}
73
74#[derive(Clone, Visit, Reflect, ComponentProvider)]
75#[reflect(derived_type = "UiNode")]
76pub struct MaterialFieldEditor {
77 widget: Widget,
78 #[visit(skip)]
79 #[reflect(hidden)]
80 sender: MessageSender,
81 text: Handle<Text>,
82 edit: Handle<Button>,
83 locate: Handle<Button>,
84 make_unique: Handle<Button>,
85 material: MaterialResource,
86 image: Handle<Image>,
87 image_preview: Handle<Image>,
88 asset_selector_mixin: AssetSelectorMixin<Material>,
89}
90
91impl Debug for MaterialFieldEditor {
92 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
93 write!(f, "MaterialFieldEditor")
94 }
95}
96
97impl Deref for MaterialFieldEditor {
98 type Target = Widget;
99
100 fn deref(&self) -> &Self::Target {
101 &self.widget
102 }
103}
104
105impl DerefMut for MaterialFieldEditor {
106 fn deref_mut(&mut self) -> &mut Self::Target {
107 &mut self.widget
108 }
109}
110
111uuid_provider!(MaterialFieldEditor = "d3fa0a7c-52d6-4cca-885e-0db8b18542e2");
112
113impl Control for MaterialFieldEditor {
114 fn draw(&self, drawing_context: &mut DrawingContext) {
115 drawing_context.push_rect_filled(&self.bounding_rect(), None);
118 drawing_context.commit(
119 self.clip_bounds(),
120 Brush::Solid(Color::TRANSPARENT),
121 CommandTexture::None,
122 &self.widget.material,
123 None,
124 );
125 }
126
127 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
128 self.widget.handle_routed_message(ui, message);
129
130 if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
131 if message.destination() == self.edit {
132 self.sender
133 .send(Message::OpenMaterialEditor(self.material.clone()));
134 } else if message.destination() == self.make_unique {
135 ui.send(
136 self.handle,
137 MaterialFieldMessage::Material(self.material.deep_copy_as_embedded()),
138 );
139 } else if message.destination() == self.locate {
140 if let Some(path) = self
141 .asset_selector_mixin
142 .resource_manager
143 .resource_path(&self.material)
144 {
145 self.sender.send(Message::ShowInAssetBrowser(path));
146 }
147 }
148 } else if let Some(MaterialFieldMessage::Material(material)) = message.data_for(self.handle)
149 {
150 if &self.material != material {
151 self.material = material.clone();
152
153 ui.send(
154 self.text,
155 TextMessage::Text(make_name(
156 &self.asset_selector_mixin.resource_manager,
157 &self.material,
158 )),
159 );
160
161 self.asset_selector_mixin
162 .request_preview(self.handle, material);
163
164 ui.try_send_response(message);
165 }
166 } else if let Some(WidgetMessage::Drop(dropped)) = message.data() {
167 if let Some(item) = ui.node(*dropped).cast::<AssetItem>() {
168 if let Some(material) = item.resource::<Material>() {
169 ui.send(self.handle(), MaterialFieldMessage::Material(material));
170 }
171 }
172 } else if let Some(AssetItemMessage::Icon {
173 texture,
174 flip_y,
175 color,
176 }) = message.data_for(self.handle)
177 {
178 for widget in [self.image, self.image_preview] {
179 ui.send(widget, ImageMessage::Texture(texture.clone()));
180 ui.send(widget, ImageMessage::Flip(*flip_y));
181 ui.send(
182 widget,
183 WidgetMessage::Background(Brush::Solid(*color).into()),
184 )
185 }
186 }
187
188 self.asset_selector_mixin
189 .handle_ui_message(Some(&self.material), ui, message);
190 }
191
192 fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
193 self.asset_selector_mixin
194 .preview_ui_message(ui, message, |resource| {
195 UiMessage::for_widget(
196 self.handle,
197 MaterialFieldMessage::Material(resource.try_cast::<Material>().unwrap()),
198 )
199 });
200 }
201}
202
203pub struct MaterialFieldEditorBuilder {
204 widget_builder: WidgetBuilder,
205}
206
207fn make_name(resource_manager: &ResourceManager, material: &MaterialResource) -> String {
208 let resource_path = resource_manager.state().resource_path(material.as_ref());
209 let header = material.header();
210 match header.state {
211 ResourceState::Ok { .. } => {
212 if let Some(path) = resource_path {
213 format!(
214 "{} - {} uses; id - {}",
215 path.display(),
216 material.use_count(),
217 material.key()
218 )
219 } else {
220 format!(
221 "Embedded - {} uses; id - {}",
222 material.use_count(),
223 material.key()
224 )
225 }
226 }
227 ResourceState::Unloaded => "Material not loading".into(),
228 ResourceState::LoadError { ref error, .. } => {
229 format!("Loading failed: {error:?}")
230 }
231 ResourceState::Pending { .. } => {
232 format!("Loading {}", header.kind)
233 }
234 }
235}
236
237impl MaterialFieldEditorBuilder {
238 pub fn new(widget_builder: WidgetBuilder) -> Self {
239 Self { widget_builder }
240 }
241
242 pub fn build(
243 self,
244 ctx: &mut BuildContext,
245 sender: MessageSender,
246 material: MaterialResource,
247 icon_request_sender: Sender<IconRequest>,
248 resource_manager: ResourceManager,
249 ) -> Handle<MaterialFieldEditor> {
250 let edit;
251 let text;
252 let select;
253 let locate;
254 let make_unique;
255 let make_unique_tooltip = "Creates a deep copy of the material, making a separate version of the material. \
256 Useful when you need to change some properties in the material, but only on some nodes that uses the material.";
257
258 let buttons = GridBuilder::new(
259 WidgetBuilder::new()
260 .on_row(1)
261 .with_child({
262 select = make_pick_button(0, ctx);
263 select
264 })
265 .with_child({
266 locate = ImageButtonBuilder::default()
267 .on_column(1)
268 .with_image(load_image!("../../../resources/locate.png"))
269 .with_tooltip("Show In Asset Browser")
270 .build_button(ctx);
271 locate
272 })
273 .with_child({
274 edit = ButtonBuilder::new(
275 WidgetBuilder::new()
276 .with_width(55.0)
277 .with_margin(Thickness::uniform(1.0))
278 .on_column(2),
279 )
280 .with_text("Edit...")
281 .build(ctx);
282 edit
283 })
284 .with_child({
285 make_unique = ButtonBuilder::new(
286 WidgetBuilder::new()
287 .with_width(100.0)
288 .with_margin(Thickness::uniform(1.0))
289 .on_column(3)
290 .with_tooltip(make_simple_tooltip(ctx, make_unique_tooltip)),
291 )
292 .with_text("Make Unique")
293 .build(ctx);
294 make_unique
295 }),
296 )
297 .add_row(Row::strict(24.0))
298 .add_column(Column::auto())
299 .add_column(Column::auto())
300 .add_column(Column::auto())
301 .add_column(Column::auto())
302 .build(ctx);
303
304 let (image_preview_tooltip, image_preview) = make_asset_preview_tooltip(None, ctx);
305
306 let image = ImageBuilder::new(
307 WidgetBuilder::new()
308 .on_column(0)
309 .with_width(52.0)
310 .with_height(52.0)
311 .with_tooltip(image_preview_tooltip)
312 .with_margin(Thickness::uniform(1.0)),
313 )
314 .build(ctx);
315
316 let content = GridBuilder::new(
317 WidgetBuilder::new()
318 .on_column(1)
319 .with_child({
320 text =
321 TextBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(1.0)))
322 .with_text(make_name(&resource_manager, &material))
323 .with_vertical_text_alignment(VerticalAlignment::Center)
324 .build(ctx);
325 text
326 })
327 .with_child(buttons),
328 )
329 .add_row(Row::auto())
330 .add_row(Row::auto())
331 .add_column(Column::stretch())
332 .build(ctx);
333
334 let editor = MaterialFieldEditor {
335 widget: self
336 .widget_builder
337 .with_preview_messages(true)
338 .with_allow_drop(true)
339 .with_child(
340 GridBuilder::new(WidgetBuilder::new().with_child(image).with_child(content))
341 .add_column(Column::auto())
342 .add_column(Column::stretch())
343 .add_row(Row::auto())
344 .build(ctx),
345 )
346 .build(ctx),
347 edit,
348 sender,
349 material: material.clone(),
350 text,
351 make_unique,
352 asset_selector_mixin: AssetSelectorMixin::new(
353 select,
354 icon_request_sender.clone(),
355 resource_manager,
356 ),
357 image,
358 image_preview,
359 locate,
360 };
361
362 let handle = ctx.add(editor);
363
364 Log::verify(icon_request_sender.send(IconRequest {
365 widget_handle: handle.to_base(),
366 resource: material.into_untyped(),
367 force_update: false,
368 }));
369
370 handle
371 }
372}
373
374#[derive(Debug)]
375pub struct MaterialPropertyEditorDefinition {
376 pub sender: Mutex<MessageSender>,
377}
378
379impl PropertyEditorDefinition for MaterialPropertyEditorDefinition {
380 fn value_type_id(&self) -> TypeId {
381 TypeId::of::<MaterialResource>()
382 }
383
384 fn create_instance(
385 &self,
386 ctx: PropertyEditorBuildContext,
387 ) -> Result<PropertyEditorInstance, InspectorError> {
388 let value = ctx.property_info.cast_value::<MaterialResource>()?;
389 let environment = EditorEnvironment::try_get_from(&ctx.environment)?;
390 Ok(PropertyEditorInstance::simple(
391 MaterialFieldEditorBuilder::new(WidgetBuilder::new()).build(
392 ctx.build_context,
393 self.sender.safe_lock().clone(),
394 value.clone(),
395 environment.icon_request_sender.clone(),
396 environment.resource_manager.clone(),
397 ),
398 ))
399 }
400
401 fn create_message(
402 &self,
403 ctx: PropertyEditorMessageContext,
404 ) -> Result<Option<UiMessage>, InspectorError> {
405 let value = ctx.property_info.cast_value::<MaterialResource>()?;
406 Ok(Some(UiMessage::for_widget(
407 ctx.instance,
408 MaterialFieldMessage::Material(value.clone()),
409 )))
410 }
411
412 fn translate_message(&self, ctx: PropertyEditorTranslationContext) -> Option<PropertyChanged> {
413 if ctx.message.direction() == MessageDirection::FromWidget {
414 if let Some(MaterialFieldMessage::Material(value)) = ctx.message.data() {
415 return Some(PropertyChanged {
416 name: ctx.name.to_string(),
417 action: FieldAction::object(value.clone()),
418 });
419 }
420 }
421 None
422 }
423}
424
425#[cfg(test)]
426mod test {
427 use crate::plugins::material::editor::MaterialFieldEditorBuilder;
428 use fyrox::asset::io::FsResourceIo;
429 use fyrox::asset::manager::ResourceManager;
430 use fyrox::core::task::TaskPool;
431 use fyrox::{gui::test::test_widget_deletion, gui::widget::WidgetBuilder};
432 use std::sync::mpsc::channel;
433 use std::sync::Arc;
434
435 #[test]
436 fn test_deletion() {
437 let resource_manager =
438 ResourceManager::new(Arc::new(FsResourceIo), Arc::new(TaskPool::new()));
439 let (sender, _) = channel();
440 test_widget_deletion(|ctx| {
441 MaterialFieldEditorBuilder::new(WidgetBuilder::new()).build(
442 ctx,
443 Default::default(),
444 Default::default(),
445 sender,
446 resource_manager,
447 )
448 });
449 }
450}