asset selector for material field editor

This commit is contained in:
Dmitry Stepanov
2025-07-12 10:52:53 +03:00
parent 56b33716b2
commit 65a678bb85
8 changed files with 192 additions and 129 deletions
+70
View File
@@ -53,6 +53,7 @@ use crate::{
},
},
};
use fyrox::core::PhantomDataSendSync;
use rust_fuzzy_search::fuzzy_compare;
use std::{
cell::Cell,
@@ -605,3 +606,72 @@ impl AssetSelectorWindowBuilder {
selector
}
}
#[derive(Reflect, Visit, Debug)]
pub struct AssetSelectorMixin<T: TypedResourceData> {
pub selector: Cell<Handle<UiNode>>,
pub select: Handle<UiNode>,
#[visit(skip)]
#[reflect(hidden)]
pub icon_request_sender: Sender<IconRequest>,
#[visit(skip)]
#[reflect(hidden)]
pub resource_manager: ResourceManager,
#[visit(skip)]
#[reflect(hidden)]
pub phantom_data_send_sync: PhantomDataSendSync<T>,
}
impl<T: TypedResourceData> Clone for AssetSelectorMixin<T> {
fn clone(&self) -> Self {
Self {
selector: self.selector.clone(),
select: self.select.clone(),
icon_request_sender: self.icon_request_sender.clone(),
resource_manager: self.resource_manager.clone(),
phantom_data_send_sync: self.phantom_data_send_sync,
}
}
}
impl<T: TypedResourceData> AssetSelectorMixin<T> {
pub fn new(
select: Handle<UiNode>,
icon_request_sender: Sender<IconRequest>,
resource_manager: ResourceManager,
) -> Self {
Self {
selector: Default::default(),
select,
icon_request_sender,
resource_manager,
phantom_data_send_sync: Default::default(),
}
}
pub fn handle_ui_message(&self, ui: &mut UserInterface, message: &UiMessage) {
if let Some(ButtonMessage::Click) = message.data() {
if message.destination() == self.select {
self.selector
.set(AssetSelectorWindowBuilder::build_for_type_and_open::<T>(
self.icon_request_sender.clone(),
self.resource_manager.clone(),
ui,
));
}
}
}
pub fn preview_ui_message<F>(&self, ui: &UserInterface, message: &mut UiMessage, converter: F)
where
F: FnOnce(&UntypedResource) -> UiMessage,
{
if message.destination() == self.selector.get() {
if let Some(WindowMessage::Close) = message.data() {
self.selector.set(Handle::NONE);
} else if let Some(AssetSelectorMessage::Select(resource)) = message.data() {
ui.send_message(converter(resource))
}
}
}
}
@@ -18,12 +18,9 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use crate::asset::selector::AssetSelectorMixin;
use crate::{
asset::{
item::AssetItem,
preview::cache::IconRequest,
selector::{AssetSelectorMessage, AssetSelectorWindowBuilder},
},
asset::{item::AssetItem, preview::cache::IconRequest},
fyrox::{
asset::{manager::ResourceManager, state::LoadError, Resource, TypedResourceData},
core::{
@@ -48,7 +45,6 @@ use crate::{
message::{MessageDirection, UiMessage},
text::{TextBuilder, TextMessage},
widget::{Widget, WidgetBuilder, WidgetMessage},
window::WindowMessage,
BuildContext, Control, Thickness, UiNode, UserInterface, VerticalAlignment,
},
},
@@ -59,7 +55,6 @@ use crate::{
};
use std::{
any::TypeId,
cell::Cell,
fmt::{Debug, Formatter},
ops::{Deref, DerefMut},
path::Path,
@@ -131,14 +126,7 @@ where
{
widget: Widget,
name: Handle<UiNode>,
select: Handle<UiNode>,
selector: Cell<Handle<UiNode>>,
#[visit(skip)]
#[reflect(hidden)]
resource_manager: ResourceManager,
#[visit(skip)]
#[reflect(hidden)]
icon_request_sender: Sender<IconRequest>,
selector_mixin: AssetSelectorMixin<T>,
#[visit(skip)]
#[reflect(hidden)]
resource: Option<Resource<T>>,
@@ -165,10 +153,7 @@ where
Self {
widget: self.widget.clone(),
name: self.name,
select: self.select,
selector: self.selector.clone(),
resource_manager: self.resource_manager.clone(),
icon_request_sender: self.icon_request_sender.clone(),
selector_mixin: self.selector_mixin.clone(),
resource: self.resource.clone(),
locate: self.locate,
sender: self.sender.clone(),
@@ -244,7 +229,7 @@ where
ui.send_message(TextMessage::text(
self.name,
MessageDirection::ToWidget,
resource_path(&self.resource_manager, resource),
resource_path(&self.selector_mixin.resource_manager, resource),
));
ui.send_message(message.reverse());
@@ -252,33 +237,29 @@ where
} else if let Some(ButtonMessage::Click) = message.data() {
if message.destination() == self.locate {
if let Some(resource) = self.resource.as_ref() {
if let Some(path) = self.resource_manager.resource_path(resource.as_ref()) {
if let Some(path) = self
.selector_mixin
.resource_manager
.resource_path(resource.as_ref())
{
self.sender.send(Message::ShowInAssetBrowser(path));
}
}
} else if message.destination() == self.select {
self.selector
.set(AssetSelectorWindowBuilder::build_for_type_and_open::<T>(
self.icon_request_sender.clone(),
self.resource_manager.clone(),
ui,
));
}
}
self.selector_mixin.handle_ui_message(ui, message);
}
fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
if message.destination() == self.selector.get() {
if let Some(WindowMessage::Close) = message.data() {
self.selector.set(Handle::NONE);
} else if let Some(AssetSelectorMessage::Select(resource)) = message.data() {
ui.send_message(ResourceFieldMessage::value(
self.selector_mixin
.preview_ui_message(ui, message, |resource| {
ResourceFieldMessage::value(
self.handle,
MessageDirection::ToWidget,
resource.try_cast::<T>(),
))
}
}
)
});
}
}
@@ -374,10 +355,7 @@ where
.with_allow_drop(true)
.build(ctx),
name,
select,
selector: Default::default(),
resource_manager,
icon_request_sender,
selector_mixin: AssetSelectorMixin::new(select, icon_request_sender, resource_manager),
resource: self.resource,
locate,
sender: self.sender,
+19 -43
View File
@@ -18,11 +18,9 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use crate::asset::selector::AssetSelectorMixin;
use crate::{
asset::{
item::AssetItem, preview::cache::IconRequest, selector::AssetSelectorMessage,
selector::AssetSelectorWindowBuilder,
},
asset::{item::AssetItem, preview::cache::IconRequest},
fyrox::{
asset::{manager::ResourceManager, untyped::UntypedResource},
core::{
@@ -31,7 +29,6 @@ use crate::{
},
graph::{BaseSceneGraph, SceneGraph},
gui::{
button::ButtonMessage,
define_constructor,
grid::{Column, GridBuilder, Row},
image::{ImageBuilder, ImageMessage},
@@ -47,7 +44,6 @@ use crate::{
popup::{PopupBuilder, PopupMessage},
stack_panel::StackPanelBuilder,
widget::{Widget, WidgetBuilder, WidgetMessage},
window::WindowMessage,
BuildContext, Control, RcUiNodeHandle, Thickness, UiNode, UserInterface,
},
resource::texture::{Texture, TextureResource},
@@ -58,7 +54,6 @@ use crate::{
};
use std::{
any::TypeId,
cell::Cell,
fmt::{Debug, Formatter},
ops::{Deref, DerefMut},
sync::mpsc::Sender,
@@ -113,21 +108,14 @@ impl TextureContextMenu {
pub struct TextureEditor {
widget: Widget,
image: Handle<UiNode>,
#[visit(skip)]
#[reflect(hidden)]
resource_manager: ResourceManager,
texture: Option<TextureResource>,
selector: Cell<Handle<UiNode>>,
select: Handle<UiNode>,
selector_mixin: AssetSelectorMixin<Texture>,
#[visit(skip)]
#[reflect(hidden)]
sender: MessageSender,
#[visit(skip)]
#[reflect(hidden)]
texture_context_menu: Option<TextureContextMenu>,
#[visit(skip)]
#[reflect(hidden)]
icon_request_sender: Sender<IconRequest>,
}
impl Debug for TextureEditor {
@@ -172,7 +160,9 @@ impl Control for TextureEditor {
ui.send_message(TextureEditorMessage::texture(
self.handle(),
MessageDirection::ToWidget,
self.resource_manager.try_request::<Texture>(relative_path),
self.selector_mixin
.resource_manager
.try_request::<Texture>(relative_path),
));
}
}
@@ -193,11 +183,11 @@ impl Control for TextureEditor {
let context_menu = self.texture_context_menu.as_mut().unwrap();
if let Some(MenuItemMessage::Click) = message.data() {
if message.destination() == context_menu.show_in_asset_browser {
if let Some(path) = self
.texture
.as_ref()
.and_then(|t| self.resource_manager.resource_path(t.as_ref()))
{
if let Some(path) = self.texture.as_ref().and_then(|t| {
self.selector_mixin
.resource_manager
.resource_path(t.as_ref())
}) {
self.sender.send(Message::ShowInAssetBrowser(path));
}
} else if message.destination() == context_menu.unassign {
@@ -208,31 +198,20 @@ impl Control for TextureEditor {
));
}
}
} else if let Some(ButtonMessage::Click) = message.data() {
if message.destination() == self.select {
self.selector.set(
AssetSelectorWindowBuilder::build_for_type_and_open::<Texture>(
self.icon_request_sender.clone(),
self.resource_manager.clone(),
ui,
),
);
}
}
self.selector_mixin.handle_ui_message(ui, message);
}
fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
if message.destination() == self.selector.get() {
if let Some(WindowMessage::Close) = message.data() {
self.selector.set(Handle::NONE);
} else if let Some(AssetSelectorMessage::Select(resource)) = message.data() {
ui.send_message(TextureEditorMessage::texture(
self.selector_mixin
.preview_ui_message(ui, message, |resource| {
TextureEditorMessage::texture(
self.handle,
MessageDirection::ToWidget,
resource.try_cast::<Texture>(),
))
}
}
)
});
}
}
@@ -295,13 +274,10 @@ impl TextureEditorBuilder {
let editor = TextureEditor {
widget,
image,
resource_manager,
texture: None,
selector: Default::default(),
select,
selector_mixin: AssetSelectorMixin::new(select, icon_request_sender, resource_manager),
sender,
texture_context_menu: None,
icon_request_sender,
};
let editor = ctx.add_node(UiNode::new(editor));
+67 -41
View File
@@ -52,7 +52,12 @@ use crate::{
Message, MessageDirection,
};
use crate::asset::preview::cache::IconRequest;
use crate::asset::selector::AssetSelectorMixin;
use crate::plugins::inspector::EditorEnvironment;
use crate::utils::make_pick_button;
use fyrox::asset::manager::ResourceManager;
use std::sync::mpsc::Sender;
use std::{
any::TypeId,
fmt::{Debug, Formatter},
@@ -79,9 +84,7 @@ pub struct MaterialFieldEditor {
edit: Handle<UiNode>,
make_unique: Handle<UiNode>,
material: MaterialResource,
#[visit(skip)]
#[reflect(hidden)]
resource_manager: ResourceManager,
asset_selector_mixin: AssetSelectorMixin<Material>,
}
impl Debug for MaterialFieldEditor {
@@ -144,7 +147,7 @@ impl Control for MaterialFieldEditor {
ui.send_message(TextMessage::text(
self.text,
MessageDirection::ToWidget,
make_name(&self.resource_manager, &self.material),
make_name(&self.asset_selector_mixin.resource_manager, &self.material),
));
ui.send_message(message.reverse());
@@ -160,6 +163,19 @@ impl Control for MaterialFieldEditor {
}
}
}
self.asset_selector_mixin.handle_ui_message(ui, message);
}
fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
self.asset_selector_mixin
.preview_ui_message(ui, message, |resource| {
MaterialFieldMessage::material(
self.handle,
MessageDirection::ToWidget,
resource.try_cast::<Material>().unwrap(),
)
});
}
}
@@ -201,17 +217,55 @@ impl MaterialFieldEditorBuilder {
ctx: &mut BuildContext,
sender: MessageSender,
material: MaterialResource,
icon_request_sender: Sender<IconRequest>,
resource_manager: ResourceManager,
) -> Handle<UiNode> {
let edit;
let text;
let select;
let make_unique;
let make_unique_tooltip = "Creates a deep copy of the material, making a separate version of the material. \
Useful when you need to change some properties in the material, but only on some nodes that uses the material.";
let buttons = GridBuilder::new(
WidgetBuilder::new()
.on_row(1)
.with_child({
edit = ButtonBuilder::new(
WidgetBuilder::new()
.with_width(40.0)
.with_margin(Thickness::uniform(1.0)),
)
.with_text("Edit...")
.build(ctx);
edit
})
.with_child({
make_unique = ButtonBuilder::new(
WidgetBuilder::new()
.with_margin(Thickness::uniform(1.0))
.on_column(1)
.with_tooltip(make_simple_tooltip(ctx, make_unique_tooltip)),
)
.with_text("Make Unique")
.build(ctx);
make_unique
})
.with_child({
select = make_pick_button(2, ctx);
select
}),
)
.add_row(Row::strict(20.0))
.add_column(Column::auto())
.add_column(Column::stretch())
.add_column(Column::auto())
.build(ctx);
let editor = MaterialFieldEditor {
widget: self
.widget_builder
.with_preview_messages(true)
.with_allow_drop(true)
.with_child(
GridBuilder::new(
@@ -225,40 +279,7 @@ impl MaterialFieldEditorBuilder {
.build(ctx);
text
})
.with_child(
GridBuilder::new(
WidgetBuilder::new()
.on_row(1)
.with_child({
edit = ButtonBuilder::new(
WidgetBuilder::new()
.with_width(40.0)
.with_margin(Thickness::uniform(1.0)),
)
.with_text("Edit...")
.build(ctx);
edit
})
.with_child({
make_unique = ButtonBuilder::new(
WidgetBuilder::new()
.with_margin(Thickness::uniform(1.0))
.on_column(1)
.with_tooltip(make_simple_tooltip(
ctx,
make_unique_tooltip,
)),
)
.with_text("Make Unique")
.build(ctx);
make_unique
}),
)
.add_row(Row::strict(20.0))
.add_column(Column::auto())
.add_column(Column::stretch())
.build(ctx),
),
.with_child(buttons),
)
.add_row(Row::auto())
.add_row(Row::auto())
@@ -271,7 +292,11 @@ impl MaterialFieldEditorBuilder {
material,
text,
make_unique,
resource_manager,
asset_selector_mixin: AssetSelectorMixin::new(
select,
icon_request_sender,
resource_manager,
),
};
ctx.add_node(UiNode::new(editor))
@@ -281,7 +306,6 @@ impl MaterialFieldEditorBuilder {
#[derive(Debug)]
pub struct MaterialPropertyEditorDefinition {
pub sender: Mutex<MessageSender>,
pub resource_manager: ResourceManager,
}
impl PropertyEditorDefinition for MaterialPropertyEditorDefinition {
@@ -294,12 +318,14 @@ impl PropertyEditorDefinition for MaterialPropertyEditorDefinition {
ctx: PropertyEditorBuildContext,
) -> Result<PropertyEditorInstance, InspectorError> {
let value = ctx.property_info.cast_value::<MaterialResource>()?;
let environment = EditorEnvironment::try_get_from(&ctx.environment)?;
Ok(PropertyEditorInstance::Simple {
editor: MaterialFieldEditorBuilder::new(WidgetBuilder::new()).build(
ctx.build_context,
self.sender.lock().clone(),
value.clone(),
self.resource_manager.clone(),
environment.icon_request_sender.clone(),
environment.resource_manager.clone(),
),
})
}
-1
View File
@@ -703,7 +703,6 @@ impl EditorPlugin for MaterialPlugin {
let container = &editor.plugins.get_mut::<InspectorPlugin>().property_editors;
container.insert(MaterialPropertyEditorDefinition {
sender: Mutex::new(editor.message_sender.clone()),
resource_manager: editor.engine.resource_manager.clone(),
});
container.insert(InheritablePropertyEditorDefinition::<MaterialResource>::new());
container.insert(InheritablePropertyEditorDefinition::<WidgetMaterial>::new());
+10 -1
View File
@@ -25,8 +25,10 @@ use crate::{
plugins::material::editor::{MaterialFieldEditorBuilder, MaterialFieldMessage},
send_sync_message, MSG_SYNC_FLAG,
};
use std::sync::mpsc::Sender;
use super::*;
use crate::asset::preview::cache::IconRequest;
use commands::*;
use fyrox::{
fxhash::FxHashMap,
@@ -190,12 +192,19 @@ impl TileMaterialEditor {
pub fn new(
ctx: &mut BuildContext,
sender: MessageSender,
icon_request_sender: Sender<IconRequest>,
resource_manager: ResourceManager,
) -> Self {
let draw_button = make_draw_button("Apply material to tiles", ctx, None);
let material = DEFAULT_TILE_MATERIAL.deep_copy();
let material_field = MaterialFieldEditorBuilder::new(WidgetBuilder::new().on_column(2))
.build(ctx, sender, material.clone(), resource_manager);
.build(
ctx,
sender,
material.clone(),
icon_request_sender,
resource_manager,
);
let material_line = make_drawable_field("Material", draw_button, material_field, ctx);
let bounds_field = TileBoundsEditorBuilder::new(WidgetBuilder::new()).build(ctx);
Self {
+6 -2
View File
@@ -24,8 +24,6 @@
//! widgets and does its own synchronization and message handling, while
//! `TileInspector` is just responsible for managing the `TileEditor` objects.
use std::fmt::Debug;
use crate::{
command::{Command, CommandGroup},
plugins::material::editor::{MaterialFieldEditorBuilder, MaterialFieldMessage},
@@ -53,8 +51,11 @@ use fyrox::{
material::{MaterialResource, MaterialResourceExtension},
scene::tilemap::{brush::*, tileset::*, *},
};
use std::fmt::Debug;
use std::sync::mpsc::Sender;
use super::*;
use crate::asset::preview::cache::IconRequest;
use commands::*;
use palette::*;
@@ -794,6 +795,7 @@ impl TileInspector {
tiles_palette: Handle<UiNode>,
tile_book: TileBook,
sender: MessageSender,
icon_request_sender: Sender<IconRequest>,
resource_manager: ResourceManager,
ctx: &mut BuildContext,
) -> Self {
@@ -807,6 +809,7 @@ impl TileInspector {
Arc::new(Mutex::new(TileMaterialEditor::new(
ctx,
sender.clone(),
icon_request_sender.clone(),
resource_manager.clone(),
))) as TileEditorRef,
Arc::new(Mutex::new(TileColorEditor::new(ctx))) as TileEditorRef,
@@ -864,6 +867,7 @@ impl TileInspector {
ctx,
sender.clone(),
DEFAULT_TILE_MATERIAL.deep_copy(),
icon_request_sender,
resource_manager,
);
let page_material_inspector = InspectorField::new("Material", page_material_field, ctx);
+2 -1
View File
@@ -215,7 +215,7 @@ impl TileSetEditor {
let tile_set_field =
ResourceFieldBuilder::<TileSet>::new(WidgetBuilder::new().on_column(1), sender.clone())
.with_resource(tile_book.get_tile_set())
.build(ctx, icon_request_sender, resource_manager.clone());
.build(ctx, icon_request_sender.clone(), resource_manager.clone());
let tile_set_selector = GridBuilder::new(
WidgetBuilder::new()
.with_visibility(tile_book.is_brush())
@@ -321,6 +321,7 @@ impl TileSetEditor {
tiles_palette,
tile_book.clone(),
sender,
icon_request_sender,
resource_manager.clone(),
ctx,
);