PartialEq impls + improvements for ui

This commit is contained in:
Dmitry Stepanov
2026-06-10 20:41:48 +03:00
parent c0978db579
commit b0113664fa
20 changed files with 298 additions and 134 deletions
@@ -47,6 +47,7 @@ use crate::{
plugins::inspector::EditorEnvironment,
};
use fyrox::core::dyntype::DynTypeWrapper;
use fyrox::gui::inspector::InspectorEnvironmentContainer;
use std::{
any::TypeId,
cell::Cell,
@@ -141,7 +142,7 @@ impl DynTypePropertyEditorBuilder {
self,
variant_selector: Handle<UiNode>,
dyn_type_uuid: Option<Uuid>,
environment: Option<Arc<dyn InspectorEnvironment>>,
environment: Option<InspectorEnvironmentContainer>,
layer_index: usize,
generate_property_string_values: bool,
filter: PropertyFilter,
@@ -51,7 +51,7 @@ use crate::{
};
use fyrox::gui::button::Button;
use fyrox::gui::inspector::InspectorContextArgs;
use fyrox::gui::inspector::{InspectorContextArgs, InspectorEnvironmentContainer};
use fyrox::gui::message::MessageData;
use fyrox::gui::utils::make_dropdown_list_option;
use fyrox::gui::{Thickness, VerticalAlignment};
@@ -216,7 +216,7 @@ impl ScriptPropertyEditorBuilder {
open_in_ide_button: Handle<Button>,
variant_selector: Handle<UiNode>,
script_uuid: Option<Uuid>,
environment: Option<Arc<dyn InspectorEnvironment>>,
environment: Option<InspectorEnvironmentContainer>,
layer_index: usize,
generate_property_string_values: bool,
filter: PropertyFilter,
+2 -1
View File
@@ -62,6 +62,7 @@ use crate::{
utils::window_content,
Editor, Message, WidgetMessage, WrapMode,
};
use fyrox::gui::inspector::InspectorEnvironmentContainer;
use std::{any::Any, sync::mpsc::Sender, sync::Arc};
pub mod editors;
@@ -93,7 +94,7 @@ pub struct EditorEnvironment {
impl EditorEnvironment {
pub fn try_get_from(
environment: &Option<Arc<dyn InspectorEnvironment>>,
environment: &Option<InspectorEnvironmentContainer>,
) -> Result<&Self, InspectorError> {
let environment = &**environment.as_ref().ok_or(InspectorError::Custom(
"Missing InspectorEnvironment".into(),
+4 -4
View File
@@ -495,7 +495,7 @@ struct ContextMenu {
paste_keys: Handle<MenuItem>,
}
#[derive(Clone, Debug)]
#[derive(Clone, PartialEq, Debug)]
struct DragEntry {
key_id: Uuid,
initial_position: Vector2<f32>,
@@ -533,7 +533,7 @@ impl OperationContext {
}
}
#[derive(Clone,PartialEq, Debug)]
#[derive(Clone, PartialEq, Debug)]
enum Selection {
Keys { keys: FxHashSet<Uuid> },
LeftTangent { key_id: Uuid },
@@ -952,7 +952,7 @@ impl Control for CurveEditor {
// but when it works, who cares.
self.zoom_to_fit_timer = Some(10);
} else {
self.zoom_to_fit(&ui.sender);
self.zoom_to_fit(&ui.ui_message_channel.sender);
}
self.invalidate_visual();
@@ -1088,7 +1088,7 @@ impl Control for CurveEditor {
if let Some(timer) = self.zoom_to_fit_timer.as_mut() {
*timer = timer.saturating_sub(1);
if *timer == 0 {
self.zoom_to_fit(&ui.sender);
self.zoom_to_fit(&ui.ui_message_channel.sender);
self.zoom_to_fit_timer = None;
}
}
+1 -1
View File
@@ -873,7 +873,7 @@ impl RenderData {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, PartialEq, Clone)]
pub struct DrawingContext {
pub render_data: RenderData,
pub transform_stack: TransformStack,
+33 -8
View File
@@ -26,8 +26,8 @@
use crate::{
button::{ButtonBuilder, ButtonMessage},
core::{
err, log::Log, ok_or_continue, ok_or_return, parking_lot::Mutex, pool::Handle,
reflect::prelude::*, some_or_return, visitor::prelude::*, SafeLock,
err, log::Log, ok_or_continue, ok_or_return, pool::Handle, reflect::prelude::*,
some_or_return, visitor::prelude::*,
},
file_browser::{
fs_tree::{sanitize_path, TreeItemPath},
@@ -52,11 +52,12 @@ use fyrox_graph::{
SceneGraph,
};
use notify::{Event, Watcher};
use std::ops::{Deref, DerefMut};
use std::{
collections::VecDeque,
fmt::{Debug, Formatter},
path::{Path, PathBuf},
sync::{mpsc::Sender, Arc},
sync::mpsc::Sender,
};
mod dialog;
@@ -72,6 +73,7 @@ use crate::button::Button;
use crate::scroll_viewer::ScrollViewer;
use crate::text::Text;
use crate::text_box::TextBox;
use crate::widget::UserData;
pub use field::*;
pub use filter::*;
pub use selector::*;
@@ -100,6 +102,29 @@ enum FsEventMessage {
}
impl MessageData for FsEventMessage {}
#[derive(Debug)]
pub struct FsWatcher(notify::RecommendedWatcher);
impl PartialEq for FsWatcher {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Deref for FsWatcher {
type Target = notify::RecommendedWatcher;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for FsWatcher {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Default, Visit, PartialEq, Reflect)]
#[reflect(type_uuid = "b7f4610e-4b0c-4671-9b4a-60bb45268928")]
#[reflect(derived_type = "UiNode")]
@@ -122,7 +147,7 @@ pub struct FileBrowser {
pub item_context_menu: RcUiNodeHandle,
#[visit(skip)]
#[reflect(hidden)]
pub watcher: Option<notify::RecommendedWatcher>,
pub watcher: Option<FsWatcher>,
}
impl ConstructorProvider<UiNode, UserInterface> for FileBrowser {
@@ -282,7 +307,7 @@ impl FileBrowser {
};
if let Some(root) = self.root.clone() {
self.set_path(&root, ui);
let tree_item_path = Arc::new(Mutex::new(TreeItemPath::root(root.clone())));
let tree_item_path = UserData::new(TreeItemPath::root(root.clone()));
ui[self.tree_root].user_data = Some(tree_item_path.clone());
self.user_data = Some(tree_item_path);
}
@@ -558,7 +583,7 @@ impl Control for FileBrowser {
ui.node(widget)
.user_data
.as_ref()
.is_some_and(|data| data.safe_lock().downcast_ref::<TreeItemPath>().is_some())
.is_some_and(|data| data.downcast::<TreeItemPath>().is_some())
}
}
@@ -819,7 +844,7 @@ fn setup_file_browser_fs_watcher(
sender: Sender<UiMessage>,
file_browser_handle: Handle<FileBrowser>,
the_path: PathBuf,
) -> Option<notify::RecommendedWatcher> {
) -> Option<FsWatcher> {
let handler = EventReceiver {
file_browser_handle,
sender,
@@ -828,7 +853,7 @@ fn setup_file_browser_fs_watcher(
match notify::RecommendedWatcher::new(handler, config) {
Ok(mut watcher) => {
Log::verify(watcher.watch(&the_path, notify::RecursiveMode::Recursive));
Some(watcher)
Some(FsWatcher(watcher))
}
Err(_) => None,
}
+3 -3
View File
@@ -20,8 +20,9 @@
use crate::file_browser::fs_tree::{sanitize_path, TreeItemPath};
use crate::file_browser::{FileType, PathFilter};
use crate::widget::UserData;
use crate::{
core::{algebra::Vector2, parking_lot::Mutex, pool::Handle},
core::{algebra::Vector2, pool::Handle},
file_browser::{
fs_tree::{self, read_dir_entries, DisksProvider},
FileBrowserBuilder, FileBrowserMessage,
@@ -35,7 +36,6 @@ use fyrox_graph::SceneGraph;
use std::{
fs::File,
path::{Path, PathBuf},
sync::Arc,
};
fn create_dir(path: impl AsRef<Path>) {
@@ -124,7 +124,7 @@ fn test_find_tree() {
let mut ui = UserInterface::new(Vector2::new(100.0, 100.0));
let root = TreeRootBuilder::new(
WidgetBuilder::new().with_user_data(Arc::new(Mutex::new(PathBuf::from("test")))),
WidgetBuilder::new().with_user_data(UserData::new(PathBuf::from("test"))),
)
.build(&mut ui.build_ctx())
.to_base();
+6 -6
View File
@@ -26,8 +26,7 @@ use crate::{
PropertyEditorDefinitionContainer, PropertyEditorInstance,
PropertyEditorMessageContext, PropertyEditorTranslationContext,
},
make_expander_container, CollectionAction, FieldAction, InspectorEnvironment,
InspectorError, PropertyChanged,
make_expander_container, CollectionAction, FieldAction, InspectorError, PropertyChanged,
},
inspector::{make_property_margin, PropertyFilter},
message::{MessageDirection, UiMessage},
@@ -36,6 +35,7 @@ use crate::{
BuildContext, Control, Thickness, UiNode, UserInterface,
};
use crate::inspector::InspectorEnvironmentContainer;
use crate::message::{DeliveryMode, MessageData};
use fyrox_graph::SceneGraph;
use std::sync::Arc;
@@ -92,7 +92,7 @@ where
{
widget_builder: WidgetBuilder,
collection: Option<I>,
environment: Option<Arc<dyn InspectorEnvironment>>,
environment: Option<InspectorEnvironmentContainer>,
definition_container: Option<Arc<PropertyEditorDefinitionContainer>>,
layer_index: usize,
generate_property_string_values: bool,
@@ -111,7 +111,7 @@ fn create_item_views(items: &[Item]) -> Vec<Handle<UiNode>> {
fn create_items<'a, 'b, T, I>(
iter: I,
environment: Option<Arc<dyn InspectorEnvironment>>,
environment: Option<InspectorEnvironmentContainer>,
definition_container: Arc<PropertyEditorDefinitionContainer>,
property_info: &FieldRef<'a, 'b>,
ctx: &mut BuildContext,
@@ -209,7 +209,7 @@ where
self
}
pub fn with_environment(mut self, environment: Option<Arc<dyn InspectorEnvironment>>) -> Self {
pub fn with_environment(mut self, environment: Option<InspectorEnvironmentContainer>) -> Self {
self.environment = environment;
self
}
@@ -317,7 +317,7 @@ where
impl<T, const N: usize> PropertyEditorDefinition for ArrayPropertyEditorDefinition<T, N>
where
T: Reflect + Clone,
T: Reflect + Clone + PartialEq,
{
fn value_type_id(&self) -> TypeId {
TypeId::of::<[T; N]>()
+1 -1
View File
@@ -66,7 +66,7 @@ where
impl<T> PropertyEditorDefinition for CellPropertyEditorDefinition<T>
where
T: Reflect + Copy,
T: Reflect + Copy + PartialEq,
{
fn value_type_id(&self) -> TypeId {
TypeId::of::<Cell<T>>()
+5 -4
View File
@@ -18,6 +18,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use crate::inspector::InspectorEnvironmentContainer;
use crate::{
button::{Button, ButtonMessage},
core::{
@@ -31,7 +32,7 @@ use crate::{
PropertyEditorMessageContext, PropertyEditorTranslationContext,
},
make_expander_container, make_property_margin, CollectionAction, FieldAction,
InspectorEnvironment, InspectorError, ObjectValue, PropertyChanged, PropertyFilter,
InspectorError, ObjectValue, PropertyChanged, PropertyFilter,
},
message::{DeliveryMode, MessageData, MessageDirection, UiMessage},
resources,
@@ -177,7 +178,7 @@ where
{
widget_builder: WidgetBuilder,
collection: Option<I>,
environment: Option<Arc<dyn InspectorEnvironment>>,
environment: Option<InspectorEnvironmentContainer>,
definition_container: Option<Arc<PropertyEditorDefinitionContainer>>,
add: Handle<Button>,
layer_index: usize,
@@ -209,7 +210,7 @@ fn create_item_views(items: &[Item], ctx: &mut BuildContext) -> Vec<Handle<UiNod
fn create_items<'a, 'b, T, I>(
iter: I,
environment: Option<Arc<dyn InspectorEnvironment>>,
environment: Option<InspectorEnvironmentContainer>,
definition_container: Arc<PropertyEditorDefinitionContainer>,
property_info: &FieldRef<'a, 'b>,
ctx: &mut BuildContext,
@@ -321,7 +322,7 @@ where
self
}
pub fn with_environment(mut self, environment: Option<Arc<dyn InspectorEnvironment>>) -> Self {
pub fn with_environment(mut self, environment: Option<InspectorEnvironmentContainer>) -> Self {
self.environment = environment;
self
}
@@ -18,6 +18,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use crate::inspector::InspectorEnvironmentContainer;
use crate::{
core::{pool::Handle, reflect::prelude::*, visitor::prelude::*},
dropdown_list::{DropdownList, DropdownListBuilder, DropdownListMessage},
@@ -28,8 +29,7 @@ use crate::{
PropertyEditorMessageContext, PropertyEditorTranslationContext,
},
make_expander_container, FieldAction, Inspector, InspectorBuilder, InspectorContext,
InspectorContextArgs, InspectorEnvironment, InspectorError, InspectorMessage,
PropertyChanged, PropertyFilter,
InspectorContextArgs, InspectorError, InspectorMessage, PropertyChanged, PropertyFilter,
},
message::{MessageData, UiMessage},
utils::make_dropdown_list_option,
@@ -76,7 +76,7 @@ pub struct EnumPropertyEditor<T: InspectableEnum> {
pub definition_container: Arc<PropertyEditorDefinitionContainer>,
#[visit(skip)]
#[reflect(hidden)]
pub environment: Option<Arc<dyn InspectorEnvironment>>,
pub environment: Option<InspectorEnvironmentContainer>,
#[visit(skip)]
#[reflect(hidden)]
pub layer_index: usize,
@@ -183,7 +183,7 @@ impl<T: InspectableEnum> Control for EnumPropertyEditor<T> {
pub struct EnumPropertyEditorBuilder {
widget_builder: WidgetBuilder,
definition_container: Option<Arc<PropertyEditorDefinitionContainer>>,
environment: Option<Arc<dyn InspectorEnvironment>>,
environment: Option<InspectorEnvironmentContainer>,
variant_selector: Handle<UiNode>,
layer_index: usize,
generate_property_string_values: bool,
@@ -211,7 +211,7 @@ impl EnumPropertyEditorBuilder {
self
}
pub fn with_environment(mut self, environment: Option<Arc<dyn InspectorEnvironment>>) -> Self {
pub fn with_environment(mut self, environment: Option<InspectorEnvironmentContainer>) -> Self {
self.environment = environment;
self
}
@@ -293,6 +293,7 @@ impl EnumPropertyEditorBuilder {
}
}
#[derive(PartialEq)]
pub struct EnumPropertyEditorDefinition<T: InspectableEnum> {
pub variant_generator: fn(usize) -> T,
pub index_generator: fn(&T) -> usize,
+17 -4
View File
@@ -21,6 +21,7 @@
//! A collection of [PropertyEditorDefinition] objects for a wide variety of types,
//! including standard Rust types and Fyrox core types.
use crate::inspector::InspectorEnvironmentContainer;
use crate::style::{StyleProperty, StylePropertyContainer};
use crate::{
absm::{EventAction, EventKind},
@@ -80,7 +81,7 @@ use crate::{
Vec4PropertyEditorDefinition,
},
},
InspectorEnvironment, InspectorError, PropertyChanged, PropertyFilter,
InspectorError, PropertyChanged, PropertyFilter,
},
key::{HotKeyEditor, KeyBinding, KeyBindingEditor},
list_view::{ListView, ListViewItem},
@@ -166,7 +167,7 @@ pub struct PropertyEditorBuildContext<'a, 'b, 'c, 'd> {
/// [fyroxed_base::inspector::EditorEnvironment](https://docs.rs/fyroxed_base/latest/fyroxed_base/inspector/struct.EditorEnvironment.html)
/// when the Inspector is being used in Fyroxed, but Inspector widgets can be used in other applications,
/// and we can access those applications by casting the environment to the appropriate type.
pub environment: Option<Arc<dyn InspectorEnvironment>>,
pub environment: Option<InspectorEnvironmentContainer>,
/// The list of the Inspectors property editors.
/// This allows one property editor to make use of other property editors.
pub definition_container: Arc<PropertyEditorDefinitionContainer>,
@@ -215,7 +216,7 @@ pub struct PropertyEditorMessageContext<'a, 'b, 'c> {
/// this property is being translated. This allows the created message to
/// adapt to the situation if we can successfully cast the given
/// [InspectorEnvironment] into a specific type.
pub environment: Option<Arc<dyn InspectorEnvironment>>,
pub environment: Option<InspectorEnvironmentContainer>,
/// When true, this indicates that an Inspector should generate strings from `format!("{:?}", field)`, for each field.
/// Having this in the property editor build context indicates how any Inspectors that are update due to the created message
/// should behave.
@@ -251,7 +252,7 @@ pub struct PropertyEditorTranslationContext<'b, 'c> {
/// [fyrox::script::Script](https://docs.rs/fyrox/latest/fyrox/script/struct.Script.html)
/// when it receives a
/// [ScriptPropertyEditorMessage::Value](https://docs.rs/fyroxed_base/latest/fyroxed_base/inspector/editors/script/enum.ScriptPropertyEditorMessage.html#variant.Value).
pub environment: Option<Arc<dyn InspectorEnvironment>>,
pub environment: Option<InspectorEnvironmentContainer>,
/// The name of the property being edited.
/// This comes from [ContextEntry::property_name](crate::inspector::ContextEntry).
pub name: &'b str,
@@ -372,6 +373,18 @@ pub struct PropertyEditorDefinitionContainer {
definitions: RwLock<FxHashMap<TypeId, PropertyEditorDefinitionContainerEntry>>,
}
impl PartialEq for PropertyEditorDefinitionContainer {
fn eq(&self, other: &Self) -> bool {
*self.context_type_id.safe_lock() == *other.context_type_id.safe_lock()
&& self
.definitions
.read()
.keys()
.zip(other.definitions.read().keys())
.all(|(a, b)| *a == *b)
}
}
impl Debug for PropertyEditorDefinitionContainer {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "PropertyEditorDefinitionContainer")
+1 -1
View File
@@ -69,7 +69,7 @@ where
impl<T> PropertyEditorDefinition for RefCellPropertyEditorDefinition<T>
where
T: Reflect + Clone,
T: Reflect + Clone + PartialEq,
{
fn value_type_id(&self) -> TypeId {
TypeId::of::<RefCell<T>>()
+1 -1
View File
@@ -502,7 +502,7 @@ where
impl<T> PropertyEditorDefinition for StyledPropertyEditorDefinition<T>
where
T: Reflect + Clone,
T: Reflect + Clone + PartialEq,
{
fn value_type_id(&self) -> TypeId {
TypeId::of::<StyledProperty<T>>()
+30 -2
View File
@@ -54,6 +54,7 @@ use fyrox_graph::{
constructor::{ConstructorProvider, GraphNodeConstructor},
SceneGraph,
};
use std::ops::Deref;
use std::{
any::{Any, TypeId},
fmt::{Debug, Display, Formatter},
@@ -432,6 +433,23 @@ pub trait InspectorEnvironment: Send + Sync + Reflect {
fn name(&self) -> String;
}
#[derive(Clone)]
pub struct InspectorEnvironmentContainer(pub Arc<dyn InspectorEnvironment>);
impl PartialEq for InspectorEnvironmentContainer {
fn eq(&self, other: &Self) -> bool {
self.0.try_compare(&*other.0).unwrap_or_default()
}
}
impl Deref for InspectorEnvironmentContainer {
type Target = dyn InspectorEnvironment;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
/// Inspector is a widget, that allows you to generate visual representation for internal fields an arbitrary
/// structure or enumeration recursively. It's primary usage is provide unified and simple way of introspection.
///
@@ -813,7 +831,7 @@ pub struct InspectorContext {
pub property_definitions: Arc<PropertyEditorDefinitionContainer>,
/// Untyped information from the application that is using the inspector. This can be used by editors that may be
/// supplied by that application, if those editors know the actual type of this value to be able to successfully cast it.
pub environment: Option<Arc<dyn InspectorEnvironment>>,
pub environment: Option<InspectorEnvironmentContainer>,
/// Type id of the object for which the context was created.
pub object_type_id: TypeId,
/// A width of the property name column.
@@ -1009,6 +1027,16 @@ fn make_simple_property_container(
#[derive(Default, Clone)]
pub struct PropertyFilter(pub Option<Arc<dyn Fn(&dyn Reflect) -> bool + Send + Sync>>);
impl PartialEq for PropertyFilter {
fn eq(&self, other: &Self) -> bool {
if let (Some(a), Some(b)) = (self.0.as_ref(), other.0.as_ref()) {
Arc::ptr_eq(a, b)
} else {
false
}
}
}
impl PropertyFilter {
pub fn new<T>(func: T) -> Self
where
@@ -1046,7 +1074,7 @@ pub struct InspectorContextArgs<'a, 'b, 'c> {
pub object: &'a dyn Reflect,
pub ctx: &'b mut BuildContext<'c>,
pub definition_container: Arc<PropertyEditorDefinitionContainer>,
pub environment: Option<Arc<dyn InspectorEnvironment>>,
pub environment: Option<InspectorEnvironmentContainer>,
pub layer_index: usize,
pub generate_property_string_values: bool,
pub filter: PropertyFilter,
+127 -70
View File
@@ -372,6 +372,12 @@ pub(crate) struct RcUiNodeHandleInner {
sender: Option<Sender<UiMessage>>,
}
impl PartialEq for RcUiNodeHandleInner {
fn eq(&self, other: &Self) -> bool {
self.handle == other.handle
}
}
impl Visit for RcUiNodeHandleInner {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
self.handle.visit(name, visitor)?;
@@ -529,7 +535,7 @@ impl NodeStatistics {
}
}
#[derive(Visit, Reflect, Debug, Clone)]
#[derive(Visit, Reflect, PartialEq, Debug, Clone)]
#[reflect(type_uuid = "9f9d5633-1568-4611-8709-3eeb615b7c0f")]
pub struct DragContext {
pub is_dragging: bool,
@@ -568,7 +574,7 @@ impl Default for MouseState {
}
}
#[derive(Copy, Clone, Visit, Reflect, Debug, Default)]
#[derive(Copy, Clone, Visit, PartialEq, Reflect, Debug, Default)]
#[reflect(type_uuid = "2f1add9c-5019-4922-9045-0bd5ca2fd63a")]
pub struct RestrictionEntry {
/// Handle to UI node to which picking must be restricted to.
@@ -584,7 +590,7 @@ pub struct RestrictionEntry {
pub stop: bool,
}
#[derive(Clone, Debug)]
#[derive(Clone, PartialEq, Debug)]
pub struct TooltipEntry {
pub tooltip: RcUiNodeHandle,
pub appear_timer: f32,
@@ -620,7 +626,7 @@ pub enum LayoutEvent {
TransformChanged(Handle<UiNode>),
}
#[derive(Clone, Debug, Visit, Reflect, Default)]
#[derive(Clone, Debug, Visit, Reflect, PartialEq, Default)]
#[reflect(type_uuid = "30bc5bc5-6592-48d8-8647-9bdbbb977ffb")]
struct DoubleClickEntry {
timer: f32,
@@ -629,13 +635,19 @@ struct DoubleClickEntry {
struct Clipboard(Option<RefCell<ClipboardContext>>);
impl PartialEq for Clipboard {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Debug for Clipboard {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Clipboard")
}
}
#[derive(Default, Debug, Clone)]
#[derive(Default, PartialEq, Debug, Clone)]
struct WidgetMethodsRegistry {
preview_message: FxHashSet<Handle<UiNode>>,
on_update: FxHashSet<Handle<UiNode>>,
@@ -681,7 +693,7 @@ pub struct UiUpdateSwitches {
pub type WidgetPool = Pool<UiNode, WidgetContainer>;
#[derive(Default, Debug, Clone, Reflect, Visit)]
#[derive(Default, Debug, Clone, PartialEq, Reflect, Visit)]
#[reflect(type_uuid = "b426e937-4050-4539-8041-ade4222539c8")]
pub enum RenderMode {
/// The UI will be re-rendered on every frame. This is the default behavior.
@@ -693,6 +705,29 @@ pub enum RenderMode {
OnChanges,
}
#[derive(Debug)]
pub struct UiMessageChannel {
pub receiver: Receiver<UiMessage>,
pub sender: Sender<UiMessage>,
}
impl UiMessageChannel {
pub fn new() -> Self {
let (sender, receiver) = mpsc::channel();
Self { receiver, sender }
}
pub fn send(&self, message: UiMessage) {
let _ = self.sender.send(message);
}
}
impl PartialEq for UiMessageChannel {
fn eq(&self, _other: &Self) -> bool {
true
}
}
#[derive(Reflect, PartialEq)]
#[reflect(type_uuid = "0d065c93-ef9c-4dd2-9fe7-e2b33c1a21b6")]
pub struct UserInterface {
@@ -709,9 +744,7 @@ pub struct UserInterface {
cursor_position: Vector2<f32>,
pub style: StyleResource,
#[reflect(hidden)]
receiver: Receiver<UiMessage>,
#[reflect(hidden)]
sender: Sender<UiMessage>,
ui_message_channel: UiMessageChannel,
stack: Vec<Handle<UiNode>>,
picking_stack: Vec<RestrictionEntry>,
#[reflect(hidden)]
@@ -727,9 +760,9 @@ pub struct UserInterface {
#[reflect(hidden)]
clipboard: Clipboard,
#[reflect(hidden)]
layout_events_receiver: Receiver<LayoutEvent>,
layout_events_receiver: LayoutEventsReceiver,
#[reflect(hidden)]
layout_events_sender: Sender<LayoutEvent>,
layout_events_sender: LayoutEventsSender,
#[reflect(hidden)]
z_index_update_set: FxHashSet<Handle<UiNode>>,
#[reflect(hidden)]
@@ -767,8 +800,7 @@ impl Debug for UserInterface {
.field("keyboard_focus_node", &self.keyboard_focus_node)
.field("cursor_position", &self.cursor_position)
.field("style", &self.style)
.field("receiver", &self.receiver)
.field("sender", &self.sender)
.field("ui_message_channel", &self.ui_message_channel)
.field("stack", &self.stack)
.field("picking_stack", &self.picking_stack)
.field("bubble_queue", &self.bubble_queue)
@@ -842,10 +874,46 @@ impl Visit for UserInterface {
}
}
#[derive(Debug, Clone)]
pub struct LayoutEventsSender(pub Sender<LayoutEvent>);
impl PartialEq for LayoutEventsSender {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Deref for LayoutEventsSender {
type Target = Sender<LayoutEvent>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug)]
pub struct LayoutEventsReceiver(pub Receiver<LayoutEvent>);
impl PartialEq for LayoutEventsReceiver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Deref for LayoutEventsReceiver {
type Target = Receiver<LayoutEvent>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Clone for UserInterface {
fn clone(&self) -> Self {
let (sender, receiver) = mpsc::channel();
let ui_message_channel = UiMessageChannel::new();
let (layout_events_sender, layout_events_receiver) = mpsc::channel();
let layout_events_sender = LayoutEventsSender(layout_events_sender);
let layout_events_receiver = LayoutEventsReceiver(layout_events_receiver);
let mut nodes = Pool::new();
for (handle, node) in self.nodes.pair_iter() {
let mut clone = node.clone_boxed();
@@ -865,8 +933,7 @@ impl Clone for UserInterface {
keyboard_focus_node: self.keyboard_focus_node,
cursor_position: self.cursor_position,
style: DEFAULT_STYLE.resource.clone(),
receiver,
sender,
ui_message_channel,
stack: self.stack.clone(),
picking_stack: self.picking_stack.clone(),
bubble_queue: self.bubble_queue.clone(),
@@ -1160,21 +1227,19 @@ pub struct PollResult {
impl UserInterface {
pub fn new(screen_size: Vector2<f32>) -> UserInterface {
let (sender, receiver) = mpsc::channel();
Self::new_with_channel(sender, receiver, screen_size)
let ui_message_channel = UiMessageChannel::new();
Self::new_with_channel(ui_message_channel, screen_size)
}
pub fn new_with_channel(
sender: Sender<UiMessage>,
receiver: Receiver<UiMessage>,
ui_message_channel: UiMessageChannel,
screen_size: Vector2<f32>,
) -> UserInterface {
let (layout_events_sender, layout_events_receiver) = mpsc::channel();
let style = DEFAULT_STYLE.resource.clone();
let mut ui = UserInterface {
screen_size,
sender,
receiver,
ui_message_channel,
visual_debug: false,
captured_node: Handle::NONE,
root_canvas: Handle::NONE,
@@ -1195,8 +1260,8 @@ impl UserInterface {
active_tooltip: Default::default(),
methods_registry: Default::default(),
clipboard: Clipboard(ClipboardContext::new().ok().map(RefCell::new)),
layout_events_receiver,
layout_events_sender,
layout_events_receiver: LayoutEventsReceiver(layout_events_receiver),
layout_events_sender: LayoutEventsSender(layout_events_sender),
z_index_update_set: Default::default(),
default_font: BUILT_IN_FONT.resource(),
double_click_entries: Default::default(),
@@ -2025,11 +2090,11 @@ impl UserInterface {
/// Returns instance of message sender which can be used to push messages into queue
/// from other threads.
pub fn sender(&self) -> Sender<UiMessage> {
self.sender.clone()
self.ui_message_channel.sender.clone()
}
pub fn send_message(&self, message: UiMessage) {
self.sender.send(message).unwrap()
self.ui_message_channel.send(message)
}
pub fn try_send_response(&self, message: &UiMessage) -> bool {
@@ -2048,13 +2113,11 @@ impl UserInterface {
}
pub fn send(&self, handle: Handle<impl ObjectOrVariant<UiNode>>, data: impl MessageData) {
self.sender
.send(
UiMessage::with_data(data)
.with_destination(handle.transmute())
.with_direction(MessageDirection::ToWidget),
)
.unwrap()
self.ui_message_channel.send(
UiMessage::with_data(data)
.with_destination(handle.transmute())
.with_direction(MessageDirection::ToWidget),
)
}
pub fn send_handled(
@@ -2062,25 +2125,21 @@ impl UserInterface {
handle: Handle<impl ObjectOrVariant<UiNode>>,
data: impl MessageData,
) {
self.sender
.send(
UiMessage::with_data(data)
.with_destination(handle.transmute())
.with_direction(MessageDirection::ToWidget)
.with_handled(true),
)
.unwrap()
self.ui_message_channel.send(
UiMessage::with_data(data)
.with_destination(handle.transmute())
.with_direction(MessageDirection::ToWidget)
.with_handled(true),
)
}
pub fn send_sync(&self, handle: Handle<impl ObjectOrVariant<UiNode>>, data: impl MessageData) {
self.sender
.send(
UiMessage::with_data(data)
.with_destination(handle.transmute())
.with_direction(MessageDirection::ToWidget)
.with_delivery_mode(DeliveryMode::SyncOnly),
)
.unwrap()
self.ui_message_channel.send(
UiMessage::with_data(data)
.with_destination(handle.transmute())
.with_direction(MessageDirection::ToWidget)
.with_delivery_mode(DeliveryMode::SyncOnly),
)
}
pub fn send_with_flags<T: MessageData>(
@@ -2089,14 +2148,12 @@ impl UserInterface {
flags: u64,
data: T,
) {
self.sender
.send(
UiMessage::with_data(data)
.with_destination(handle.transmute())
.with_direction(MessageDirection::ToWidget)
.with_flags(flags),
)
.unwrap()
self.ui_message_channel.send(
UiMessage::with_data(data)
.with_destination(handle.transmute())
.with_direction(MessageDirection::ToWidget)
.with_flags(flags),
)
}
pub fn send_many<const N: usize, T: MessageData>(
@@ -2120,13 +2177,11 @@ impl UserInterface {
}
pub fn post<T: MessageData>(&self, handle: Handle<impl ObjectOrVariant<UiNode>>, data: T) {
self.sender
.send(
UiMessage::with_data(data)
.with_destination(handle.transmute())
.with_direction(MessageDirection::FromWidget),
)
.unwrap()
self.ui_message_channel.send(
UiMessage::with_data(data)
.with_destination(handle.transmute())
.with_direction(MessageDirection::FromWidget),
)
}
pub fn post_many<const N: usize, T: MessageData>(
@@ -2248,7 +2303,7 @@ impl UserInterface {
message: None,
};
while let Ok(message) = self.receiver.try_recv() {
while let Ok(message) = self.ui_message_channel.receiver.try_recv() {
poll_result.processed_messages += 1;
if let Some(message) = self.poll_single_message(message) {
poll_result.message = Some(message);
@@ -2584,7 +2639,7 @@ impl UserInterface {
/// Find any tooltips that are being hovered and activate them.
/// As well, update their time.
fn update_tooltips(&mut self, dt: f32) {
let sender = self.sender.clone();
let sender = self.ui_message_channel.sender.clone();
if let Some(entry) = self.active_tooltip.as_mut() {
if entry.shown {
entry.disappear_timer -= dt;
@@ -3488,13 +3543,15 @@ impl UserInterface {
let (mut ui, data) = {
let data = io.load_file(path.as_ref()).await?;
let mut visitor = Visitor::load_from_memory(&data)?;
let (sender, receiver) = mpsc::channel();
let ui_message_channel = UiMessageChannel::new();
visitor.blackboard.register(constructors);
visitor.blackboard.register(Arc::new(sender.clone()));
visitor
.blackboard
.register(Arc::new(ui_message_channel.sender.clone()));
visitor.blackboard.register(Arc::new(resource_manager));
visitor.blackboard.register(dyn_type_constructors);
let mut ui =
UserInterface::new_with_channel(sender, receiver, Vector2::new(100.0, 100.0));
UserInterface::new_with_channel(ui_message_channel, Vector2::new(100.0, 100.0));
ui.visit("Ui", &mut visitor)?;
(ui, data)
};
@@ -3637,7 +3694,7 @@ impl SceneGraph for UserInterface {
let node = node.to_base();
self.isolate_node(node);
let sender = self.sender.clone();
let sender = self.ui_message_channel.sender.clone();
let mut stack = vec![node];
while let Some(handle) = stack.pop() {
if self.prev_picked_node == handle {
+1 -1
View File
@@ -150,7 +150,7 @@ pub static LIGHT_STYLE: LazyLock<BuiltInResource<Style>> = LazyLock::new(|| {
/// the style and why do we need to store the value as well? The answer is flexibility. In this
/// approach, style becomes not necessary and the value can be hardcoded. Also, the values of such
/// properties can be updated individually.
#[derive(Clone, Debug, PartialEq, Reflect, Default)]
#[derive(Clone, Debug, Reflect, Default)]
#[reflect(
bounds = "T: Reflect + Clone + PartialEq",
type_uuid = "71e84137-2832-42a1-bcb5-f8ee95784997"
+21 -3
View File
@@ -50,6 +50,7 @@ use crate::{
};
use copypasta::ClipboardProvider;
use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
use std::ops::Deref;
use std::{
cell::RefCell,
fmt::{Debug, Formatter},
@@ -195,6 +196,23 @@ impl SelectionRange {
/// the filter, and `false` - otherwise.
pub type FilterCallback = dyn FnMut(char) -> bool + Send;
#[derive(Clone)]
pub struct TextBoxFilter(pub Arc<Mutex<FilterCallback>>);
impl PartialEq for TextBoxFilter {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Deref for TextBoxFilter {
type Target = Mutex<FilterCallback>;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
/// TextBox is a text widget that allows you to edit text and create specialized input fields. It has various options like
/// word wrapping, text alignment, and so on.
///
@@ -427,7 +445,7 @@ pub struct TextBox {
/// Current character filter of the text box.
#[visit(skip)]
#[reflect(hidden)]
pub filter: Option<Arc<Mutex<FilterCallback>>>,
pub filter: Option<TextBoxFilter>,
/// Current text commit mode of the text box.
pub commit_mode: InheritableVariable<TextCommitMode>,
/// `true` if the multiline mode is active.
@@ -1527,7 +1545,7 @@ pub struct TextBoxBuilder<'a> {
text: String,
caret_brush: Brush,
selection_brush: Brush,
filter: Option<Arc<Mutex<FilterCallback>>>,
filter: Option<TextBoxFilter>,
vertical_alignment: VerticalAlignment,
horizontal_alignment: HorizontalAlignment,
wrap: WrapMode,
@@ -1613,7 +1631,7 @@ impl<'a> TextBoxBuilder<'a> {
}
/// Sets the desired character filter of the text box. See [`FilterCallback`] for more info.
pub fn with_filter(mut self, filter: Arc<Mutex<FilterCallback>>) -> Self {
pub fn with_filter(mut self, filter: TextBoxFilter) -> Self {
self.filter = Some(filter);
self
}
+3 -3
View File
@@ -18,10 +18,11 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use crate::widget::UserData;
use crate::{
border::BorderBuilder,
button::{Button, ButtonBuilder},
core::{algebra::Vector2, color::Color, parking_lot::Mutex, pool::Handle},
core::{algebra::Vector2, color::Color, pool::Handle},
decorator::DecoratorBuilder,
formatted_text::WrapMode,
grid::{Column, GridBuilder, Row},
@@ -37,7 +38,6 @@ use fyrox_texture::{
CompressionOptions, TextureImportOptions, TextureMinificationFilter, TextureResource,
TextureResourceExtension,
};
use std::sync::Arc;
use uuid::Uuid;
pub enum ArrowDirection {
@@ -233,7 +233,7 @@ pub fn make_dropdown_list_option_universal<T: Send + 'static>(
BorderBuilder::new(
WidgetBuilder::new()
.with_height(height)
.with_user_data(Arc::new(Mutex::new(user_data)))
.with_user_data(UserData::new(user_data))
.with_child(
TextBuilder::new(WidgetBuilder::new().with_margin(text_margin()))
.with_vertical_text_alignment(VerticalAlignment::Center)
+32 -13
View File
@@ -41,9 +41,10 @@ use crate::{
resource::{StyleResource, StyleResourceExt},
Style, StyledProperty, DEFAULT_STYLE,
},
BuildContext, HorizontalAlignment, LayoutEvent, MouseButton, MouseState, RcUiNodeHandle,
Thickness, UiNode, UserInterface, VerticalAlignment,
BuildContext, HorizontalAlignment, LayoutEvent, LayoutEventsSender, MouseButton, MouseState,
RcUiNodeHandle, Thickness, UiNode, UserInterface, VerticalAlignment,
};
use fyrox_core::parking_lot::{MappedMutexGuard, MutexGuard};
use fyrox_core::pool::ObjectOrVariant;
use fyrox_graph::SceneGraph;
use fyrox_material::{Material, MaterialResource};
@@ -54,7 +55,7 @@ use std::{
cmp::Ordering,
fmt::{Debug, Formatter},
ops::{Deref, DerefMut},
sync::{mpsc::Sender, Arc},
sync::Arc,
};
/// Sorting predicate that is used to sort widgets by some criteria.
@@ -536,6 +537,25 @@ impl WidgetRenderDataSet {
}
}
#[derive(Clone, Debug)]
pub struct UserData(pub Arc<Mutex<dyn Any + Send>>);
impl UserData {
pub fn new<T: Any + Send>(v: T) -> Self {
Self(Arc::new(Mutex::new(v)))
}
pub fn downcast<T: Any + Send>(&self) -> Option<MappedMutexGuard<T>> {
MutexGuard::try_map(self.0.safe_lock(), |v| v.downcast_mut::<T>()).ok()
}
}
impl PartialEq for UserData {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
/// Widget is a base UI element, that is always used to build derived, more complex, widgets. In general, it is a container
/// for layout information, basic visual appearance, visibility options, parent-child information. It does almost nothing
/// on its own, instead, the user interface modifies its state accordingly.
@@ -619,7 +639,7 @@ pub struct Widget {
/// Optional, user-defined data.
#[reflect(hidden)]
#[visit(skip)]
pub user_data: Option<Arc<Mutex<dyn Any + Send>>>,
pub user_data: Option<UserData>,
/// A flag, that defines whether the widget should be drawn in a separate drawing pass after any other widget that draws
/// normally.
pub draw_on_top: InheritableVariable<bool>,
@@ -675,7 +695,7 @@ pub struct Widget {
/// Internal sender for layout events.
#[reflect(hidden)]
#[visit(skip)]
pub layout_events_sender: Option<Sender<LayoutEvent>>,
pub layout_events_sender: Option<LayoutEventsSender>,
/// Unique identifier of the widget.
pub id: Uuid,
/// A flag, that indicates whether this widget is a root widget of a hierarchy of widgets
@@ -1564,11 +1584,10 @@ impl Widget {
/// Tries to fetch user-defined data of the specified type `T`.
#[inline]
pub fn user_data_cloned<T: Clone + 'static>(&self) -> Option<T> {
self.user_data.as_ref().and_then(|v| {
let guard = v.safe_lock();
guard.downcast_ref::<T>().cloned()
})
pub fn user_data_cloned<T: Any + Send + Clone + 'static>(&self) -> Option<T> {
self.user_data
.as_ref()
.and_then(|v| Some(T::clone(&*v.downcast::<T>()?)))
}
/// Returns current clipping bounds of the widget. It is valid only after at least one layout pass.
@@ -1701,7 +1720,7 @@ pub struct WidgetBuilder {
/// Whether the drop of the widget is allowed or not.
pub allow_drop: bool,
/// User-defined data.
pub user_data: Option<Arc<Mutex<dyn Any + Send>>>,
pub user_data: Option<UserData>,
/// Whether to draw the widget on top of any other or not.
pub draw_on_top: bool,
/// Whether the widget is enabled or not.
@@ -1981,7 +2000,7 @@ impl WidgetBuilder {
/// Sets the desired widget user data.
pub fn with_user_data_value_opt<T: Any + Send>(mut self, user_data: Option<T>) -> Self {
if let Some(data) = user_data {
self.user_data = Some(Arc::new(Mutex::new(data)));
self.user_data = Some(UserData::new(data));
}
self
}
@@ -1992,7 +2011,7 @@ impl WidgetBuilder {
}
/// Sets the desired widget user data.
pub fn with_user_data(mut self, user_data: Arc<Mutex<dyn Any + Send>>) -> Self {
pub fn with_user_data(mut self, user_data: UserData) -> Self {
self.user_data = Some(user_data);
self
}