From 3ac361c09e50cdb08acb06529a81059f71355129 Mon Sep 17 00:00:00 2001 From: chaosprint Date: Wed, 5 Jul 2023 07:15:17 +0200 Subject: [PATCH 1/4] add dragarea --- guest/rust/Cargo.lock | 7 ++ guest/rust/Cargo.toml | 1 + guest/rust/examples/ui/nodegraph/Cargo.toml | 19 +++ guest/rust/examples/ui/nodegraph/ambient.toml | 7 ++ .../rust/examples/ui/nodegraph/src/client.rs | 52 +++++++++ shared_crates/ui/src/button.rs | 1 + shared_crates/ui/src/dragarea.rs | 100 ++++++++++++++++ shared_crates/ui/src/lib.rs | 7 ++ shared_crates/ui/src/node.rs | 110 ++++++++++++++++++ shared_crates/ui/src/prelude.rs | 8 +- 10 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 guest/rust/examples/ui/nodegraph/Cargo.toml create mode 100644 guest/rust/examples/ui/nodegraph/ambient.toml create mode 100644 guest/rust/examples/ui/nodegraph/src/client.rs create mode 100644 shared_crates/ui/src/dragarea.rs create mode 100644 shared_crates/ui/src/node.rs diff --git a/guest/rust/Cargo.lock b/guest/rust/Cargo.lock index 4dc0a1c6a0..00947190cb 100644 --- a/guest/rust/Cargo.lock +++ b/guest/rust/Cargo.lock @@ -287,6 +287,13 @@ dependencies = [ "ambient_api", ] +[[package]] +name = "ambient_example_nodegraph" +version = "0.3.0-dev" +dependencies = [ + "ambient_api", +] + [[package]] name = "ambient_example_physics" version = "0.3.0-dev" diff --git a/guest/rust/Cargo.toml b/guest/rust/Cargo.toml index 544abdaffa..f3e40b8abc 100644 --- a/guest/rust/Cargo.toml +++ b/guest/rust/Cargo.toml @@ -30,6 +30,7 @@ members = [ "examples/ui/auto_editor", "examples/ui/button", "examples/ui/counter", + "examples/ui/nodegraph", "examples/ui/clock", "examples/ui/dock_layout", "examples/ui/editors", diff --git a/guest/rust/examples/ui/nodegraph/Cargo.toml b/guest/rust/examples/ui/nodegraph/Cargo.toml new file mode 100644 index 0000000000..a882c65712 --- /dev/null +++ b/guest/rust/examples/ui/nodegraph/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ambient_example_nodegraph" + +edition = "2021" +publish = false +rust-version = { workspace = true } +version = { workspace = true } + +[dependencies] +ambient_api = { workspace = true } + +[[bin]] +name = "nodegraph_client" +path = "src/client.rs" +required-features = ["client"] + +[features] +client = ["ambient_api/client"] +server = ["ambient_api/server"] diff --git a/guest/rust/examples/ui/nodegraph/ambient.toml b/guest/rust/examples/ui/nodegraph/ambient.toml new file mode 100644 index 0000000000..768ee0da4c --- /dev/null +++ b/guest/rust/examples/ui/nodegraph/ambient.toml @@ -0,0 +1,7 @@ +[ember] +id = "ambient_example_nodegraph" +name = "Nodegraph" +version = "0.0.1" +repository = "https://github.com/AmbientRun/Ambient/tree/main/guest/rust/examples/ui/nodegraph" +type = "Game" +categories = ["Example"] diff --git a/guest/rust/examples/ui/nodegraph/src/client.rs b/guest/rust/examples/ui/nodegraph/src/client.rs new file mode 100644 index 0000000000..85ea8f0d6c --- /dev/null +++ b/guest/rust/examples/ui/nodegraph/src/client.rs @@ -0,0 +1,52 @@ +use ambient_api::{ + components::core::{ + layout::{ + align_horizontal_center, align_vertical_center, height, space_between_items, width, + }, + rect::{ + background_color, border_color, border_radius, border_thickness, line_from, line_to, + line_width, + }, + transform::translation, + }, + prelude::*, +}; + +#[element_component] +fn App(_hooks: &mut Hooks) -> Element { + let (text, set_text) = _hooks.use_state("".to_string()); + + let editor = TextEditor::new(text.clone(), set_text.clone()) + .placeholder(Some("type your node...")) + .auto_focus() + .el() + .with(width(), 100.) + .with(height(), 60.) + .with(background_color(), vec4(0.2, 0.6, 0.2, 0.6)) + .with(translation(), vec3(0., 40., 0.)); + + // .with_default(align_horizontal_center()) + // .with_default(align_vertical_center()); + + let body = Rectangle::el() + .with(width(), 100.) + .with(height(), 60.) + .with(translation(), vec3(0., 30., 0.)) + .with(background_color(), vec4(0.2, 0.2, 0.6, 0.6)); + // .children(vec![text]); + + let dragarea = Rectangle + .el() + .with(width(), 100.) + .with(height(), 30.) + .with(background_color(), vec4(0.6, 0.2, 0.2, 0.6)) + .children(vec![body, editor]); + // FocusRoot::el([dragarea.with_dragarea().el()]) + + Node::el() +} + +#[main] +pub fn main() { + App.el().spawn_interactive(); +} diff --git a/shared_crates/ui/src/button.rs b/shared_crates/ui/src/button.rs index 7b8ca0b817..400bf4169a 100644 --- a/shared_crates/ui/src/button.rs +++ b/shared_crates/ui/src/button.rs @@ -326,6 +326,7 @@ pub fn Button( content } } + impl Button { /// Create a new [Button] with the given content and callback. pub fn new>( diff --git a/shared_crates/ui/src/dragarea.rs b/shared_crates/ui/src/dragarea.rs new file mode 100644 index 0000000000..6d89f7d28b --- /dev/null +++ b/shared_crates/ui/src/dragarea.rs @@ -0,0 +1,100 @@ +//! Defines the [DragArea] element. + +use ambient_element::{to_owned, Element, ElementComponent, Hooks}; +use ambient_guest_bridge::components::{app::cursor_position, transform::translation}; +use ambient_guest_bridge::{ + components::input::{mouse_over, mouse_pickable_max, mouse_pickable_min}, + messages, +}; +use ambient_shared_types::CursorIcon; +use glam::Vec3; + +#[derive(Debug, Clone)] +/// An area that can be dragged. +pub struct DragArea { + /// The inner element. + pub inner: Element, +} + +impl DragArea { + /// Create a new ClickArea. + pub fn new(inner: Element) -> Self { + Self { inner } + } +} +impl ElementComponent for DragArea { + fn render(self: Box, hooks: &mut Hooks) -> Element { + let Self { inner } = *self; + let id = hooks.use_ref_with(|_| None); + let mouse_over_count = hooks.use_ref_with(|_| 0); + let (moving, set_moving) = hooks.use_state(false); + let (mouse_click_pos, set_mouse_click_pos) = hooks.use_state((0.0, 0.0)); + let (click_pos, set_click_pos) = hooks.use_state((0.0, 0.0)); + hooks.use_frame({ + to_owned![id, mouse_over_count]; + move |world| { + if let Some(id) = *id.lock() { + if moving { + let mouse_pos = world.resource(cursor_position()); + let pos = world.get(id, translation()).unwrap_or(Vec3::ZERO); + world + .set( + id, + translation(), + Vec3 { + x: click_pos.0 + mouse_pos.x - mouse_click_pos.0, + y: click_pos.1 + mouse_pos.y - mouse_click_pos.1, + z: pos.z, + }, + ) + .unwrap(); + } + let next = world.get(id, mouse_over()).unwrap_or(0); + let mut state = mouse_over_count.lock(); + // if *state == 0 && next > 0 { + // // println!("mouse enter"); + // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Move); + // } + // if *state > 0 && next == 0 { + // // println!("mouse leave"); + // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Default); + // } + + if next > 0 { + ambient_guest_bridge::window::set_cursor(world, CursorIcon::Move); + } else { + ambient_guest_bridge::window::set_cursor(world, CursorIcon::Arrow); + } + + *state = next; + } + } + }); + + hooks.use_runtime_message::({ + to_owned![id, mouse_over_count, set_moving]; + move |world, event| { + if let Some(id) = *id.lock() { + if *mouse_over_count.lock() > 0 { + if event.pressed && event.button == 0 { + set_moving(true); + let mouse_pos = world.resource(cursor_position()); + let pos = world.get(id, translation()).unwrap_or(Vec3::ZERO); + set_mouse_click_pos((mouse_pos.x, mouse_pos.y)); + set_click_pos((pos.x, pos.y)); + } else { + set_moving(false); + } + } + } + } + }); + + inner + .init(mouse_pickable_min(), Vec3::ZERO) + .init(mouse_pickable_max(), Vec3::ZERO) + .on_spawned(move |_, new_id, _| { + *id.lock() = Some(new_id); + }) + } +} diff --git a/shared_crates/ui/src/lib.rs b/shared_crates/ui/src/lib.rs index 484f7bedf3..6757ca5e2f 100644 --- a/shared_crates/ui/src/lib.rs +++ b/shared_crates/ui/src/lib.rs @@ -50,9 +50,11 @@ use glam::{vec3, Mat4, UVec2, Vec3, Vec4}; pub mod button; pub mod clickarea; pub mod default_theme; +pub mod dragarea; pub mod dropdown; pub mod editor; pub mod layout; +pub mod node; pub mod prelude; pub mod prompt; pub mod screens; @@ -228,11 +230,16 @@ pub trait UIExt { fn with_padding_even(self, padding: f32) -> Self; /// Adds margin to all sides of this element. fn with_margin_even(self, margin: f32) -> Self; + /// Wraps this element in a [DragArea] element. + fn with_dragarea(self) -> dragarea::DragArea; } impl UIExt for Element { fn with_clickarea(self) -> ClickArea { ClickArea::new(self) } + fn with_dragarea(self) -> dragarea::DragArea { + dragarea::DragArea::new(self) + } fn with_background(self, background: Vec4) -> Self { with_rect(self).with(background_color(), background) } diff --git a/shared_crates/ui/src/node.rs b/shared_crates/ui/src/node.rs new file mode 100644 index 0000000000..dae9982e9d --- /dev/null +++ b/shared_crates/ui/src/node.rs @@ -0,0 +1,110 @@ +//! Defines the [Node] element. + +use crate::prelude::*; +use ambient_element::{element_component, to_owned, Element, ElementComponent, Hooks}; +use ambient_guest_bridge::{ + components::{ + app::cursor_position, + input::{mouse_over, mouse_pickable_max, mouse_pickable_min}, + layout::{ + align_vertical_center, fit_horizontal_parent, height, margin, min_height, padding, + space_between_items, width, + }, + rect::{background_color, border_color, border_radius, border_thickness}, + text::font_style, + transform::translation, + }, + messages, +}; +use ambient_shared_types::CursorIcon; +use glam::{vec3, vec4, Vec3, Vec4}; + +#[element_component] +/// A button UI element. +pub fn Node(hooks: &mut Hooks) -> Element { + let in_id = hooks.use_ref_with(|_| None); + let out_id = hooks.use_ref_with(|_| None); + let mouse_over_count = hooks.use_ref_with(|_| 0); + + hooks.use_frame({ + to_owned![out_id, mouse_over_count]; + move |world| { + if let Some(id) = *out_id.lock() { + let next = world.get(id, mouse_over()).unwrap_or(0); + let mut state = mouse_over_count.lock(); + // if *state == 0 && next > 0 { + // println!("mouse enter outlet"); + // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Arrow); + // } + // if *state > 0 && next == 0 { + // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Default); + // } + + if next > 0 { + ambient_guest_bridge::window::set_cursor(world, CursorIcon::Grab); + }; + + // else { + // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Default); + // } + + *state = next; + } + } + }); + + hooks.use_runtime_message::({ + to_owned![out_id, mouse_over_count]; + move |world, event| { + if let Some(id) = *out_id.lock() { + if *mouse_over_count.lock() > 0 { + if event.pressed && event.button == 0 { + println!("mouse left down"); + // set_moving(true); + // let mouse_pos = world.resource(cursor_position()); + // let pos = world.get(id, translation()).unwrap_or(Vec3::ZERO); + // set_mouse_click_pos((mouse_pos.x, mouse_pos.y)); + // set_click_pos((pos.x, pos.y)); + } else { + // set_moving(false); + } + } + } + } + }); + + let inlet = Rectangle::el() + .with(width(), 5.) + .with(height(), 10.) + // .with(background_color(), vec4(0.6, 0.2, 0.6, 0.6)) + // .with(border_radius(), Vec4::ONE * 5.) + .init(mouse_pickable_min(), Vec3::ZERO) + .init(mouse_pickable_max(), Vec3::ZERO) + .on_spawned(move |_, new_id, _| { + *in_id.lock() = Some(new_id); + }); + let outlet = Rectangle::el() + .with(width(), 5.) + .with(height(), 10.) + // .with(background_color(), vec4(0.2, 0.2, 0.6, 0.6)) + // .with(border_radius(), Vec4::ONE * 5.) + .with(translation(), vec3(60., 0., 0.)) + .init(mouse_pickable_min(), Vec3::ZERO) + .init(mouse_pickable_max(), Vec3::ZERO) + .on_spawned(move |_, new_id, _| { + *out_id.lock() = Some(new_id); + }); + Rectangle::el() + .with(background_color(), vec4(0.2, 0.6, 0.6, 0.6)) + .with(width(), 60.) + .with(height(), 30.) + .children(vec![inlet, outlet]) + .with_dragarea() + .el() +} + +#[element_component] +/// A button UI element. +pub fn Graph(hooks: &mut Hooks) -> Element { + crate::text::Text::el("Graph") +} diff --git a/shared_crates/ui/src/prelude.rs b/shared_crates/ui/src/prelude.rs index c28a214345..9931c3b30e 100644 --- a/shared_crates/ui/src/prelude.rs +++ b/shared_crates/ui/src/prelude.rs @@ -1,10 +1,10 @@ //! A prelude for users of the crate. Imports all the most commonly used types and functions. pub use crate::{ - button::*, clickarea::*, default_theme::*, dropdown::*, editor::*, layout::*, prompt::*, - screens::*, scroll_area::*, select::*, tabs::*, text::*, throbber::*, use_focus, - use_window_logical_resolution, use_window_physical_resolution, with_rect, Focus, FocusRoot, - Line, Rectangle, UIBase, UIElement, UIExt, + button::*, clickarea::*, default_theme::*, dragarea::*, dropdown::*, editor::*, layout::*, + node::*, prompt::*, screens::*, scroll_area::*, select::*, tabs::*, text::*, throbber::*, + use_focus, use_window_logical_resolution, use_window_physical_resolution, with_rect, Focus, + FocusRoot, Line, Rectangle, UIBase, UIElement, UIExt, }; pub use ambient_cb::{cb, Cb}; pub use ambient_element::{ From 6eaf412cf45d5645741d01021814a6a114d00586 Mon Sep 17 00:00:00 2001 From: chaosprint Date: Wed, 5 Jul 2023 19:31:02 +0200 Subject: [PATCH 2/4] hotkey for new node in graph --- .../rust/examples/ui/nodegraph/src/client.rs | 44 +++---- shared_crates/ui/src/dragarea.rs | 1 + shared_crates/ui/src/node.rs | 117 +++++++++++++++--- 3 files changed, 126 insertions(+), 36 deletions(-) diff --git a/guest/rust/examples/ui/nodegraph/src/client.rs b/guest/rust/examples/ui/nodegraph/src/client.rs index 85ea8f0d6c..b757c54812 100644 --- a/guest/rust/examples/ui/nodegraph/src/client.rs +++ b/guest/rust/examples/ui/nodegraph/src/client.rs @@ -14,36 +14,38 @@ use ambient_api::{ #[element_component] fn App(_hooks: &mut Hooks) -> Element { - let (text, set_text) = _hooks.use_state("".to_string()); + // let (text, set_text) = _hooks.use_state("".to_string()); - let editor = TextEditor::new(text.clone(), set_text.clone()) - .placeholder(Some("type your node...")) - .auto_focus() - .el() - .with(width(), 100.) - .with(height(), 60.) - .with(background_color(), vec4(0.2, 0.6, 0.2, 0.6)) - .with(translation(), vec3(0., 40., 0.)); + // let editor = TextEditor::new(text.clone(), set_text.clone()) + // .placeholder(Some("type your node...")) + // .auto_focus() + // .el() + // .with(width(), 100.) + // .with(height(), 60.) + // .with(background_color(), vec4(0.2, 0.6, 0.2, 0.6)) + // .with(translation(), vec3(0., 40., 0.)); // .with_default(align_horizontal_center()) // .with_default(align_vertical_center()); - let body = Rectangle::el() - .with(width(), 100.) - .with(height(), 60.) - .with(translation(), vec3(0., 30., 0.)) - .with(background_color(), vec4(0.2, 0.2, 0.6, 0.6)); + // let body = Rectangle::el() + // .with(width(), 100.) + // .with(height(), 60.) + // .with(translation(), vec3(0., 30., 0.)) + // .with(background_color(), vec4(0.2, 0.2, 0.6, 0.6)); // .children(vec![text]); - let dragarea = Rectangle - .el() - .with(width(), 100.) - .with(height(), 30.) - .with(background_color(), vec4(0.6, 0.2, 0.2, 0.6)) - .children(vec![body, editor]); + // let dragarea = Rectangle + // .el() + // .with(width(), 100.) + // .with(height(), 30.) + // .with(background_color(), vec4(0.6, 0.2, 0.2, 0.6)) + // .children(vec![body, editor]); // FocusRoot::el([dragarea.with_dragarea().el()]) - Node::el() + // Node::el() + + Graph::el() } #[main] diff --git a/shared_crates/ui/src/dragarea.rs b/shared_crates/ui/src/dragarea.rs index 6d89f7d28b..a743f370e5 100644 --- a/shared_crates/ui/src/dragarea.rs +++ b/shared_crates/ui/src/dragarea.rs @@ -1,6 +1,7 @@ //! Defines the [DragArea] element. use ambient_element::{to_owned, Element, ElementComponent, Hooks}; +use ambient_guest_bridge::components::transform::{local_to_parent, local_to_world}; use ambient_guest_bridge::components::{app::cursor_position, transform::translation}; use ambient_guest_bridge::{ components::input::{mouse_over, mouse_pickable_max, mouse_pickable_min}, diff --git a/shared_crates/ui/src/node.rs b/shared_crates/ui/src/node.rs index dae9982e9d..9ca4b582d2 100644 --- a/shared_crates/ui/src/node.rs +++ b/shared_crates/ui/src/node.rs @@ -12,12 +12,13 @@ use ambient_guest_bridge::{ }, rect::{background_color, border_color, border_radius, border_thickness}, text::font_style, - transform::translation, + transform::{local_to_parent, local_to_world, translation}, }, messages, }; -use ambient_shared_types::CursorIcon; -use glam::{vec3, vec4, Vec3, Vec4}; +use ambient_shared_types::{CursorIcon, ModifiersState, VirtualKeyCode}; +use glam::{vec2, vec3, vec4, Vec2, Vec3, Vec4}; +use std::str::FromStr; #[element_component] /// A button UI element. @@ -74,31 +75,54 @@ pub fn Node(hooks: &mut Hooks) -> Element { }); let inlet = Rectangle::el() - .with(width(), 5.) + .with(width(), 10.) .with(height(), 10.) - // .with(background_color(), vec4(0.6, 0.2, 0.6, 0.6)) - // .with(border_radius(), Vec4::ONE * 5.) + .with(background_color(), vec4(0.5, 0.8, 1.0, 1.0)) + .with(border_radius(), Vec4::ONE * 5.) + .with(translation(), vec3(-5., 20., -0.1)) .init(mouse_pickable_min(), Vec3::ZERO) .init(mouse_pickable_max(), Vec3::ZERO) .on_spawned(move |_, new_id, _| { *in_id.lock() = Some(new_id); }); let outlet = Rectangle::el() - .with(width(), 5.) + .with(width(), 10.) .with(height(), 10.) - // .with(background_color(), vec4(0.2, 0.2, 0.6, 0.6)) - // .with(border_radius(), Vec4::ONE * 5.) - .with(translation(), vec3(60., 0., 0.)) + .with(background_color(), vec4(0.5, 0.8, 1.0, 1.0)) + .with(border_radius(), Vec4::ONE * 5.) + .with(translation(), vec3(195., 20., -0.1)) .init(mouse_pickable_min(), Vec3::ZERO) .init(mouse_pickable_max(), Vec3::ZERO) .on_spawned(move |_, new_id, _| { *out_id.lock() = Some(new_id); }); + let select = DropdownSelect { + content: Text::el("Select"), + on_select: cb(|_| {}), + items: vec![Text::el("First"), Text::el("Second")], + inline: false, + } + .el() + .with_padding_even(10.) + .with_margin_even(10.); + let body = Rectangle::el() + .with(background_color(), vec4(0.4, 0.4, 0.4, 0.6)) + .with(width(), 200.) + .with(height(), 300.) + .with(translation(), vec3(0., 10., 0.)) + .with(border_radius(), vec4(0., 0., 5., 5.)) + .with(border_color(), vec4(0.2, 0.2, 0.2, 0.6)) + .with(border_thickness(), 1.0) + .with_padding_even(10.) + .with_margin_even(10.) + .children(vec![select]); + Rectangle::el() - .with(background_color(), vec4(0.2, 0.6, 0.6, 0.6)) - .with(width(), 60.) - .with(height(), 30.) - .children(vec![inlet, outlet]) + .with(background_color(), vec4(0.2, 0.2, 0.2, 0.6)) + .with(width(), 200.) + .with(height(), 10.) + .with(border_radius(), vec4(5., 5., 0., 0.)) + .children(vec![inlet, outlet, body]) .with_dragarea() .el() } @@ -106,5 +130,68 @@ pub fn Node(hooks: &mut Hooks) -> Element { #[element_component] /// A button UI element. pub fn Graph(hooks: &mut Hooks) -> Element { - crate::text::Text::el("Graph") + let (nodes, set_nodes) = hooks.use_state(vec![]); + // let (nodes_pos, set_nodes_pos) = hooks.use_state(vec![]); + hooks.use_runtime_message::({ + to_owned![nodes]; + move |world, event| { + // let modifiers = ModifiersState::from_bits(event.modifiers).unwrap(); + // if modifiers.contains(ModifiersState::SHIFT) { + // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Crosshair); + // } else { + // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Default); + // } + + if let Some(virtual_keycode) = event + .keycode + .as_deref() + .and_then(|x| VirtualKeyCode::from_str(x).ok()) + { + println!("key: {:?}", virtual_keycode); + if virtual_keycode != VirtualKeyCode::I { + return; + } + if event.pressed { + let mut nodes = nodes.clone(); + // let mut nodes_pos = nodes_pos.clone(); + // nodes_pos.push(world.resource(cursor_position()).clone()); + nodes.push(NodeInfo { + pos: world.resource(cursor_position()).clone(), + }); + set_nodes(nodes); + } + } + // if event.pressed && event.button == 0 { + // println!("mouse left down"); + // println!("cursor pos: {:?}", world.resource(cursor_position())); + // let mut nodes = nodes.clone(); + // nodes.push(NodeInfo { + // pos: world.resource(cursor_position()).clone(), + // }); + // set_nodes(nodes); + // } + } + }); + + Group::el( + nodes + .iter() + .enumerate() + .map(move |(i, node)| Node::el()) + .collect::>(), + ) } + +/// Node info. +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct NodeInfo { + /// pos + pos: Vec2, +} + +// #[derive(Debug, PartialEq, Clone, Copy)] +// pub enum NodeKind { +// Number, +// Bang, +// Math, +// } From 2052b95cdec6a14a0545ae83707276b68429b40b Mon Sep 17 00:00:00 2001 From: chaosprint Date: Wed, 5 Jul 2023 22:44:41 +0200 Subject: [PATCH 3/4] first working connection --- .../rust/examples/ui/nodegraph/src/client.rs | 2 +- shared_crates/ui/src/dragarea.rs | 2 + shared_crates/ui/src/node.rs | 287 +++++++++++++++--- 3 files changed, 252 insertions(+), 39 deletions(-) diff --git a/guest/rust/examples/ui/nodegraph/src/client.rs b/guest/rust/examples/ui/nodegraph/src/client.rs index b757c54812..170b8a5c4d 100644 --- a/guest/rust/examples/ui/nodegraph/src/client.rs +++ b/guest/rust/examples/ui/nodegraph/src/client.rs @@ -45,7 +45,7 @@ fn App(_hooks: &mut Hooks) -> Element { // Node::el() - Graph::el() + GraphFull::el() } #[main] diff --git a/shared_crates/ui/src/dragarea.rs b/shared_crates/ui/src/dragarea.rs index a743f370e5..702d3a7468 100644 --- a/shared_crates/ui/src/dragarea.rs +++ b/shared_crates/ui/src/dragarea.rs @@ -86,6 +86,8 @@ impl ElementComponent for DragArea { } else { set_moving(false); } + } else { + set_moving(false); } } } diff --git a/shared_crates/ui/src/node.rs b/shared_crates/ui/src/node.rs index 9ca4b582d2..507005a431 100644 --- a/shared_crates/ui/src/node.rs +++ b/shared_crates/ui/src/node.rs @@ -5,12 +5,16 @@ use ambient_element::{element_component, to_owned, Element, ElementComponent, Ho use ambient_guest_bridge::{ components::{ app::cursor_position, + ecs::parent, input::{mouse_over, mouse_pickable_max, mouse_pickable_min}, layout::{ align_vertical_center, fit_horizontal_parent, height, margin, min_height, padding, space_between_items, width, }, - rect::{background_color, border_color, border_radius, border_thickness}, + rect::{ + background_color, border_color, border_radius, border_thickness, line_from, line_to, + line_width, + }, text::font_style, transform::{local_to_parent, local_to_world, translation}, }, @@ -21,18 +25,199 @@ use glam::{vec2, vec3, vec4, Vec2, Vec3, Vec4}; use std::str::FromStr; #[element_component] -/// A button UI element. +/// Test UI element. +pub fn GraphFull(hooks: &mut Hooks) -> Element { + let (nodes, set_nodes) = hooks.use_state(vec![]); + let in_id = hooks.use_ref_with(|_| vec![]); + let out_id = hooks.use_ref_with(|_| vec![]); + let start_id = hooks.use_ref_with(|_| None); + let (lines, set_lines) = hooks.use_state(vec![]); + // let in_mouse_over_count = hooks.use_ref_with(|_| 0); + // let out_mouse_over_count = hooks.use_ref_with(|_| 0); + + hooks.use_runtime_message::({ + to_owned![out_id, in_id, start_id, lines, set_lines]; + move |world, event| { + println!("mouse input: {:?}", event); + for id in out_id.lock().iter() { + if event.pressed && event.button == 0 { + let mouse_over_state = world.get(*id, mouse_over()).unwrap_or(0); + if mouse_over_state > 0 { + println!("prepare to connect"); + let pos = world.get(*id, translation()).unwrap_or(Vec3::ZERO); + let p = world.get(*id, parent()).unwrap(); + let p_pos = world.get(p, translation()).unwrap_or(Vec3::ZERO); + let start_pos = pos + p_pos + vec3(5.0, 5.0, 0.0); + *start_id.lock() = Some(start_pos); + } + } + } + + for id in in_id.lock().iter() { + if !event.pressed && event.button == 0 { + let mouse_over_state = world.get(*id, mouse_over()).unwrap_or(0); + if mouse_over_state > 0 { + println!("prepare to drop the connection"); + if let Some(start_id) = *start_id.lock() { + let mut l = lines.clone(); + let pos = world.get(*id, translation()).unwrap_or(Vec3::ZERO); + let p = world.get(*id, parent()).unwrap(); + let p_pos = world.get(p, translation()).unwrap_or(Vec3::ZERO); + let end_pos = pos + p_pos + vec3(5.0, 5.0, 0.0); + l.push((start_id, end_pos)); + set_lines(l); + // println!("connect {} to {}", start_id, id); + } else { + println!("no start id"); + // *start_id.lock() = None; + } + } else { + println!("no start id"); + // *start_id.lock() = None; + } + } + } + } + }); + + hooks.use_runtime_message::({ + to_owned![nodes]; + move |world, event| { + if let Some(virtual_keycode) = event + .keycode + .as_deref() + .and_then(|x| VirtualKeyCode::from_str(x).ok()) + { + println!("key: {:?}", virtual_keycode); + if virtual_keycode != VirtualKeyCode::I { + return; + } + if event.pressed { + let mut nodes = nodes.clone(); + nodes.push(NodeInfo { + pos: world.resource(cursor_position()).clone().extend(-0.001), + }); + set_nodes(nodes); + } + } + } + }); + let ga = Group::el( + nodes + .iter() + .map(move |node| { + println!("node: {:?}", node); + + let inlet = Rectangle::el() + .with(width(), 10.) + .with(height(), 10.) + .with(background_color(), vec4(0.5, 0.8, 1.0, 1.0)) + .with(border_radius(), Vec4::ONE * 5.) + .with(translation(), vec3(-5., 20., -0.1)) + .init(mouse_pickable_min(), Vec3::ZERO) + .init(mouse_pickable_max(), Vec3::ZERO) + .on_spawned({ + to_owned![in_id]; + move |_, new_id, _| { + in_id.lock().push(new_id); + // *in_id.lock() = Some(new_id); + } + }); + let outlet = Rectangle::el() + .with(width(), 10.) + .with(height(), 10.) + .with(background_color(), vec4(0.5, 0.8, 1.0, 1.0)) + .with(border_radius(), Vec4::ONE * 5.) + .with(translation(), vec3(195., 20., -0.1)) + .init(mouse_pickable_min(), Vec3::ZERO) + .init(mouse_pickable_max(), Vec3::ZERO) + .on_spawned({ + to_owned![out_id]; + move |_, new_id, _| { + out_id.lock().push(new_id); + } + }); + let select = DropdownSelect { + content: Text::el("Select"), + on_select: cb(|_| {}), + items: vec![Text::el("First"), Text::el("Second")], + inline: false, + } + .el() + .with_padding_even(10.); + // .with_margin_even(10.); + let body = Rectangle::el() + .with(background_color(), vec4(0.4, 0.4, 0.4, 0.6)) + .with(width(), 200.) + .with(height(), 300.) + .with(translation(), vec3(0., 10., 0.)) + .with(border_radius(), vec4(0., 0., 5., 5.)) + .with(border_color(), vec4(0.2, 0.2, 0.2, 0.6)) + .with(border_thickness(), 1.0) + .with_padding_even(10.) + .with_margin_even(10.) + .children(vec![select]); + + Rectangle::el() + .with(background_color(), vec4(0.2, 0.2, 0.2, 0.6)) + .with(width(), 200.) + .with(height(), 10.) + .with(border_radius(), vec4(5., 5., 0., 0.)) + .children(vec![inlet, outlet, body]) + .with_dragarea() + .el() + .on_spawned({ + to_owned![node]; + move |world, new_id, _| { + println!("spawned node: {}", new_id); + // id_list.lock().push(new_id); + // id_list.lock().push(new_id); + world.set(new_id, translation(), node.pos).unwrap(); + } + }) + + // Node::el().on_spawned({ + // to_owned![id_list, node]; + // move |world, new_id, _| { + // println!("spawned node: {}", new_id); + // id_list.lock().push(new_id); + // // id_list.lock().push(new_id); + // world.set(new_id, translation(), node.pos).unwrap(); + // } + // }) + }) + .collect::>(), + ); + let gb = Group::el( + lines + .iter() + .map(|(from, to)| { + // let from = world.get(l.0, translation()).unwrap_or(Vec3::ZERO); + Line.el() + .with(line_from(), *from) + .with(line_to(), *to) + .with(line_width(), 2.) + .with(background_color(), vec4(0.5, 0.8, 1.0, 1.)) + }) + .collect::>(), + ); + Group::el([ga, gb]) +} + +#[element_component] +/// A Node UI element. pub fn Node(hooks: &mut Hooks) -> Element { let in_id = hooks.use_ref_with(|_| None); let out_id = hooks.use_ref_with(|_| None); - let mouse_over_count = hooks.use_ref_with(|_| 0); + let in_mouse_over_count = hooks.use_ref_with(|_| 0); + let out_mouse_over_count = hooks.use_ref_with(|_| 0); hooks.use_frame({ - to_owned![out_id, mouse_over_count]; + to_owned![out_id, in_id, in_mouse_over_count, out_mouse_over_count]; move |world| { if let Some(id) = *out_id.lock() { let next = world.get(id, mouse_over()).unwrap_or(0); - let mut state = mouse_over_count.lock(); + let mut state = out_mouse_over_count.lock(); // if *state == 0 && next > 0 { // println!("mouse enter outlet"); // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Arrow); @@ -49,18 +234,39 @@ pub fn Node(hooks: &mut Hooks) -> Element { // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Default); // } + *state = next; + } + if let Some(id) = *in_id.lock() { + let next = world.get(id, mouse_over()).unwrap_or(0); + let mut state = in_mouse_over_count.lock(); + // if *state == 0 && next > 0 { + // println!("mouse enter inlet"); + // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Arrow); + // } + // if *state > 0 && next == 0 { + // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Default); + // } + + if next > 0 { + ambient_guest_bridge::window::set_cursor(world, CursorIcon::Grab); + }; + + // else { + // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Default); + // } + *state = next; } } }); hooks.use_runtime_message::({ - to_owned![out_id, mouse_over_count]; + to_owned![out_id, in_id, in_mouse_over_count, out_mouse_over_count]; move |world, event| { if let Some(id) = *out_id.lock() { - if *mouse_over_count.lock() > 0 { + if *out_mouse_over_count.lock() > 0 { if event.pressed && event.button == 0 { - println!("mouse left down"); + println!("mouse left down at {:?}", id); // set_moving(true); // let mouse_pos = world.resource(cursor_position()); // let pos = world.get(id, translation()).unwrap_or(Vec3::ZERO); @@ -71,6 +277,13 @@ pub fn Node(hooks: &mut Hooks) -> Element { } } } + if let Some(id) = *in_id.lock() { + if *in_mouse_over_count.lock() > 0 { + if !event.pressed && event.button == 0 { + println!("mouse left up at {:?}", id); + } + } + } } }); @@ -131,17 +344,11 @@ pub fn Node(hooks: &mut Hooks) -> Element { /// A button UI element. pub fn Graph(hooks: &mut Hooks) -> Element { let (nodes, set_nodes) = hooks.use_state(vec![]); - // let (nodes_pos, set_nodes_pos) = hooks.use_state(vec![]); + let id_list = hooks.use_ref_with(|_| vec![]); + // (vec2(0., 0.), vec2(0., 0.)) hooks.use_runtime_message::({ to_owned![nodes]; move |world, event| { - // let modifiers = ModifiersState::from_bits(event.modifiers).unwrap(); - // if modifiers.contains(ModifiersState::SHIFT) { - // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Crosshair); - // } else { - // ambient_guest_bridge::window::set_cursor(world, CursorIcon::Default); - // } - if let Some(virtual_keycode) = event .keycode .as_deref() @@ -153,31 +360,42 @@ pub fn Graph(hooks: &mut Hooks) -> Element { } if event.pressed { let mut nodes = nodes.clone(); - // let mut nodes_pos = nodes_pos.clone(); - // nodes_pos.push(world.resource(cursor_position()).clone()); nodes.push(NodeInfo { - pos: world.resource(cursor_position()).clone(), + pos: world.resource(cursor_position()).clone().extend(-0.001), }); set_nodes(nodes); } } - // if event.pressed && event.button == 0 { - // println!("mouse left down"); - // println!("cursor pos: {:?}", world.resource(cursor_position())); - // let mut nodes = nodes.clone(); - // nodes.push(NodeInfo { - // pos: world.resource(cursor_position()).clone(), - // }); - // set_nodes(nodes); - // } } }); + // hooks.use_frame({ + // to_owned![id_list]; + // move |world| { + // let mut id_list = id_list.lock(); + // for id in id_list.iter() { + // let pos = world.get(*id, translation()).unwrap_or(Vec3::ZERO); + // // println!("pos: {:?}", pos); + // } + // } + // }); + Group::el( nodes .iter() - .enumerate() - .map(move |(i, node)| Node::el()) + .map(move |node| { + println!("node: {:?}", node); + // Group::el(vec![ + Node::el().on_spawned({ + to_owned![id_list, node]; + move |world, new_id, _| { + println!("spawned node: {}", new_id); + id_list.lock().push(new_id); + // id_list.lock().push(new_id); + world.set(new_id, translation(), node.pos).unwrap(); + } + }) + }) .collect::>(), ) } @@ -186,12 +404,5 @@ pub fn Graph(hooks: &mut Hooks) -> Element { #[derive(Debug, PartialEq, Clone, Copy)] pub struct NodeInfo { /// pos - pos: Vec2, + pos: Vec3, } - -// #[derive(Debug, PartialEq, Clone, Copy)] -// pub enum NodeKind { -// Number, -// Bang, -// Math, -// } From ca98c01630922726723a0f21dfc4044bd2410a32 Mon Sep 17 00:00:00 2001 From: chaosprint Date: Thu, 6 Jul 2023 13:40:29 +0200 Subject: [PATCH 4/4] move edges when nodes move --- .../rust/examples/ui/nodegraph/src/client.rs | 33 +--- shared_crates/ui/src/node.rs | 176 +++++++++--------- 2 files changed, 85 insertions(+), 124 deletions(-) diff --git a/guest/rust/examples/ui/nodegraph/src/client.rs b/guest/rust/examples/ui/nodegraph/src/client.rs index 170b8a5c4d..11dbd589d6 100644 --- a/guest/rust/examples/ui/nodegraph/src/client.rs +++ b/guest/rust/examples/ui/nodegraph/src/client.rs @@ -14,38 +14,7 @@ use ambient_api::{ #[element_component] fn App(_hooks: &mut Hooks) -> Element { - // let (text, set_text) = _hooks.use_state("".to_string()); - - // let editor = TextEditor::new(text.clone(), set_text.clone()) - // .placeholder(Some("type your node...")) - // .auto_focus() - // .el() - // .with(width(), 100.) - // .with(height(), 60.) - // .with(background_color(), vec4(0.2, 0.6, 0.2, 0.6)) - // .with(translation(), vec3(0., 40., 0.)); - - // .with_default(align_horizontal_center()) - // .with_default(align_vertical_center()); - - // let body = Rectangle::el() - // .with(width(), 100.) - // .with(height(), 60.) - // .with(translation(), vec3(0., 30., 0.)) - // .with(background_color(), vec4(0.2, 0.2, 0.6, 0.6)); - // .children(vec![text]); - - // let dragarea = Rectangle - // .el() - // .with(width(), 100.) - // .with(height(), 30.) - // .with(background_color(), vec4(0.6, 0.2, 0.2, 0.6)) - // .children(vec![body, editor]); - // FocusRoot::el([dragarea.with_dragarea().el()]) - - // Node::el() - - GraphFull::el() + Graph::el() } #[main] diff --git a/shared_crates/ui/src/node.rs b/shared_crates/ui/src/node.rs index 507005a431..4ad33ba1b1 100644 --- a/shared_crates/ui/src/node.rs +++ b/shared_crates/ui/src/node.rs @@ -21,34 +21,76 @@ use ambient_guest_bridge::{ messages, }; use ambient_shared_types::{CursorIcon, ModifiersState, VirtualKeyCode}; -use glam::{vec2, vec3, vec4, Vec2, Vec3, Vec4}; +use glam::{vec2, vec3, vec4, Mat4, Vec2, Vec3, Vec4}; use std::str::FromStr; #[element_component] -/// Test UI element. -pub fn GraphFull(hooks: &mut Hooks) -> Element { +/// A Graph Element that you can put lots of nodes +pub fn Graph(hooks: &mut Hooks) -> Element { let (nodes, set_nodes) = hooks.use_state(vec![]); let in_id = hooks.use_ref_with(|_| vec![]); let out_id = hooks.use_ref_with(|_| vec![]); let start_id = hooks.use_ref_with(|_| None); let (lines, set_lines) = hooks.use_state(vec![]); - // let in_mouse_over_count = hooks.use_ref_with(|_| 0); - // let out_mouse_over_count = hooks.use_ref_with(|_| 0); + let (temp_line, set_temp_line) = hooks.use_state(None::<(Vec3, Vec3)>); + let temp_line_toggle = hooks.use_ref_with(|_| false); + hooks.use_frame({ + to_owned![lines, set_lines, temp_line, set_temp_line, temp_line_toggle]; + move |world| { + if *temp_line_toggle.lock() { + if let Some(line) = temp_line { + let mouse_pos = world.resource(cursor_position()); + set_temp_line(Some((line.0, mouse_pos.extend(line.1.z)))); + } + } + + for (index, ((from_id, from_pos), (to_id, to_pos))) in lines.iter().enumerate() { + // let new_from_pos = world.get(*from_id, translation()).unwrap_or(Vec3::ZERO); + // let new_to_pos = world.get(*to_id, translation()).unwrap_or(Vec3::ZERO); + let ltw = world.get(*from_id, local_to_world()).unwrap(); + let (_, _, new_from_pos) = Mat4::to_scale_rotation_translation(<w); + let new_from_pos = new_from_pos + vec3(5.0, 5.0, 0.0); + + if new_from_pos != *from_pos { + let mut l = lines.clone(); + l[index] = ((*from_id, new_from_pos), (*to_id, *to_pos)); + set_lines(l); + } + + let ltw = world.get(*to_id, local_to_world()).unwrap(); + let (_, _, new_to_pos) = Mat4::to_scale_rotation_translation(<w); + let new_to_pos = new_to_pos + vec3(5.0, 5.0, 0.0); + + if new_to_pos != *to_pos { + let mut l = lines.clone(); + l[index] = ((*from_id, *from_pos), (*to_id, new_to_pos)); + set_lines(l); + } + } + } + }); hooks.use_runtime_message::({ - to_owned![out_id, in_id, start_id, lines, set_lines]; + to_owned![ + out_id, + in_id, + start_id, + lines, + set_lines, + set_temp_line, + temp_line_toggle + ]; move |world, event| { - println!("mouse input: {:?}", event); for id in out_id.lock().iter() { if event.pressed && event.button == 0 { let mouse_over_state = world.get(*id, mouse_over()).unwrap_or(0); if mouse_over_state > 0 { - println!("prepare to connect"); - let pos = world.get(*id, translation()).unwrap_or(Vec3::ZERO); - let p = world.get(*id, parent()).unwrap(); - let p_pos = world.get(p, translation()).unwrap_or(Vec3::ZERO); - let start_pos = pos + p_pos + vec3(5.0, 5.0, 0.0); - *start_id.lock() = Some(start_pos); + let ltw = world.get(*id, local_to_world()).unwrap(); + let (_, _, pos) = Mat4::to_scale_rotation_translation(<w); + let start_pos = pos + vec3(5.0, 5.0, 0.0); + *start_id.lock() = Some((id.clone(), start_pos)); + *temp_line_toggle.lock() = true; + set_temp_line(Some((start_pos, start_pos))) } } } @@ -57,26 +99,25 @@ pub fn GraphFull(hooks: &mut Hooks) -> Element { if !event.pressed && event.button == 0 { let mouse_over_state = world.get(*id, mouse_over()).unwrap_or(0); if mouse_over_state > 0 { - println!("prepare to drop the connection"); if let Some(start_id) = *start_id.lock() { let mut l = lines.clone(); - let pos = world.get(*id, translation()).unwrap_or(Vec3::ZERO); - let p = world.get(*id, parent()).unwrap(); - let p_pos = world.get(p, translation()).unwrap_or(Vec3::ZERO); - let end_pos = pos + p_pos + vec3(5.0, 5.0, 0.0); - l.push((start_id, end_pos)); + let ltw = world.get(*id, local_to_world()).unwrap(); + let (_, _, pos) = Mat4::to_scale_rotation_translation(<w); + let end_pos = pos + vec3(5.0, 5.0, 0.0); + l.push((start_id, (id.clone(), end_pos))); set_lines(l); - // println!("connect {} to {}", start_id, id); } else { println!("no start id"); - // *start_id.lock() = None; } } else { - println!("no start id"); - // *start_id.lock() = None; + println!("dropped to none inlet"); } } } + if !event.pressed { + *temp_line_toggle.lock() = false; + set_temp_line(None); + } } }); @@ -88,7 +129,6 @@ pub fn GraphFull(hooks: &mut Hooks) -> Element { .as_deref() .and_then(|x| VirtualKeyCode::from_str(x).ok()) { - println!("key: {:?}", virtual_keycode); if virtual_keycode != VirtualKeyCode::I { return; } @@ -102,12 +142,11 @@ pub fn GraphFull(hooks: &mut Hooks) -> Element { } } }); - let ga = Group::el( + let group_nodes = Group::el( nodes .iter() .map(move |node| { println!("node: {:?}", node); - let inlet = Rectangle::el() .with(width(), 10.) .with(height(), 10.) @@ -120,7 +159,6 @@ pub fn GraphFull(hooks: &mut Hooks) -> Element { to_owned![in_id]; move |_, new_id, _| { in_id.lock().push(new_id); - // *in_id.lock() = Some(new_id); } }); let outlet = Rectangle::el() @@ -170,8 +208,6 @@ pub fn GraphFull(hooks: &mut Hooks) -> Element { to_owned![node]; move |world, new_id, _| { println!("spawned node: {}", new_id); - // id_list.lock().push(new_id); - // id_list.lock().push(new_id); world.set(new_id, translation(), node.pos).unwrap(); } }) @@ -188,10 +224,10 @@ pub fn GraphFull(hooks: &mut Hooks) -> Element { }) .collect::>(), ); - let gb = Group::el( + let group_edges = Group::el( lines .iter() - .map(|(from, to)| { + .map(|((_, from), (_, to))| { // let from = world.get(l.0, translation()).unwrap_or(Vec3::ZERO); Line.el() .with(line_from(), *from) @@ -201,7 +237,23 @@ pub fn GraphFull(hooks: &mut Hooks) -> Element { }) .collect::>(), ); - Group::el([ga, gb]) + let temp_line_el = { + to_owned![temp_line_toggle, temp_line]; + if let Some(line) = temp_line { + if *temp_line_toggle.lock() { + Line.el() + .with(line_from(), line.0) + .with(line_to(), line.1) + .with(line_width(), 2.) + .with(background_color(), vec4(0.5, 0.8, 1.0, 1.)) + } else { + Element::new() + } + } else { + Element::new() + } + }; + Group::el([group_nodes, group_edges, temp_line_el]) } #[element_component] @@ -340,66 +392,6 @@ pub fn Node(hooks: &mut Hooks) -> Element { .el() } -#[element_component] -/// A button UI element. -pub fn Graph(hooks: &mut Hooks) -> Element { - let (nodes, set_nodes) = hooks.use_state(vec![]); - let id_list = hooks.use_ref_with(|_| vec![]); - // (vec2(0., 0.), vec2(0., 0.)) - hooks.use_runtime_message::({ - to_owned![nodes]; - move |world, event| { - if let Some(virtual_keycode) = event - .keycode - .as_deref() - .and_then(|x| VirtualKeyCode::from_str(x).ok()) - { - println!("key: {:?}", virtual_keycode); - if virtual_keycode != VirtualKeyCode::I { - return; - } - if event.pressed { - let mut nodes = nodes.clone(); - nodes.push(NodeInfo { - pos: world.resource(cursor_position()).clone().extend(-0.001), - }); - set_nodes(nodes); - } - } - } - }); - - // hooks.use_frame({ - // to_owned![id_list]; - // move |world| { - // let mut id_list = id_list.lock(); - // for id in id_list.iter() { - // let pos = world.get(*id, translation()).unwrap_or(Vec3::ZERO); - // // println!("pos: {:?}", pos); - // } - // } - // }); - - Group::el( - nodes - .iter() - .map(move |node| { - println!("node: {:?}", node); - // Group::el(vec![ - Node::el().on_spawned({ - to_owned![id_list, node]; - move |world, new_id, _| { - println!("spawned node: {}", new_id); - id_list.lock().push(new_id); - // id_list.lock().push(new_id); - world.set(new_id, translation(), node.pos).unwrap(); - } - }) - }) - .collect::>(), - ) -} - /// Node info. #[derive(Debug, PartialEq, Clone, Copy)] pub struct NodeInfo {