Pending nodes (#7541)

Implement *pending* node status (#7435).

Design reference:
https://user-images.githubusercontent.com/3919101/257222729-838cfe84-7447-40a3-8aa1-5424de66b0b7.png

Demo:
[vokoscreenNG-2023-08-09_22-29-13.webm](https://github.com/enso-org/enso/assets/1047859/2906599d-920f-44df-9c0f-c617ebbd5ecc)

# Important Notes
- Reduce alpha value of node backgrounds, label widgets, and method widget icons while the engine reports "pending" state.
- Use FRP and themes for method widgets.
- Update node input area to use `define_endpoints_2`.
- Also remove some code supporting the disused *profiling mode*.
This commit is contained in:
Kaz Wesley 2023-08-17 09:40:50 -07:00 committed by GitHub
parent ea6fe3bbef
commit d15b3db0ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 232 additions and 738 deletions

View File

@ -544,13 +544,14 @@ impl Handle {
/// Returns information about all the nodes currently present in this graph. /// Returns information about all the nodes currently present in this graph.
pub fn nodes(&self) -> FallibleResult<Vec<Node>> { pub fn nodes(&self) -> FallibleResult<Vec<Node>> {
let node_infos = self.all_node_infos()?; Ok(self
let mut nodes = Vec::new(); .all_node_infos()?
for info in node_infos { .into_iter()
let metadata = self.module.node_metadata(info.id()).ok(); .map(|info| {
nodes.push(Node { info, metadata }) let metadata = self.module.node_metadata(info.id()).ok();
} Node { info, metadata }
Ok(nodes) })
.collect())
} }
/// Returns information about all the connections between graph's nodes. /// Returns information about all the connections between graph's nodes.

View File

@ -65,14 +65,12 @@ pub struct ComputedValueInfo {
impl ComputedValueInfo { impl ComputedValueInfo {
fn apply_update(&mut self, update: ExpressionUpdate) { fn apply_update(&mut self, update: ExpressionUpdate) {
// We do not erase method_call information to avoid ports "flickering" on every computation. // We do not erase this information to avoid ports "flickering" on every computation.
// the method_call should be updated soon anyway. // The method_call should be updated soon anyway.
// The type of the expression could also be kept, but so far we use the "lack of type"
// information to inform user the results are recomputed.
if !matches!(update.payload, ExpressionUpdatePayload::Pending { .. }) { if !matches!(update.payload, ExpressionUpdatePayload::Pending { .. }) {
self.method_call = update.method_call.map(|mc| mc.method_pointer); self.method_call = update.method_call.map(|mc| mc.method_pointer);
self.typename = update.typename.map(ImString::new);
} }
self.typename = update.typename.map(ImString::new);
self.payload = update.payload self.payload = update.payload
} }
} }
@ -688,7 +686,7 @@ mod tests {
let update1 = value_update_with_dataflow_error(expr2); let update1 = value_update_with_dataflow_error(expr2);
let update2 = value_update_with_dataflow_panic(expr3, error_msg); let update2 = value_update_with_dataflow_panic(expr3, error_msg);
registry.apply_updates(vec![update1, update2]); registry.apply_updates(vec![update1, update2]);
assert_eq!(registry.get(&expr1).unwrap().typename, Some(typename1.into())); assert_eq!(registry.get(&expr1).unwrap().typename, Some(typename1.clone().into()));
assert!(matches!(registry.get(&expr1).unwrap().payload, ExpressionUpdatePayload::Value { assert!(matches!(registry.get(&expr1).unwrap().payload, ExpressionUpdatePayload::Value {
warnings: None, warnings: None,
})); }));
@ -708,10 +706,9 @@ mod tests {
// Set pending value // Set pending value
let update1 = value_pending_update(expr1); let update1 = value_pending_update(expr1);
registry.apply_updates(vec![update1]); registry.apply_updates(vec![update1]);
// Method pointer should not be cleared to avoid port's flickering. // Method pointer and type are not affected; pending state is shown by an opacity change.
assert_eq!(registry.get(&expr1).unwrap().method_call, Some(method_ptr)); assert_eq!(registry.get(&expr1).unwrap().method_call, Some(method_ptr));
// The type is erased to show invalidated path. assert_eq!(registry.get(&expr1).unwrap().typename, Some(typename1.into()));
assert_eq!(registry.get(&expr1).unwrap().typename, None);
assert!(matches!( assert!(matches!(
registry.get(&expr1).unwrap().payload, registry.get(&expr1).unwrap().payload,
ExpressionUpdatePayload::Pending { message: None, progress: None } ExpressionUpdatePayload::Pending { message: None, progress: None }

View File

@ -12,6 +12,7 @@ use crate::presenter::graph::state::State;
use double_representation::context_switch::Context; use double_representation::context_switch::Context;
use double_representation::context_switch::ContextSwitch; use double_representation::context_switch::ContextSwitch;
use double_representation::context_switch::ContextSwitchExpression; use double_representation::context_switch::ContextSwitchExpression;
use engine_protocol::language_server::ExpressionUpdatePayload;
use enso_frp as frp; use enso_frp as frp;
use futures::future::LocalBoxFuture; use futures::future::LocalBoxFuture;
use ide_view as view; use ide_view as view;
@ -411,6 +412,15 @@ impl Model {
self.state.update_from_controller().set_node_error_from_payload(expression, payload) self.state.update_from_controller().set_node_error_from_payload(expression, payload)
} }
fn refresh_node_pending(&self, expression: ast::Id) -> Option<(ViewNodeId, bool)> {
let registry = self.controller.computed_value_info_registry();
let is_pending = registry
.get(&expression)
.map(|info| matches!(info.payload, ExpressionUpdatePayload::Pending { .. }))
.unwrap_or_default();
self.state.update_from_controller().set_node_pending(expression, is_pending)
}
/// Extract the expression's current type from controllers. /// Extract the expression's current type from controllers.
fn expression_type(&self, id: ast::Id) -> Option<view::graph_editor::Type> { fn expression_type(&self, id: ast::Id) -> Option<view::graph_editor::Type> {
let registry = self.controller.computed_value_info_registry(); let registry = self.controller.computed_value_info_registry();
@ -746,6 +756,7 @@ impl Graph {
update_expression <= update_expressions; update_expression <= update_expressions;
view.set_expression_usage_type <+ update_expression.filter_map(f!((id) model.refresh_expression_type(*id))); view.set_expression_usage_type <+ update_expression.filter_map(f!((id) model.refresh_expression_type(*id)));
view.set_node_error_status <+ update_expression.filter_map(f!((id) model.refresh_node_error(*id))); view.set_node_error_status <+ update_expression.filter_map(f!((id) model.refresh_node_error(*id)));
view.set_node_pending_status <+ update_expression.filter_map(f!((id) model.refresh_node_pending(*id)));
self.init_widgets(reset_node_types, update_expression.clone_ref()); self.init_widgets(reset_node_types, update_expression.clone_ref());

View File

@ -24,7 +24,7 @@ use ide_view::graph_editor::EdgeEndpoint;
/// A single node data. /// A single node data.
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Clone, Debug)] #[derive(Clone, Debug, Default)]
pub struct Node { pub struct Node {
pub view_id: Option<ViewNodeId>, pub view_id: Option<ViewNodeId>,
pub position: Vector2, pub position: Vector2,
@ -33,27 +33,12 @@ pub struct Node {
pub is_frozen: bool, pub is_frozen: bool,
pub context_switch: Option<ContextSwitchExpression>, pub context_switch: Option<ContextSwitchExpression>,
pub error: Option<node_view::Error>, pub error: Option<node_view::Error>,
pub is_pending: bool,
pub visualization: Option<visualization_view::Path>, pub visualization: Option<visualization_view::Path>,
/// Indicate whether this node view is updated automatically by changes from the controller /// Indicate whether this node view is updated automatically by changes from the controller
/// or view, or will be explicitly updated.. /// or view, or will be explicitly updated..
expression_auto_update: bool, disable_expression_auto_update: bool,
}
impl Default for Node {
fn default() -> Self {
Self {
view_id: None,
position: Vector2::default(),
expression: node_view::Expression::default(),
is_skipped: false,
is_frozen: false,
context_switch: None,
error: None,
visualization: None,
expression_auto_update: true,
}
}
} }
/// The set of node states. /// The set of node states.
@ -293,12 +278,19 @@ impl State {
// When node is in process of being created, it is not yet present in the state. In that // When node is in process of being created, it is not yet present in the state. In that
// case the initial expression update needs to be processed. Otherwise the node would be // case the initial expression update needs to be processed. Otherwise the node would be
// created without any expression. // created without any expression.
self.nodes.borrow().get(node).map_or(true, |node| node.expression_auto_update) let auto_update_disabled = self
.nodes
.borrow()
.get(node)
.map_or_default(|node| node.disable_expression_auto_update);
!auto_update_disabled
} }
/// Set the flag that indicates if the node should be synced with its AST automatically. /// Set the flag that indicates if the node should be synced with its AST automatically.
pub fn allow_expression_auto_updates(&self, node: ast::Id, allow: bool) { pub fn allow_expression_auto_updates(&self, node: ast::Id, allow: bool) {
self.nodes.borrow_mut().get_mut(node).for_each(|node| node.expression_auto_update = allow); if let Some(node) = self.nodes.borrow_mut().get_mut(node) {
node.disable_expression_auto_update = !allow;
}
} }
} }
@ -452,6 +444,22 @@ impl<'a> ControllerChange<'a> {
} }
} }
/// Set whether this node is currently awaiting completion of execution.
pub fn set_node_pending(
&self,
node_id: ast::Id,
is_pending: bool,
) -> Option<(ViewNodeId, bool)> {
let mut nodes = self.nodes.borrow_mut();
let displayed = nodes.get_mut(node_id)?;
if displayed.is_pending != is_pending {
displayed.is_pending = is_pending;
Some((displayed.view_id?, is_pending))
} else {
None
}
}
fn convert_payload_to_error( fn convert_payload_to_error(
&self, &self,
node_id: AstNodeId, node_id: AstNodeId,

View File

@ -1,7 +1,3 @@
//! NOTE
//! This file is under a heavy development. It contains commented lines of code and some code may
//! be of poor quality. Expect drastic changes.
// === Standard Linter Configuration === // === Standard Linter Configuration ===
#![deny(non_ascii_idents)] #![deny(non_ascii_idents)]
#![warn(unsafe_code)] #![warn(unsafe_code)]
@ -17,7 +13,6 @@
use ensogl::prelude::*; use ensogl::prelude::*;
use enso_frp as frp;
use ensogl::application::Application; use ensogl::application::Application;
use ensogl::display::object::ObjectOps; use ensogl::display::object::ObjectOps;
use ensogl::display::shape::StyleWatch; use ensogl::display::shape::StyleWatch;
@ -30,7 +25,6 @@ use ide_view::graph_editor;
use ide_view::graph_editor::component::node::vcs; use ide_view::graph_editor::component::node::vcs;
use ide_view::graph_editor::component::node::Expression; use ide_view::graph_editor::component::node::Expression;
use ide_view::graph_editor::GraphEditor; use ide_view::graph_editor::GraphEditor;
use ide_view::graph_editor::NodeProfilingStatus;
use ide_view::graph_editor::Type; use ide_view::graph_editor::Type;
use ide_view::project; use ide_view::project;
use ide_view::root; use ide_view::root;
@ -53,26 +47,6 @@ pub fn main() {
} }
fn _fence<T, Out>(network: &frp::Network, trigger: T) -> (frp::Stream, frp::Stream<bool>)
where
T: frp::HasOutput<Output = Out>,
T: Into<frp::Stream<Out>>,
Out: frp::Data, {
let trigger = trigger.into();
frp::extend! { network
def trigger_ = trigger.constant(());
def runner = source::<()>();
def switch = any_mut();
switch.attach(&trigger_);
def triggered = trigger.map(f_!(runner.emit(())));
switch.attach(&triggered);
def condition = switch.toggle_true();
}
let runner = runner.into();
(runner, condition)
}
// ================== // ==================
// === Mock Types === // === Mock Types ===
@ -178,18 +152,22 @@ fn init(app: &Application) {
let dummy_node_added_id = graph_editor.model.add_node(); let dummy_node_added_id = graph_editor.model.add_node();
let dummy_node_edited_id = graph_editor.model.add_node(); let dummy_node_edited_id = graph_editor.model.add_node();
let dummy_node_unchanged_id = graph_editor.model.add_node(); let dummy_node_unchanged_id = graph_editor.model.add_node();
let dummy_node_pending_id = graph_editor.model.add_node();
graph_editor.frp.set_node_position.emit((dummy_node_added_id, Vector2(-450.0, 50.0))); graph_editor.frp.set_node_position.emit((dummy_node_added_id, Vector2(-450.0, 50.0)));
graph_editor.frp.set_node_position.emit((dummy_node_edited_id, Vector2(-450.0, 125.0))); graph_editor.frp.set_node_position.emit((dummy_node_edited_id, Vector2(-450.0, 125.0)));
graph_editor.frp.set_node_position.emit((dummy_node_unchanged_id, Vector2(-450.0, 200.0))); graph_editor.frp.set_node_position.emit((dummy_node_unchanged_id, Vector2(-450.0, 200.0)));
graph_editor.frp.set_node_position.emit((dummy_node_pending_id, Vector2(-450.0, 275.0)));
let dummy_node_added_expr = expression_mock_string("This node was added."); let dummy_node_added_expr = expression_mock_string("This node was added.");
let dummy_node_edited_expr = expression_mock_string("This node was edited."); let dummy_node_edited_expr = expression_mock_string("This node was edited.");
let dummy_node_unchanged_expr = expression_mock_string("This node was not changed."); let dummy_node_unchanged_expr = expression_mock_string("This node was not changed.");
let dummy_node_pending_expr = expression_mock_string("This node is pending.");
graph_editor.frp.set_node_expression.emit((dummy_node_added_id, dummy_node_added_expr)); graph_editor.frp.set_node_expression.emit((dummy_node_added_id, dummy_node_added_expr));
graph_editor.frp.set_node_expression.emit((dummy_node_edited_id, dummy_node_edited_expr)); graph_editor.frp.set_node_expression.emit((dummy_node_edited_id, dummy_node_edited_expr));
graph_editor.frp.set_node_expression.emit((dummy_node_unchanged_id, dummy_node_unchanged_expr)); graph_editor.frp.set_node_expression.emit((dummy_node_unchanged_id, dummy_node_unchanged_expr));
graph_editor.frp.set_node_expression.emit((dummy_node_pending_id, dummy_node_pending_expr));
graph_editor.frp.set_node_vcs_status.emit((dummy_node_added_id, Some(vcs::Status::Edited))); graph_editor.frp.set_node_vcs_status.emit((dummy_node_added_id, Some(vcs::Status::Edited)));
graph_editor.frp.set_node_vcs_status.emit((dummy_node_edited_id, Some(vcs::Status::Added))); graph_editor.frp.set_node_vcs_status.emit((dummy_node_edited_id, Some(vcs::Status::Added)));
@ -197,6 +175,7 @@ fn init(app: &Application) {
.frp .frp
.set_node_vcs_status .set_node_vcs_status
.emit((dummy_node_unchanged_id, Some(vcs::Status::Unchanged))); .emit((dummy_node_unchanged_id, Some(vcs::Status::Unchanged)));
graph_editor.frp.set_node_pending_status.emit((dummy_node_pending_id, true));
// === Types (Port Coloring) === // === Types (Port Coloring) ===
@ -232,16 +211,6 @@ fn init(app: &Application) {
}); });
// === Profiling ===
let node1_status = NodeProfilingStatus::Finished { duration: 500.0 };
graph_editor.set_node_profiling_status(node1_id, node1_status);
let node2_status = NodeProfilingStatus::Finished { duration: 1000.0 };
graph_editor.set_node_profiling_status(node2_id, node2_status);
let node3_status = NodeProfilingStatus::Finished { duration: 1500.0 };
graph_editor.set_node_profiling_status(node3_id, node3_status);
// === Execution Modes === // === Execution Modes ===
graph_editor.set_available_execution_environments(make_dummy_execution_environments()); graph_editor.set_available_execution_environments(make_dummy_execution_environments());

View File

@ -8,8 +8,6 @@
pub mod add_node_button; pub mod add_node_button;
pub mod edge; pub mod edge;
pub mod node; pub mod node;
#[warn(missing_docs)]
pub mod profiling;
pub mod type_coloring; pub mod type_coloring;
pub mod visualization; pub mod visualization;

View File

@ -210,7 +210,8 @@ fn junction_points(
// The target attachment will extend as far toward the edge of the node as it can without // The target attachment will extend as far toward the edge of the node as it can without
// rising above the source. // rising above the source.
let attachment_height = target_max_attachment_height.map(|dy| min(dy, target.y().abs())); let attachment_height = target_max_attachment_height.map(|dy| min(dy, target.y().abs()));
let attachment_y = target.y() + attachment_height.unwrap_or_default(); let attachment_y =
target.y() + attachment_height.unwrap_or_default() + target_size.y() / 2.0;
let target_attachment = Vector2(target.x(), attachment_y); let target_attachment = Vector2(target.x(), attachment_y);
(vec![source, target_attachment], max_radius, attachment) (vec![source, target_attachment], max_radius, attachment)
} else { } else {

View File

@ -41,7 +41,7 @@ mod attachment {
/// appears to pass through the top of the node. Without this adjustment, inexact /// appears to pass through the top of the node. Without this adjustment, inexact
/// floating-point math and anti-aliasing would cause a 1-pixel gap artifact right where /// floating-point math and anti-aliasing would cause a 1-pixel gap artifact right where
/// the attachment should meet the corner at the edge of the node. /// the attachment should meet the corner at the edge of the node.
pub(super) const LENGTH_ADJUSTMENT: f32 = 0.1; pub(super) const LENGTH_ADJUSTMENT: f32 = 1.0;
} }

View File

@ -4,7 +4,6 @@ use crate::prelude::*;
use ensogl::display::shape::*; use ensogl::display::shape::*;
use ensogl::display::traits::*; use ensogl::display::traits::*;
use crate::component::node::profiling::ProfilingLabel;
use crate::component::visualization; use crate::component::visualization;
use crate::selection::BoundingBox; use crate::selection::BoundingBox;
use crate::tooltip; use crate::tooltip;
@ -40,8 +39,6 @@ pub mod expression;
pub mod growth_animation; pub mod growth_animation;
pub mod input; pub mod input;
pub mod output; pub mod output;
#[warn(missing_docs)]
pub mod profiling;
#[deny(missing_docs)] #[deny(missing_docs)]
pub mod vcs; pub mod vcs;
@ -213,6 +210,7 @@ ensogl::define_endpoints_2! {
disable_visualization (), disable_visualization (),
set_visualization (Option<visualization::Definition>), set_visualization (Option<visualization::Definition>),
set_disabled (bool), set_disabled (bool),
set_pending (bool),
set_connections (HashMap<span_tree::PortId, color::Lcha>), set_connections (HashMap<span_tree::PortId, color::Lcha>),
set_expression (Expression), set_expression (Expression),
edit_expression (text::Range<text::Byte>, ImString), edit_expression (text::Range<text::Byte>, ImString),
@ -239,7 +237,6 @@ ensogl::define_endpoints_2! {
set_view_mode (view::Mode), set_view_mode (view::Mode),
set_profiling_min_global_duration (f32), set_profiling_min_global_duration (f32),
set_profiling_max_global_duration (f32), set_profiling_max_global_duration (f32),
set_profiling_status (profiling::Status),
/// Indicate whether on hover the quick action icons should appear. /// Indicate whether on hover the quick action icons should appear.
show_quick_action_bar_on_hover (bool), show_quick_action_bar_on_hover (bool),
set_execution_environment (ExecutionEnvironment), set_execution_environment (ExecutionEnvironment),
@ -378,7 +375,6 @@ pub struct NodeModel {
pub display_object: display::object::Instance, pub display_object: display::object::Instance,
pub background: Background, pub background: Background,
pub error_indicator: Rectangle, pub error_indicator: Rectangle,
pub profiling_label: ProfilingLabel,
pub input: input::Area, pub input: input::Area,
pub output: output::Area, pub output: output::Area,
pub visualization: visualization::Container, pub visualization: visualization::Container,
@ -403,12 +399,10 @@ impl NodeModel {
.set_pointer_events(false) .set_pointer_events(false)
.set_color(color::Rgba::transparent()) .set_color(color::Rgba::transparent())
.set_border_and_inset(ERROR_BORDER_WIDTH); .set_border_and_inset(ERROR_BORDER_WIDTH);
let profiling_label = ProfilingLabel::new(app);
let background = Background::new(&style); let background = Background::new(&style);
let vcs_indicator = vcs::StatusIndicator::new(app); let vcs_indicator = vcs::StatusIndicator::new(app);
let display_object = display::object::Instance::new_named("Node"); let display_object = display::object::Instance::new_named("Node");
display_object.add_child(&profiling_label);
display_object.add_child(&background); display_object.add_child(&background);
display_object.add_child(&vcs_indicator); display_object.add_child(&vcs_indicator);
@ -441,7 +435,6 @@ impl NodeModel {
display_object, display_object,
background, background,
error_indicator, error_indicator,
profiling_label,
input, input,
output, output,
visualization, visualization,
@ -655,6 +648,7 @@ impl Node {
model.input.set_connections <+ input.set_connections; model.input.set_connections <+ input.set_connections;
model.input.set_disabled <+ input.set_disabled; model.input.set_disabled <+ input.set_disabled;
model.input.set_pending <+ input.set_pending;
model.input.update_widgets <+ input.update_widgets; model.input.update_widgets <+ input.update_widgets;
model.output.set_expression_visibility <+ input.set_output_expression_visibility; model.output.set_expression_visibility <+ input.set_output_expression_visibility;
@ -712,7 +706,6 @@ impl Node {
model.input.set_view_mode <+ input.set_view_mode; model.input.set_view_mode <+ input.set_view_mode;
model.input.set_edit_ready_mode <+ input.set_edit_ready_mode; model.input.set_edit_ready_mode <+ input.set_edit_ready_mode;
model.profiling_label.set_view_mode <+ input.set_view_mode;
model.vcs_indicator.set_visibility <+ input.set_view_mode.map(|&mode| { model.vcs_indicator.set_visibility <+ input.set_view_mode.map(|&mode| {
!matches!(mode,view::Mode::Profiling {..}) !matches!(mode,view::Mode::Profiling {..})
}); });
@ -813,8 +806,8 @@ impl Node {
visualization.set_view_state <+ vis_preview_visible.on_false().constant(visualization::ViewState::Disabled); visualization.set_view_state <+ vis_preview_visible.on_false().constant(visualization::ViewState::Disabled);
} }
frp::extend! { network frp::extend! { network
update_error <- all(input.set_error,preview_visible); update_error <- all(input.set_error, preview_visible);
eval update_error([model]((error,visible)){ eval update_error([model]((error, visible)){
if *visible { if *visible {
model.set_error(error.as_ref()); model.set_error(error.as_ref());
} else { } else {
@ -830,17 +823,6 @@ impl Node {
} }
frp::extend! { network
// === Profiling Indicator ===
model.profiling_label.set_min_global_duration
<+ input.set_profiling_min_global_duration;
model.profiling_label.set_max_global_duration
<+ input.set_profiling_max_global_duration;
model.profiling_label.set_status <+ input.set_profiling_status;
model.input.set_profiling_status <+ input.set_profiling_status;
}
frp::extend! { network frp::extend! { network
// === Tooltip === // === Tooltip ===
@ -880,8 +862,19 @@ impl Node {
let port_color_tint = style_frp.get_color_lcha(theme::graph_editor::node::port_color_tint); let port_color_tint = style_frp.get_color_lcha(theme::graph_editor::node::port_color_tint);
let editing_color = style_frp.get_color_lcha(theme::graph_editor::node::background); let editing_color = style_frp.get_color_lcha(theme::graph_editor::node::background);
let pending_alpha_factor =
style_frp.get_number(theme::graph_editor::node::pending::alpha_factor);
base_color_source <- source(); base_color_source <- source();
out.base_color <+ base_color_source; adjusted_base_color <- all_with3(
&base_color_source, &frp.set_pending, &pending_alpha_factor,
|c: &color::Lcha, pending, factor| {
match *pending {
true => c.multiply_alpha(*factor),
false => *c,
}
}
);
out.base_color <+ adjusted_base_color;
out.port_color <+ out.base_color.all_with(&port_color_tint, |c, tint| tint.over(*c)); out.port_color <+ out.base_color.all_with(&port_color_tint, |c, tint| tint.over(*c));
background_color <- model.input.frp.editing.switch(&frp.base_color, &editing_color); background_color <- model.input.frp.editing.switch(&frp.base_color, &editing_color);
node_colors <- all(background_color, frp.port_color); node_colors <- all(background_color, frp.port_color);
@ -908,6 +901,7 @@ impl Node {
model.error_visualization.set_layer(visualization::Layer::Front); model.error_visualization.set_layer(visualization::Layer::Front);
frp.set_error.emit(None); frp.set_error.emit(None);
frp.set_disabled.emit(false); frp.set_disabled.emit(false);
frp.set_pending.emit(false);
frp.show_quick_action_bar_on_hover.emit(true); frp.show_quick_action_bar_on_hover.emit(true);
let widget = gui::Widget::new(app, frp, model); let widget = gui::Widget::new(app, frp, model);

View File

@ -8,7 +8,6 @@ use ensogl::display::traits::*;
use crate::node; use crate::node;
use crate::node::input::widget; use crate::node::input::widget;
use crate::node::input::widget::OverrideKey; use crate::node::input::widget::OverrideKey;
use crate::node::profiling;
use crate::view; use crate::view;
use crate::CallWidgetsConfig; use crate::CallWidgetsConfig;
use crate::GraphLayers; use crate::GraphLayers;
@ -306,16 +305,16 @@ impl Model {
/// ///
/// See also: [`controller::graph::widget`] module of `enso-gui` crate. /// See also: [`controller::graph::widget`] module of `enso-gui` crate.
#[profile(Debug)] #[profile(Debug)]
fn request_widget_config_overrides(&self, expression: &Expression, area_frp: &FrpEndpoints) { fn request_widget_config_overrides(&self, expression: &Expression, area_frp: &api::Private) {
for (call_id, target_id) in expression.target_map.iter() { for (call_id, target_id) in expression.target_map.iter() {
area_frp.source.requested_widgets.emit((*call_id, *target_id)); area_frp.output.requested_widgets.emit((*call_id, *target_id));
} }
} }
/// Set a displayed expression, updating the input ports. `is_editing` indicates whether the /// Set a displayed expression, updating the input ports. `is_editing` indicates whether the
/// expression is being edited by the user. /// expression is being edited by the user.
#[profile(Debug)] #[profile(Debug)]
fn set_expression(&self, new_expression: impl Into<node::Expression>, area_frp: &FrpEndpoints) { fn set_expression(&self, new_expression: impl Into<node::Expression>, area_frp: &api::Private) {
let new_expression = Expression::from(new_expression.into()); let new_expression = Expression::from(new_expression.into());
debug!("Set expression: \n{:?}", new_expression.tree_pretty_printer()); debug!("Set expression: \n{:?}", new_expression.tree_pretty_printer());
@ -346,7 +345,7 @@ impl Model {
// === FRP === // === FRP ===
// =========== // ===========
ensogl::define_endpoints! { ensogl::define_endpoints_2! {
Input { Input {
/// Set the node expression. /// Set the node expression.
set_expression (node::Expression), set_expression (node::Expression),
@ -372,6 +371,9 @@ ensogl::define_endpoints! {
/// Disable the node (aka "skip mode"). /// Disable the node (aka "skip mode").
set_disabled (bool), set_disabled (bool),
/// Set the node pending (awaiting execution completion).
set_pending (bool),
/// Set read-only mode for input ports. /// Set read-only mode for input ports.
set_read_only (bool), set_read_only (bool),
@ -385,7 +387,6 @@ ensogl::define_endpoints! {
set_ports_active (bool), set_ports_active (bool),
set_view_mode (view::Mode), set_view_mode (view::Mode),
set_profiling_status (profiling::Status),
/// Set the expression USAGE type. This is not the definition type, which can be set with /// Set the expression USAGE type. This is not the definition type, which can be set with
/// `set_expression` instead. In case the usage type is set to None, ports still may be /// `set_expression` instead. In case the usage type is set to None, ports still may be
@ -457,10 +458,10 @@ impl Area {
// This is meant to be on top of FRP network. Read more about `Node` docs to // This is meant to be on top of FRP network. Read more about `Node` docs to
// learn more about the architecture and the importance of the hover // learn more about the architecture and the importance of the hover
// functionality. // functionality.
frp.output.source.on_port_hover <+ model.widget_tree.on_port_hover; frp.private.output.on_port_hover <+ model.widget_tree.on_port_hover;
frp.output.source.on_port_press <+ model.widget_tree.on_port_press; frp.private.output.on_port_press <+ model.widget_tree.on_port_press;
port_hover <- frp.on_port_hover.map(|t| t.is_on()); port_hover <- frp.on_port_hover.map(|t| t.is_on());
frp.output.source.body_hover <+ frp.set_hover || port_hover; frp.private.output.body_hover <+ frp.set_hover || port_hover;
// === Cursor setup === // === Cursor setup ===
@ -474,12 +475,12 @@ impl Area {
edit_or_ready <- frp.set_edit_ready_mode || set_editing; edit_or_ready <- frp.set_edit_ready_mode || set_editing;
reacts_to_hover <- all_with(&edit_or_ready, ports_active, |e, a| *e && !a); reacts_to_hover <- all_with(&edit_or_ready, ports_active, |e, a| *e && !a);
port_vis <- all_with(&set_editing, ports_active, |e, a| !e && *a); port_vis <- all_with(&set_editing, ports_active, |e, a| !e && *a);
frp.output.source.ports_visible <+ port_vis; frp.private.output.ports_visible <+ port_vis;
frp.output.source.editing <+ set_editing; frp.private.output.editing <+ set_editing;
model.widget_tree.set_ports_visible <+ frp.ports_visible; model.widget_tree.set_ports_visible <+ frp.ports_visible;
model.widget_tree.set_edit_ready_mode <+ frp.set_edit_ready_mode; model.widget_tree.set_edit_ready_mode <+ frp.set_edit_ready_mode;
refresh_edges <- model.widget_tree.connected_port_updated.debounce(); refresh_edges <- model.widget_tree.connected_port_updated.debounce();
frp.output.source.input_edges_need_refresh <+ refresh_edges; frp.private.output.input_edges_need_refresh <+ refresh_edges;
// === Label Hover === // === Label Hover ===
@ -497,28 +498,28 @@ impl Area {
model.widget_tree.pointer_style, model.widget_tree.pointer_style,
hovered_port_pointer hovered_port_pointer
].fold(); ].fold();
frp.output.source.pointer_style <+ pointer_style; frp.private.output.pointer_style <+ pointer_style;
// === Properties === // === Properties ===
let widget_tree_object = model.widget_tree.display_object(); let widget_tree_object = model.widget_tree.display_object();
widget_tree_width <- widget_tree_object.on_resized.map(|size| size.x()); widget_tree_width <- widget_tree_object.on_resized.map(|size| size.x());
edit_label_width <- all(model.edit_mode_label.width, init)._0(); edit_label_width <- all(model.edit_mode_label.width, init)._0();
padded_edit_label_width <- edit_label_width.map(|t| t + 2.0 * TEXT_OFFSET); padded_edit_label_width <- edit_label_width.map(|t| t + 2.0 * TEXT_OFFSET);
frp.output.source.width <+ set_editing.switch( frp.private.output.width <+ set_editing.switch(
&widget_tree_width, &widget_tree_width,
&padded_edit_label_width &padded_edit_label_width
); );
// === Expression === // === Expression ===
let frp_endpoints = &frp.output; let frp_endpoints = &frp.private;
eval frp.set_expression([frp_endpoints, model](expr) model.set_expression(expr, &frp_endpoints)); eval frp.set_expression([frp_endpoints, model](expr) model.set_expression(expr, &frp_endpoints));
legit_edit <- frp.input.edit_expression.gate(&set_editing); legit_edit <- frp.input.edit_expression.gate(&set_editing);
model.edit_mode_label.select <+ legit_edit.map(|(range, _)| (range.start.into(), range.end.into())); model.edit_mode_label.select <+ legit_edit.map(|(range, _)| (range.start.into(), range.end.into()));
model.edit_mode_label.insert <+ legit_edit._1(); model.edit_mode_label.insert <+ legit_edit._1();
expression_edited <- model.edit_mode_label.content.gate(&set_editing); expression_edited <- model.edit_mode_label.content.gate(&set_editing);
selections_edited <- model.edit_mode_label.selections.gate(&set_editing); selections_edited <- model.edit_mode_label.selections.gate(&set_editing);
frp.output.source.expression_edit <+ selections_edited.gate(&set_editing).map2( frp.private.output.expression_edit <+ selections_edited.gate(&set_editing).map2(
&model.edit_mode_label.content, &model.edit_mode_label.content,
f!([model](selection, full_content) { f!([model](selection, full_content) {
let full_content = full_content.into(); let full_content = full_content.into();
@ -527,7 +528,7 @@ impl Area {
(full_content, selections) (full_content, selections)
}) })
); );
frp.output.source.on_port_code_update <+ expression_edited.map(|e| { frp.private.output.on_port_code_update <+ expression_edited.map(|e| {
// Treat edit mode update as a code modification at the span tree root. // Treat edit mode update as a code modification at the span tree root.
(default(), e.into()) (default(), e.into())
}); });
@ -537,8 +538,8 @@ impl Area {
(crumbs.clone(), expression) (crumbs.clone(), expression)
}); });
frp.output.source.on_port_code_update <+ widget_code_update; frp.private.output.on_port_code_update <+ widget_code_update;
frp.output.source.request_import <+ model.widget_tree.request_import; frp.private.output.request_import <+ model.widget_tree.request_import;
// === Widgets === // === Widgets ===
@ -546,16 +547,16 @@ impl Area {
eval frp.set_connections((conn) model.set_connections(conn)); eval frp.set_connections((conn) model.set_connections(conn));
eval frp.set_expression_usage_type(((id,tp)) model.set_expression_usage_type(*id,tp.clone())); eval frp.set_expression_usage_type(((id,tp)) model.set_expression_usage_type(*id,tp.clone()));
eval frp.set_disabled ((disabled) model.widget_tree.set_disabled(*disabled)); eval frp.set_disabled ((disabled) model.widget_tree.set_disabled(*disabled));
eval frp.set_pending ((pending) model.widget_tree.set_pending(*pending));
eval_ model.widget_tree.rebuild_required(model.rebuild_widget_tree_if_dirty()); eval_ model.widget_tree.rebuild_required(model.rebuild_widget_tree_if_dirty());
frp.output.source.widget_tree_rebuilt <+ model.widget_tree.on_rebuild_finished; frp.private.output.widget_tree_rebuilt <+ model.widget_tree.on_rebuild_finished;
// === View Mode === // === View Mode ===
frp.output.source.view_mode <+ frp.set_view_mode; frp.private.output.view_mode <+ frp.set_view_mode;
model.widget_tree.set_read_only <+ frp.set_read_only; model.widget_tree.set_read_only <+ frp.set_read_only;
model.widget_tree.set_view_mode <+ frp.set_view_mode; model.widget_tree.set_view_mode <+ frp.set_view_mode;
model.widget_tree.set_profiling_status <+ frp.set_profiling_status;
model.widget_tree.node_base_color <+ frp.set_node_colors._0(); model.widget_tree.node_base_color <+ frp.set_node_colors._0();
model.widget_tree.node_port_color <+ frp.set_node_colors._1(); model.widget_tree.node_port_color <+ frp.set_node_colors._1();
} }

View File

@ -131,8 +131,8 @@ ensogl::define_endpoints_2! {
set_edit_ready_mode (bool), set_edit_ready_mode (bool),
set_read_only (bool), set_read_only (bool),
set_view_mode (crate::view::Mode), set_view_mode (crate::view::Mode),
set_profiling_status (crate::node::profiling::Status),
set_disabled (bool), set_disabled (bool),
set_pending (bool),
node_base_color (color::Lcha), node_base_color (color::Lcha),
node_port_color (color::Lcha), node_port_color (color::Lcha),
} }
@ -579,7 +579,6 @@ pub struct WidgetsFrp {
/// combination of `set_read_only`, `set_edit_ready_mode` and `set_ports_visible` signals. /// combination of `set_read_only`, `set_edit_ready_mode` and `set_ports_visible` signals.
pub(super) allow_interaction: frp::Sampler<bool>, pub(super) allow_interaction: frp::Sampler<bool>,
pub(super) set_view_mode: frp::Sampler<crate::view::Mode>, pub(super) set_view_mode: frp::Sampler<crate::view::Mode>,
pub(super) set_profiling_status: frp::Sampler<crate::node::profiling::Status>,
pub(super) hovered_port_children: frp::Sampler<HashSet<WidgetIdentity>>, pub(super) hovered_port_children: frp::Sampler<HashSet<WidgetIdentity>>,
/// Remove given tree node's reference from the widget tree, and send its only remaining strong /// Remove given tree node's reference from the widget tree, and send its only remaining strong
/// reference to a new widget owner using [`SpanWidget::receive_ownership`] method. This will /// reference to a new widget owner using [`SpanWidget::receive_ownership`] method. This will
@ -647,7 +646,6 @@ impl Tree {
set_edit_ready_mode <- frp.set_edit_ready_mode.sampler(); set_edit_ready_mode <- frp.set_edit_ready_mode.sampler();
set_read_only <- frp.set_read_only.sampler(); set_read_only <- frp.set_read_only.sampler();
set_view_mode <- frp.set_view_mode.sampler(); set_view_mode <- frp.set_view_mode.sampler();
set_profiling_status <- frp.set_profiling_status.sampler();
node_base_color <- frp.node_base_color.sampler(); node_base_color <- frp.node_base_color.sampler();
node_port_color <- frp.node_port_color.sampler(); node_port_color <- frp.node_port_color.sampler();
on_port_hover <- any(...); on_port_hover <- any(...);
@ -679,7 +677,6 @@ impl Tree {
set_read_only, set_read_only,
allow_interaction, allow_interaction,
set_view_mode, set_view_mode,
set_profiling_status,
transfer_ownership, transfer_ownership,
value_changed, value_changed,
request_import, request_import,
@ -721,10 +718,15 @@ impl Tree {
self.notify_dirty(self.model.set_disabled(disabled)); self.notify_dirty(self.model.set_disabled(disabled));
} }
/// Set pending status for given span tree node. The pending nodes will be semi-transparent.
/// The widgets might change behavior depending on the pending status.
pub fn set_pending(&self, pending: bool) {
self.notify_dirty(self.model.set_pending(pending));
}
/// Rebuild tree if it has been marked as dirty. The dirty flag is marked whenever more data /// Rebuild tree if it has been marked as dirty. The dirty flag is marked whenever more data
/// external to the span-tree is provided, using `set_config_override`, `set_usage_type`, /// external to the span-tree is provided, using `set_config_override`, `set_usage_type`,
/// `set_connections` or `set_disabled` methods of the widget tree. /// `set_connections`, `set_disabled`, or `set_pending` methods of the widget tree.
pub fn rebuild_tree_if_dirty( pub fn rebuild_tree_if_dirty(
&self, &self,
tree: &span_tree::SpanTree, tree: &span_tree::SpanTree,
@ -949,6 +951,7 @@ struct TreeModel {
connected_map: Rc<RefCell<HashMap<PortId, color::Lcha>>>, connected_map: Rc<RefCell<HashMap<PortId, color::Lcha>>>,
usage_type_map: Rc<RefCell<HashMap<ast::Id, crate::Type>>>, usage_type_map: Rc<RefCell<HashMap<ast::Id, crate::Type>>>,
node_disabled: Cell<bool>, node_disabled: Cell<bool>,
node_pending: Cell<bool>,
tree_dirty: Cell<bool>, tree_dirty: Cell<bool>,
} }
@ -968,6 +971,7 @@ impl TreeModel {
app, app,
display_object, display_object,
node_disabled: default(), node_disabled: default(),
node_pending: default(),
nodes_map: default(), nodes_map: default(),
hierarchy: default(), hierarchy: default(),
ports_map: default(), ports_map: default(),
@ -1018,6 +1022,12 @@ impl TreeModel {
self.mark_dirty_flag(prev_disabled != disabled) self.mark_dirty_flag(prev_disabled != disabled)
} }
/// Set the execution status under given widget. It may cause the tree to be marked as dirty.
fn set_pending(&self, pending: bool) -> bool {
let prev_pending = self.node_pending.replace(pending);
self.mark_dirty_flag(prev_pending != pending)
}
/// Get parent of a node under given pointer, if exists. /// Get parent of a node under given pointer, if exists.
#[allow(dead_code)] #[allow(dead_code)]
pub fn parent(&self, pointer: WidgetIdentity) -> Option<WidgetIdentity> { pub fn parent(&self, pointer: WidgetIdentity) -> Option<WidgetIdentity> {
@ -1106,6 +1116,7 @@ impl TreeModel {
let usage_type_map = self.usage_type_map.borrow(); let usage_type_map = self.usage_type_map.borrow();
let old_nodes = self.nodes_map.take(); let old_nodes = self.nodes_map.take();
let node_disabled = self.node_disabled.get(); let node_disabled = self.node_disabled.get();
let node_pending = self.node_pending.get();
// Old hierarchy is not used during the rebuild, so we might as well reuse the allocation. // Old hierarchy is not used during the rebuild, so we might as well reuse the allocation.
let mut hierarchy = self.hierarchy.take(); let mut hierarchy = self.hierarchy.take();
@ -1117,6 +1128,7 @@ impl TreeModel {
app, app,
frp, frp,
node_disabled, node_disabled,
node_pending,
node_expression, node_expression,
layers, layers,
styles, styles,
@ -1184,6 +1196,8 @@ pub struct NodeInfo {
/// Whether the node is disabled, i.e. its expression is not currently used in the computation. /// Whether the node is disabled, i.e. its expression is not currently used in the computation.
/// Widgets of disabled nodes are usually grayed out. /// Widgets of disabled nodes are usually grayed out.
pub disabled: bool, pub disabled: bool,
/// Whether the node is awaiting execution completion.
pub pending: bool,
/// Inferred type of Enso expression at this node's span. May differ from the definition type /// Inferred type of Enso expression at this node's span. May differ from the definition type
/// stored in the span tree. /// stored in the span tree.
pub usage_type: Option<crate::Type>, pub usage_type: Option<crate::Type>,
@ -1524,6 +1538,7 @@ struct TreeBuilder<'a> {
app: Application, app: Application,
frp: WidgetsFrp, frp: WidgetsFrp,
node_disabled: bool, node_disabled: bool,
node_pending: bool,
node_expression: &'a str, node_expression: &'a str,
layers: &'a GraphLayers, layers: &'a GraphLayers,
styles: &'a StyleWatchFrp, styles: &'a StyleWatchFrp,
@ -1657,6 +1672,7 @@ impl<'a> TreeBuilder<'a> {
}); });
let disabled = self.node_disabled; let disabled = self.node_disabled;
let pending = self.node_pending;
let info = NodeInfo { let info = NodeInfo {
identity: widget_id, identity: widget_id,
@ -1665,6 +1681,7 @@ impl<'a> TreeBuilder<'a> {
connection, connection,
subtree_connection, subtree_connection,
disabled, disabled,
pending,
usage_type, usage_type,
}; };

View File

@ -10,9 +10,9 @@ use span_tree::node::Kind;
/// ============= // =============
/// === Style === // === Style ===
/// ============= // =============
#[derive(Clone, Debug, Default, PartialEq, FromTheme)] #[derive(Clone, Debug, Default, PartialEq, FromTheme)]
#[base_path = "theme::widget::label"] #[base_path = "theme::widget::label"]
@ -25,6 +25,7 @@ struct Style {
disabled_weight: f32, disabled_weight: f32,
placeholder_color: color::Rgba, placeholder_color: color::Rgba,
placeholder_weight: f32, placeholder_weight: f32,
pending_alpha: f32,
} }
// ============== // ==============
@ -39,6 +40,7 @@ ensogl::define_endpoints_2! {
Input { Input {
content(ImString), content(ImString),
text_color(ColorState), text_color(ColorState),
text_pending(bool),
text_weight(Option<text::Weight>), text_weight(Option<text::Weight>),
text_sdf_weight(f32), text_sdf_weight(f32),
} }
@ -90,9 +92,10 @@ impl SpanWidget for Widget {
parent_port_hovered <- widgets_frp.hovered_port_children.map(move |h| h.contains(&id)); parent_port_hovered <- widgets_frp.hovered_port_children.map(move |h| h.contains(&id));
parent_port_hovered <- parent_port_hovered.on_change(); parent_port_hovered <- parent_port_hovered.on_change();
text_color <- frp.text_color.on_change(); text_color <- frp.text_color.on_change();
label_color <- all_with3( text_pending <- frp.text_pending.on_change();
&style, &text_color, &parent_port_hovered, label_color <- all_with4(
|style, state, hovered| state.to_color(*hovered, style) &style, &text_color, &parent_port_hovered, &text_pending,
|style, state, hovered, text_pending| state.to_color(*hovered, style, *text_pending)
).debounce().on_change(); ).debounce().on_change();
color_anim.target <+ label_color; color_anim.target <+ label_color;
eval color_anim.value((color) label.set_property_default(color)); eval color_anim.value((color) label.set_property_default(color));
@ -147,8 +150,9 @@ impl SpanWidget for Widget {
let text_weight = bold.then_some(text::Weight::Bold); let text_weight = bold.then_some(text::Weight::Bold);
let input = &self.frp.public.input; let input = &self.frp.public.input;
input.content.emit(content); input.content(content);
input.text_color.emit(color_state); input.text_color(color_state);
input.text_pending(ctx.info.pending);
input.text_weight(text_weight); input.text_weight(text_weight);
} }
} }
@ -201,14 +205,18 @@ impl ColorState {
text::Weight::from(weight_num as u16) text::Weight::from(weight_num as u16)
}) })
} }
fn to_color(self, is_hovered: bool, style: &Style) -> color::Lcha {
match self { fn to_color(self, is_hovered: bool, style: &Style, text_pending: bool) -> color::Lcha {
let base_color = color::Lcha::from(match self {
_ if is_hovered => style.connected_color, _ if is_hovered => style.connected_color,
ColorState::Base => style.base_color, ColorState::Base => style.base_color,
ColorState::Connected => style.connected_color, ColorState::Connected => style.connected_color,
ColorState::Disabled => style.disabled_color, ColorState::Disabled => style.disabled_color,
ColorState::Placeholder => style.placeholder_color, ColorState::Placeholder => style.placeholder_color,
});
match text_pending {
true => base_color.multiply_alpha(style.pending_alpha),
false => base_color,
} }
.into()
} }
} }

View File

@ -10,12 +10,18 @@ use ensogl_icons::SIZE;
// ================= // =============
// === Constants === // === Style ===
// ================= // =============
/// Distance between the icon and next widget. #[derive(Clone, Debug, Default, PartialEq, FromTheme)]
const ICON_GAP: f32 = 10.0; #[base_path = "theme::widget::method"]
struct Style {
/// Distance between the icon and next widget.
icon_gap: f32,
/// Alpha channel value for the icon displayed on a node in the execution-pending state.
pending_icon_alpha: f32,
}
@ -27,6 +33,13 @@ const ICON_GAP: f32 = 10.0;
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct Config; pub struct Config;
ensogl::define_endpoints_2! {
Input {
set_icon(IconId),
set_icon_state(IconState),
}
}
/// A widget for selecting a single value from a list of available options. The options can be /// A widget for selecting a single value from a list of available options. The options can be
/// provided as a static list of strings from argument `tag_values`, or as a dynamic expression. /// provided as a static list of strings from argument `tag_values`, or as a dynamic expression.
#[derive(Debug, display::Object)] #[derive(Debug, display::Object)]
@ -34,8 +47,8 @@ pub struct Config;
pub struct Widget { pub struct Widget {
display_object: object::Instance, display_object: object::Instance,
icon_wrapper: object::Instance, icon_wrapper: object::Instance,
icon_id: IconId, icon_view: Rc<RefCell<AnyIcon>>,
icon: AnyIcon, frp: Frp,
} }
/// Extension which existence in given subtree prevents the widget from being created again. /// Extension which existence in given subtree prevents the widget from being created again.
@ -54,7 +67,7 @@ impl SpanWidget for Widget {
Score::only_if(matches) Score::only_if(matches)
} }
fn new(_: &Config, _: &ConfigContext) -> Self { fn new(_: &Config, ctx: &ConfigContext) -> Self {
// ╭─display_object──────────────────╮ // ╭─display_object──────────────────╮
// │ ╭ icon ─╮ ╭ content ──────────╮ │ // │ ╭ icon ─╮ ╭ content ──────────╮ │
// │ │ │ │ │ │ // │ │ │ │ │ │
@ -66,15 +79,42 @@ impl SpanWidget for Widget {
display_object display_object
.use_auto_layout() .use_auto_layout()
.set_row_flow() .set_row_flow()
.set_gap_x(ICON_GAP)
.set_children_alignment_left_center() .set_children_alignment_left_center()
.justify_content_center_y(); .justify_content_center_y();
let icon_wrapper = object::Instance::new_named("icon_wrapper"); let icon_wrapper = object::Instance::new_named("icon_wrapper");
icon_wrapper.set_size((SIZE, SIZE)); icon_wrapper.set_size((SIZE, SIZE));
let mut this = Self { display_object, icon_wrapper, icon_id: default(), icon: default() }; let icon_view = Rc::new(RefCell::new(AnyIcon::default()));
this.set_icon(IconId::default());
this let frp = Frp::new();
let network = &frp.network;
let style = ctx.cached_style::<Style>(network);
frp::extend! { network
icon_gap <- style.on_change().map(|style| style.icon_gap);
eval icon_gap((gap) display_object.set_gap_x(*gap).void());
set_icon <- frp.set_icon.on_change();
eval set_icon([icon_view, icon_wrapper] (icon_id) {
let icon = icon_id.cached_view();
icon.set_size((SIZE, SIZE));
icon_wrapper.replace_children(&[icon.display_object()]);
icon_view.replace(icon);
});
icon_state_dirty <- any(...);
icon_state_dirty <+ frp.set_icon_state.on_change();
icon_state_dirty <+ frp.set_icon_state.sample(&set_icon);
icon_alpha <- all_with(&icon_state_dirty, &style, |state, style| state.alpha(style));
eval icon_alpha((alpha)
icon_view.borrow().r_component.set(Vector4(1.0, 1.0, 1.0, *alpha)));
}
frp.set_icon(IconId::default());
frp.set_icon_state(IconState::Ready);
Self {
display_object,
icon_wrapper,
icon_view,
frp,
}
} }
fn configure(&mut self, _: &Config, mut ctx: ConfigContext) { fn configure(&mut self, _: &Config, mut ctx: ConfigContext) {
@ -86,21 +126,34 @@ impl SpanWidget for Widget {
.and_then(|app| app.icon_name.as_ref()) .and_then(|app| app.icon_name.as_ref())
.and_then(|name| name.parse::<IconId>().ok()) .and_then(|name| name.parse::<IconId>().ok())
.unwrap_or(IconId::Method); .unwrap_or(IconId::Method);
if icon_id != self.icon_id { self.frp.set_icon(icon_id);
self.set_icon(icon_id); self.frp.set_icon_state(match ctx.info.pending {
} false => IconState::Ready,
true => IconState::Pending,
});
let child = ctx.builder.child_widget(ctx.span_node, ctx.info.nesting_level); let child = ctx.builder.child_widget(ctx.span_node, ctx.info.nesting_level);
self.display_object.replace_children(&[&self.icon_wrapper, &child.root_object]); self.display_object.replace_children(&[&self.icon_wrapper, &child.root_object]);
} }
} }
impl Widget {
fn set_icon(&mut self, icon_id: IconId) { // === IconState ===
self.icon_id = icon_id;
self.icon = icon_id.cached_view(); /// The display state of the method's icon.
self.icon.set_size((SIZE, SIZE)); #[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
self.icon.r_component.set(Vector4(1.0, 1.0, 1.0, 1.0)); pub enum IconState {
self.icon_wrapper.replace_children(&[self.icon.display_object()]); /// The normal state.
#[default]
Ready,
/// The state when awaiting a pending computation.
Pending,
}
impl IconState {
fn alpha(self, style: &Style) -> f32 {
match self {
IconState::Ready => 1.0,
IconState::Pending => style.pending_icon_alpha,
}
} }
} }

View File

@ -1,240 +0,0 @@
//! Provides [`Status`] to represent a node's execution status, [`Status::display_color`] to express
//! that status as a color and [`ProfilingLabel`] to display a node's execution status.
use crate::prelude::*;
use ensogl::display::shape::*;
use crate::view;
use enso_frp as frp;
use ensogl::application::Application;
use ensogl::data::color;
use ensogl::display;
use ensogl::gui::text;
// ==============
// === Status ===
// ==============
/// Describes whether the source code in a node is currently running or already finished. If it is
/// finished then the status contains the number of milliseconds that it took to run the code.
#[derive(Debug, Copy, Clone)]
pub enum Status {
/// The node's code is still running.
Running,
/// The node finished execution.
Finished {
/// How many milliseconds the node took to execute.
duration: f32,
},
}
impl Status {
/// Returns `true` if the node is still running.
pub fn is_running(self) -> bool {
matches!(self, Status::Running)
}
/// Returns `true` if the node finished execution.
pub fn is_finished(self) -> bool {
matches!(self, Status::Finished { .. })
}
}
impl Default for Status {
fn default() -> Self {
Status::Running
}
}
impl Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Status::Running => {
write!(f, "")
}
Status::Finished { duration } => {
let milliseconds = duration;
let seconds = milliseconds / 1000.0;
let minutes = seconds / 60.0;
let hours = minutes / 60.0;
if hours >= 1.0 {
write!(f, "{hours:.1} h")
} else if minutes >= 1.0 {
write!(f, "{minutes:.1} m")
} else if seconds >= 1.0 {
write!(f, "{seconds:.1} s")
} else {
write!(f, "{milliseconds:.0} ms")
}
}
}
}
}
// =============
// === Color ===
// =============
/// A theme that determines how we express the running time of a node (compared to other nodes on
/// the stage) in a color. The color's lightness and chroma in LCh color space are directly taken
/// from the theme. The chroma will be `min_time_hue` for the node with the shortest running time,
/// `max_time_hue` for the node with the longest running time and linearly interpolated in-between
/// depending on the relative running, time for all other nodes.
#[derive(Debug, Copy, Clone, Default)]
pub struct Theme {
/// The lightness for all running times.
pub lightness: f32,
/// The chroma for all running times.
pub chroma: f32,
/// The hue for the minimum running time.
pub min_time_hue: f32,
/// The hue for the maximum running time.
pub max_time_hue: f32,
}
impl Theme {
/// Returns a new `Sampler` exposing the profiling theme, as it is defined in `styles` at path
/// `ensogl_hardcoded_theme::graph_editor::node::profiling`. The sampler is registered under
/// `network`.
pub fn from_styles(styles: &StyleWatchFrp, network: &frp::Network) -> frp::Sampler<Theme> {
use ensogl_hardcoded_theme::graph_editor::node::profiling as theme_path;
let lightness = styles.get_number_or(theme_path::lightness, 0.5);
let chroma = styles.get_number_or(theme_path::chroma, 1.0);
let min_time_hue = styles.get_number_or(theme_path::min_time_hue, 0.4);
let max_time_hue = styles.get_number_or(theme_path::max_time_hue, 0.1);
frp::extend! { network
init_theme <- source::<()>();
theme <- all_with5(&lightness,&chroma,&min_time_hue,&max_time_hue,&init_theme
,|&lightness,&chroma,&min_time_hue,&max_time_hue,_|
Theme {lightness,chroma,min_time_hue,max_time_hue});
theme_sampler <- theme.sampler();
}
init_theme.emit(());
theme_sampler
}
}
impl Status {
/// Expresses the profiling status as a color, depending on the minimum and maximum running
/// time of any node on the stage and a [`Theme`] that allows to tweak how the colors are
/// chosen. A node that is still running will be treated like finished node with the current
/// maximum execution time.
pub fn display_color(
self,
min_global_duration: f32,
max_global_duration: f32,
theme: Theme,
) -> color::Lch {
let duration = match self {
Status::Running => max_global_duration,
Status::Finished { duration } => duration,
};
let duration_delta = max_global_duration - min_global_duration;
let hue_delta = theme.max_time_hue - theme.min_time_hue;
let relative_duration = if duration_delta != 0.0 && !duration_delta.is_nan() {
(duration - min_global_duration) / duration_delta
} else {
0.0
};
let relative_hue = relative_duration;
let hue = theme.min_time_hue + relative_hue * hue_delta;
color::Lch::new(theme.lightness, theme.chroma, hue)
}
}
// ============
// === Frp ===
// ============
ensogl::define_endpoints! {
Input {
set_status (Status),
set_min_global_duration (f32),
set_max_global_duration (f32),
set_view_mode (view::Mode),
}
}
// ==========================
// === Running Time Label ===
// ==========================
/// A `display::Object` providing a label for nodes that displays the node's running time in
/// profiling mode after the node finished execution. The node's execution status has to be provided
/// through `set_status`, the view mode through `set_view_mode`, the minimum and maximum running
/// time of any node on the stage through `set_min_global_duration` and `set_max_global_duration`.
/// The color of the label will reflect the status and be determined by [`Status::display_color`].
/// The necessary theme will be taken from the application's style sheet. The origin of the label,
/// as a `display::Object` should be placed on the node's center.
#[derive(Clone, CloneRef, Debug, Deref, display::Object)]
pub struct ProfilingLabel {
display_object: display::object::Instance,
label: text::Text,
#[deref]
frp: Frp,
styles: StyleWatchFrp,
}
impl ProfilingLabel {
/// Constructs a `ProfilingLabel` for the given application.
pub fn new(app: &Application) -> Self {
let scene = &app.display.default_scene;
let display_object = display::object::Instance::new();
let label = text::Text::new(app);
display_object.add_child(&label);
label.set_y(crate::component::node::input::area::TEXT_SIZE / 2.0);
let frp = Frp::new();
let network = &frp.network;
let color = color::Animation::new(network);
frp::extend! { network
// === Visibility ===
visibility <- all_with(&frp.set_view_mode,&frp.set_status,|mode,status| {
matches!((mode,status),(view::Mode::Profiling,Status::Finished {..}))
});
color.target_alpha <+ visibility.map(|&is_visible| {
if is_visible { 1.0 } else { 0.0 }
});
// === Color ===
let styles = StyleWatchFrp::new(&scene.style_sheet);
let theme = Theme::from_styles(&styles,network);
color.target_color <+ all_with4
(&frp.set_status,&frp.set_min_global_duration,&frp.set_max_global_duration,&theme,
|&status,&min,&max,&theme| status.display_color(min,max,theme)
);
label.set_property_default <+ color.value.ref_into_some();
// === Position ===
let x_offset = crate::component::node::input::area::TEXT_OFFSET;
eval label.width((&width) label.set_x(-width-x_offset));
// === Content ===
label.set_content <+ frp.set_status.map(|status| status.to_im_string());
}
ProfilingLabel { display_object, label, frp, styles }
}
}

View File

@ -1,196 +0,0 @@
//! Provides a button that can be used to toggle the editor's profiling mode.
use crate::prelude::*;
use ensogl::display::shape::*;
use crate::view;
use enso_frp as frp;
use ensogl::application::tooltip;
use ensogl::application::Application;
use ensogl::data::color;
use ensogl::display;
use ensogl_component::toggle_button;
use ensogl_component::toggle_button::ToggleButton;
// ============
// === Icon ===
// ============
/// Defines an icon as described here:
/// https://github.com/enso-org/ide/pull/1546#issuecomment-838169795
///
/// It consists of a *circle outline* with an *aperture* in the lower right quadrant. The edges
/// of the aperture have rounded *caps*. In the center is an *inner circle* with a *needle*
/// pointing to the lower right.
mod icon {
use super::*;
use ensogl_component::toggle_button::ColorableShape;
ensogl::shape! {
alignment = center;
(style: Style, color_rgba: Vector4<f32>) {
let fill_color = Var::<color::Rgba>::from(color_rgba);
let width = Var::<Pixels>::from("input_size.x");
let height = Var::<Pixels>::from("input_size.y");
// === Measurements ===
let unit = &width * 0.3;
let outer_circle_radius = &unit * 1.0;
let outer_circle_thickness = &unit * 0.33;
let inner_circle_radius = &unit * 0.2;
let needle_angle = (135.0_f32).to_radians().radians();
let needle_radius_inner = &unit * 0.14;
let needle_radius_outer = &unit * 0.09;
let needle_length = &outer_circle_radius-&needle_radius_outer;
let aperture_cap_1_x = &outer_circle_radius-&outer_circle_thickness*0.5;
let aperture_cap_2_y = -(&outer_circle_radius-&outer_circle_thickness*0.5);
// === Circle Outline ===
let circle = Circle(&outer_circle_radius);
let gap = Circle(&outer_circle_radius-&outer_circle_thickness);
let circle_outline = circle - gap;
// === Aperture ===
// To produce the aperture, we cut a triangular gap from the outline and use small
// circular caps to round off the edges.
// We make the gap a little bit larger than the circle to be sure that we really cover
// everything that we want to cut, even if there are rounding errors or other
// imprecisions.
let aperture_gap_size = &outer_circle_radius * 1.1;
let aperture_gap_angle = needle_angle+180.0_f32.to_radians().radians();
let aperture_gap = Triangle(&aperture_gap_size*2.0,aperture_gap_size.clone());
let aperture_gap = aperture_gap.rotate(aperture_gap_angle);
let aperture_gap = aperture_gap.translate_x(&aperture_gap_size*2.0.sqrt()*0.25);
let aperture_gap = aperture_gap.translate_y(-&aperture_gap_size*2.0.sqrt()*0.25);
let aperture_cap_1 = Circle(&outer_circle_thickness*0.5);
let aperture_cap_1 = aperture_cap_1.translate_x(aperture_cap_1_x);
let aperture_cap_2 = Circle(&outer_circle_thickness*0.5);
let aperture_cap_2 = aperture_cap_2.translate_y(aperture_cap_2_y);
let circle_outline = circle_outline - aperture_gap + aperture_cap_1 + aperture_cap_2;
// === Needle ===
let needle = UnevenCapsule(needle_radius_outer,needle_radius_inner,needle_length);
let needle = needle.rotate(needle_angle);
let inner_circle = Circle(inner_circle_radius);
// === Composition ===
let shape = (circle_outline + needle + inner_circle).fill(fill_color);
let hover_area = Rect((&width,&height)).fill(INVISIBLE_HOVER_COLOR);
(shape + hover_area).into()
}
}
impl ColorableShape for Shape {
fn set_color(&self, color: color::Rgba) {
self.color_rgba.set(Vector4::new(color.red, color.green, color.blue, color.alpha));
}
}
}
// ============
// === Frp ===
// ============
ensogl::define_endpoints! {
Input {
set_view_mode (view::Mode),
}
Output {
view_mode (view::Mode),
}
}
// =======================
// === ProfilingButton ===
// =======================
/// A toggle button that can be used to toggle the graph editor's view mode. It positions itself in
/// the upper right corner of the scene.
#[derive(Debug, Clone, CloneRef, Deref, display::Object)]
pub struct Button {
#[deref]
frp: Frp,
#[display_object]
button: ToggleButton<icon::Shape>,
styles: StyleWatchFrp,
}
impl Button {
/// Constructs a new button for toggling the editor's view mode.
pub fn new(app: &Application) -> Button {
let scene = &app.display.default_scene;
let style_sheet = &scene.style_sheet;
let styles = StyleWatchFrp::new(style_sheet);
let frp = Frp::new();
let network = &frp.network;
let tooltip_style = tooltip::Style::set_label("Profile".to_owned())
.with_placement(tooltip::Placement::Left);
let button = ToggleButton::<icon::Shape>::new(app, tooltip_style);
scene.layers.panel.add(&button);
button.set_size(Vector2(32.0, 32.0));
frp::extend! { network
// === State ===
frp.source.view_mode <+ button.state.map(|&toggled| {
if toggled { view::Mode::Profiling } else { view::Mode::Normal }
});
button.set_state <+ frp.set_view_mode.map(|&mode| mode.is_profiling());
// === Position ===
eval scene.frp.camera_changed([button,scene](_) {
let screen = scene.camera().screen();
button.set_x(screen.width/2.0 - 16.0);
button.set_y(screen.height/2.0 - 16.0);
});
// === Color ===
use ensogl_hardcoded_theme::graph_editor::profiling_button as button_theme;
let toggled_color = styles.get_color(button_theme::toggled);
let toggled_hovered_color = styles.get_color(button_theme::toggled_hovered);
init_color_scheme <- source::<()>();
button.set_color_scheme <+ all_with3(
&toggled_color,
&toggled_hovered_color,
&init_color_scheme,
f!([style_sheet] (&toggled, &toggled_hovered, _)
toggle_button::ColorScheme {
toggled : Some(toggled.into()),
toggled_hovered : Some(toggled_hovered.into()),
..toggle_button::default_color_scheme(&style_sheet)
}
)
);
}
init_color_scheme.emit(());
Button { frp, button, styles }
}
}

View File

@ -43,8 +43,6 @@ pub mod data;
pub mod execution_environment; pub mod execution_environment;
pub mod new_node_position; pub mod new_node_position;
#[warn(missing_docs)] #[warn(missing_docs)]
pub mod profiling;
#[warn(missing_docs)]
pub mod view; pub mod view;
mod layers; mod layers;
@ -88,7 +86,6 @@ use span_tree::PortId;
// === Export === // === Export ===
// ============== // ==============
pub use crate::node::profiling::Status as NodeProfilingStatus;
pub use layers::GraphLayers; pub use layers::GraphLayers;
@ -552,9 +549,9 @@ ensogl::define_endpoints_2! {
/// Collapse the selected nodes into a new node. /// Collapse the selected nodes into a new node.
collapse_selected_nodes(), collapse_selected_nodes(),
/// Indicate whether this node had an error or not. /// Indicate whether this node had an error or not.
set_node_error_status(NodeId,Option<node::error::Error>), set_node_error_status(NodeId, Option<node::error::Error>),
/// Indicate whether this node has finished execution. /// Indicate whether this node has finished execution.
set_node_profiling_status(NodeId,node::profiling::Status), set_node_pending_status(NodeId, bool),
// === Visualization === // === Visualization ===
@ -1759,16 +1756,6 @@ impl GraphEditorModel {
node.set_read_only <+ self.frp.input.set_read_only; node.set_read_only <+ self.frp.input.set_read_only;
// === Profiling ===
let profiling_min_duration = &self.profiling_statuses.min_duration;
node.set_profiling_min_global_duration <+ self.profiling_statuses.min_duration;
node.set_profiling_min_global_duration(profiling_min_duration.value());
let profiling_max_duration = &self.profiling_statuses.max_duration;
node.set_profiling_max_global_duration <+ self.profiling_statuses.max_duration;
node.set_profiling_max_global_duration(profiling_max_duration.value());
// === Execution Environment === // === Execution Environment ===
node.set_execution_environment <+ self.frp.output.execution_environment; node.set_execution_environment <+ self.frp.output.execution_environment;
@ -1809,7 +1796,6 @@ pub struct GraphEditorModel {
visualizations: Visualizations, visualizations: Visualizations,
frp: api::Private, frp: api::Private,
frp_public: api::Public, frp_public: api::Public,
profiling_statuses: profiling::Statuses,
styles_frp: StyleWatchFrp, styles_frp: StyleWatchFrp,
selection_controller: selection::Controller, selection_controller: selection::Controller,
} }
@ -1831,7 +1817,6 @@ impl GraphEditorModel {
let app = app.clone_ref(); let app = app.clone_ref();
let navigator = Navigator::new(scene, &scene.camera()); let navigator = Navigator::new(scene, &scene.camera());
let tooltip = Tooltip::new(&app); let tooltip = Tooltip::new(&app);
let profiling_statuses = profiling::Statuses::new();
let add_node_button = Rc::new(component::add_node_button::AddNodeButton::new(&app)); let add_node_button = Rc::new(component::add_node_button::AddNodeButton::new(&app));
let drop_manager = let drop_manager =
ensogl_drop_manager::Manager::new(&scene.dom.root.clone_ref().into(), scene); ensogl_drop_manager::Manager::new(&scene.dom.root.clone_ref().into(), scene);
@ -1858,7 +1843,6 @@ impl GraphEditorModel {
touch_state, touch_state,
visualizations, visualizations,
navigator, navigator,
profiling_statuses,
add_node_button, add_node_button,
frp: frp.private.clone_ref(), frp: frp.private.clone_ref(),
frp_public: frp.public.clone_ref(), frp_public: frp.public.clone_ref(),
@ -3020,17 +3004,12 @@ fn init_remaining_graph_editor_frp(
} }
// === Set Node Pending ===
// === Profiling ===
frp::extend! { network frp::extend! { network
eval inputs.set_node_profiling_status([model]((node_id,status)) { eval inputs.set_node_pending_status([model]((node_id, is_pending)) {
model.with_node(*node_id, |node| { model.with_node(*node_id, |n| n.set_pending.emit(is_pending))
model.profiling_statuses.set(*node_id,*status); });
node.set_profiling_status(status);
})
});
} }
@ -3287,7 +3266,6 @@ fn init_remaining_graph_editor_frp(
eval out.node_selected ((id) model.nodes.select(id)); eval out.node_selected ((id) model.nodes.select(id));
eval out.node_deselected ((id) model.nodes.deselect(id)); eval out.node_deselected ((id) model.nodes.deselect(id));
eval out.node_removed ((id) model.remove_node(*id)); eval out.node_removed ((id) model.remove_node(*id));
model.profiling_statuses.remove <+ out.node_removed;
out.on_visualization_select <+ out.node_removed.map(|&id| Switch::Off(id)); out.on_visualization_select <+ out.node_removed.map(|&id| Switch::Off(id));
// === Remove implementation === // === Remove implementation ===

View File

@ -1,104 +0,0 @@
//! [`ProfilingStatuses`] can be used to collect the profiling statuses of all nodes in a graph. It
//! exposes their minimum and maximum running times through its FRP endpoints. The structure needs
//! to be updated whenever a node is added or deleted or changes its profiling status.
use crate::prelude::*;
use crate::node;
use crate::NodeId;
use bimap::BiBTreeMap;
use enso_frp as frp;
use ordered_float::OrderedFloat;
// =====================
// === FRP Endpoints ===
// =====================
ensogl::define_endpoints! {
Input {
/// Informs the `Statuses` collection about the profiling status of a node.
set(NodeId,node::profiling::Status),
/// Removes a node's information from the collection.
remove (NodeId)
}
Output {
/// The minimum running time of any node in milliseconds. Is positive infinity if no status
/// was registered.
min_duration (f32),
/// The maximum running time of any node in milliseconds. Is 0.0 if no status was
/// registered.
max_duration (f32),
}
}
// ================
// === Statuses ===
// ================
/// Can be used to track the execution statuses of all nodes in the graph editor. Exposes the
/// minimum and maximum running time through FRP endpoints.
#[derive(Debug, Clone, CloneRef, Default)]
pub struct Statuses {
frp: Frp,
durations: Rc<RefCell<BiBTreeMap<NodeId, OrderedFloat<f32>>>>,
}
impl Deref for Statuses {
type Target = Frp;
fn deref(&self) -> &Self::Target {
&self.frp
}
}
impl Statuses {
/// Creates a new `Statuses` collection.
pub fn new() -> Self {
let frp = Frp::new();
let durations = Rc::new(RefCell::new(BiBTreeMap::<NodeId, OrderedFloat<f32>>::new()));
let network = &frp.network;
frp::extend! { network
min_and_max_from_set <- frp.set.map(f!([durations]((node,status)) {
match status {
node::profiling::Status::Finished {duration} => {
durations.borrow_mut().insert(*node,OrderedFloat(*duration));
},
_ => {
durations.borrow_mut().remove_by_left(node);
},
};
Self::min_and_max(&durations.borrow())
}));
min_and_max_from_remove <- frp.remove.map(f!([durations](node) {
durations.borrow_mut().remove_by_left(node);
Self::min_and_max(&durations.borrow())
}));
min_and_max <- any(&min_and_max_from_set,&min_and_max_from_remove);
frp.source.min_duration <+ min_and_max._0().on_change();
frp.source.max_duration <+ min_and_max._1().on_change();
}
frp.source.min_duration.emit(Self::min_and_max(durations.borrow().deref()).0);
frp.source.max_duration.emit(Self::min_and_max(durations.borrow().deref()).1);
Self { frp, durations }
}
fn min_and_max(durations: &BiBTreeMap<NodeId, OrderedFloat<f32>>) -> (f32, f32) {
let mut durations = durations.right_values().copied();
let min = durations.next().map(OrderedFloat::into_inner).unwrap_or(f32::INFINITY);
let max = durations.last().map(OrderedFloat::into_inner).unwrap_or(0.0);
(min, max)
}
}

View File

@ -560,11 +560,8 @@ define_themes! { [light:0, dark:1]
stripe_gap = 20.0 , 20.0; stripe_gap = 20.0 , 20.0;
stripe_angle = 135.0 , 135.0; stripe_angle = 135.0 , 135.0;
} }
profiling { pending {
lightness = code::types::lightness , code::types::lightness; alpha_factor = 0.5;
chroma = code::types::chroma , code::types::chroma;
min_time_hue = 0.38 , 0.38;
max_time_hue = 0.07 , 0.07;
} }
type_label { type_label {
offset_y = -23.0, -23.0; offset_y = -23.0, -23.0;
@ -614,10 +611,6 @@ define_themes! { [light:0, dark:1]
chroma_factor = 0.8 , 1.0; chroma_factor = 0.8 , 1.0;
} }
} }
profiling_button {
toggled = Lcha(0.7,0.5,0.12,1.0) , Lcha(0.7,0.5,0.12,1.0);
toggled_hovered = Lcha(0.55,0.5,0.12,1.0) , Lcha(0.85,0.5,0.12,1.0);
}
add_node_button { add_node_button {
margin = 14.0, 14.0; margin = 14.0, 14.0;
size = 60.0, 60.0; size = 60.0, 60.0;
@ -710,6 +703,7 @@ define_themes! { [light:0, dark:1]
/// this style. /// this style.
connected_color = Lcha(1.0,0.0,0.0,1.0); connected_color = Lcha(1.0,0.0,0.0,1.0);
connected_weight = 400.0; connected_weight = 400.0;
pending_alpha = graph_editor::node::pending::alpha_factor;
} }
separator { separator {
color = Rgba(0.0, 0.0, 0.0, 0.12); color = Rgba(0.0, 0.0, 0.0, 0.12);
@ -722,6 +716,10 @@ define_themes! { [light:0, dark:1]
margin = widget::separator::margin; margin = widget::separator::margin;
weight = 400.0; weight = 400.0;
} }
method {
icon_gap = 10.0;
pending_icon_alpha = graph_editor::node::pending::alpha_factor;
}
} }
colors { colors {
dimming { dimming {