Add node button to enable/disable output context (#6205)

Closes #5929: Adding a node button to enable and disable the output context for that particular node.

I also added a temporary shortcut (cmd-shift-c) to switch the execution environment so we can properly test it.


https://user-images.githubusercontent.com/607786/230036314-052b734a-1846-4057-93d8-2152e1e0cce6.mp4

# Important Notes
While we're waiting to integrate it with the language server, the execution environment is temporarily stored in the presenter. (Otherwise we'd have to define it in multiple places and the behaviour would look rather weird.)

I also fixed a bug where the view didn't get any updates when the context switch expression changed. I'll make a comment on the relevant part. I think the SKIP and FREEZE macros might have the same issue.
This commit is contained in:
Stijn ("stain") Seghers 2023-04-11 19:05:37 +02:00 committed by GitHub
parent 5b3cf6f503
commit 810127b887
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 327 additions and 109 deletions

View File

@ -19,6 +19,7 @@ use ide_view as view;
use ide_view::graph_editor::component::node as node_view;
use ide_view::graph_editor::component::visualization as visualization_view;
use ide_view::graph_editor::EdgeEndpoint;
use view::graph_editor::ExecutionEnvironment;
use view::graph_editor::WidgetUpdates;
@ -82,13 +83,15 @@ pub fn default_node_position() -> Vector2 {
#[derive(Debug)]
struct Model {
project: model::Project,
controller: controller::ExecutedGraph,
view: view::graph_editor::GraphEditor,
state: Rc<State>,
_visualization: Visualization,
widget: controller::Widget,
_execution_stack: CallStack,
project: model::Project,
controller: controller::ExecutedGraph,
view: view::graph_editor::GraphEditor,
state: Rc<State>,
_visualization: Visualization,
widget: controller::Widget,
_execution_stack: CallStack,
// TODO(#5930): Move me once we synchronise the execution environment with the language server.
execution_environment: Rc<Cell<ExecutionEnvironment>>,
}
impl Model {
@ -115,6 +118,7 @@ impl Model {
_visualization: visualization,
widget,
_execution_stack: execution_stack,
execution_environment: Default::default(),
}
}
@ -166,16 +170,6 @@ impl Model {
);
}
/// TODO(#5930): Provide the state of the output context in the current environment.
fn output_context_enabled(&self) -> bool {
true
}
/// TODO(#5930): Provide the current execution environment of the project.
fn execution_environment(&self) -> &str {
"design"
}
/// Sets or clears a context switch expression for the specified node.
///
/// A context switch expression allows enabling or disabling the execution of a particular node
@ -185,22 +179,21 @@ impl Model {
///
/// The behavior of this function can be summarized in the following table:
/// ```ignore
/// | Context Enabled | Active | Action |
/// |-----------------|-------------|--------------|
/// | Yes | Yes | Add Disable |
/// | Yes | No | Clear |
/// | No | Yes | Add Enable |
/// | No | No | Clear |
/// | Global Context Permission | Active | Action |
/// |---------------------------|-------------|--------------|
/// | Enabled | Yes | Add Disable |
/// | Enabled | No | Clear |
/// | Disabled | Yes | Add Enable |
/// | Disabled | No | Clear |
/// ```
/// TODO(#5929): Connect this function with buttons on nodes.
#[allow(dead_code)]
fn node_action_context_switch(&self, id: ViewNodeId, active: bool) {
let context = Context::Output;
let current_state = self.output_context_enabled();
let environment = self.execution_environment().into();
let environment = self.execution_environment.get();
let current_state = environment.output_context_enabled();
let switch = if current_state { ContextSwitch::Disable } else { ContextSwitch::Enable };
let expr = if active {
Some(ContextSwitchExpression { switch, context, environment })
let environment_name = environment.to_string().into();
Some(ContextSwitchExpression { switch, context, environment: environment_name })
} else {
None
};
@ -500,6 +493,15 @@ impl Model {
}
}
}
fn toggle_execution_environment(&self) -> ExecutionEnvironment {
let new_environment = match self.execution_environment.get() {
ExecutionEnvironment::Live => ExecutionEnvironment::Design,
ExecutionEnvironment::Design => ExecutionEnvironment::Live,
};
self.execution_environment.set(new_environment);
new_environment
}
}
@ -538,18 +540,14 @@ impl ExpressionUpdate {
self.freeze_updated.map(|freeze| (self.id, freeze))
}
/// An updated status of output context switch (`true` if output context is explicitly enabled
/// for the node, `false` otherwise). `None` if the status was not updated.
fn output_context(&self) -> Option<(ViewNodeId, bool)> {
/// An updated status of output context switch: `true` (or `false`) if the output context was
/// explicitly enabled (or disabled) for the node, `None` otherwise. The outer `Option` is
/// `None` if the status was not updated.
fn output_context(&self) -> Option<(ViewNodeId, Option<bool>)> {
self.context_switch_updated.as_ref().map(|context_switch_expr| {
use Context::*;
use ContextSwitch::*;
let enabled = match context_switch_expr {
Some(ContextSwitchExpression { switch: Enable, context: Output, .. }) => true,
Some(ContextSwitchExpression { switch: Disable, context: Output, .. }) => false,
None => false,
};
(self.id, enabled)
let switch =
context_switch_expr.as_ref().map(|expr| expr.switch == ContextSwitch::Enable);
(self.id, switch)
})
}
}
@ -720,6 +718,14 @@ impl Graph {
}));
// === Execution Environment ===
// TODO(#5930): Delete me once we synchronise the execution environment with the
// language server.
view.set_execution_environment <+ view.toggle_execution_environment.map(
f_!(model.toggle_execution_environment()));
// === Refreshing Nodes ===
remove_node <= update_data.map(|update| update.remove_nodes());
@ -727,12 +733,7 @@ impl Graph {
update_node_expression <- expression_update.map(ExpressionUpdate::expression);
set_node_skip <- expression_update.filter_map(ExpressionUpdate::skip);
set_node_freeze <- expression_update.filter_map(ExpressionUpdate::freeze);
// TODO(#5930): Use project model to retrieve a current state of the output context.
output_context_enabled <- update_view.constant(true);
output_context_updated <- expression_update.filter_map(ExpressionUpdate::output_context);
_context_switch_highlighted <- output_context_updated.map2(&output_context_enabled,
|(node_id, enabled_for_node), enabled_globally| (*node_id, enabled_for_node != enabled_globally)
);
set_node_context_switch <- expression_update.filter_map(ExpressionUpdate::output_context);
set_node_position <= update_data.map(|update| update.set_node_positions());
set_node_visualization <= update_data.map(|update| update.set_node_visualizations());
enable_vis <- set_node_visualization.filter_map(|(id,path)| path.is_some().as_some(*id));
@ -741,8 +742,7 @@ impl Graph {
view.set_node_expression <+ update_node_expression;
view.set_node_skip <+ set_node_skip;
view.set_node_freeze <+ set_node_freeze;
// TODO (#5929): Connect to the view when the API is ready.
// view.highlight_output_context_switch <+ context_switch_highlighted;
view.set_node_context_switch <+ set_node_context_switch;
view.set_node_position <+ set_node_position;
view.set_visualization <+ set_node_visualization;
view.enable_visualization <+ enable_vis;
@ -789,6 +789,7 @@ impl Graph {
eval view.nodes_collapsed(((nodes, _)) model.nodes_collapsed(nodes));
eval view.enabled_visualization_path(((node_id, path)) model.node_visualization_changed(*node_id, path.clone()));
eval view.node_expression_span_set(((node_id, crumbs, expression)) model.node_expression_span_set(*node_id, crumbs, expression.clone_ref()));
eval view.node_action_context_switch(((node_id, active)) model.node_action_context_switch(*node_id, *active));
eval view.node_action_skip(((node_id, enabled)) model.node_action_skip(*node_id, *enabled));
eval view.node_action_freeze(((node_id, enabled)) model.node_action_freeze(*node_id, *enabled));
eval view.request_import((import_path) model.add_import_if_missing(import_path));

View File

@ -94,13 +94,13 @@ pub fn entry_point_icons() {
skip_icon.color_rgba.set(dark_green.into());
place_icon(&world, skip_icon, 20.0, y);
let disable_reevaluation_icon = action_bar::icon::disable_reevaluation::View::new();
disable_reevaluation_icon.color_rgba.set(dark_green.into());
place_icon(&world, disable_reevaluation_icon, 40.0, y);
let disable_output_context_icon = action_bar::icon::disable_output_context::View::new();
disable_output_context_icon.color_rgba.set(dark_green.into());
place_icon(&world, disable_output_context_icon, 40.0, y);
let enable_reevaluation_icon = action_bar::icon::enable_reevaluation::View::new();
enable_reevaluation_icon.color_rgba.set(dark_green.into());
place_icon(&world, enable_reevaluation_icon, 60.0, y);
let enable_output_context_icon = action_bar::icon::enable_output_context::View::new();
enable_output_context_icon.color_rgba.set(dark_green.into());
place_icon(&world, enable_output_context_icon, 60.0, y);
}
/// Create a grid with pixel squares to help development of icons.

View File

@ -11,6 +11,7 @@ use crate::component::visualization;
use crate::selection::BoundingBox;
use crate::tooltip;
use crate::view;
use crate::ExecutionEnvironment;
use crate::Type;
use crate::WidgetUpdates;
@ -306,6 +307,9 @@ ensogl::define_endpoints_2! {
edit_expression (text::Range<text::Byte>, ImString),
set_skip_macro (bool),
set_freeze_macro (bool),
/// Set whether the output context is explicitly enabled: `Some(true/false)` for
/// enabled/disabled; `None` for no context switch expression.
set_context_switch (Option<bool>),
set_comment (Comment),
set_error (Option<Error>),
/// Set the expression USAGE type. This is not the definition type, which can be set with
@ -326,7 +330,8 @@ ensogl::define_endpoints_2! {
set_profiling_max_global_duration (f32),
set_profiling_status (profiling::Status),
/// 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),
}
Output {
/// Press event. Emitted when user clicks on non-active part of the node, like its
@ -342,6 +347,7 @@ ensogl::define_endpoints_2! {
/// and update the node with new expression tree using `set_expression`.
on_expression_modified (span_tree::Crumbs, ImString),
comment (Comment),
context_switch (bool),
skip (bool),
freeze (bool),
hover (bool),
@ -801,6 +807,7 @@ impl Node {
// === Action Bar ===
let visualization_button_state = action_bar.action_visibility.clone_ref();
out.context_switch <+ action_bar.action_context_switch;
out.skip <+ action_bar.action_skip;
out.freeze <+ action_bar.action_freeze;
show_action_bar <- out.hover && input.show_quick_action_bar_on_hover;
@ -808,6 +815,8 @@ impl Node {
eval input.show_quick_action_bar_on_hover((value) action_bar.show_on_hover(value));
action_bar.set_action_freeze_state <+ input.set_freeze_macro;
action_bar.set_action_skip_state <+ input.set_skip_macro;
action_bar.set_action_context_switch_state <+ input.set_context_switch;
action_bar.set_execution_environment <+ input.set_execution_environment;
// === View Mode ===

View File

@ -3,6 +3,8 @@
use crate::prelude::*;
use ensogl::display::shape::*;
use crate::ExecutionEnvironment;
use enso_config::ARGS;
use enso_frp as frp;
use ensogl::application::tooltip;
@ -31,9 +33,11 @@ const BUTTON_OFFSET: f32 = 0.5;
/// Grow the hover area in x direction by this amount. Used to close the gap between action
/// icons and node.
const HOVER_EXTENSION_X: f32 = 15.0;
const VISIBILITY_TOOLTIP_LABEL: &str = "Show preview";
const DISABLE_OUTPUT_CONTEXT_TOOLTIP_LABEL: &str = "Don't write to files and databases";
const ENABLE_OUTPUT_CONTEXT_TOOLTIP_LABEL: &str = "Allow writing to files and databases";
const FREEZE_TOOLTIP_LABEL: &str = "Freeze";
const SKIP_TOOLTIP_LABEL: &str = "Skip";
const VISIBILITY_TOOLTIP_LABEL: &str = "Show preview";
// ===============
@ -65,20 +69,25 @@ mod hover_area {
ensogl::define_endpoints! {
Input {
set_size (Vector2),
set_visibility (bool),
set_action_visibility_state (bool),
set_action_skip_state (bool),
set_action_freeze_state (bool),
show_on_hover (bool),
set_size (Vector2),
set_visibility (bool),
set_action_visibility_state (bool),
set_action_skip_state (bool),
set_action_freeze_state (bool),
/// Set whether the output context is explicitly enabled: `Some(true/false)` for
/// enabled/disabled; `None` for no context switch expression.
set_action_context_switch_state (Option<bool>),
show_on_hover (bool),
set_execution_environment (ExecutionEnvironment),
}
Output {
mouse_over (),
mouse_out (),
action_visibility (bool),
action_freeze (bool),
action_skip (bool),
mouse_over (),
mouse_out (),
action_visibility (bool),
action_context_switch (bool),
action_freeze (bool),
action_skip (bool),
}
}
@ -91,29 +100,40 @@ ensogl::define_endpoints! {
#[derive(Clone, CloneRef, Debug)]
struct Icons {
display_object: display::object::Instance,
freeze: ToggleButton<icon::freeze::Shape>,
visibility: ToggleButton<icon::visibility::Shape>,
context_switch: ContextSwitchButton,
freeze: ToggleButton<icon::freeze::Shape>,
skip: ToggleButton<icon::skip::Shape>,
}
impl Icons {
fn new(app: &Application) -> Self {
let display_object = display::object::Instance::new();
let freeze = labeled_button(app, FREEZE_TOOLTIP_LABEL);
let visibility = labeled_button(app, VISIBILITY_TOOLTIP_LABEL);
let context_switch = ContextSwitchButton::enable(app);
let freeze = labeled_button(app, FREEZE_TOOLTIP_LABEL);
let skip = labeled_button(app, SKIP_TOOLTIP_LABEL);
display_object.add_child(&visibility);
display_object.add_child(&context_switch);
if ARGS.groups.feature_preview.options.skip_and_freeze.value {
display_object.add_child(&freeze);
display_object.add_child(&skip);
}
Self { display_object, freeze, visibility, skip }
Self { display_object, visibility, context_switch, freeze, skip }
}
fn set_visibility(&self, visible: bool) {
self.visibility.frp.set_visibility(visible);
self.context_switch.set_visibility(visible);
self.freeze.frp.set_visibility(visible);
self.skip.frp.set_visibility(visible);
self.visibility.frp.set_visibility(visible);
}
fn set_color_scheme(&self, color_scheme: &toggle_button::ColorScheme) {
self.visibility.frp.set_color_scheme(color_scheme);
self.context_switch.set_color_scheme(color_scheme);
self.freeze.frp.set_color_scheme(color_scheme);
self.skip.frp.set_color_scheme(color_scheme);
}
}
@ -130,6 +150,77 @@ fn labeled_button<Icon: ColorableShape>(app: &Application, label: &str) -> Toggl
// =============================
// === Context Switch Button ===
// =============================
/// A button to enable/disable the output context for a particular node. It holds two buttons
/// internally for each shape, but only one is shown at a time, based on the execution environment
/// which sets the global permission for the output context.
#[derive(Clone, CloneRef, Debug)]
struct ContextSwitchButton {
globally_enabled: Rc<Cell<bool>>,
disable_button: ToggleButton<icon::disable_output_context::Shape>,
enable_button: ToggleButton<icon::enable_output_context::Shape>,
display_object: display::object::Instance,
}
impl ContextSwitchButton {
fn enable(app: &Application) -> Self {
let display_object = display::object::Instance::new();
let disable_button = labeled_button(app, DISABLE_OUTPUT_CONTEXT_TOOLTIP_LABEL);
let enable_button = labeled_button(app, ENABLE_OUTPUT_CONTEXT_TOOLTIP_LABEL);
disable_button.set_size((100.pc(), 100.pc()));
enable_button.set_size((100.pc(), 100.pc()));
display_object.add_child(&enable_button);
let globally_enabled = Rc::new(Cell::new(false));
Self { globally_enabled, disable_button, enable_button, display_object }
}
/// Set the button's on/off state based on whether the output context is explicitly enabled for
/// this node. `output_context_enabled` is `Some(true/false)` for enabled/disabled; `None` for
/// no context switch expression.
fn set_state(&self, output_context_enabled: Option<bool>) {
let disable_button_active = !output_context_enabled.unwrap_or(true);
self.disable_button.set_state(disable_button_active);
let enable_button_active = output_context_enabled.unwrap_or(false);
self.enable_button.set_state(enable_button_active);
}
/// Swap the buttons if the execution environment changed.
fn set_execution_environment(&self, environment: &ExecutionEnvironment) {
if environment.output_context_enabled() != self.globally_enabled.get() {
if environment.output_context_enabled() {
self.remove_child(&self.enable_button);
self.add_child(&self.disable_button);
self.globally_enabled.set(true);
} else {
self.remove_child(&self.disable_button);
self.add_child(&self.enable_button);
self.globally_enabled.set(false);
}
}
}
fn set_visibility(&self, visible: bool) {
self.disable_button.set_visibility(visible);
self.enable_button.set_visibility(visible);
}
fn set_color_scheme(&self, color_scheme: &toggle_button::ColorScheme) {
self.disable_button.set_color_scheme(color_scheme);
self.enable_button.set_color_scheme(color_scheme);
}
}
impl display::Object for ContextSwitchButton {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
}
}
// ========================
// === Action Bar Model ===
// ========================
@ -155,14 +246,18 @@ impl Model {
let styles = StyleWatch::new(&scene.style_sheet);
shapes.add_sub_shape(&hover_area);
shapes.add_sub_shape(&icons.freeze.view());
shapes.add_sub_shape(&icons.visibility.view());
shapes.add_sub_shape(&icons.context_switch.disable_button.view());
shapes.add_sub_shape(&icons.context_switch.enable_button.view());
shapes.add_sub_shape(&icons.freeze.view());
shapes.add_sub_shape(&icons.skip.view());
ensogl::shapes_order_dependencies! {
scene => {
hover_area -> icon::freeze;
hover_area -> icon::visibility;
hover_area -> icon::disable_output_context;
hover_area -> icon::enable_output_context;
hover_area -> icon::freeze;
hover_area -> icon::skip;
}
}
@ -176,13 +271,13 @@ impl Model {
self
}
fn place_button_in_slot<T: ColorableShape>(&self, button: &ToggleButton<T>, index: usize) {
fn place_button_in_slot(&self, button: &dyn display::Object, index: usize) {
let icon_size = self.icon_size();
let index = index as f32;
let padding = BUTTON_PADDING;
let offset = BUTTON_OFFSET;
button.set_x(((1.0 + padding) * index + offset) * icon_size.x);
button.frp.set_size(icon_size);
button.set_size(icon_size);
}
fn icon_size(&self) -> Vector2 {
@ -211,17 +306,18 @@ impl Model {
self.icons.set_x(-size.x / 2.0);
self.place_button_in_slot(&self.icons.visibility, 0);
self.place_button_in_slot(&self.icons.context_switch, 1);
if ARGS.groups.feature_preview.options.skip_and_freeze.value {
self.place_button_in_slot(&self.icons.skip, 1);
self.place_button_in_slot(&self.icons.freeze, 2);
self.place_button_in_slot(&self.icons.skip, 2);
self.place_button_in_slot(&self.icons.freeze, 3);
}
let buttons_count = if ARGS.groups.feature_preview.options.skip_and_freeze.value {
// Toggle visualization, skip and freeze buttons.
3
4
} else {
// Toggle visualization button only.
1
2
};
self.layout_hover_area_to_cover_buttons(buttons_count);
@ -287,6 +383,12 @@ impl ActionBar {
eval frp.set_action_visibility_state ((state) model.icons.visibility.set_state(state));
eval frp.set_action_skip_state ((state) model.icons.skip.set_state(state));
eval frp.set_action_freeze_state ((state) model.icons.freeze.set_state(state));
eval frp.set_action_context_switch_state ((state)
model.icons.context_switch.set_state(*state)
);
eval frp.set_execution_environment ((environment)
model.icons.context_switch.set_execution_environment(environment)
);
// === Mouse Interactions ===
@ -300,9 +402,45 @@ impl ActionBar {
// === Icon Actions ===
frp.source.action_skip <+ model.icons.skip.state;
frp.source.action_freeze <+ model.icons.freeze.state;
frp.source.action_visibility <+ model.icons.visibility.state;
frp.source.action_skip <+ model.icons.skip.state;
frp.source.action_freeze <+ model.icons.freeze.state;
disable_context_button_clicked <- model.icons.context_switch.disable_button.is_pressed.on_true();
enable_context_button_clicked <- model.icons.context_switch.enable_button.is_pressed.on_true();
output_context_disabled <- model.icons.context_switch.disable_button.state
.sample(&disable_context_button_clicked);
output_context_enabled <- model.icons.context_switch.enable_button.state
.sample(&enable_context_button_clicked);
frp.source.action_context_switch <+ any(&output_context_disabled, &output_context_enabled);
// Setting the state of the context switch button is necessary because e.g. toggling
// the "enable" button when there's a "disable" expression should cause the "disable"
// button to change state as well.
frp.set_action_context_switch_state <+ output_context_disabled.map2(
&model.icons.context_switch.enable_button.state,
|disabled, enabled| {
match (disabled, enabled) {
(true, _) => Some(false),
(false, false) => None,
(false, true) => {
error!("Invalid node action bar button state: context switch buttons were both on.");
Some(true)
}
}
}
);
frp.set_action_context_switch_state <+ output_context_enabled.map2(
&model.icons.context_switch.disable_button.state,
|enabled, disabled| {
match (enabled, disabled) {
(true, _) => Some(true),
(false, false) => None,
(false, true) => {
error!("Invalid node action bar button state: context switch buttons were both on.");
Some(false)
}
}
}
);
}
let color_scheme = toggle_button::ColorScheme {
@ -320,10 +458,7 @@ impl ActionBar {
),
..default()
};
model.icons.freeze.frp.set_color_scheme(&color_scheme);
model.icons.skip.frp.set_color_scheme(&color_scheme);
model.icons.visibility.frp.set_color_scheme(&color_scheme);
model.icons.set_color_scheme(&color_scheme);
frp.show_on_hover.emit(true);
visibility_init.emit(false);

View File

@ -167,8 +167,8 @@ pub mod skip {
}
}
/// Icon for the button to disable re-evaluation. Looks like a crossed-out arrow loop.
pub mod disable_reevaluation {
/// Icon for the button to disable the output context. Looks like a crossed-out arrow loop.
pub mod disable_output_context {
use super::*;
ensogl::shape! {
@ -198,8 +198,8 @@ pub mod disable_reevaluation {
}
}
/// Icon for the button to enable re-evaluation. Looks like an arrow loop.
pub mod enable_reevaluation {
/// Icon for the button to enable the output context. Looks like an arrow loop.
pub mod enable_output_context {
use super::*;
ensogl::shape! {

View File

@ -576,6 +576,13 @@ ensogl::define_endpoints_2! {
toggle_profiling_mode(),
// === Execution Environment ===
set_execution_environment(ExecutionEnvironment),
// TODO(#5930): Temporary shortcut for testing different execution environments
toggle_execution_environment(),
// === Debug ===
/// Enable or disable debug-only features.
@ -616,6 +623,9 @@ ensogl::define_endpoints_2! {
edit_node_expression ((NodeId, text::Range<text::Byte>, ImString)),
set_node_skip ((NodeId,bool)),
set_node_freeze ((NodeId,bool)),
/// Set whether the output context is explicitly enabled for a node: `Some(true/false)` for
/// enabled/disabled; `None` for no context switch expression.
set_node_context_switch ((NodeId, Option<bool>)),
set_node_comment ((NodeId,node::Comment)),
set_node_position ((NodeId,Vector2)),
set_expression_usage_type ((NodeId,ast::Id,Option<Type>)),
@ -690,26 +700,27 @@ ensogl::define_endpoints_2! {
// === Other ===
// FIXME: To be refactored
node_added (NodeId, Option<NodeSource>, bool),
node_removed (NodeId),
nodes_collapsed ((Vec<NodeId>, NodeId)),
node_hovered (Option<Switch<NodeId>>),
node_selected (NodeId),
node_deselected (NodeId),
node_position_set ((NodeId,Vector2)),
node_position_set_batched ((NodeId,Vector2)),
node_expression_set ((NodeId,ImString)),
node_expression_span_set ((NodeId, span_tree::Crumbs, ImString)),
node_expression_edited ((NodeId,ImString,Vec<Selection<text::Byte>>)),
node_comment_set ((NodeId,String)),
node_entered (NodeId),
node_exited (),
node_editing_started (NodeId),
node_editing_finished (NodeId),
node_action_freeze ((NodeId, bool)),
node_action_skip ((NodeId, bool)),
node_edit_mode (bool),
nodes_labels_visible (bool),
node_added (NodeId, Option<NodeSource>, bool),
node_removed (NodeId),
nodes_collapsed ((Vec<NodeId>, NodeId)),
node_hovered (Option<Switch<NodeId>>),
node_selected (NodeId),
node_deselected (NodeId),
node_position_set ((NodeId,Vector2)),
node_position_set_batched ((NodeId,Vector2)),
node_expression_set ((NodeId,ImString)),
node_expression_span_set ((NodeId, span_tree::Crumbs, ImString)),
node_expression_edited ((NodeId,ImString,Vec<Selection<text::Byte>>)),
node_comment_set ((NodeId,String)),
node_entered (NodeId),
node_exited (),
node_editing_started (NodeId),
node_editing_finished (NodeId),
node_action_context_switch ((NodeId, bool)),
node_action_freeze ((NodeId, bool)),
node_action_skip ((NodeId, bool)),
node_edit_mode (bool),
nodes_labels_visible (bool),
/// `None` value as a visualization path denotes a disabled visualization.
@ -1621,6 +1632,10 @@ impl GraphEditorModelWithNetwork {
// === Actions ===
model.frp.private.output.node_action_context_switch <+ node.view.context_switch.map(
f!([] (active) (node_id, *active))
);
eval node.view.freeze ((is_frozen) {
model.frp.private.output.node_action_freeze.emit((node_id,*is_frozen));
});
@ -1685,6 +1700,11 @@ impl GraphEditorModelWithNetwork {
let profiling_max_duration = &self.model.profiling_statuses.max_duration;
node.set_profiling_max_global_duration <+ self.model.profiling_statuses.max_duration;
node.set_profiling_max_global_duration(profiling_max_duration.value());
// === Execution Environment ===
node.set_execution_environment <+ self.model.frp.set_execution_environment;
}
@ -1998,6 +2018,13 @@ impl GraphEditorModel {
}
}
fn set_node_context_switch(&self, node_id: impl Into<NodeId>, context_switch: &Option<bool>) {
let node_id = node_id.into();
if let Some(node) = self.nodes.get_cloned_ref(&node_id) {
node.set_context_switch(*context_switch);
}
}
fn set_node_comment(&self, node_id: impl Into<NodeId>, comment: impl Into<node::Comment>) {
let node_id = node_id.into();
let comment = comment.into();
@ -2670,6 +2697,8 @@ impl application::View for GraphEditor {
(Press, "debug_mode", "ctrl shift enter", "debug_push_breadcrumb"),
(Press, "debug_mode", "ctrl shift up", "debug_pop_breadcrumb"),
(Press, "debug_mode", "ctrl n", "add_node_at_cursor"),
// TODO(#5930): Temporary shortcut for testing different execution environments
(Press, "", "cmd shift c", "toggle_execution_environment"),
]
.iter()
.map(|(a, b, c, d)| Self::self_shortcut_when(*a, *c, *d, *b))
@ -3182,11 +3211,14 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
}
// === Set Node SKIP and FREEZE macros ===
// === Set Node SKIP/FREEZE macros and context switch expression ===
frp::extend! { network
eval inputs.set_node_skip(((id, skip)) model.set_node_skip(id, skip));
eval inputs.set_node_freeze(((id, freeze)) model.set_node_freeze(id, freeze));
eval inputs.set_node_context_switch(((id, context_switch))
model.set_node_context_switch(id, context_switch)
);
}
@ -3874,6 +3906,47 @@ impl display::Object for GraphEditor {
// =============================
// === Execution Environment ===
// =============================
// TODO(#5930): Move me once we synchronise the execution environment with the language server.
/// The execution environment which controls the global execution of functions with side effects.
///
/// For more information, see
/// https://github.com/enso-org/design/blob/main/epics/basic-libraries/write-action-control/design.md.
#[derive(Debug, Clone, CloneRef, Copy, Default)]
pub enum ExecutionEnvironment {
/// Allows editing the graph, but the `Output` context is disabled, so it prevents accidental
/// changes.
#[default]
Design,
/// Unrestricted, live editing of data.
Live,
}
impl ExecutionEnvironment {
/// Returns whether the output context is enabled for this execution environment.
pub fn output_context_enabled(&self) -> bool {
match self {
Self::Design => false,
Self::Live => true,
}
}
}
impl Display for ExecutionEnvironment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let name = match self {
Self::Design => "design",
Self::Live => "live",
};
write!(f, "{name}")
}
}
// =============
// === Tests ===
// =============