diff --git a/app/gui/docs/product/shortcuts.md b/app/gui/docs/product/shortcuts.md index bf2a77c009e5..1d7a7edd64e4 100644 --- a/app/gui/docs/product/shortcuts.md +++ b/app/gui/docs/product/shortcuts.md @@ -123,7 +123,7 @@ broken and require further investigation. #### Debug | Shortcut | Action | -| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | +| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------- | | ctrl + shift + d | Toggle Debug Mode. All actions below are only possible when it is activated. | | ctrl + alt + shift + i | Open the developer console. | | ctrl + alt + shift + r | Reload the visual interface. | @@ -134,3 +134,4 @@ broken and require further investigation. | ctrl + shift + enter | Push a hardcoded breadcrumb without navigating. | | ctrl + shift + arrow up | Pop a breadcrumb without navigating. | | cmd + i | Reload visualizations. To see the effect in the currently shown visualizations, you need to switch to another and switch back. | +| ctrl + shift + b | | Toggle read-only mode. | diff --git a/app/gui/src/presenter/project.rs b/app/gui/src/presenter/project.rs index e45ad2dd4397..b9490454c7a4 100644 --- a/app/gui/src/presenter/project.rs +++ b/app/gui/src/presenter/project.rs @@ -199,10 +199,13 @@ impl Model { self.ide_controller.set_component_browser_private_entries_visibility(!visibility); } - fn toggle_read_only(&self) { - let read_only = self.controller.model.read_only(); - self.controller.model.set_read_only(!read_only); - info!("New read only state: {}.", self.controller.model.read_only()); + /// Toggle the read-only mode, return the new state. + fn toggle_read_only(&self) -> bool { + let current_state = self.controller.model.read_only(); + let new_state = !current_state; + self.controller.model.set_read_only(new_state); + info!("New read only state: {}.", new_state); + new_state } fn restore_project_snapshot(&self) { @@ -381,7 +384,7 @@ impl Project { eval_ view.execution_context_restart(model.execution_context_restart()); - eval_ view.toggle_read_only(model.toggle_read_only()); + view.set_read_only <+ view.toggle_read_only.map(f_!(model.toggle_read_only())); } let graph_controller = self.model.graph_controller.clone_ref(); diff --git a/app/gui/view/graph-editor/src/component/breadcrumbs.rs b/app/gui/view/graph-editor/src/component/breadcrumbs.rs index dacbd089e094..83f3aac288af 100644 --- a/app/gui/view/graph-editor/src/component/breadcrumbs.rs +++ b/app/gui/view/graph-editor/src/component/breadcrumbs.rs @@ -105,6 +105,8 @@ ensogl::define_endpoints! { gap_width (f32), /// Set whether the project was changed since the last snapshot save. set_project_changed(bool), + /// Set read-only mode for this component. + set_read_only(bool), } Output { /// Signalizes when a new breadcrumb is pushed. @@ -124,6 +126,8 @@ ensogl::define_endpoints! { project_name_hovered (bool), /// Indicates whether the project name was clicked. project_mouse_down (), + /// Indicates if the read-only mode is enabled. + read_only(bool), } } @@ -521,6 +525,11 @@ impl Breadcrumbs { frp.source.pointer_style <+ model.project_name.frp.output.pointer_style; + + // === Read-only mode === + + frp.source.read_only <+ frp.input.set_read_only; + model.project_name.set_read_only <+ frp.input.set_read_only; } Self { model, frp } diff --git a/app/gui/view/graph-editor/src/component/breadcrumbs/project_name.rs b/app/gui/view/graph-editor/src/component/breadcrumbs/project_name.rs index fdfc35ab2fa3..2f5e39beb61f 100644 --- a/app/gui/view/graph-editor/src/component/breadcrumbs/project_name.rs +++ b/app/gui/view/graph-editor/src/component/breadcrumbs/project_name.rs @@ -78,6 +78,8 @@ ensogl::define_endpoints_2! { ide_text_edit_mode (bool), /// Set whether the project was changed since the last snapshot save. set_project_changed(bool), + /// Set the read-only mode for this component. + set_read_only(bool), } Output { @@ -88,6 +90,7 @@ ensogl::define_endpoints_2! { edit_mode (bool), selected (bool), is_hovered (bool), + read_only (bool), } } @@ -265,6 +268,10 @@ impl ProjectName { let input = &frp.private.input; let output = &frp.private.output; frp::extend! { network + // === Read-only mode === + + output.read_only <+ input.set_read_only; + // === Mouse IO === @@ -402,9 +409,9 @@ impl View for ProjectName { fn default_shortcuts() -> Vec { use shortcut::ActionType::*; [ - (Press, "", "enter", "commit"), + (Press, "!read_only", "enter", "commit"), (Release, "", "escape", "cancel_editing"), - (DoublePress, "is_hovered", "left-mouse-button", "start_editing"), + (DoublePress, "is_hovered & !read_only", "left-mouse-button", "start_editing"), ] .iter() .map(|(a, b, c, d)| Self::self_shortcut_when(*a, *c, *d, *b)) diff --git a/app/gui/view/graph-editor/src/component/node.rs b/app/gui/view/graph-editor/src/component/node.rs index 9aacd9f7aac6..a1e096ff3ebb 100644 --- a/app/gui/view/graph-editor/src/component/node.rs +++ b/app/gui/view/graph-editor/src/component/node.rs @@ -332,6 +332,7 @@ ensogl::define_endpoints_2! { /// Indicate whether on hover the quick action icons should appear. show_quick_action_bar_on_hover (bool), set_execution_environment (ExecutionEnvironment), + set_read_only (bool), } Output { /// Press event. Emitted when user clicks on non-active part of the node, like its @@ -827,6 +828,12 @@ impl Node { model.vcs_indicator.set_visibility <+ input.set_view_mode.map(|&mode| { !matches!(mode,view::Mode::Profiling {..}) }); + + + // === Read-only mode === + + action_bar.set_read_only <+ input.set_read_only; + model.input.set_read_only <+ input.set_read_only; } diff --git a/app/gui/view/graph-editor/src/component/node/action_bar.rs b/app/gui/view/graph-editor/src/component/node/action_bar.rs index 43dafc537866..b0ac9343b380 100644 --- a/app/gui/view/graph-editor/src/component/node/action_bar.rs +++ b/app/gui/view/graph-editor/src/component/node/action_bar.rs @@ -79,6 +79,8 @@ ensogl::define_endpoints! { set_action_context_switch_state (Option), show_on_hover (bool), set_execution_environment (ExecutionEnvironment), + /// Set the read-only mode for the buttons. + set_read_only (bool), } Output { @@ -129,6 +131,12 @@ impl Icons { self.skip.frp.set_visibility(visible); } + fn set_read_only(&self, read_only: bool) { + self.context_switch.set_read_only(read_only); + self.freeze.frp.set_read_only(read_only); + self.skip.frp.set_read_only(read_only); + } + 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); @@ -207,6 +215,11 @@ impl ContextSwitchButton { self.enable_button.set_visibility(visible); } + fn set_read_only(&self, read_only: bool) { + self.disable_button.set_read_only(read_only); + self.enable_button.set_read_only(read_only); + } + 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); @@ -375,6 +388,10 @@ impl ActionBar { let model = &self.model; frp::extend! { network + // === Read-only mode === + + eval frp.set_read_only((read_only) model.icons.set_read_only(*read_only)); + // === Input Processing === diff --git a/app/gui/view/graph-editor/src/component/node/input/area.rs b/app/gui/view/graph-editor/src/component/node/input/area.rs index af7d93195eab..57116779425f 100644 --- a/app/gui/view/graph-editor/src/component/node/input/area.rs +++ b/app/gui/view/graph-editor/src/component/node/input/area.rs @@ -597,6 +597,7 @@ impl Model { })); area_frp.source.on_port_code_update <+ code_update; area_frp.source.request_import <+ widget.request_import; + widget.set_read_only <+ area_frp.set_read_only; } } @@ -936,6 +937,8 @@ ensogl::define_endpoints! { set_view_mode (view::Mode), set_profiling_status (profiling::Status), + /// Set read-only mode for input ports. + set_read_only (bool), } Output { diff --git a/app/gui/view/graph-editor/src/component/node/input/widget.rs b/app/gui/view/graph-editor/src/component/node/input/widget.rs index 360897849606..ee9fadb63e1d 100644 --- a/app/gui/view/graph-editor/src/component/node/input/widget.rs +++ b/app/gui/view/graph-editor/src/component/node/input/widget.rs @@ -42,6 +42,7 @@ ensogl::define_endpoints_2! { set_current_value (Option), set_focused (bool), set_visible (bool), + set_read_only (bool), } Output { value_changed(Option), @@ -148,6 +149,7 @@ pub struct SampledFrp { set_focused: frp::Sampler, out_value_changed: frp::Any>, out_request_import: frp::Any, + set_read_only: frp::Sampler, } @@ -193,9 +195,17 @@ impl View { set_current_value <- input.set_current_value.sampler(); set_visible <- input.set_visible.sampler(); set_focused <- input.set_focused.sampler(); + set_read_only <- input.set_read_only.sampler(); let out_value_changed = frp.private.output.value_changed.clone_ref(); let out_request_import = frp.private.output.request_import.clone_ref(); - let sampled_frp = SampledFrp { set_current_value, set_visible, set_focused, out_value_changed, out_request_import }; + let sampled_frp = SampledFrp { + set_current_value, + set_visible, + set_focused, + out_value_changed, + out_request_import, + set_read_only + }; eval widget_data([model, sampled_frp]((meta, node_data)) { model.set_widget_data(&sampled_frp, meta, node_data); @@ -415,8 +425,8 @@ impl SingleChoiceModel { let dropdown = Rc::new(RefCell::new(dropdown)); frp::extend! { network - let dot_clicked = activation_shape.events_deprecated.mouse_down_primary.clone_ref(); - toggle_focus <- dot_clicked.map(f!([display_object](()) !display_object.is_focused())); + clicked <- activation_shape.events_deprecated.mouse_down_primary.gate_not(&frp.set_read_only); + toggle_focus <- clicked.map(f!([display_object](()) !display_object.is_focused())); set_focused <- any(toggle_focus, frp.set_focused); eval set_focused([display_object](focus) match focus { true => display_object.focus(), diff --git a/app/gui/view/graph-editor/src/lib.rs b/app/gui/view/graph-editor/src/lib.rs index d395057029b1..0acc3081ed31 100644 --- a/app/gui/view/graph-editor/src/lib.rs +++ b/app/gui/view/graph-editor/src/lib.rs @@ -449,6 +449,11 @@ ensogl::define_endpoints_2! { space_for_window_buttons (Vector2), + // === Read-only mode === + + set_read_only(bool), + + // === Node Selection === /// Node press event @@ -662,6 +667,11 @@ ensogl::define_endpoints_2! { debug_mode (bool), + + // === Read-only mode === + + read_only (bool), + // === Edge === has_detached_edge (bool), @@ -1702,6 +1712,11 @@ impl GraphEditorModelWithNetwork { node.set_view_mode <+ self.model.frp.view_mode; + // === Read-only mode === + + node.set_read_only <+ self.model.frp.set_read_only; + + // === Profiling === let profiling_min_duration = &self.model.profiling_statuses.min_duration; @@ -2659,15 +2674,15 @@ impl application::View for GraphEditor { fn default_shortcuts() -> Vec { use shortcut::ActionType::*; [ - (Press, "!node_editing", "tab", "start_node_creation"), - (Press, "!node_editing", "enter", "start_node_creation"), + (Press, "!node_editing & !read_only", "tab", "start_node_creation"), + (Press, "!node_editing & !read_only", "enter", "start_node_creation"), // === Drag === (Press, "", "left-mouse-button", "node_press"), (Release, "", "left-mouse-button", "node_release"), - (Press, "!node_editing", "backspace", "remove_selected_nodes"), - (Press, "!node_editing", "delete", "remove_selected_nodes"), + (Press, "!node_editing & !read_only", "backspace", "remove_selected_nodes"), + (Press, "!node_editing & !read_only", "delete", "remove_selected_nodes"), (Press, "has_detached_edge", "escape", "drop_dragged_edge"), - (Press, "", "cmd g", "collapse_selected_nodes"), + (Press, "!read_only", "cmd g", "collapse_selected_nodes"), // === Visualization === (Press, "!node_editing", "space", "press_visualization_visibility"), (DoublePress, "!node_editing", "space", "double_press_visualization_visibility"), @@ -2694,17 +2709,17 @@ impl application::View for GraphEditor { "ctrl space", "cycle_visualization_for_selected_node", ), - (DoublePress, "", "left-mouse-button", "enter_hovered_node"), - (DoublePress, "", "left-mouse-button", "start_node_creation_from_port"), - (Press, "", "right-mouse-button", "start_node_creation_from_port"), - (Press, "!node_editing", "cmd enter", "enter_selected_node"), - (Press, "", "alt enter", "exit_node"), + (DoublePress, "!read_only", "left-mouse-button", "enter_hovered_node"), + (DoublePress, "!read_only", "left-mouse-button", "start_node_creation_from_port"), + (Press, "!read_only", "right-mouse-button", "start_node_creation_from_port"), + (Press, "!node_editing & !read_only", "cmd enter", "enter_selected_node"), + (Press, "!read_only", "alt enter", "exit_node"), // === Node Editing === - (Press, "", "cmd", "edit_mode_on"), - (Release, "", "cmd", "edit_mode_off"), - (Press, "", "cmd left-mouse-button", "edit_mode_on"), - (Release, "", "cmd left-mouse-button", "edit_mode_off"), - (Press, "node_editing", "cmd enter", "stop_editing"), + (Press, "!read_only", "cmd", "edit_mode_on"), + (Release, "!read_only", "cmd", "edit_mode_off"), + (Press, "!read_only", "cmd left-mouse-button", "edit_mode_on"), + (Release, "!read_only", "cmd left-mouse-button", "edit_mode_off"), + (Press, "node_editing & !read_only", "cmd enter", "stop_editing"), // === Profiling Mode === (Press, "", "cmd p", "toggle_profiling_mode"), // === Debug === @@ -2761,6 +2776,23 @@ fn new_graph_editor(app: &Application) -> GraphEditor { let out = &frp.private.output; let selection_controller = &model.selection_controller; + + + // ====================== + // === Read-only mode === + // ====================== + + frp::extend! { network + out.read_only <+ inputs.set_read_only; + model.breadcrumbs.set_read_only <+ inputs.set_read_only; + + // Drop the currently dragged edge if read-only mode is enabled. + read_only_enabled <- inputs.set_read_only.on_true(); + inputs.drop_dragged_edge <+ read_only_enabled; + } + + + // ======================== // === Scene Navigation === // ======================== @@ -2928,7 +2960,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { } }); edge_click <- map2(&edge_mouse_down,&cursor_pos_in_scene,|edge_id,pos|(*edge_id,*pos)); - valid_edge_disconnect_click <- edge_click.gate_not(&has_detached_edge); + valid_edge_disconnect_click <- edge_click.gate_not(&has_detached_edge).gate_not(&inputs.set_read_only); edge_is_source_click <- valid_edge_disconnect_click.map(f!([model]((edge_id,pos)) { if let Some(edge) = model.edges.get_cloned_ref(edge_id){ @@ -2963,8 +2995,8 @@ fn new_graph_editor(app: &Application) -> GraphEditor { attach_all_edge_inputs <- any (port_input_mouse_up, inputs.press_node_input, inputs.set_detached_edge_targets); attach_all_edge_outputs <- any (port_output_mouse_up, inputs.press_node_output, inputs.set_detached_edge_sources); - create_edge_from_output <- node_output_touch.down.gate_not(&has_detached_edge_on_output_down); - create_edge_from_input <- node_input_touch.down.map(|value| value.clone()); + create_edge_from_output <- node_output_touch.down.gate_not(&has_detached_edge_on_output_down).gate_not(&inputs.set_read_only); + create_edge_from_input <- node_input_touch.down.map(|value| value.clone()).gate_not(&inputs.set_read_only); on_new_edge <- any(&output_down,&input_down); let selection_mode = selection::get_mode(network,inputs); @@ -3043,7 +3075,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { // === Adding Node === frp::extend! { network - let node_added_with_button = model.add_node_button.clicked.clone_ref(); + node_added_with_button <- model.add_node_button.clicked.gate_not(&inputs.set_read_only); input_start_node_creation_from_port <- inputs.hover_node_output.sample( &inputs.start_node_creation_from_port); diff --git a/app/gui/view/src/code_editor.rs b/app/gui/view/src/code_editor.rs index a7bfd1ec03b2..88083ffd01b6 100644 --- a/app/gui/view/src/code_editor.rs +++ b/app/gui/view/src/code_editor.rs @@ -36,6 +36,8 @@ ensogl::define_endpoints! { hide(), /// Toggle Code Editor visibility. toggle(), + /// Set read-only mode for this component. + set_read_only(bool), } Output { @@ -90,12 +92,15 @@ impl View { hide <- any(frp.input.hide,hide_after_toggle); eval_ show (height_fraction.set_target_value(HEIGHT_FRACTION)); - eval_ show ({ + focus <- show.gate_not(&frp.set_read_only); + eval_ focus ({ model.deprecated_focus(); model.focus(); }); eval_ hide (height_fraction.set_target_value(0.0)); - eval_ hide ([model] { + enable_read_only <- frp.set_read_only.on_true(); + defocus <- any(hide, enable_read_only); + eval_ defocus ([model] { model.remove_all_cursors(); model.deprecated_defocus(); model.blur(); diff --git a/app/gui/view/src/project.rs b/app/gui/view/src/project.rs index 753ee17ecc59..acf620d2c0a0 100644 --- a/app/gui/view/src/project.rs +++ b/app/gui/view/src/project.rs @@ -104,6 +104,7 @@ ensogl::define_endpoints! { /// Restart the program execution. execution_context_restart(), toggle_read_only(), + set_read_only(bool), } Output { @@ -363,6 +364,7 @@ impl View { let network = &frp.network; let searcher = &model.searcher.frp(); let graph = &model.graph_editor.frp; + let code_editor = &model.code_editor; let project_list = &model.project_list; let searcher_anchor = DEPRECATED_Animation::>::new(network); @@ -394,6 +396,13 @@ impl View { eval_ frp.show_graph_editor(model.show_graph_editor()); eval_ frp.hide_graph_editor(model.hide_graph_editor()); + + // === Read-only mode === + + graph.set_read_only <+ frp.set_read_only; + code_editor.set_read_only <+ frp.set_read_only; + + // === Searcher Position and Size === let main_cam = app.display.default_scene.layers.main.camera(); diff --git a/lib/rust/ensogl/component/list-editor/src/item.rs b/lib/rust/ensogl/component/list-editor/src/item.rs index 995117c18cb2..5b43eb4aacd4 100644 --- a/lib/rust/ensogl/component/list-editor/src/item.rs +++ b/lib/rust/ensogl/component/list-editor/src/item.rs @@ -1,9 +1,10 @@ use ensogl_core::prelude::*; +use crate::placeholder::StrongPlaceholder; + use ensogl_core::display; use ensogl_core::Animation; -use crate::placeholder::StrongPlaceholder; ensogl_core::define_endpoints_2! { diff --git a/lib/rust/ensogl/component/list-editor/src/lib.rs b/lib/rust/ensogl/component/list-editor/src/lib.rs index 3831c1a2ed75..b59f7df9293a 100644 --- a/lib/rust/ensogl/component/list-editor/src/lib.rs +++ b/lib/rust/ensogl/component/list-editor/src/lib.rs @@ -73,9 +73,6 @@ #![allow(clippy::bool_to_int_with_if)] #![allow(clippy::let_and_return)] -pub mod item; -pub mod placeholder; - use ensogl_core::display::shape::compound::rectangle::*; use ensogl_core::display::world::*; use ensogl_core::prelude::*; @@ -91,12 +88,19 @@ use ensogl_core::gui::cursor; use ensogl_core::gui::cursor::Cursor; use ensogl_core::Animation; use ensogl_core::Easing; - use item::Item; use placeholder::Placeholder; use placeholder::StrongPlaceholder; +// ============== +// === Export === +// ============== + +pub mod item; +pub mod placeholder; + + // ================= // === Constants === diff --git a/lib/rust/ensogl/component/toggle-button/src/lib.rs b/lib/rust/ensogl/component/toggle-button/src/lib.rs index e9ccc5e5f3aa..21f3efabd47d 100644 --- a/lib/rust/ensogl/component/toggle-button/src/lib.rs +++ b/lib/rust/ensogl/component/toggle-button/src/lib.rs @@ -58,6 +58,8 @@ ensogl_core::define_endpoints! { set_size (Vector2), toggle (), set_state (bool), + /// Read only mode forbids changing the state of the button by clicking. + set_read_only (bool), } Output { state (bool), @@ -237,7 +239,8 @@ impl ToggleButton { // === State === - toggle <- any_(frp.toggle, icon.mouse_down_primary); + clicked <- icon.mouse_down_primary.gate_not(&frp.set_read_only); + toggle <- any_(frp.toggle, clicked); frp.source.state <+ frp.state.not().sample(&toggle); frp.source.state <+ frp.set_state; diff --git a/lib/rust/ensogl/core/src/display/shape/compound/rectangle.rs b/lib/rust/ensogl/core/src/display/shape/compound/rectangle.rs index f7b717e379ce..5e8b849de5ac 100644 --- a/lib/rust/ensogl/core/src/display/shape/compound/rectangle.rs +++ b/lib/rust/ensogl/core/src/display/shape/compound/rectangle.rs @@ -12,7 +12,6 @@ use crate::display::style::data::DataMatch; use crate::display::style::Path; - // ============== // === Export === // ==============