#![forbid(unsafe_code)]
#![allow(irrefutable_let_patterns)]
#![allow(clippy::float_cmp)]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::from_over_into)]
#![allow(clippy::new_without_default)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::type_complexity)]
#![allow(clippy::doc_lazy_continuation)]
#![allow(clippy::mutable_key_type)]
pub use copypasta;
pub use fyrox_core as core;
use message::TouchPhase;
pub mod absm;
mod alignment;
pub mod animation;
pub mod bit;
pub mod border;
pub mod brush;
mod build;
pub mod button;
pub mod canvas;
pub mod check_box;
pub mod color;
mod control;
pub mod curve;
pub mod decorator;
pub mod dock;
pub mod draw;
pub mod dropdown_list;
pub mod dropdown_menu;
pub mod expander;
pub mod file_browser;
pub mod font;
pub mod formatted_text;
pub mod grid;
pub mod image;
pub mod inspector;
pub mod key;
pub mod list_view;
pub mod loader;
pub mod log;
pub mod matrix;
pub mod menu;
pub mod message;
pub mod messagebox;
pub mod navigation;
pub mod nine_patch;
mod node;
pub mod numeric;
pub mod path;
pub mod popup;
pub mod progress_bar;
pub mod range;
pub mod rect;
pub mod screen;
pub mod scroll_bar;
pub mod scroll_panel;
pub mod scroll_viewer;
pub mod searchbar;
pub mod selector;
pub mod stack_panel;
pub mod style;
pub mod tab_control;
pub mod text;
pub mod text_box;
mod thickness;
pub mod thumb;
pub mod toggle;
pub mod tree;
pub mod utils;
pub mod uuid;
pub mod vec;
pub mod vector_image;
pub mod widget;
pub mod window;
pub mod wrap_panel;
use crate::{
brush::Brush,
canvas::Canvas,
constructor::WidgetConstructorContainer,
container::WidgetContainer,
core::{
algebra::{Matrix3, Vector2},
color::Color,
math::Rect,
pool::{Handle, Pool},
reflect::prelude::*,
uuid::uuid,
visitor::prelude::*,
},
core::{parking_lot::Mutex, pool::Ticket, uuid::Uuid, uuid_provider, TypeUuidProvider},
draw::{CommandTexture, Draw, DrawingContext},
font::FontResource,
font::BUILT_IN_FONT,
message::{
ButtonState, CursorIcon, KeyboardModifiers, MessageDirection, MouseButton, OsEvent,
UiMessage,
},
popup::{Placement, PopupMessage},
widget::{Widget, WidgetBuilder, WidgetMessage},
};
use copypasta::ClipboardContext;
use fxhash::{FxHashMap, FxHashSet};
use fyrox_resource::{
io::FsResourceIo, io::ResourceIo, manager::ResourceManager, untyped::UntypedResource, Resource,
ResourceData,
};
use serde::{Deserialize, Serialize};
use std::{
any::TypeId,
cell::{Ref, RefCell, RefMut},
collections::{btree_set::BTreeSet, hash_map::Entry, VecDeque},
error::Error,
fmt::{Debug, Formatter},
ops::{Deref, DerefMut, Index, IndexMut},
path::Path,
sync::{
mpsc::{self, Receiver, Sender, TryRecvError},
Arc,
},
};
use strum_macros::{AsRefStr, EnumString, VariantNames};
pub use alignment::*;
pub use build::*;
pub use control::*;
use fyrox_core::futures::future::join_all;
use fyrox_core::log::Log;
use fyrox_graph::{
AbstractSceneGraph, AbstractSceneNode, BaseSceneGraph, NodeHandleMap, NodeMapping, PrefabData,
SceneGraph, SceneGraphNode,
};
pub use node::*;
pub use thickness::*;
use crate::constructor::new_widget_constructor_container;
use crate::message::RoutingStrategy;
use crate::style::resource::{StyleResource, StyleResourceExt};
use crate::style::{Style, DEFAULT_STYLE};
pub use fyrox_animation as generic_animation;
use fyrox_core::pool::ErasedHandle;
use fyrox_resource::untyped::ResourceKind;
pub use fyrox_texture as texture;
#[derive(Default, Reflect, Debug)]
pub(crate) struct RcUiNodeHandleInner {
handle: Handle<UiNode>,
#[reflect(hidden)]
sender: Option<Sender<UiMessage>>,
}
impl Visit for RcUiNodeHandleInner {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
self.handle.visit(name, visitor)?;
if visitor.is_reading() {
self.sender = Some(
visitor
.blackboard
.get::<Sender<UiMessage>>()
.expect("Ui message sender must be provided for correct deserialization!")
.clone(),
);
}
Ok(())
}
}
impl Drop for RcUiNodeHandleInner {
fn drop(&mut self) {
if let Some(sender) = self.sender.as_ref() {
let _ = sender.send(WidgetMessage::remove(
self.handle,
MessageDirection::ToWidget,
));
} else {
Log::warn(format!(
"There's no message sender for shared handle {}. The object \
won't be destroyed.",
self.handle
))
}
}
}
#[derive(Clone, Default, Visit, Reflect, TypeUuidProvider)]
#[type_uuid(id = "9111a53b-05dc-4c75-aab1-71d5b1c93311")]
pub struct RcUiNodeHandle(Arc<Mutex<RcUiNodeHandleInner>>);
impl Debug for RcUiNodeHandle {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let handle = self.0.lock().handle;
writeln!(
f,
"RcUiNodeHandle - {}:{} with {} uses",
handle.index(),
handle.generation(),
Arc::strong_count(&self.0)
)
}
}
impl PartialEq for RcUiNodeHandle {
fn eq(&self, other: &Self) -> bool {
let a = self.0.lock().handle;
let b = other.0.lock().handle;
a == b
}
}
impl RcUiNodeHandle {
#[inline]
pub fn new(handle: Handle<UiNode>, sender: Sender<UiMessage>) -> Self {
Self(Arc::new(Mutex::new(RcUiNodeHandleInner {
handle,
sender: Some(sender),
})))
}
#[inline]
pub fn handle(&self) -> Handle<UiNode> {
self.0.lock().handle
}
}
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Eq,
Visit,
Reflect,
Default,
Serialize,
Deserialize,
AsRefStr,
EnumString,
VariantNames,
)]
pub enum Orientation {
#[default]
Vertical,
Horizontal,
}
uuid_provider!(Orientation = "1c6ad1b0-3f4c-48be-87dd-6929cb3577bf");
#[derive(Default, Clone)]
pub struct NodeStatistics(pub FxHashMap<&'static str, isize>);
impl NodeStatistics {
pub fn new(ui: &UserInterface) -> NodeStatistics {
let mut statistics = Self::default();
for node in ui.nodes.iter() {
statistics
.0
.entry(BaseControl::type_name(&*node.0))
.and_modify(|counter| *counter += 1)
.or_insert(1);
}
statistics
}
fn unite_type_names(&self, prev_stats: &NodeStatistics) -> BTreeSet<&'static str> {
let mut union = BTreeSet::default();
for stats in [self, prev_stats] {
for &type_name in stats.0.keys() {
union.insert(type_name);
}
}
union
}
fn count_of(&self, type_name: &str) -> isize {
self.0.get(type_name).cloned().unwrap_or_default()
}
pub fn print_diff(&self, prev_stats: &NodeStatistics, show_unchanged: bool) {
println!("**** Diff UI Node Statistics ****");
for type_name in self.unite_type_names(prev_stats) {
let count = self.count_of(type_name);
let prev_count = prev_stats.count_of(type_name);
let delta = count - prev_count;
if delta != 0 || show_unchanged {
println!("{type_name}: \x1b[93m{delta}\x1b[0m");
}
}
}
pub fn print_changed(&self, prev_stats: &NodeStatistics) {
println!("**** Changed UI Node Statistics ****");
for type_name in self.unite_type_names(prev_stats) {
let count = self.count_of(type_name);
let prev_count = prev_stats.count_of(type_name);
if count - prev_count != 0 {
println!("{type_name}: \x1b[93m{count}\x1b[0m");
}
}
}
}
#[derive(Visit, Reflect, Debug, Clone)]
pub struct DragContext {
pub is_dragging: bool,
pub drag_node: Handle<UiNode>,
pub click_pos: Vector2<f32>,
pub drag_preview: Handle<UiNode>,
}
impl Default for DragContext {
fn default() -> Self {
Self {
is_dragging: false,
drag_node: Default::default(),
click_pos: Vector2::new(0.0, 0.0),
drag_preview: Default::default(),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Visit, Reflect)]
pub struct MouseState {
pub left: ButtonState,
pub right: ButtonState,
pub middle: ButtonState,
}
impl Default for MouseState {
fn default() -> Self {
Self {
left: ButtonState::Released,
right: ButtonState::Released,
middle: ButtonState::Released,
}
}
}
#[derive(Copy, Clone, Visit, Reflect, Debug, Default)]
pub struct RestrictionEntry {
pub handle: Handle<UiNode>,
pub stop: bool,
}
#[derive(Clone, Debug)]
pub struct TooltipEntry {
pub tooltip: RcUiNodeHandle,
pub appear_timer: f32,
pub shown: bool,
pub disappear_timer: f32,
pub max_time: f32,
}
impl TooltipEntry {
fn new(tooltip: RcUiNodeHandle, appear_timeout: f32, disappear_timeout: f32) -> TooltipEntry {
Self {
tooltip,
appear_timer: appear_timeout,
shown: false,
disappear_timer: disappear_timeout,
max_time: disappear_timeout,
}
}
}
#[derive(Debug)]
pub enum LayoutEvent {
MeasurementInvalidated(Handle<UiNode>),
ArrangementInvalidated(Handle<UiNode>),
VisibilityChanged(Handle<UiNode>),
ZIndexChanged(Handle<UiNode>),
}
#[derive(Clone, Debug, Visit, Reflect, Default)]
struct DoubleClickEntry {
timer: f32,
click_count: u32,
}
struct Clipboard(Option<RefCell<ClipboardContext>>);
impl Debug for Clipboard {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Clipboard")
}
}
#[derive(Default, Debug, Clone)]
struct WidgetMethodsRegistry {
preview_message: FxHashSet<Handle<UiNode>>,
on_update: FxHashSet<Handle<UiNode>>,
handle_os_event: FxHashSet<Handle<UiNode>>,
}
impl WidgetMethodsRegistry {
fn register<T: Control + ?Sized>(&mut self, node: &T) {
let node_handle = node.handle();
if node.preview_messages && !self.preview_message.insert(node_handle) {
Log::warn(format!(
"Widget {node_handle} `preview_message` method is already registered!"
));
}
if node.handle_os_events && !self.handle_os_event.insert(node_handle) {
Log::warn(format!(
"Widget {node_handle} `handle_os_event` method is already registered!"
));
}
if node.need_update && !self.on_update.insert(node_handle) {
Log::warn(format!(
"Widget {node_handle} `on_update` method is already registered!"
));
}
}
fn unregister<T: Control + ?Sized>(&mut self, node: &T) {
let node_handle = node.handle();
self.preview_message.remove(&node_handle);
self.on_update.remove(&node_handle);
self.handle_os_event.remove(&node_handle);
}
}
#[derive(Clone, PartialEq, Eq, Default)]
pub struct UiUpdateSwitches {
pub node_overrides: Option<FxHashSet<Handle<UiNode>>>,
}
#[derive(Reflect, Debug)]
pub struct UserInterface {
screen_size: Vector2<f32>,
nodes: Pool<UiNode, WidgetContainer>,
#[reflect(hidden)]
drawing_context: DrawingContext,
visual_debug: bool,
root_canvas: Handle<UiNode>,
picked_node: Handle<UiNode>,
prev_picked_node: Handle<UiNode>,
captured_node: Handle<UiNode>,
keyboard_focus_node: Handle<UiNode>,
cursor_position: Vector2<f32>,
pub style: StyleResource,
#[reflect(hidden)]
receiver: Receiver<UiMessage>,
#[reflect(hidden)]
sender: Sender<UiMessage>,
stack: Vec<Handle<UiNode>>,
picking_stack: Vec<RestrictionEntry>,
#[reflect(hidden)]
bubble_queue: VecDeque<Handle<UiNode>>,
drag_context: DragContext,
mouse_state: MouseState,
keyboard_modifiers: KeyboardModifiers,
cursor_icon: CursorIcon,
#[reflect(hidden)]
active_tooltip: Option<TooltipEntry>,
#[reflect(hidden)]
methods_registry: WidgetMethodsRegistry,
#[reflect(hidden)]
clipboard: Clipboard,
#[reflect(hidden)]
layout_events_receiver: Receiver<LayoutEvent>,
#[reflect(hidden)]
layout_events_sender: Sender<LayoutEvent>,
need_update_global_transform: bool,
#[reflect(hidden)]
z_index_update_set: FxHashSet<Handle<UiNode>>,
#[reflect(hidden)]
pub default_font: FontResource,
#[reflect(hidden)]
double_click_entries: FxHashMap<MouseButton, DoubleClickEntry>,
pub double_click_time_slice: f32,
pub tooltip_appear_delay: f32,
}
impl Visit for UserInterface {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
let mut region = visitor.enter_region(name)?;
if region.is_reading() {
self.nodes.clear();
self.root_canvas = Handle::NONE;
self.methods_registry = Default::default();
}
self.screen_size.visit("ScreenSize", &mut region)?;
self.nodes.visit("Nodes", &mut region)?;
self.visual_debug.visit("VisualDebug", &mut region)?;
self.root_canvas.visit("RootCanvas", &mut region)?;
self.picked_node.visit("PickedNode", &mut region)?;
self.prev_picked_node.visit("PrevPickedNode", &mut region)?;
self.captured_node.visit("CapturedNode", &mut region)?;
self.keyboard_focus_node
.visit("KeyboardFocusNode", &mut region)?;
self.cursor_position.visit("CursorPosition", &mut region)?;
self.picking_stack.visit("PickingStack", &mut region)?;
self.drag_context.visit("DragContext", &mut region)?;
self.mouse_state.visit("MouseState", &mut region)?;
self.keyboard_modifiers
.visit("KeyboardModifiers", &mut region)?;
self.cursor_icon.visit("CursorIcon", &mut region)?;
self.double_click_time_slice
.visit("DoubleClickTimeSlice", &mut region)?;
let _ = self
.tooltip_appear_delay
.visit("TooltipAppearDelay", &mut region);
if region.is_reading() {
for node in self.nodes.iter() {
self.methods_registry.register(node.deref());
}
}
Ok(())
}
}
impl Clone for UserInterface {
fn clone(&self) -> Self {
let (sender, receiver) = mpsc::channel();
let (layout_events_sender, layout_events_receiver) = mpsc::channel();
let mut nodes = Pool::new();
for (handle, node) in self.nodes.pair_iter() {
let mut clone = node.clone_boxed();
clone.layout_events_sender = Some(layout_events_sender.clone());
nodes.spawn_at_handle(handle, UiNode(clone)).unwrap();
}
Self {
screen_size: self.screen_size,
nodes,
drawing_context: self.drawing_context.clone(),
visual_debug: self.visual_debug,
root_canvas: self.root_canvas,
picked_node: self.picked_node,
prev_picked_node: self.prev_picked_node,
captured_node: self.captured_node,
keyboard_focus_node: self.keyboard_focus_node,
cursor_position: self.cursor_position,
style: StyleResource::new_ok(ResourceKind::Embedded, Style::dark_style()),
receiver,
sender,
stack: self.stack.clone(),
picking_stack: self.picking_stack.clone(),
bubble_queue: self.bubble_queue.clone(),
drag_context: self.drag_context.clone(),
mouse_state: self.mouse_state,
keyboard_modifiers: self.keyboard_modifiers,
cursor_icon: self.cursor_icon,
active_tooltip: self.active_tooltip.clone(),
methods_registry: self.methods_registry.clone(),
clipboard: Clipboard(ClipboardContext::new().ok().map(RefCell::new)),
layout_events_receiver,
layout_events_sender,
need_update_global_transform: self.need_update_global_transform,
z_index_update_set: self.z_index_update_set.clone(),
default_font: self.default_font.clone(),
double_click_entries: self.double_click_entries.clone(),
double_click_time_slice: self.double_click_time_slice,
tooltip_appear_delay: self.tooltip_appear_delay,
}
}
}
impl Default for UserInterface {
fn default() -> Self {
Self::new(Vector2::new(100.0, 100.0))
}
}
#[derive(Default)]
pub struct UiContainer {
pool: Pool<UserInterface>,
}
impl UiContainer {
pub fn new() -> Self {
Self::default()
}
pub fn new_with_ui(ui: UserInterface) -> Self {
let mut pool = Pool::new();
let _ = pool.spawn(ui);
Self { pool }
}
pub fn first(&self) -> &UserInterface {
self.pool
.first_ref()
.expect("The container must have at least one user interface.")
}
pub fn first_mut(&mut self) -> &mut UserInterface {
self.pool
.first_mut()
.expect("The container must have at least one user interface.")
}
pub fn is_valid_handle(&self, handle: Handle<UserInterface>) -> bool {
self.pool.is_valid_handle(handle)
}
pub fn pair_iter(&self) -> impl Iterator<Item = (Handle<UserInterface>, &UserInterface)> {
self.pool.pair_iter()
}
pub fn pair_iter_mut(
&mut self,
) -> impl Iterator<Item = (Handle<UserInterface>, &mut UserInterface)> {
self.pool.pair_iter_mut()
}
pub fn try_get(&self, handle: Handle<UserInterface>) -> Option<&UserInterface> {
self.pool.try_borrow(handle)
}
pub fn try_get_mut(&mut self, handle: Handle<UserInterface>) -> Option<&mut UserInterface> {
self.pool.try_borrow_mut(handle)
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &UserInterface> {
self.pool.iter()
}
#[inline]
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut UserInterface> {
self.pool.iter_mut()
}
#[inline]
pub fn add(&mut self, scene: UserInterface) -> Handle<UserInterface> {
self.pool.spawn(scene)
}
#[inline]
pub fn clear(&mut self) {
self.pool.clear()
}
#[inline]
pub fn remove(&mut self, handle: Handle<UserInterface>) {
self.pool.free(handle);
}
pub fn take_reserve(
&mut self,
handle: Handle<UserInterface>,
) -> (Ticket<UserInterface>, UserInterface) {
self.pool.take_reserve(handle)
}
pub fn put_back(
&mut self,
ticket: Ticket<UserInterface>,
scene: UserInterface,
) -> Handle<UserInterface> {
self.pool.put_back(ticket, scene)
}
pub fn forget_ticket(&mut self, ticket: Ticket<UserInterface>) {
self.pool.forget_ticket(ticket)
}
}
impl Index<Handle<UserInterface>> for UiContainer {
type Output = UserInterface;
#[inline]
fn index(&self, index: Handle<UserInterface>) -> &Self::Output {
&self.pool[index]
}
}
impl IndexMut<Handle<UserInterface>> for UiContainer {
#[inline]
fn index_mut(&mut self, index: Handle<UserInterface>) -> &mut Self::Output {
&mut self.pool[index]
}
}
fn is_on_screen(node: &UiNode, nodes: &Pool<UiNode, WidgetContainer>) -> bool {
let bounds = node.clip_bounds();
let mut parent = node.parent();
while parent.is_some() {
let parent_node = nodes.borrow(parent);
if !parent_node.clip_bounds().intersects(bounds) {
return false;
}
parent = parent_node.parent();
}
true
}
fn draw_node(
nodes: &Pool<UiNode, WidgetContainer>,
node_handle: Handle<UiNode>,
drawing_context: &mut DrawingContext,
) {
let node = &nodes[node_handle];
if !node.is_globally_visible() {
return;
}
if !is_on_screen(node, nodes) {
return;
}
let pushed = if !is_node_enabled(nodes, node_handle) {
drawing_context.push_opacity(0.4);
true
} else if let Some(opacity) = node.opacity() {
drawing_context.push_opacity(opacity);
true
} else {
false
};
drawing_context.transform_stack.push(node.visual_transform);
{
let start_index = drawing_context.get_commands().len();
node.draw(drawing_context);
let end_index = drawing_context.get_commands().len();
node.command_indices
.borrow_mut()
.extend(start_index..end_index);
}
for &child_node in node.children().iter() {
if !nodes[child_node].is_draw_on_top() {
draw_node(nodes, child_node, drawing_context);
}
}
{
let start_index = drawing_context.get_commands().len();
node.post_draw(drawing_context);
let end_index = drawing_context.get_commands().len();
node.command_indices
.borrow_mut()
.extend(start_index..end_index);
}
drawing_context.transform_stack.pop();
if pushed {
drawing_context.pop_opacity();
}
}
fn is_node_enabled(nodes: &Pool<UiNode, WidgetContainer>, handle: Handle<UiNode>) -> bool {
let root_node = &nodes[handle];
let mut enabled = root_node.enabled();
let mut parent = root_node.parent();
while parent.is_some() {
let node = &nodes[parent];
if !node.enabled() {
enabled = false;
break;
}
parent = node.parent();
}
enabled
}
#[derive(Debug)]
pub struct SubGraph {
pub root: (Ticket<UiNode>, UiNode),
pub descendants: Vec<(Ticket<UiNode>, UiNode)>,
pub parent: Handle<UiNode>,
}
fn remap_handles(old_new_mapping: &NodeHandleMap<UiNode>, ui: &mut UserInterface) {
for (_, &new_node_handle) in old_new_mapping.inner().iter() {
old_new_mapping.remap_handles(
&mut ui.nodes[new_node_handle],
&[TypeId::of::<UntypedResource>()],
);
}
}
impl UserInterface {
pub fn new(screen_size: Vector2<f32>) -> UserInterface {
let (sender, receiver) = mpsc::channel();
Self::new_with_channel(sender, receiver, screen_size)
}
pub fn new_with_channel(
sender: Sender<UiMessage>,
receiver: Receiver<UiMessage>,
screen_size: Vector2<f32>,
) -> UserInterface {
let (layout_events_sender, layout_events_receiver) = mpsc::channel();
let style = StyleResource::new_ok(ResourceKind::Embedded, Style::dark_style());
let mut ui = UserInterface {
screen_size,
sender,
receiver,
visual_debug: false,
captured_node: Handle::NONE,
root_canvas: Handle::NONE,
nodes: Pool::new(),
cursor_position: Vector2::new(0.0, 0.0),
drawing_context: DrawingContext::new(style.clone()),
picked_node: Handle::NONE,
prev_picked_node: Handle::NONE,
style,
keyboard_focus_node: Handle::NONE,
stack: Default::default(),
picking_stack: Default::default(),
bubble_queue: Default::default(),
drag_context: Default::default(),
mouse_state: Default::default(),
keyboard_modifiers: Default::default(),
cursor_icon: Default::default(),
active_tooltip: Default::default(),
methods_registry: Default::default(),
clipboard: Clipboard(ClipboardContext::new().ok().map(RefCell::new)),
layout_events_receiver,
layout_events_sender,
need_update_global_transform: Default::default(),
z_index_update_set: Default::default(),
default_font: BUILT_IN_FONT.resource(),
double_click_entries: Default::default(),
double_click_time_slice: 0.5, tooltip_appear_delay: 0.55,
};
let root_node = UiNode::new(Canvas {
widget: WidgetBuilder::new().build(&ui.build_ctx()),
});
ui.root_canvas = ui.add_node(root_node);
ui.keyboard_focus_node = ui.root_canvas;
ui
}
pub fn set_tooltip_appear_delay(&mut self, appear_delay: f32) {
self.tooltip_appear_delay = appear_delay;
}
pub fn tooltip_appear_delay(&self) -> f32 {
self.tooltip_appear_delay
}
pub fn active_tooltip(&self) -> Option<&TooltipEntry> {
self.active_tooltip.as_ref()
}
pub fn keyboard_modifiers(&self) -> KeyboardModifiers {
self.keyboard_modifiers
}
pub fn build_ctx(&mut self) -> BuildContext<'_> {
self.into()
}
#[inline]
pub fn capture_mouse(&mut self, node: Handle<UiNode>) -> bool {
if self.captured_node.is_none() {
self.captured_node = node;
true
} else {
false
}
}
#[inline]
pub fn release_mouse_capture(&mut self) {
self.captured_node = Handle::NONE;
}
#[inline]
pub fn get_drawing_context(&self) -> &DrawingContext {
&self.drawing_context
}
#[inline]
pub fn get_drawing_context_mut(&mut self) -> &mut DrawingContext {
&mut self.drawing_context
}
pub fn is_node_enabled(&self, handle: Handle<UiNode>) -> bool {
is_node_enabled(&self.nodes, handle)
}
fn update_global_visibility(&mut self, from: Handle<UiNode>) {
self.stack.clear();
self.stack.push(from);
while let Some(node_handle) = self.stack.pop() {
let (widget, parent) = self
.nodes
.try_borrow_dependant_mut(node_handle, |n| n.parent());
if let Some(widget) = widget {
self.stack.extend_from_slice(widget.children());
let visibility = if let Some(parent) = parent {
widget.visibility() && parent.is_globally_visible()
} else {
widget.visibility()
};
if widget.prev_global_visibility != visibility {
let _ = self
.layout_events_sender
.send(LayoutEvent::MeasurementInvalidated(node_handle));
let _ = self
.layout_events_sender
.send(LayoutEvent::ArrangementInvalidated(node_handle));
}
widget.set_global_visibility(visibility);
}
}
}
fn update_visual_transform(&mut self, from: Handle<UiNode>) {
self.stack.clear();
self.stack.push(from);
while let Some(node_handle) = self.stack.pop() {
let (widget, parent) = self
.nodes
.try_borrow_dependant_mut(node_handle, |n| n.parent());
let widget = widget.unwrap();
if widget.is_globally_visible() {
self.stack.extend_from_slice(widget.children());
let mut layout_transform = widget.layout_transform;
layout_transform[6] = widget.actual_local_position().x;
layout_transform[7] = widget.actual_local_position().y;
let visual_transform = if let Some(parent) = parent {
parent.visual_transform * layout_transform * widget.render_transform
} else {
layout_transform * widget.render_transform
};
widget.visual_transform = visual_transform;
widget.on_visual_transform_changed();
}
}
}
pub fn screen_size(&self) -> Vector2<f32> {
self.screen_size
}
pub fn set_screen_size(&mut self, screen_size: Vector2<f32>) {
self.screen_size = screen_size;
}
fn handle_layout_events(&mut self) {
fn invalidate_recursive_up(
nodes: &Pool<UiNode, WidgetContainer>,
node: Handle<UiNode>,
callback: fn(&UiNode),
) {
if let Some(node_ref) = nodes.try_borrow(node) {
(callback)(node_ref);
if node_ref.parent().is_some() {
invalidate_recursive_up(nodes, node_ref.parent(), callback);
}
}
}
while let Ok(layout_event) = self.layout_events_receiver.try_recv() {
match layout_event {
LayoutEvent::MeasurementInvalidated(node) => {
invalidate_recursive_up(&self.nodes, node, |node_ref| {
node_ref.measure_valid.set(false)
});
}
LayoutEvent::ArrangementInvalidated(node) => {
invalidate_recursive_up(&self.nodes, node, |node_ref| {
node_ref.arrange_valid.set(false)
});
self.need_update_global_transform = true;
}
LayoutEvent::VisibilityChanged(node) => {
self.update_global_visibility(node);
}
LayoutEvent::ZIndexChanged(node) => {
if let Some(node_ref) = self.nodes.try_borrow(node) {
self.z_index_update_set.insert(node_ref.parent);
}
}
}
}
for node_handle in self.z_index_update_set.drain() {
let mbc = self.nodes.begin_multi_borrow();
if let Ok(mut node) = mbc.try_get_mut(node_handle) {
node.children.sort_by_key(|handle| {
mbc.try_get(*handle).map(|c| *c.z_index).unwrap_or_default()
});
};
}
}
pub fn invalidate_layout(&mut self) {
for node in self.nodes.iter_mut() {
node.invalidate_layout();
}
}
pub fn update_layout(&mut self, screen_size: Vector2<f32>) {
self.screen_size = screen_size;
self.handle_layout_events();
self.measure_node(self.root_canvas, screen_size);
let arrangement_changed = self.arrange_node(
self.root_canvas,
&Rect::new(0.0, 0.0, screen_size.x, screen_size.y),
);
if self.need_update_global_transform {
self.update_visual_transform(self.root_canvas);
self.need_update_global_transform = false;
}
if arrangement_changed {
self.calculate_clip_bounds(
self.root_canvas,
Rect::new(0.0, 0.0, self.screen_size.x, self.screen_size.y),
);
}
}
pub fn update(&mut self, screen_size: Vector2<f32>, dt: f32, switches: &UiUpdateSwitches) {
for entry in self.double_click_entries.values_mut() {
entry.timer -= dt;
}
self.update_layout(screen_size);
if let Some(node_overrides) = switches.node_overrides.as_ref() {
for &handle in node_overrides.iter() {
let (ticket, mut node) = self.nodes.take_reserve(handle);
node.update(dt, self);
self.nodes.put_back(ticket, node);
}
} else {
let update_subs = std::mem::take(&mut self.methods_registry.on_update);
for &handle in update_subs.iter() {
let (ticket, mut node) = self.nodes.take_reserve(handle);
node.update(dt, self);
self.nodes.put_back(ticket, node);
}
self.methods_registry.on_update = update_subs;
}
self.update_tooltips(dt);
if !self.drag_context.is_dragging {
self.cursor_icon = CursorIcon::default();
let mut handle = self.picked_node;
while handle.is_some() {
let node = &self.nodes[handle];
if let Some(cursor) = node.cursor() {
self.cursor_icon = cursor;
break;
}
handle = node.parent();
}
}
}
pub fn style(&self) -> &StyleResource {
&self.style
}
pub fn set_style(&mut self, style: StyleResource) {
self.style = style;
fn notify_depth_first(node: Handle<UiNode>, ui: &UserInterface) {
if let Some(node_ref) = ui.try_get(node) {
for child in node_ref.children.iter() {
notify_depth_first(*child, ui);
}
ui.send_message(WidgetMessage::style(
node,
MessageDirection::ToWidget,
ui.style.clone(),
));
}
}
notify_depth_first(self.root_canvas, self);
}
pub fn cursor(&self) -> CursorIcon {
self.cursor_icon
}
pub fn set_time(&mut self, elapsed_time: f32) {
self.drawing_context.elapsed_time = elapsed_time;
}
pub fn draw(&mut self) -> &DrawingContext {
self.drawing_context.clear();
for node in self.nodes.iter_mut() {
node.command_indices.get_mut().clear();
}
draw_node(&self.nodes, self.root_canvas, &mut self.drawing_context);
self.stack.clear();
self.stack.push(self.root());
while let Some(node_handle) = self.stack.pop() {
let node = &self.nodes[node_handle];
if !is_on_screen(node, &self.nodes) {
continue;
}
if node.is_draw_on_top() {
draw_node(&self.nodes, node_handle, &mut self.drawing_context);
}
for &child in node.children() {
self.stack.push(child);
}
}
if self.visual_debug {
if self.picked_node.is_some() {
let bounds = self.nodes.borrow(self.picked_node).screen_bounds();
self.drawing_context.push_rect(&bounds, 1.0);
self.drawing_context.commit(
bounds,
Brush::Solid(Color::WHITE),
CommandTexture::None,
None,
);
}
if self.keyboard_focus_node.is_some() {
let bounds = self.nodes.borrow(self.keyboard_focus_node).screen_bounds();
self.drawing_context.push_rect(&bounds, 1.0);
self.drawing_context.commit(
bounds,
Brush::Solid(Color::GREEN),
CommandTexture::None,
None,
);
}
}
if let Some(keyboard_focus_node) = self.nodes.try_borrow(self.keyboard_focus_node) {
if keyboard_focus_node.global_visibility && keyboard_focus_node.accepts_input {
let bounds = keyboard_focus_node.screen_bounds().inflate(1.0, 1.0);
self.drawing_context.push_rounded_rect(&bounds, 1.0, 2.0, 6);
self.drawing_context.commit(
bounds,
DEFAULT_STYLE
.resource
.get_or_default(Style::BRUSH_BRIGHT_BLUE),
CommandTexture::None,
None,
);
}
}
&self.drawing_context
}
pub fn clipboard(&self) -> Option<Ref<ClipboardContext>> {
self.clipboard.0.as_ref().map(|v| v.borrow())
}
pub fn clipboard_mut(&self) -> Option<RefMut<ClipboardContext>> {
self.clipboard.0.as_ref().map(|v| v.borrow_mut())
}
pub fn arrange_node(&self, handle: Handle<UiNode>, final_rect: &Rect<f32>) -> bool {
let node = self.node(handle);
if node.is_arrange_valid() && node.prev_arrange.get() == *final_rect {
return false;
}
if node.visibility() {
node.prev_arrange.set(*final_rect);
let margin = node.margin().axes_margin();
let mut size = Vector2::new(
(final_rect.w() - margin.x).max(0.0),
(final_rect.h() - margin.y).max(0.0),
);
let available_size = size;
if node.horizontal_alignment() != HorizontalAlignment::Stretch {
size.x = size.x.min(node.desired_size().x - margin.x);
}
if node.vertical_alignment() != VerticalAlignment::Stretch {
size.y = size.y.min(node.desired_size().y - margin.y);
}
if node.width() > 0.0 {
size.x = node.width();
}
if node.height() > 0.0 {
size.y = node.height();
}
size = transform_size(size, &node.layout_transform);
if !node.ignore_layout_rounding {
size.x = size.x.ceil();
size.y = size.y.ceil();
}
size = node.arrange_override(self, size);
size.x = size.x.min(final_rect.w());
size.y = size.y.min(final_rect.h());
let transformed_rect =
Rect::new(0.0, 0.0, size.x, size.y).transform(&node.layout_transform);
size = transformed_rect.size;
let mut origin =
final_rect.position - transformed_rect.position + node.margin().offset();
match node.horizontal_alignment() {
HorizontalAlignment::Center | HorizontalAlignment::Stretch => {
origin.x += (available_size.x - size.x) * 0.5;
}
HorizontalAlignment::Right => origin.x += available_size.x - size.x,
_ => (),
}
match node.vertical_alignment() {
VerticalAlignment::Center | VerticalAlignment::Stretch => {
origin.y += (available_size.y - size.y) * 0.5;
}
VerticalAlignment::Bottom => origin.y += available_size.y - size.y,
_ => (),
}
if !node.ignore_layout_rounding {
origin.x = origin.x.floor();
origin.y = origin.y.floor();
}
node.commit_arrange(origin, size);
}
true
}
pub fn measure_node(&self, handle: Handle<UiNode>, available_size: Vector2<f32>) -> bool {
let node = self.node(handle);
if node.is_measure_valid() && node.prev_measure.get() == available_size {
return false;
}
if node.visibility() {
node.prev_measure.set(available_size);
let axes_margin = node.margin().axes_margin();
let mut inner_size = available_size - axes_margin;
inner_size.x = inner_size.x.max(0.0);
inner_size.y = inner_size.y.max(0.0);
let mut size = Vector2::new(
if node.width() > 0.0 {
node.width()
} else {
inner_size.x
},
if node.height() > 0.0 {
node.height()
} else {
inner_size.y
},
);
size = transform_size(size, &node.layout_transform);
size.x = size.x.clamp(node.min_size().x, node.max_size().x);
size.y = size.y.clamp(node.min_size().y, node.max_size().y);
let mut desired_size = node.measure_override(self, size);
desired_size = Rect::new(0.0, 0.0, desired_size.x, desired_size.y)
.transform(&node.layout_transform)
.size;
if !node.width().is_nan() {
desired_size.x = node.width();
}
if !node.height().is_nan() {
desired_size.y = node.height();
}
desired_size.x = desired_size.x.clamp(node.min_size().x, node.max_size().x);
desired_size.y = desired_size.y.clamp(node.min_size().y, node.max_size().y);
desired_size += axes_margin;
if node.ignore_layout_rounding {
desired_size.x = desired_size.x.min(available_size.x);
desired_size.y = desired_size.y.min(available_size.y);
} else {
desired_size.x = desired_size.x.min(available_size.x).ceil();
desired_size.y = desired_size.y.min(available_size.y).ceil();
}
node.commit_measure(desired_size);
} else {
node.commit_measure(Vector2::new(0.0, 0.0));
}
true
}
fn is_node_clipped(&self, node_handle: Handle<UiNode>, pt: Vector2<f32>) -> bool {
let mut clipped = true;
let widget = self.nodes.borrow(node_handle);
if widget.is_globally_visible() {
clipped = !widget.clip_bounds().contains(pt);
if !clipped {
for command_index in widget.command_indices.borrow().iter() {
if let Some(command) = self.drawing_context.get_commands().get(*command_index) {
if let Some(geometry) = command.clipping_geometry.as_ref() {
if geometry.is_contains_point(pt) {
clipped = false;
break;
}
}
}
}
}
if !widget.parent().is_none() && !clipped {
clipped |= self.is_node_clipped(widget.parent(), pt);
}
}
clipped
}
fn is_node_contains_point(&self, node_handle: Handle<UiNode>, pt: Vector2<f32>) -> bool {
let widget = self.nodes.borrow(node_handle);
if !widget.is_globally_visible() {
return false;
}
if !self.is_node_clipped(node_handle, pt) {
for command_index in widget.command_indices.borrow().iter() {
if let Some(command) = self.drawing_context.get_commands().get(*command_index) {
if self.drawing_context.is_command_contains_point(command, pt) {
return true;
}
}
}
}
false
}
fn pick_node(
&self,
node_handle: Handle<UiNode>,
pt: Vector2<f32>,
level: &mut i32,
) -> Handle<UiNode> {
let widget = self.nodes.borrow(node_handle);
if !widget.is_hit_test_visible()
|| !widget.enabled()
|| !widget.clip_bounds().intersects(Rect {
position: Default::default(),
size: self.screen_size,
})
{
return Handle::NONE;
}
let (mut picked, mut topmost_picked_level) = if self.is_node_contains_point(node_handle, pt)
{
(node_handle, *level)
} else {
(Handle::NONE, 0)
};
for child_handle in widget.children() {
*level += 1;
let picked_child = self.pick_node(*child_handle, pt, level);
if picked_child.is_some() && *level > topmost_picked_level {
topmost_picked_level = *level;
picked = picked_child;
}
}
picked
}
pub fn cursor_position(&self) -> Vector2<f32> {
self.cursor_position
}
pub fn hit_test_unrestricted(&self, pt: Vector2<f32>) -> Handle<UiNode> {
let mut level = 0;
self.pick_node(self.root_canvas, pt, &mut level)
}
pub fn hit_test(&self, pt: Vector2<f32>) -> Handle<UiNode> {
if self.nodes.is_valid_handle(self.captured_node) {
self.captured_node
} else if self.picking_stack.is_empty() {
self.hit_test_unrestricted(pt)
} else {
for root in self.picking_stack.iter().rev() {
if self.nodes.is_valid_handle(root.handle) {
let mut level = 0;
let picked = self.pick_node(root.handle, pt, &mut level);
if picked.is_some() {
return picked;
}
}
if root.stop {
break;
}
}
Handle::NONE
}
}
pub fn is_node_child_of(
&self,
node_handle: Handle<UiNode>,
root_handle: Handle<UiNode>,
) -> bool {
self.nodes
.borrow(root_handle)
.has_descendant(node_handle, self)
}
fn calculate_clip_bounds(&self, node: Handle<UiNode>, parent_bounds: Rect<f32>) {
let node = &self.nodes[node];
let screen_bounds = if *node.clip_to_bounds {
node.screen_bounds()
} else {
Rect::new(0.0, 0.0, self.screen_size.x, self.screen_size.y)
};
node.clip_bounds.set(
screen_bounds
.clip_by(parent_bounds)
.unwrap_or(screen_bounds),
);
for &child in node.children() {
self.calculate_clip_bounds(child, node.clip_bounds.get());
}
}
pub fn sender(&self) -> Sender<UiMessage> {
self.sender.clone()
}
pub fn send_message(&self, message: UiMessage) {
self.sender.send(message).unwrap()
}
fn make_topmost(&mut self, node: Handle<UiNode>) {
let parent = self.node(node).parent();
if parent.is_some() {
let parent = &mut self.nodes[parent];
parent.remove_child(node);
parent.add_child(node, false);
}
}
fn make_lowermost(&mut self, node: Handle<UiNode>) {
let parent = self.node(node).parent();
if parent.is_some() {
let parent = &mut self.nodes[parent];
parent.remove_child(node);
parent.add_child(node, true);
}
}
fn bubble_message(&mut self, message: &mut UiMessage) {
self.bubble_queue.clear();
self.bubble_queue.push_back(message.destination());
let mut parent = self.nodes[message.destination()].parent();
while parent.is_some() && self.nodes.is_valid_handle(parent) {
self.bubble_queue.push_back(parent);
parent = self.nodes[parent].parent();
}
while let Some(handle) = self.bubble_queue.pop_front() {
let (ticket, mut node) = self.nodes.take_reserve(handle);
node.handle_routed_message(self, message);
self.nodes.put_back(ticket, node);
}
}
pub fn poll_message(&mut self) -> Option<UiMessage> {
match self.receiver.try_recv() {
Ok(mut message) => {
if !self.nodes.is_valid_handle(message.destination()) {
return Some(message);
}
if message.need_perform_layout() {
self.update_layout(self.screen_size);
}
for &handle in self.methods_registry.preview_message.iter() {
if let Some(node_ref) = self.nodes.try_borrow(handle) {
node_ref.preview_message(self, &mut message);
}
}
match message.routing_strategy {
RoutingStrategy::BubbleUp => self.bubble_message(&mut message),
RoutingStrategy::Direct => {
let (ticket, mut node) = self.nodes.take_reserve(message.destination());
node.handle_routed_message(self, &mut message);
self.nodes.put_back(ticket, node);
}
}
if let Some(msg) = message.data::<WidgetMessage>() {
match msg {
WidgetMessage::Focus => {
if self.nodes.is_valid_handle(message.destination())
&& message.direction() == MessageDirection::ToWidget
{
self.request_focus(message.destination());
}
}
WidgetMessage::Unfocus => {
if self.nodes.is_valid_handle(message.destination())
&& message.direction() == MessageDirection::ToWidget
{
self.request_focus(self.root_canvas);
}
}
WidgetMessage::Topmost => {
if self.nodes.is_valid_handle(message.destination()) {
self.make_topmost(message.destination());
}
}
WidgetMessage::Lowermost => {
if self.nodes.is_valid_handle(message.destination()) {
self.make_lowermost(message.destination());
}
}
WidgetMessage::Unlink => {
if self.nodes.is_valid_handle(message.destination()) {
self.unlink_node(message.destination());
let node = &self.nodes[message.destination()];
let new_position = node.screen_position();
self.send_message(WidgetMessage::desired_position(
message.destination(),
MessageDirection::ToWidget,
new_position,
));
}
}
&WidgetMessage::LinkWith(parent) => {
if self.nodes.is_valid_handle(message.destination())
&& self.nodes.is_valid_handle(parent)
{
self.link_nodes(message.destination(), parent, false);
}
}
&WidgetMessage::LinkWithReverse(parent) => {
if self.nodes.is_valid_handle(message.destination())
&& self.nodes.is_valid_handle(parent)
{
self.link_nodes(message.destination(), parent, true);
}
}
WidgetMessage::ReplaceChildren(children) => {
if self.nodes.is_valid_handle(message.destination()) {
let old_children =
self.node(message.destination()).children().to_vec();
for child in old_children.iter() {
if self.nodes.is_valid_handle(*child) {
if children.contains(child) {
self.unlink_node(*child);
} else {
self.remove_node(*child);
}
}
}
for &child in children.iter() {
if self.nodes.is_valid_handle(child) {
self.link_nodes(child, message.destination(), false);
}
}
}
}
WidgetMessage::Remove => {
if self.nodes.is_valid_handle(message.destination()) {
self.remove_node(message.destination());
}
}
WidgetMessage::ContextMenu(context_menu) => {
if self.nodes.is_valid_handle(message.destination()) {
let node = self.nodes.borrow_mut(message.destination());
node.set_context_menu(context_menu.clone());
}
}
WidgetMessage::Tooltip(tooltip) => {
if self.nodes.is_valid_handle(message.destination()) {
let node = self.nodes.borrow_mut(message.destination());
node.set_tooltip(tooltip.clone());
}
}
WidgetMessage::Center => {
if self.nodes.is_valid_handle(message.destination()) {
let node = self.node(message.destination());
let size = node.actual_initial_size();
let parent = node.parent();
let parent_size = if parent.is_some() {
self.node(parent).actual_initial_size()
} else {
self.screen_size
};
self.send_message(WidgetMessage::desired_position(
message.destination(),
MessageDirection::ToWidget,
(parent_size - size).scale(0.5),
));
}
}
WidgetMessage::RenderTransform(_) => {
if self.nodes.is_valid_handle(message.destination()) {
self.update_visual_transform(message.destination());
}
}
WidgetMessage::AdjustPositionToFit => {
if self.nodes.is_valid_handle(message.destination()) {
let node = self.node(message.destination());
let mut position = node.actual_local_position();
let size = node.actual_initial_size();
let parent = node.parent();
let parent_size = if parent.is_some() {
self.node(parent).actual_initial_size()
} else {
self.screen_size
};
if position.x < 0.0 {
position.x = 0.0;
}
if position.x + size.x > parent_size.x {
position.x -= (position.x + size.x) - parent_size.x;
}
if position.y < 0.0 {
position.y = 0.0;
}
if position.y + size.y > parent_size.y {
position.y -= (position.y + size.y) - parent_size.y;
}
self.send_message(WidgetMessage::desired_position(
message.destination(),
MessageDirection::ToWidget,
position,
));
}
}
WidgetMessage::Align {
relative_to,
horizontal_alignment,
vertical_alignment,
margin,
} => {
if let (Some(node), Some(relative_node)) = (
self.try_get(message.destination()),
self.try_get(*relative_to),
) {
let relative_node_screen_size = relative_node.screen_bounds().size;
let relative_node_screen_position = relative_node.screen_position();
let node_screen_size = node.screen_bounds().size;
let mut screen_anchor_point = Vector2::default();
match horizontal_alignment {
HorizontalAlignment::Stretch => {
}
HorizontalAlignment::Left => {
screen_anchor_point.x =
relative_node_screen_position.x + margin.left;
}
HorizontalAlignment::Center => {
screen_anchor_point.x = relative_node_screen_position.x
+ (relative_node_screen_size.x
+ node_screen_size.x
+ margin.left
+ margin.right)
* 0.5;
}
HorizontalAlignment::Right => {
screen_anchor_point.x = relative_node_screen_position.x
+ relative_node_screen_size.x
- node_screen_size.x
- margin.right;
}
}
match vertical_alignment {
VerticalAlignment::Stretch => {
}
VerticalAlignment::Top => {
screen_anchor_point.y =
relative_node_screen_position.y + margin.top;
}
VerticalAlignment::Center => {
screen_anchor_point.y = relative_node_screen_position.y
+ (relative_node_screen_size.y
+ node_screen_size.y
+ margin.top
+ margin.bottom)
* 0.5;
}
VerticalAlignment::Bottom => {
screen_anchor_point.y = relative_node_screen_position.y
+ (relative_node_screen_size.y
- node_screen_size.y
- margin.bottom);
}
}
if let Some(parent) = self.try_get(node.parent()) {
let local_anchor_point =
parent.screen_to_local(screen_anchor_point);
self.send_message(WidgetMessage::desired_position(
message.destination(),
MessageDirection::ToWidget,
local_anchor_point,
));
}
}
}
WidgetMessage::MouseDown { button, .. } => {
if *button == MouseButton::Right {
if let Some(picked) = self.nodes.try_borrow(self.picked_node) {
let (context_menu, target) = if picked.context_menu().is_some()
{
(picked.context_menu(), self.picked_node)
} else {
let parent_handle = picked.find_by_criteria_up(self, |n| {
n.context_menu().is_some()
});
if let Some(parent) = self.nodes.try_borrow(parent_handle) {
(parent.context_menu(), parent_handle)
} else {
(None, Handle::NONE)
}
};
if let Some(context_menu) = context_menu {
self.send_message(PopupMessage::placement(
context_menu.handle(),
MessageDirection::ToWidget,
Placement::Cursor(target),
));
self.send_message(PopupMessage::open(
context_menu.handle(),
MessageDirection::ToWidget,
));
self.send_message(PopupMessage::owner(
context_menu.handle(),
MessageDirection::ToWidget,
self.picked_node,
));
}
}
}
}
_ => {}
}
}
Some(message)
}
Err(e) => match e {
TryRecvError::Empty => None,
TryRecvError::Disconnected => unreachable!(),
},
}
}
pub fn screen_to_root_canvas_space(&self, position: Vector2<f32>) -> Vector2<f32> {
self.node(self.root()).screen_to_local(position)
}
fn show_tooltip(&self, tooltip: RcUiNodeHandle) {
self.send_message(WidgetMessage::visibility(
tooltip.handle(),
MessageDirection::ToWidget,
true,
));
self.send_message(WidgetMessage::topmost(
tooltip.handle(),
MessageDirection::ToWidget,
));
self.send_message(WidgetMessage::desired_position(
tooltip.handle(),
MessageDirection::ToWidget,
self.screen_to_root_canvas_space(self.cursor_position() + Vector2::new(0.0, 16.0)),
));
self.send_message(WidgetMessage::adjust_position_to_fit(
tooltip.handle(),
MessageDirection::ToWidget,
));
}
fn replace_or_update_tooltip(&mut self, tooltip: RcUiNodeHandle, disappear_timeout: f32) {
if let Some(entry) = self.active_tooltip.as_mut() {
if entry.tooltip == tooltip {
if entry.shown {
entry.disappear_timer = disappear_timeout;
}
} else {
let old_tooltip = entry.tooltip.clone();
entry.shown = false;
entry.appear_timer = self.tooltip_appear_delay;
entry.disappear_timer = disappear_timeout;
entry.tooltip = tooltip.clone();
self.send_message(WidgetMessage::visibility(
old_tooltip.handle(),
MessageDirection::ToWidget,
false,
));
}
} else {
self.active_tooltip = Some(TooltipEntry::new(
tooltip,
self.tooltip_appear_delay,
disappear_timeout,
));
}
}
fn update_tooltips(&mut self, dt: f32) {
let sender = self.sender.clone();
if let Some(entry) = self.active_tooltip.as_mut() {
if entry.shown {
entry.disappear_timer -= dt;
if entry.disappear_timer <= 0.0 {
sender
.send(WidgetMessage::visibility(
entry.tooltip.handle(),
MessageDirection::ToWidget,
false,
))
.unwrap();
self.active_tooltip = None;
}
} else {
let mut tooltip_owner_hovered = false;
let mut handle = self.picked_node;
while let Some(node) = self.nodes.try_borrow(handle) {
if let Some(tooltip) = node.tooltip.as_ref() {
if &entry.tooltip == tooltip {
tooltip_owner_hovered = true;
break;
}
}
handle = node.parent();
}
if tooltip_owner_hovered {
entry.appear_timer -= dt;
if entry.appear_timer <= 0.0 {
entry.shown = true;
let tooltip = entry.tooltip.clone();
self.show_tooltip(tooltip);
}
} else {
self.active_tooltip = None;
}
}
}
let mut handle = self.picked_node;
while let Some(node) = self.nodes.try_borrow(handle) {
let parent = node.parent();
if let Some(tooltip) = node.tooltip() {
let disappear_timeout = node.tooltip_time();
self.replace_or_update_tooltip(tooltip, disappear_timeout);
break;
} else if let Some(entry) = self.active_tooltip.as_mut() {
if entry.tooltip.handle() == handle {
entry.disappear_timer = entry.max_time;
break;
}
}
handle = parent;
}
}
pub fn captured_node(&self) -> Handle<UiNode> {
self.captured_node
}
fn try_set_picked_node(&mut self, node: Handle<UiNode>) -> bool {
if self.picked_node != node {
self.picked_node = node;
self.reset_double_click_entries();
true
} else {
false
}
}
fn reset_double_click_entries(&mut self) {
for entry in self.double_click_entries.values_mut() {
entry.timer = self.double_click_time_slice;
entry.click_count = 0;
}
}
fn request_focus(&mut self, new_focused: Handle<UiNode>) {
if self.keyboard_focus_node != new_focused {
if self.keyboard_focus_node.is_some() {
self.send_message(WidgetMessage::unfocus(
self.keyboard_focus_node,
MessageDirection::FromWidget,
));
}
self.keyboard_focus_node = new_focused;
if self.keyboard_focus_node.is_some() {
self.send_message(WidgetMessage::focus(
self.keyboard_focus_node,
MessageDirection::FromWidget,
));
}
}
}
pub fn process_os_event(&mut self, event: &OsEvent) -> bool {
let mut event_processed = false;
match event {
&OsEvent::MouseInput { button, state, .. } => {
match button {
MouseButton::Left => self.mouse_state.left = state,
MouseButton::Right => self.mouse_state.right = state,
MouseButton::Middle => self.mouse_state.middle = state,
_ => {}
}
match state {
ButtonState::Pressed => {
let picked_changed =
self.try_set_picked_node(self.hit_test(self.cursor_position));
let mut emit_double_click = false;
if !picked_changed {
match self.double_click_entries.entry(button) {
Entry::Occupied(e) => {
let entry = e.into_mut();
if entry.timer > 0.0 {
entry.click_count += 1;
if entry.click_count >= 2 {
entry.click_count = 0;
entry.timer = self.double_click_time_slice;
emit_double_click = true;
}
} else {
entry.timer = self.double_click_time_slice;
entry.click_count = 1;
}
}
Entry::Vacant(entry) => {
entry.insert(DoubleClickEntry {
timer: self.double_click_time_slice,
click_count: 1,
});
}
}
}
if self.picked_node.is_some() {
self.stack.clear();
self.stack.push(self.picked_node);
while let Some(handle) = self.stack.pop() {
let node = &self.nodes[handle];
if node.is_drag_allowed() {
self.drag_context.drag_node = handle;
self.stack.clear();
break;
} else if node.parent().is_some() {
self.stack.push(node.parent());
}
}
self.drag_context.click_pos = self.cursor_position;
}
self.request_focus(self.picked_node);
if self.picked_node.is_some() {
self.send_message(WidgetMessage::mouse_down(
self.picked_node,
MessageDirection::FromWidget,
self.cursor_position,
button,
));
event_processed = true;
}
if emit_double_click {
self.send_message(WidgetMessage::double_click(
self.picked_node,
MessageDirection::FromWidget,
button,
));
}
}
ButtonState::Released => {
if self.picked_node.is_some() {
self.send_message(WidgetMessage::mouse_up(
self.picked_node,
MessageDirection::FromWidget,
self.cursor_position,
button,
));
if self.drag_context.is_dragging {
self.drag_context.is_dragging = false;
self.cursor_icon = CursorIcon::Default;
self.stack.clear();
self.stack.push(self.picked_node);
while let Some(handle) = self.stack.pop() {
let node = &self.nodes[handle];
if node.is_drop_allowed() {
self.send_message(WidgetMessage::drop(
handle,
MessageDirection::FromWidget,
self.drag_context.drag_node,
));
self.stack.clear();
break;
} else if node.parent().is_some() {
self.stack.push(node.parent());
}
}
}
self.drag_context.drag_node = Handle::NONE;
if self.nodes.is_valid_handle(self.drag_context.drag_preview) {
self.remove_node(self.drag_context.drag_preview);
self.drag_context.drag_preview = Default::default();
}
event_processed = true;
}
}
}
}
OsEvent::CursorMoved { position } => {
self.cursor_position = *position;
self.try_set_picked_node(self.hit_test(self.cursor_position));
if !self.drag_context.is_dragging
&& self.mouse_state.left == ButtonState::Pressed
&& self.picked_node.is_some()
&& self.nodes.is_valid_handle(self.drag_context.drag_node)
&& (self.drag_context.click_pos - *position).norm() > 5.0
{
self.drag_context.drag_preview =
self.copy_node_with_limit(self.drag_context.drag_node, Some(30));
self.nodes[self.drag_context.drag_preview].set_opacity(Some(0.5));
let mut stack = vec![self.drag_context.drag_preview];
while let Some(handle) = stack.pop() {
let preview_node = &mut self.nodes[handle];
preview_node.hit_test_visibility.set_value_silent(false);
stack.extend_from_slice(preview_node.children());
}
self.drag_context.is_dragging = true;
self.send_message(WidgetMessage::drag_started(
self.picked_node,
MessageDirection::FromWidget,
self.drag_context.drag_node,
));
self.cursor_icon = CursorIcon::Crosshair;
}
if self.drag_context.is_dragging
&& self.nodes.is_valid_handle(self.drag_context.drag_preview)
{
self.send_message(WidgetMessage::desired_position(
self.drag_context.drag_preview,
MessageDirection::ToWidget,
*position,
));
}
if self.picked_node != self.prev_picked_node && self.prev_picked_node.is_some() {
let prev_picked_node = self.nodes.borrow_mut(self.prev_picked_node);
if prev_picked_node.is_mouse_directly_over {
prev_picked_node.is_mouse_directly_over = false;
self.send_message(WidgetMessage::mouse_leave(
self.prev_picked_node,
MessageDirection::FromWidget,
));
}
}
if self.picked_node.is_some() {
let picked_node = self.nodes.borrow_mut(self.picked_node);
if !picked_node.is_mouse_directly_over {
picked_node.is_mouse_directly_over = true;
self.send_message(WidgetMessage::mouse_enter(
self.picked_node,
MessageDirection::FromWidget,
));
}
self.send_message(WidgetMessage::mouse_move(
self.picked_node,
MessageDirection::FromWidget,
self.cursor_position,
self.mouse_state,
));
if self.drag_context.is_dragging {
self.send_message(WidgetMessage::drag_over(
self.picked_node,
MessageDirection::FromWidget,
self.drag_context.drag_node,
));
}
event_processed = true;
}
}
OsEvent::MouseWheel(_, y) => {
if self.picked_node.is_some() {
self.send_message(WidgetMessage::mouse_wheel(
self.picked_node,
MessageDirection::FromWidget,
self.cursor_position,
*y,
));
event_processed = true;
}
}
OsEvent::KeyboardInput {
button,
state,
text,
} => {
if let Some(keyboard_focus_node) = self.try_get(self.keyboard_focus_node) {
if keyboard_focus_node.is_globally_visible() {
match state {
ButtonState::Pressed => {
self.send_message(WidgetMessage::key_down(
self.keyboard_focus_node,
MessageDirection::FromWidget,
*button,
));
if !text.is_empty() {
self.send_message(WidgetMessage::text(
self.keyboard_focus_node,
MessageDirection::FromWidget,
text.clone(),
));
}
}
ButtonState::Released => self.send_message(WidgetMessage::key_up(
self.keyboard_focus_node,
MessageDirection::FromWidget,
*button,
)),
}
event_processed = true;
}
}
}
&OsEvent::KeyboardModifiers(modifiers) => {
self.keyboard_modifiers = modifiers;
}
OsEvent::Touch {
phase,
location,
force,
id,
} => match phase {
TouchPhase::Started => {
self.cursor_position = *location;
let picked_changed =
self.try_set_picked_node(self.hit_test(self.cursor_position));
let mut emit_double_tap = false;
if !picked_changed {
match self.double_click_entries.entry(MouseButton::Left) {
Entry::Occupied(e) => {
let entry = e.into_mut();
if entry.timer > 0.0 {
entry.click_count += 1;
if entry.click_count >= 2 {
entry.click_count = 0;
entry.timer = self.double_click_time_slice;
emit_double_tap = true;
}
} else {
entry.timer = self.double_click_time_slice;
entry.click_count = 1;
}
}
Entry::Vacant(entry) => {
entry.insert(DoubleClickEntry {
timer: self.double_click_time_slice,
click_count: 1,
});
}
}
}
if self.picked_node.is_some() {
self.stack.clear();
self.stack.push(self.picked_node);
while let Some(handle) = self.stack.pop() {
let node = &self.nodes[handle];
if node.is_drag_allowed() {
self.drag_context.drag_node = handle;
self.stack.clear();
break;
} else if node.parent().is_some() {
self.stack.push(node.parent());
}
}
self.drag_context.click_pos = self.cursor_position;
}
self.request_focus(self.picked_node);
if self.picked_node.is_some() {
self.send_message(WidgetMessage::touch_started(
self.picked_node,
MessageDirection::FromWidget,
self.cursor_position,
*force,
*id,
));
event_processed = true;
}
if emit_double_tap {
self.send_message(WidgetMessage::double_tap(
self.picked_node,
MessageDirection::FromWidget,
*location,
*force,
*id,
));
}
}
TouchPhase::Moved => {
self.cursor_position = *location;
self.try_set_picked_node(self.hit_test(self.cursor_position));
if self.picked_node.is_some() {
self.stack.clear();
self.stack.push(self.picked_node);
while let Some(handle) = self.stack.pop() {
let node = &self.nodes[handle];
if node.is_drag_allowed() {
self.drag_context.drag_node = handle;
self.stack.clear();
break;
} else if node.parent().is_some() {
self.stack.push(node.parent());
}
}
self.drag_context.click_pos = self.cursor_position;
}
self.request_focus(self.picked_node);
if self.picked_node.is_some() {
self.send_message(WidgetMessage::touch_moved(
self.picked_node,
MessageDirection::FromWidget,
self.cursor_position,
*force,
*id,
));
event_processed = true;
}
}
TouchPhase::Ended => {
if self.picked_node.is_some() {
self.send_message(WidgetMessage::touch_ended(
self.picked_node,
MessageDirection::FromWidget,
self.cursor_position,
*id,
));
if self.drag_context.is_dragging {
self.drag_context.is_dragging = false;
self.stack.clear();
self.stack.push(self.picked_node);
while let Some(handle) = self.stack.pop() {
let node = &self.nodes[handle];
if node.is_drop_allowed() {
self.send_message(WidgetMessage::drop(
handle,
MessageDirection::FromWidget,
self.drag_context.drag_node,
));
self.stack.clear();
break;
} else if node.parent().is_some() {
self.stack.push(node.parent());
}
}
}
self.drag_context.drag_node = Handle::NONE;
if self.nodes.is_valid_handle(self.drag_context.drag_preview) {
self.remove_node(self.drag_context.drag_preview);
self.drag_context.drag_preview = Default::default();
}
event_processed = true;
}
}
TouchPhase::Cancelled => {
if self.picked_node.is_some() {
self.send_message(WidgetMessage::touch_cancelled(
self.picked_node,
MessageDirection::FromWidget,
self.cursor_position,
*id,
));
if self.drag_context.is_dragging {
self.drag_context.is_dragging = false;
self.cursor_icon = CursorIcon::Default;
self.stack.clear();
}
self.drag_context.drag_node = Handle::NONE;
if self.nodes.is_valid_handle(self.drag_context.drag_preview) {
self.remove_node(self.drag_context.drag_preview);
self.drag_context.drag_preview = Default::default();
}
event_processed = true;
}
}
},
}
self.prev_picked_node = self.picked_node;
let on_os_event_subs = std::mem::take(&mut self.methods_registry.handle_os_event);
for &handle in on_os_event_subs.iter() {
let (ticket, mut node) = self.nodes.take_reserve(handle);
node.handle_os_event(handle, self, event);
self.nodes.put_back(ticket, node);
}
self.methods_registry.handle_os_event = on_os_event_subs;
event_processed
}
pub fn nodes(&self) -> &Pool<UiNode, WidgetContainer> {
&self.nodes
}
pub fn root(&self) -> Handle<UiNode> {
self.root_canvas
}
#[inline]
pub fn take_reserve(&mut self, handle: Handle<UiNode>) -> (Ticket<UiNode>, UiNode) {
self.isolate_node(handle);
self.nodes.take_reserve(handle)
}
#[inline]
pub fn put_back(&mut self, ticket: Ticket<UiNode>, node: UiNode) -> Handle<UiNode> {
let handle = self.nodes.put_back(ticket, node);
self.link_nodes(handle, self.root_canvas, false);
handle
}
#[inline]
pub fn forget_ticket(&mut self, ticket: Ticket<UiNode>, node: UiNode) -> UiNode {
self.nodes.forget_ticket(ticket);
node
}
#[inline]
pub fn take_reserve_sub_graph(&mut self, root: Handle<UiNode>) -> SubGraph {
let mut descendants = Vec::new();
let root_ref = &mut self.nodes[root];
let mut stack = root_ref.children().to_vec();
let parent = root_ref.parent;
while let Some(handle) = stack.pop() {
stack.extend_from_slice(self.nodes[handle].children());
descendants.push(self.nodes.take_reserve(handle));
}
SubGraph {
root: self.take_reserve(root),
descendants,
parent,
}
}
#[inline]
pub fn put_sub_graph_back(&mut self, sub_graph: SubGraph) -> Handle<UiNode> {
for (ticket, node) in sub_graph.descendants {
self.nodes.put_back(ticket, node);
}
let (ticket, node) = sub_graph.root;
let root_handle = self.put_back(ticket, node);
self.link_nodes(root_handle, sub_graph.parent, false);
root_handle
}
#[inline]
pub fn forget_sub_graph(&mut self, sub_graph: SubGraph) {
for (ticket, _) in sub_graph.descendants {
self.nodes.forget_ticket(ticket);
}
let (ticket, _) = sub_graph.root;
self.nodes.forget_ticket(ticket);
}
pub fn push_picking_restriction(&mut self, restriction: RestrictionEntry) {
if let Some(top) = self.top_picking_restriction() {
assert_ne!(top.handle, restriction.handle);
}
self.picking_stack.push(restriction);
}
pub fn remove_picking_restriction(&mut self, node: Handle<UiNode>) {
if let Some(pos) = self.picking_stack.iter().position(|h| h.handle == node) {
self.picking_stack.remove(pos);
}
}
pub fn picking_restriction_stack(&self) -> &[RestrictionEntry] {
&self.picking_stack
}
pub fn drop_picking_restrictions(&mut self) {
self.picking_stack.clear();
}
pub fn top_picking_restriction(&self) -> Option<RestrictionEntry> {
self.picking_stack.last().cloned()
}
pub fn drag_context(&self) -> &DragContext {
&self.drag_context
}
#[inline]
pub fn link_nodes(
&mut self,
child_handle: Handle<UiNode>,
parent_handle: Handle<UiNode>,
in_front: bool,
) {
assert_ne!(child_handle, parent_handle);
self.isolate_node(child_handle);
self.nodes[child_handle].set_parent(parent_handle);
self.nodes[parent_handle].add_child(child_handle, in_front);
}
#[inline]
pub fn node_mut(&mut self, node_handle: Handle<UiNode>) -> &mut UiNode {
self.nodes.borrow_mut(node_handle)
}
#[inline]
pub fn try_get_node_mut(&mut self, node_handle: Handle<UiNode>) -> Option<&mut UiNode> {
self.nodes.try_borrow_mut(node_handle)
}
pub fn copy_node(&mut self, node: Handle<UiNode>) -> Handle<UiNode> {
let mut old_new_mapping = NodeHandleMap::default();
let root = self.copy_node_recursive(node, &mut old_new_mapping);
remap_handles(&old_new_mapping, self);
root
}
#[allow(clippy::unnecessary_to_owned)] fn copy_node_recursive(
&mut self,
node_handle: Handle<UiNode>,
old_new_mapping: &mut NodeHandleMap<UiNode>,
) -> Handle<UiNode> {
let node = self.nodes.borrow(node_handle);
let mut cloned = UiNode(node.clone_boxed());
cloned.id = Uuid::new_v4();
let mut cloned_children = Vec::new();
for child in node.children().to_vec() {
cloned_children.push(self.copy_node_recursive(child, old_new_mapping));
}
cloned.set_children(cloned_children);
let copy_handle = self.add_node(cloned);
old_new_mapping.insert(node_handle, copy_handle);
copy_handle
}
pub fn copy_node_to<Post>(
&self,
node: Handle<UiNode>,
dest: &mut UserInterface,
post_process_callback: &mut Post,
) -> (Handle<UiNode>, NodeHandleMap<UiNode>)
where
Post: FnMut(Handle<UiNode>, Handle<UiNode>, &mut UiNode),
{
let mut old_new_mapping = NodeHandleMap::default();
let root =
self.copy_node_to_recursive(node, dest, &mut old_new_mapping, post_process_callback);
remap_handles(&old_new_mapping, dest);
(root, old_new_mapping)
}
fn copy_node_to_recursive<Post>(
&self,
node_handle: Handle<UiNode>,
dest: &mut UserInterface,
old_new_mapping: &mut NodeHandleMap<UiNode>,
post_process_callback: &mut Post,
) -> Handle<UiNode>
where
Post: FnMut(Handle<UiNode>, Handle<UiNode>, &mut UiNode),
{
let node = self.nodes.borrow(node_handle);
let children = node.children.clone();
let mut cloned = UiNode(node.clone_boxed());
cloned.children.clear();
cloned.parent = Handle::NONE;
cloned.id = Uuid::new_v4();
let cloned_node_handle = dest.add_node(cloned);
for child in children {
let cloned_child_node_handle =
self.copy_node_to_recursive(child, dest, old_new_mapping, post_process_callback);
dest.link_nodes(cloned_child_node_handle, cloned_node_handle, false);
}
old_new_mapping.insert(node_handle, cloned_node_handle);
post_process_callback(
cloned_node_handle,
node_handle,
dest.try_get_node_mut(cloned_node_handle).unwrap(),
);
cloned_node_handle
}
pub fn copy_node_with_limit(
&mut self,
node: Handle<UiNode>,
limit: Option<usize>,
) -> Handle<UiNode> {
let mut old_new_mapping = NodeHandleMap::default();
let mut counter = 0;
let root =
self.copy_node_recursive_with_limit(node, &mut old_new_mapping, limit, &mut counter);
remap_handles(&old_new_mapping, self);
root
}
#[allow(clippy::unnecessary_to_owned)] fn copy_node_recursive_with_limit(
&mut self,
node_handle: Handle<UiNode>,
old_new_mapping: &mut NodeHandleMap<UiNode>,
limit: Option<usize>,
counter: &mut usize,
) -> Handle<UiNode> {
if let Some(limit) = limit {
if *counter >= limit {
return Default::default();
}
}
let Some(node) = self.nodes.try_borrow(node_handle) else {
return Default::default();
};
let mut cloned = UiNode(node.clone_boxed());
cloned.id = Uuid::new_v4();
let mut cloned_children = Vec::new();
for child in node.children().to_vec() {
let cloned_child =
self.copy_node_recursive_with_limit(child, old_new_mapping, limit, counter);
if cloned_child.is_some() {
cloned_children.push(cloned_child);
} else {
break;
}
}
cloned.set_children(cloned_children);
let copy_handle = self.add_node(cloned);
old_new_mapping.insert(node_handle, copy_handle);
*counter += 1;
copy_handle
}
pub fn save(&mut self, path: &Path) -> Result<Visitor, VisitError> {
let mut visitor = Visitor::new();
self.visit("Ui", &mut visitor)?;
visitor.save_binary(path)?;
Ok(visitor)
}
#[allow(clippy::arc_with_non_send_sync)]
pub async fn load_from_file<P: AsRef<Path>>(
path: P,
resource_manager: ResourceManager,
) -> Result<Self, VisitError> {
Self::load_from_file_ex(
path,
Arc::new(new_widget_constructor_container()),
resource_manager,
&FsResourceIo,
)
.await
}
fn restore_dynamic_node_data(&mut self) {
for (handle, widget) in self.nodes.pair_iter_mut() {
widget.handle = handle;
widget.layout_events_sender = Some(self.layout_events_sender.clone());
widget.invalidate_layout();
widget.notify_z_index_changed();
}
}
pub fn resolve(&mut self) {
self.restore_dynamic_node_data();
self.restore_original_handles_and_inherit_properties(&[], |_, _| {});
self.update_visual_transform(self.root_canvas);
self.update_global_visibility(self.root_canvas);
let instances = self.restore_integrity(|model, model_data, handle, dest_graph| {
model_data.copy_node_to(handle, dest_graph, &mut |_, original_handle, node| {
node.set_inheritance_data(original_handle, model.clone());
})
});
self.remap_handles(&instances);
}
pub fn collect_used_resources(&self) -> FxHashSet<UntypedResource> {
let mut collection = FxHashSet::default();
fyrox_resource::collect_used_resources(self, &mut collection);
collection
}
#[allow(clippy::arc_with_non_send_sync)]
pub async fn load_from_file_ex<P: AsRef<Path>>(
path: P,
constructors: Arc<WidgetConstructorContainer>,
resource_manager: ResourceManager,
io: &dyn ResourceIo,
) -> Result<Self, VisitError> {
let mut ui = {
let mut visitor = Visitor::load_from_memory(&io.load_file(path.as_ref()).await?)?;
let (sender, receiver) = mpsc::channel();
visitor.blackboard.register(constructors);
visitor.blackboard.register(Arc::new(sender.clone()));
visitor.blackboard.register(Arc::new(resource_manager));
let mut ui =
UserInterface::new_with_channel(sender, receiver, Vector2::new(100.0, 100.0));
ui.visit("Ui", &mut visitor)?;
ui
};
Log::info("UserInterface - Collecting resources used by the scene...");
let used_resources = ui.collect_used_resources();
let used_resources_count = used_resources.len();
Log::info(format!(
"UserInterface - {used_resources_count} resources collected. Waiting them to load..."
));
join_all(used_resources.into_iter()).await;
ui.resolve();
Ok(ui)
}
}
impl PrefabData for UserInterface {
type Graph = Self;
#[inline]
fn graph(&self) -> &Self::Graph {
self
}
#[inline]
fn mapping(&self) -> NodeMapping {
NodeMapping::UseHandles
}
}
impl AbstractSceneGraph for UserInterface {
fn try_get_node_untyped(&self, handle: ErasedHandle) -> Option<&dyn AbstractSceneNode> {
self.nodes
.try_borrow(handle.into())
.map(|n| n as &dyn AbstractSceneNode)
}
fn try_get_node_untyped_mut(
&mut self,
handle: ErasedHandle,
) -> Option<&mut dyn AbstractSceneNode> {
self.nodes
.try_borrow_mut(handle.into())
.map(|n| n as &mut dyn AbstractSceneNode)
}
}
impl BaseSceneGraph for UserInterface {
type Prefab = Self;
type Node = UiNode;
#[inline]
fn root(&self) -> Handle<Self::Node> {
self.root_canvas
}
#[inline]
fn set_root(&mut self, root: Handle<Self::Node>) {
self.root_canvas = root;
}
#[inline]
fn try_get(&self, handle: Handle<Self::Node>) -> Option<&Self::Node> {
self.nodes.try_borrow(handle)
}
#[inline]
fn try_get_mut(&mut self, handle: Handle<Self::Node>) -> Option<&mut Self::Node> {
self.nodes.try_borrow_mut(handle)
}
#[inline]
fn is_valid_handle(&self, handle: Handle<Self::Node>) -> bool {
self.nodes.is_valid_handle(handle)
}
#[inline]
fn add_node(&mut self, mut node: Self::Node) -> Handle<Self::Node> {
let children = node.children().to_vec();
node.clear_children();
let node_handle = self.nodes.spawn(node);
if self.root_canvas.is_some() {
self.link_nodes(node_handle, self.root_canvas, false);
}
for child in children {
self.link_nodes(child, node_handle, false)
}
let node = self.nodes[node_handle].deref_mut();
node.layout_events_sender = Some(self.layout_events_sender.clone());
node.handle = node_handle;
self.methods_registry.register(node);
node.invalidate_layout();
node.notify_z_index_changed();
self.layout_events_sender
.send(LayoutEvent::VisibilityChanged(node_handle))
.unwrap();
node_handle
}
#[inline]
fn remove_node(&mut self, node: Handle<Self::Node>) {
self.isolate_node(node);
let sender = self.sender.clone();
let mut stack = vec![node];
while let Some(handle) = stack.pop() {
if self.prev_picked_node == handle {
self.prev_picked_node = Handle::NONE;
}
if self.picked_node == handle {
self.try_set_picked_node(Handle::NONE);
}
if self.captured_node == handle {
self.captured_node = Handle::NONE;
}
if self.keyboard_focus_node == handle {
self.keyboard_focus_node = Handle::NONE;
}
self.remove_picking_restriction(handle);
let node_ref = self.nodes.borrow(handle);
stack.extend_from_slice(node_ref.children());
node_ref.on_remove(&sender);
self.methods_registry.unregister(node_ref.deref());
self.nodes.free(handle);
}
}
#[inline]
fn link_nodes(&mut self, child: Handle<Self::Node>, parent: Handle<Self::Node>) {
self.link_nodes(child, parent, false)
}
#[inline]
fn unlink_node(&mut self, node_handle: Handle<Self::Node>) {
self.isolate_node(node_handle);
self.link_nodes(node_handle, self.root_canvas, false);
}
#[inline]
fn isolate_node(&mut self, node_handle: Handle<Self::Node>) {
let node = self.nodes.borrow_mut(node_handle);
let parent_handle = node.parent();
if parent_handle.is_some() {
node.set_parent(Handle::NONE);
self.nodes[parent_handle].remove_child(node_handle);
}
}
}
impl SceneGraph for UserInterface {
#[inline]
fn pair_iter(&self) -> impl Iterator<Item = (Handle<Self::Node>, &Self::Node)> {
self.nodes.pair_iter()
}
#[inline]
fn linear_iter(&self) -> impl Iterator<Item = &Self::Node> {
self.nodes.iter()
}
#[inline]
fn linear_iter_mut(&mut self) -> impl Iterator<Item = &mut Self::Node> {
self.nodes.iter_mut()
}
}
pub trait UserInterfaceResourceExtension {
fn instantiate(&self, ui: &mut UserInterface) -> (Handle<UiNode>, NodeHandleMap<UiNode>);
}
impl UserInterfaceResourceExtension for Resource<UserInterface> {
fn instantiate(&self, ui: &mut UserInterface) -> (Handle<UiNode>, NodeHandleMap<UiNode>) {
let resource = self.clone();
let mut data = self.state();
let data = data.data().expect("The resource must be loaded!");
let (root, mapping) =
data.copy_node_to(data.root_canvas, ui, &mut |_, original_handle, node| {
node.set_inheritance_data(original_handle, resource.clone());
});
ui.node_mut(root).is_resource_instance_root = true;
(root, mapping)
}
}
fn is_approx_zero(v: f32) -> bool {
v.abs() <= 10.0 * f32::EPSILON
}
fn are_close(value1: f32, value2: f32) -> bool {
if value1 == value2 {
return true;
}
let eps = (value1.abs() + value2.abs() + 10.0) * f32::EPSILON;
let delta = value1 - value2;
(-eps < delta) && (eps > delta)
}
fn greater_than_or_close(value1: f32, value2: f32) -> bool {
(value1 > value2) || are_close(value1, value2)
}
fn less_than_or_close(value1: f32, value2: f32) -> bool {
(value1 < value2) || are_close(value1, value2)
}
fn transform_size(transform_space_bounds: Vector2<f32>, matrix: &Matrix3<f32>) -> Vector2<f32> {
let mut x_constr: f32 = transform_space_bounds.x;
let mut y_constr: f32 = transform_space_bounds.y;
if is_approx_zero(x_constr) || is_approx_zero(y_constr) {
return Vector2::new(0.0, 0.0);
}
let x_constr_infinite = x_constr.is_infinite();
let y_constr_infinite = y_constr.is_infinite();
if x_constr_infinite && y_constr_infinite {
return Vector2::new(f32::INFINITY, f32::INFINITY);
} else if x_constr_infinite
{
x_constr = y_constr;
} else if y_constr_infinite {
y_constr = x_constr;
}
if !matrix.is_invertible() {
return Vector2::new(0.0, 0.0);
}
let a = matrix[(0, 0)];
let b = matrix[(0, 1)];
let c = matrix[(1, 0)];
let d = matrix[(1, 1)];
let mut w;
let mut h;
if is_approx_zero(b) || is_approx_zero(c) {
let y_cover_d = if y_constr_infinite {
f32::INFINITY
} else {
(y_constr / d).abs()
};
let x_cover_a = if x_constr_infinite {
f32::INFINITY
} else {
(x_constr / a).abs()
};
if is_approx_zero(b) {
if is_approx_zero(c) {
h = y_cover_d;
w = x_cover_a;
} else {
h = (0.5 * (x_constr / c).abs()).min(y_cover_d);
w = x_cover_a - ((c * h) / a);
}
} else {
w = (0.5 * (y_constr / b).abs()).min(x_cover_a);
h = y_cover_d - ((b * w) / d);
}
} else if is_approx_zero(a) || is_approx_zero(d) {
let y_cover_b = (y_constr / b).abs();
let x_cover_c = (x_constr / c).abs();
if is_approx_zero(a) {
if is_approx_zero(d) {
h = x_cover_c;
w = y_cover_b;
} else {
h = (0.5 * (y_constr / d).abs()).min(x_cover_c);
w = y_cover_b - ((d * h) / b);
}
} else {
w = (0.5 * (x_constr / a).abs()).min(y_cover_b);
h = x_cover_c - ((a * w) / c);
}
} else {
let x_cover_a = (x_constr / a).abs(); let x_cover_c = (x_constr / c).abs();
let y_cover_b = (y_constr / b).abs(); let y_cover_d = (y_constr / d).abs();
w = y_cover_b.min(x_cover_a) * 0.5;
h = x_cover_c.min(y_cover_d) * 0.5;
if (greater_than_or_close(x_cover_a, y_cover_b) && less_than_or_close(x_cover_c, y_cover_d))
|| (less_than_or_close(x_cover_a, y_cover_b)
&& greater_than_or_close(x_cover_c, y_cover_d))
{
let child_bounds_tr = Rect::new(0.0, 0.0, w, h).transform(matrix);
let expand_factor =
(x_constr / child_bounds_tr.size.x).min(y_constr / child_bounds_tr.size.y);
if !expand_factor.is_nan() && !expand_factor.is_infinite() {
w *= expand_factor;
h *= expand_factor;
}
}
}
Vector2::new(w, h)
}
uuid_provider!(UserInterface = "0d065c93-ef9c-4dd2-9fe7-e2b33c1a21b6");
impl ResourceData for UserInterface {
fn type_uuid(&self) -> Uuid {
<Self as TypeUuidProvider>::type_uuid()
}
fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
self.save(path)?;
Ok(())
}
fn can_be_saved(&self) -> bool {
true
}
}
pub mod test {
use crate::{
core::{algebra::Vector2, pool::Handle},
message::MessageDirection,
widget::WidgetMessage,
BuildContext, UiNode, UserInterface,
};
pub fn test_widget_deletion(constructor: impl FnOnce(&mut BuildContext) -> Handle<UiNode>) {
let screen_size = Vector2::new(100.0, 100.0);
let mut ui = UserInterface::new(screen_size);
let widget = constructor(&mut ui.build_ctx());
ui.send_message(WidgetMessage::remove(widget, MessageDirection::ToWidget));
ui.update(screen_size, 1.0 / 60.0, &Default::default());
while ui.poll_message().is_some() {}
assert_eq!(ui.nodes().alive_count(), 1);
}
}
#[cfg(test)]
mod test_inner {
use crate::{
border::BorderBuilder,
core::algebra::{Rotation2, UnitComplex, Vector2},
message::MessageDirection,
message::{ButtonState, KeyCode},
text_box::TextBoxBuilder,
transform_size,
widget::{WidgetBuilder, WidgetMessage},
OsEvent, UserInterface,
};
use fyrox_graph::BaseSceneGraph;
#[test]
fn test_transform_size() {
let input = Vector2::new(100.0, 100.0);
let transform =
Rotation2::from(UnitComplex::from_angle(45.0f32.to_radians())).to_homogeneous();
let transformed = transform_size(input, &transform);
dbg!(input, transformed);
}
#[test]
fn center() {
let screen_size = Vector2::new(1000.0, 1000.0);
let widget_size = Vector2::new(100.0, 100.0);
let mut ui = UserInterface::new(screen_size);
let widget = BorderBuilder::new(
WidgetBuilder::new()
.with_width(widget_size.x)
.with_height(widget_size.y),
)
.build(&mut ui.build_ctx());
ui.update(screen_size, 0.0, &Default::default()); ui.send_message(WidgetMessage::center(widget, MessageDirection::ToWidget));
while ui.poll_message().is_some() {}
ui.update(screen_size, 0.0, &Default::default());
let expected_position = (screen_size - widget_size).scale(0.5);
let actual_position = ui.node(widget).actual_local_position();
assert_eq!(actual_position, expected_position);
}
#[test]
fn test_keyboard_focus() {
let screen_size = Vector2::new(1000.0, 1000.0);
let mut ui = UserInterface::new(screen_size);
let text_box = TextBoxBuilder::new(WidgetBuilder::new()).build(&mut ui.build_ctx());
ui.update(screen_size, 0.0, &Default::default());
assert!(ui.poll_message().is_none());
ui.send_message(WidgetMessage::focus(text_box, MessageDirection::ToWidget));
assert_eq!(
ui.poll_message(),
Some(WidgetMessage::focus(text_box, MessageDirection::ToWidget))
);
assert_eq!(
ui.poll_message(),
Some(WidgetMessage::unfocus(
ui.root(),
MessageDirection::FromWidget
))
);
assert_eq!(
ui.poll_message(),
Some(WidgetMessage::focus(text_box, MessageDirection::FromWidget))
);
ui.process_os_event(&OsEvent::KeyboardInput {
button: KeyCode::KeyA,
state: ButtonState::Pressed,
text: "A".to_string(),
});
let msg = WidgetMessage::key_down(text_box, MessageDirection::FromWidget, KeyCode::KeyA);
msg.set_handled(true);
assert_eq!(ui.poll_message(), Some(msg));
assert_eq!(
ui.poll_message(),
Some(WidgetMessage::text(
text_box,
MessageDirection::FromWidget,
'A'.to_string()
))
);
assert!(ui.poll_message().is_none());
}
}