use crate::scene::selector::{AllowedType, NodeSelectorWindow, SelectedHandle};
use crate::{
fyrox::{
core::{
color::Color, pool::ErasedHandle, pool::Handle, reflect::prelude::*,
type_traits::prelude::*, visitor::prelude::*, SafeLock,
},
graph::SceneGraph,
gui::{
brush::Brush,
button::{ButtonBuilder, ButtonMessage},
draw::{CommandTexture, Draw, DrawingContext},
grid::{Column, GridBuilder, Row},
image::ImageBuilder,
inspector::{
editors::{
PropertyEditorBuildContext, PropertyEditorDefinition, PropertyEditorInstance,
PropertyEditorMessageContext, PropertyEditorTranslationContext,
},
FieldKind, InspectorError, PropertyChanged,
},
message::MessageDirection,
style::{resource::StyleResourceExt, Style},
text::{TextBuilder, TextMessage},
utils::make_simple_tooltip,
widget::{Widget, WidgetBuilder, WidgetMessage},
window::{WindowBuilder, WindowMessage, WindowTitle},
BuildContext, Control, Thickness,
},
},
load_image_internal,
message::MessageSender,
scene::selector::{HierarchyNode, NodeSelectorMessage, NodeSelectorWindowBuilder},
world::item::SceneItem,
Message, UiMessage, UiNode, UserInterface, VerticalAlignment,
};
use fyrox::core::PhantomDataSendSync;
use fyrox::gui::button::Button;
use fyrox::gui::image::Image;
use fyrox::gui::message::MessageData;
use fyrox::gui::text::Text;
use fyrox::gui::window::WindowAlignment;
use std::{
any::TypeId,
fmt::{Debug, Formatter},
ops::{Deref, DerefMut},
sync::Mutex,
};
pub enum HandlePropertyEditorMessage<T: Reflect> {
Value(Handle<T>),
}
impl<T: Reflect> MessageData for HandlePropertyEditorMessage<T> {}
impl<T: Reflect> Clone for HandlePropertyEditorMessage<T> {
fn clone(&self) -> Self {
match self {
Self::Value(v) => Self::Value(*v),
}
}
}
impl<T: Reflect> Debug for HandlePropertyEditorMessage<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Value(v) => v.fmt(f),
}
}
}
impl<T: Reflect> PartialEq for HandlePropertyEditorMessage<T> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Value(left), Self::Value(right)) => left.eq(right),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HandlePropertyEditorNameMessage(pub Option<String>);
impl MessageData for HandlePropertyEditorNameMessage {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HandlePropertyEditorHierarchyMessage(pub HierarchyNode);
impl MessageData for HandlePropertyEditorHierarchyMessage {}
#[derive(Visit, Reflect, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "3ceca8c1-c365-4f03-a413-062f8f3cd685")]
#[reflect(derived_type = "UiNode")]
pub struct HandlePropertyEditor<T: Reflect> {
widget: Widget,
text: Handle<Text>,
locate: Handle<Button>,
select: Handle<Button>,
make_unassigned: Handle<Button>,
value: Handle<T>,
#[visit(skip)]
#[reflect(hidden)]
sender: MessageSender,
selector: Handle<NodeSelectorWindow>,
pick: Handle<Button>,
}
impl<T: Reflect> Debug for HandlePropertyEditor<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "HandlePropertyEditor")
}
}
impl<T: Reflect> Clone for HandlePropertyEditor<T> {
fn clone(&self) -> Self {
Self {
widget: self.widget.clone(),
text: self.text,
value: self.value,
sender: self.sender.clone(),
selector: self.selector,
locate: self.locate,
select: self.select,
make_unassigned: self.make_unassigned,
pick: self.pick,
}
}
}
impl<T: Reflect> Deref for HandlePropertyEditor<T> {
type Target = Widget;
fn deref(&self) -> &Self::Target {
&self.widget
}
}
impl<T: Reflect> DerefMut for HandlePropertyEditor<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.widget
}
}
impl<T: Reflect> Control for HandlePropertyEditor<T> {
fn draw(&self, drawing_context: &mut DrawingContext) {
drawing_context.push_rect_filled(&self.bounding_rect(), None);
drawing_context.commit(
self.clip_bounds(),
Brush::Solid(Color::TRANSPARENT),
CommandTexture::None,
&self.material,
None,
);
}
fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
self.widget.handle_routed_message(ui, message);
if let Some(msg) = message.data_for::<HandlePropertyEditorNameMessage>(self.handle()) {
let value = &msg.0;
let value = if let Some(value) = value {
Some(value.as_str())
} else if self.value.is_none() {
Some("Unassigned")
} else {
None
};
if let Some(value) = value {
ui.send(
self.text,
TextMessage::Text(format!("{} ({})", value, self.value)),
);
let color = if self.value.is_none() {
ui.style.property(Style::BRUSH_WARNING)
} else {
ui.style.property(Style::BRUSH_FOREGROUND)
};
ui.send(self.text, WidgetMessage::Foreground(color));
} else {
ui.send(
self.text,
TextMessage::Text(format!("<Invalid handle!> ({})", self.value)),
);
ui.send(
self.text,
WidgetMessage::Foreground(ui.style.property(Style::BRUSH_ERROR)),
);
};
}
if let Some(msg) = message.data_for::<HandlePropertyEditorHierarchyMessage>(self.handle()) {
let value = &msg.0;
ui.send(self.selector, NodeSelectorMessage::Hierarchy(value.clone()));
ui.send(
self.selector,
NodeSelectorMessage::Selection(vec![SelectedHandle {
handle: self.value.into(),
inner_type_id: TypeId::of::<T>(),
derived_type_ids: T::derived_types().to_vec(),
}]),
);
}
if let Some(msg) = message.data_for::<HandlePropertyEditorMessage<T>>(self.handle()) {
match msg {
HandlePropertyEditorMessage::Value(handle) => {
if self.value != *handle {
self.value = *handle;
ui.send_message(message.reverse());
}
request_name_sync(&self.sender, self.handle, self.value.into());
}
}
} else if let Some(WidgetMessage::Drop(dropped)) = message.data() {
if message.destination() == self.handle() {
if let Some(item) = ui.node(*dropped).cast::<SceneItem>() {
ui.send(
self.handle(),
HandlePropertyEditorMessage::<T>::Value(
item.entity_handle.into(),
),
)
}
}
} else if let Some(ButtonMessage::Click) = message.data() {
if message.destination == self.locate {
self.sender.send(Message::LocateObject {
handle: self.value.into(),
});
} else if message.destination == self.select {
self.sender.send(Message::SelectObject {
handle: self.value.into(),
});
} else if message.destination == self.make_unassigned {
ui.send(
self.handle,
HandlePropertyEditorMessage::Value(Handle::<T>::NONE),
);
} else if message.destination == self.pick {
let node_selector = NodeSelectorWindowBuilder::new(
WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(400.0))
.with_title(WindowTitle::text("Select a Node"))
.open(false),
)
.with_allowed_types(
[AllowedType {
id: TypeId::of::<T>(),
name: std::any::type_name::<T>().to_string(),
}]
.into_iter()
.collect(),
)
.build(&mut ui.build_ctx());
ui.send(
node_selector,
WindowMessage::Open {
alignment: WindowAlignment::Center,
modal: true,
focus_content: true,
},
);
self.sender
.send(Message::ProvideSceneHierarchy { view: self.handle });
self.selector = node_selector;
}
}
}
fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
if let Some(NodeSelectorMessage::Selection(selection)) = message.data_from(self.selector) {
if let Some(suitable) = selection.iter().find(|selected| {
selected.inner_type_id == TypeId::of::<T>()
|| selected.derived_type_ids.contains(&TypeId::of::<T>())
}) {
ui.send(
self.handle,
HandlePropertyEditorMessage::<T>::Value(suitable.handle.into()),
);
}
} else if let Some(WindowMessage::Close) = message.data() {
if message.destination() == self.selector {
ui.send(self.selector, WidgetMessage::Remove);
}
}
}
}
struct HandlePropertyEditorBuilder<T: Reflect> {
widget_builder: WidgetBuilder,
value: Handle<T>,
sender: MessageSender,
}
fn make_icon(data: &[u8], color: Color, ctx: &mut BuildContext) -> Handle<Image> {
ImageBuilder::new(
WidgetBuilder::new()
.with_width(12.0)
.with_height(12.0)
.with_margin(Thickness::uniform(3.0))
.with_background(Brush::Solid(color).into()),
)
.with_opt_texture(load_image_internal(data))
.build(ctx)
}
fn make_button(
ctx: &mut BuildContext,
data: &[u8],
color: Color,
column: usize,
tooltip: &str,
) -> Handle<Button> {
ButtonBuilder::new(
WidgetBuilder::new()
.with_margin(Thickness::uniform(1.0))
.with_tooltip(make_simple_tooltip(ctx, tooltip))
.with_width(20.0)
.with_height(20.0)
.on_column(column),
)
.with_content(make_icon(data, color, ctx))
.build(ctx)
}
impl<T: Reflect> HandlePropertyEditorBuilder<T> {
pub fn new(widget_builder: WidgetBuilder, sender: MessageSender) -> Self {
Self {
widget_builder,
sender,
value: Default::default(),
}
}
pub fn with_value(mut self, value: Handle<T>) -> Self {
self.value = value;
self
}
pub fn build(self, ctx: &mut BuildContext) -> Handle<HandlePropertyEditor<T>> {
let text = TextBuilder::new(
WidgetBuilder::new()
.on_column(0)
.with_vertical_alignment(VerticalAlignment::Center),
)
.with_vertical_text_alignment(VerticalAlignment::Center)
.with_text(if self.value.is_none() {
"Unassigned".to_owned()
} else {
"Err: Desync!".to_owned()
})
.build(ctx);
let locate_img = include_bytes!("../../../../resources/locate.png");
let locate = make_button(ctx, locate_img, Color::repeat(180), 2, "Locate Object");
let select_img = include_bytes!("../../../../resources/select_in_wv.png");
let select = make_button(ctx, select_img, Color::repeat(180), 3, "Select Object");
let cross_img = include_bytes!("../../../../resources/cross.png");
let make_unassigned = make_button(
ctx,
cross_img,
Color::opaque(180, 0, 0),
4,
"Make Unassigned",
);
let pick_img = include_bytes!("../../../../resources/pick.png");
let pick = make_button(ctx, pick_img, Color::opaque(0, 180, 0), 1, "Set...");
let grid = GridBuilder::new(
WidgetBuilder::new()
.with_child(text)
.with_child(pick)
.with_child(locate)
.with_child(select)
.with_child(make_unassigned),
)
.add_row(Row::auto())
.add_column(Column::stretch())
.add_column(Column::auto())
.add_column(Column::auto())
.add_column(Column::auto())
.add_column(Column::auto())
.build(ctx);
let editor = HandlePropertyEditor {
widget: self
.widget_builder
.with_tooltip(make_simple_tooltip(
ctx,
"Use <Alt+Mouse Drag> in World Viewer to assign the value here.",
))
.with_preview_messages(true)
.with_allow_drop(true)
.with_child(grid)
.build(ctx),
text,
value: self.value,
sender: self.sender,
selector: Default::default(),
locate,
select,
make_unassigned,
pick,
};
ctx.add(editor)
}
}
pub struct NodeHandlePropertyEditorDefinition<T: Reflect> {
sender: Mutex<MessageSender>,
#[allow(dead_code)]
type_info: PhantomDataSendSync<T>,
}
impl<T: Reflect> Debug for NodeHandlePropertyEditorDefinition<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Node Handle")
}
}
impl<T: Reflect> NodeHandlePropertyEditorDefinition<T> {
pub fn new(sender: MessageSender) -> Self {
Self {
sender: Mutex::new(sender),
type_info: PhantomDataSendSync::default(),
}
}
}
impl<T: Reflect> PropertyEditorDefinition for NodeHandlePropertyEditorDefinition<T> {
fn value_type_id(&self) -> TypeId {
TypeId::of::<Handle<T>>()
}
fn create_instance(
&self,
ctx: PropertyEditorBuildContext,
) -> Result<PropertyEditorInstance, InspectorError> {
let value = ctx.property_info.cast_value::<Handle<T>>()?;
let sender = self.sender.safe_lock().unwrap().clone();
let editor = HandlePropertyEditorBuilder::new(WidgetBuilder::new(), sender.clone())
.with_value(*value)
.build(ctx.build_context);
request_name_sync(&sender, editor.to_base(), ErasedHandle::from(*value));
Ok(PropertyEditorInstance::simple(editor))
}
fn create_message(
&self,
ctx: PropertyEditorMessageContext,
) -> Result<Option<UiMessage>, InspectorError> {
let value = ctx.property_info.cast_value::<Handle<T>>()?;
Ok(Some(UiMessage::for_widget(
ctx.instance,
HandlePropertyEditorMessage::Value(*value),
)))
}
fn translate_message(&self, ctx: PropertyEditorTranslationContext) -> Option<PropertyChanged> {
if ctx.message.direction() == MessageDirection::FromWidget {
if let Some(HandlePropertyEditorMessage::Value(value)) =
ctx.message.data::<HandlePropertyEditorMessage<T>>()
{
return Some(PropertyChanged {
name: ctx.name.to_string(),
value: FieldKind::object(*value),
});
}
}
None
}
}
fn request_name_sync(sender: &MessageSender, editor: Handle<UiNode>, handle: ErasedHandle) {
sender.send(Message::SyncNodeHandleName {
view: editor,
handle,
});
}
#[cfg(test)]
mod test {
use crate::plugins::inspector::editors::handle::HandlePropertyEditorBuilder;
use fyrox::scene::node::Node;
use fyrox::{gui::test::test_widget_deletion, gui::widget::WidgetBuilder};
#[test]
fn test_deletion() {
test_widget_deletion(|ctx| {
HandlePropertyEditorBuilder::<Node>::new(WidgetBuilder::new(), Default::default())
.build(ctx)
});
}
}