Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Widgets integrated with graph nodes #6347

Merged
merged 47 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
ea01ead
common widget configuration struct
Frizi Mar 20, 2023
d721161
wip widget tree
Frizi Mar 24, 2023
94b3d06
proper alignment for widgets
Frizi Apr 2, 2023
62fd98e
widgets on nodes wip
Frizi Apr 4, 2023
5c840ed
use replace_children
Frizi Apr 4, 2023
8a4cbb6
fix object parenting after dynamic widget type update
Frizi Apr 4, 2023
1906293
lazy replace_children, finish get_port_widget_pointer
Frizi Apr 12, 2023
7572273
handle widget offsets at port level, add port padding
Frizi Apr 12, 2023
651db04
wip port mouse handling
Frizi Apr 13, 2023
6083603
edge dragging
Frizi Apr 14, 2023
f8ec02b
correct spacing for widgets without ports, bigger hover area for prim…
Frizi Apr 14, 2023
371fb9e
better node coloring and more docs
Frizi Apr 17, 2023
0dfec50
add more docs
Frizi Apr 18, 2023
1db7de2
properly propagate expression usage type
Frizi Apr 18, 2023
a790859
fix edge color and origin width update
Frizi Apr 18, 2023
107a357
fix label widget hover color
Frizi Apr 18, 2023
cd91a67
lint
Frizi Apr 19, 2023
7810d2a
do not create dropdown elements until opened
Frizi Apr 19, 2023
119f431
fix vector editor default metadata selection
Frizi Apr 19, 2023
4775d57
allow multiple widgets on the same span
Frizi Apr 20, 2023
d8082a2
add widget tree hierarchy and iterators
Frizi Apr 20, 2023
ff01e79
handle edit mode transition cursor
Frizi Apr 20, 2023
149a8c7
lint and tests
Frizi Apr 20, 2023
66dafee
changelog
Frizi Apr 20, 2023
cc5f970
fix graph editor tests
Frizi Apr 20, 2023
202c62f
fix span-tree tests
Frizi Apr 20, 2023
7e56012
use nested label widget for single-child dropdown, improve hover beha…
Frizi Apr 21, 2023
adfcbdc
store connection data using crumbs
Frizi Apr 21, 2023
46fb9f4
fix port memory leak
Frizi Apr 21, 2023
c99c4dc
address part of review comments
Frizi Apr 21, 2023
a05209b
rename widget updates to configuration
Frizi Apr 21, 2023
907ea2c
configuration -> definition
Frizi Apr 21, 2023
8b9a219
widget metadata -> configuration
Frizi Apr 21, 2023
43c2f09
document input widget module
Frizi Apr 21, 2023
2133de7
rewrite and test replace_children
Frizi Apr 24, 2023
4c2da3c
use superbox for widget ports
Frizi Apr 24, 2023
aaadab3
fix node size on initialization
Frizi Apr 24, 2023
cdb00eb
split single choice widget frp
Frizi Apr 24, 2023
b7eb3b7
make it clear that span tree holds parent offset
Frizi Apr 24, 2023
cf7a04b
fixes after rebase
Frizi Apr 25, 2023
e5190d5
self-review
Frizi Apr 25, 2023
00a455b
fix sibling_offset
Frizi Apr 25, 2023
c19da31
provide widget child id to the parent
Frizi Apr 25, 2023
269883d
Merge branch 'develop' into wip/frizi/widgets-on-node
Frizi Apr 25, 2023
ac80f65
fix list editor cursor and port visibility
Frizi Apr 26, 2023
9f07c5f
format
Frizi Apr 26, 2023
9325260
Merge branch 'develop' into wip/frizi/widgets-on-node
Frizi Apr 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@
quickly understand each button's function.
- [File associations are created on Windows and macOS][6077]. This allows
opening Enso files by double-clicking them in the file explorer.
- [Added capability to create node widgets with complex UI][6347]. Node widgets
such as dropdown can now be placed in the node and affect the code text flow.
- [The IDE UI element for selecting the execution mode of the project is now
sending messages to the backend.][6341].

Expand Down Expand Up @@ -593,6 +595,7 @@
[6253]: https://github.com/enso-org/enso/pull/6253
[6294]: https://github.com/enso-org/enso/pull/6294
[6383]: https://github.com/enso-org/enso/pull/6383
[6347]: https://github.com/enso-org/enso/pull/6347

#### Enso Compiler

Expand Down
12 changes: 6 additions & 6 deletions app/gui/language/span-tree/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ use ast::Ast;



/// ==============
/// === Errors ===
/// ==============
// ==============
// === Errors ===
// ==============

/// Error returned when tried to perform an action which is not available for specific SpanTree
/// node.
Expand All @@ -35,9 +35,9 @@ pub struct AstSpanTreeMismatch;



/// =====================
/// === Actions Trait ===
/// =====================
// =====================
// === Actions Trait ===
// =====================

/// Action enum used mainly for error messages.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
Expand Down
30 changes: 16 additions & 14 deletions app/gui/language/span-tree/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,23 @@ pub trait Builder<T: Payload>: Sized {
/// Add new AST-type child to node. Returns the child's builder which may be used to further
/// extend this branch of the tree.
fn add_child(
self,
offset: usize,
mut self,
parent_offset: usize,
len: usize,
kind: impl Into<node::Kind>,
crumbs: impl IntoCrumbs,
) -> ChildBuilder<Self, T> {
let kind = kind.into();
let node = Node::<T>::new().with_kind(kind).with_size(len.into());
let child = node::Child { node, offset: offset.into(), ast_crumbs: crumbs.into_crumbs() };
let prev_child = self.node_being_built().children.last();
let prev_child_end = prev_child.map_or(0, |c| (c.parent_offset + c.node.size).as_usize());
let sibling_offset = parent_offset.saturating_sub(prev_child_end);
let child = node::Child {
node,
parent_offset: parent_offset.into(),
sibling_offset: sibling_offset.into(),
ast_crumbs: crumbs.into_crumbs(),
};
ChildBuilder { built: child, parent: self }
}

Expand All @@ -46,14 +54,8 @@ pub trait Builder<T: Payload>: Sized {
}

/// Add an Empty-type child to node.
fn add_empty_child(mut self, offset: usize, kind: impl Into<node::Kind>) -> Self {
let child = node::Child {
node: Node::<T>::new().with_kind(kind),
offset: offset.into(),
ast_crumbs: vec![],
};
self.node_being_built().children.push(child);
self
fn add_empty_child(self, offset: usize, kind: impl Into<node::Kind>) -> Self {
self.add_leaf(offset, 0, kind, ast::crumbs![])
}

/// Set expression id for this node.
Expand All @@ -65,9 +67,9 @@ pub trait Builder<T: Payload>: Sized {



/// ================
/// === Builders ===
/// ================
// ================
// === Builders ===
// ================

// === SpanTree Builder ===

Expand Down
54 changes: 33 additions & 21 deletions app/gui/language/span-tree/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,17 @@ impl<T: Payload> SpanTreeGenerator<T> for String {
#[derivative(Default(bound = ""))]
struct ChildGenerator<T> {
current_offset: ByteDiff,
sibling_offset: ByteDiff,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call note: to be probably deleted as, current_offset probably should be offset to sibling.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that there is a good reason for storing offset to parent. The child method allows random access to any child by index, and it requires parent offset to easily calculate the child span's start. It is used everywhere, as it is the base of indexing the tree by crumbs. So we have to it. I will rename the fields and fix some inaccurate comments to make it a little better.

children: Vec<node::Child<T>>,
}

impl<T: Payload> ChildGenerator<T> {
/// Add spacing to current generator state. It will be taken into account for the next generated
/// children's offsets
fn spacing(&mut self, size: usize) {
self.current_offset += (size as i32).byte_diff();
let offset = (size as i32).byte_diff();
self.current_offset += offset;
self.sibling_offset += offset;
}

fn generate_ast_node(
Expand All @@ -115,27 +118,26 @@ impl<T: Payload> ChildGenerator<T> {
}

fn add_node(&mut self, ast_crumbs: ast::Crumbs, node: Node<T>) -> &mut node::Child<T> {
let offset = self.current_offset;
let child = node::Child { node, offset, ast_crumbs };
let parent_offset = self.current_offset;
let sibling_offset = self.sibling_offset;
let child = node::Child { node, parent_offset, sibling_offset, ast_crumbs };
self.current_offset += child.node.size;
self.sibling_offset = 0.byte_diff();
self.children.push(child);
self.children.last_mut().unwrap()
}

fn generate_empty_node(&mut self, insert_type: InsertionPointType) -> &mut node::Child<T> {
let child = node::Child {
node: Node::<T>::new().with_kind(insert_type),
offset: self.current_offset,
ast_crumbs: vec![],
};
self.children.push(child);
self.children.last_mut().unwrap()
self.add_node(vec![], Node::<T>::new().with_kind(insert_type))
}

fn reverse_children(&mut self) {
self.children.reverse();
let mut last_parent_offset = 0.byte_diff();
for child in &mut self.children {
child.offset = self.current_offset - child.offset - child.node.size;
child.parent_offset = self.current_offset - child.parent_offset - child.node.size;
child.sibling_offset = child.parent_offset - last_parent_offset;
last_parent_offset = child.parent_offset;
}
}

Expand All @@ -149,9 +151,9 @@ impl<T: Payload> ChildGenerator<T> {



/// =============================
/// === Trait Implementations ===
/// =============================
// =============================
// === Trait Implementations ===
// =============================

/// Helper structure constructed from Ast that consists base of prefix application.
///
Expand Down Expand Up @@ -528,6 +530,10 @@ fn generate_node_for_prefix_chain<T: Payload>(
context: &impl Context,
) -> FallibleResult<Node<T>> {
let app_base = ApplicationBase::from_prefix_chain(this);

// If actual method arguments are not resolved, we still want to assign correct call ID to all
// argument spans. This is required for correct handling of span tree actions, as it is used to
// determine correct reinsertion point for removed span.
let fallback_call_id = app_base.call_id;
let mut application = app_base.resolve(context);

Expand Down Expand Up @@ -811,29 +817,35 @@ fn tree_generate_node<T: Payload>(
if let Some(leaf_info) = &tree.leaf_info {
size = ByteDiff::from(leaf_info.len());
} else {
let mut offset = ByteDiff::from(0);
let mut parent_offset = ByteDiff::from(0);
let mut sibling_offset = ByteDiff::from(0);
for (index, raw_span_info) in tree.span_info.iter().enumerate() {
match raw_span_info {
SpanSeed::Space(ast::SpanSeedSpace { space }) => offset += ByteDiff::from(space),
SpanSeed::Space(ast::SpanSeedSpace { space }) => {
parent_offset += ByteDiff::from(space);
sibling_offset += ByteDiff::from(space);
}
SpanSeed::Token(ast::SpanSeedToken { token }) => {
let kind = node::Kind::Token;
let size = ByteDiff::from(token.len());
let ast_crumbs = vec![TreeCrumb { index }.into()];
let node = Node { kind, size, ..default() };
children.push(node::Child { node, offset, ast_crumbs });
offset += size;
children.push(node::Child { node, parent_offset, sibling_offset, ast_crumbs });
parent_offset += size;
sibling_offset = 0.byte_diff();
}
SpanSeed::Child(ast::SpanSeedChild { node }) => {
let kind = node::Kind::argument();
let node = node.generate_node(kind, context)?;
let child_size = node.size;
let ast_crumbs = vec![TreeCrumb { index }.into()];
children.push(node::Child { node, offset, ast_crumbs });
offset += child_size;
children.push(node::Child { node, parent_offset, sibling_offset, ast_crumbs });
parent_offset += child_size;
sibling_offset = 0.byte_diff();
}
}
}
size = offset;
size = parent_offset;
}
let payload = default();
Ok(Node { kind, parenthesized, size, children, ast_id, payload })
Expand Down
72 changes: 34 additions & 38 deletions app/gui/language/span-tree/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,32 +224,32 @@ impl<T> SpanTree<T> {
///
/// Example output with AST ids removed for clarity:
/// ```text
/// operator6.join operator31 Join_Kind.Inner ["County"] Root
/// operator6.join operator31 Join_Kind.Inner ["County"] ├─Chained
/// operator6.join operator31 Join_Kind.Inner ["County"] │ ├── Chained
/// operator6.join operator31 Join_Kind.Inner │ ├── Chained
/// operator6.join operator31 ├── Chained
/// operator6.join ├── Operation
/// ├── InsertionPoint(BeforeTarget)
/// operator6 ├── This
/// ├── InsertionPoint(AfterTarget)
/// . ├── Operation
/// join ├── Argument
/// ╰── InsertionPoint(Append)
/// operator31 ╰── Argument name="right"
/// Join_Kind.Inner │ ╰── Argument name="join_kind"
/// ├── InsertionPoint(BeforeTarget)
/// Join_Kind │ ├── This
/// ├── InsertionPoint(AfterTarget)
/// . ├── Operation
/// Inner │ ├── Argument
/// ╰── InsertionPoint(Append)
/// ["County"] │ ╰── Argument name="on"
/// [ ├── Token
/// "County" │ ├── Argument
/// ] ╰── Token
/// ╰── InsertionPoint(ExpectedArgument(3)) name="right_prefix"
/// ▲╰── InsertionPoint(ExpectedArgument(4)) name="on_problems"
/// ▷operator4.join operator2 Join_Kind.Inner ["County"]Root
/// ▷operator4.join operator2 Join_Kind.Inner ["County"] ├─Chained
/// ▷operator4.join operator2 Join_Kind.Inner ["County"]├─Chained
/// ▷operator4.join operator2 Join_Kind.Inner │ │ ├─Chained
/// ▷operator4.join operator2◁ │ │ │ ├─Chained
/// ▷operator4.join │ │ │ │ ├─Operation
/// ▷◁ │ │ │ │ │ ├─InsertionPoint(BeforeArgument(0))
/// ▷operator4◁ │ │ │ │ │ ├─Argument name="self"
/// ▷.◁ │ │ │ │ │ ├─Operation
/// ▷◁ │ │ │ │ │ ├─InsertionPoint(BeforeArgument(1))
/// join │ │ │ │ │ ├─Argument
/// ▷◁ │ │ │ │ │ ╰─InsertionPoint(Append)
/// ▷operator2◁ │ │ │ │ ╰─Argument name="right"
/// Join_Kind.Inner │ │ │ ╰─Argument name="join_kind"
/// ▷◁ │ │ │ ├─InsertionPoint(BeforeArgument(0))
/// Join_Kind │ │ │ ├─Argument
/// ▷.◁ │ │ │ ├─Operation
/// ▷◁ │ │ │ ├─InsertionPoint(BeforeArgument(1))
/// Inner │ │ │ ├─Argument
/// ▷◁ │ │ │ ╰─InsertionPoint(Append)
/// ["County"] │ │ ╰─Argument name="on"
/// ▷[◁ │ │ ├─Token
/// "County" │ │ ├─Argument
/// ▷]◁ │ │ ╰─Token
/// ▷◁ ╰─InsertionPoint(ExpectedArgument) name="right_prefix"
/// ▷◁ ╰─InsertionPoint(ExpectedArgument) name="on_problems"
/// ```
pub fn debug_print(&self, code: &str) -> String {
use std::fmt::Write;
Expand All @@ -261,7 +261,7 @@ impl<T> SpanTree<T> {
}

let mut buffer = String::new();
let span_padding = " ".repeat(code.len() + 1);
let span_padding = " ".repeat(code.len() + 2);

struct PrintState {
indent: String,
Expand All @@ -271,21 +271,17 @@ impl<T> SpanTree<T> {
self.root_ref().dfs_with_layer_data(state, |node, state| {
let span = node.span();
let node_code = &code[span];
buffer.push_str(&span_padding[0..node.span_offset.into()]);
let mut written = node.span_offset.into();
if node_code.is_empty() {
buffer.push('▲');
written += 1;
} else {
buffer.push_str(node_code);
written += node_code.len();
}
buffer.push_str(&span_padding[0..node.span_offset.value]);
buffer.push('▷');
buffer.push_str(node_code);
buffer.push('◁');
Frizi marked this conversation as resolved.
Show resolved Hide resolved
let written = node.span_offset.value + node_code.len() + 2;
buffer.push_str(&span_padding[written..]);

let indent = if let Some(index) = node.crumbs.last() {
let is_last = *index == state.num_children - 1;
let indent_targeted = if is_last { "╰── " } else { "├── " };
let indent_continue = if is_last { " " } else { " " };
let indent_targeted = if is_last { " ╰─" } else { " ├─" };
let indent_continue = if is_last { " " } else { " " };

buffer.push_str(&state.indent);
buffer.push_str(indent_targeted);
Expand Down
Loading