From 03e105d42e00c18c9ba7c971543f84efabd539cc Mon Sep 17 00:00:00 2001 From: Ilya Bogdanov Date: Mon, 14 Feb 2022 13:19:08 +0300 Subject: [PATCH] Debug Mode for Graph Editor (#3264) --- CHANGELOG.md | 6 + app/gui/docs/product/shortcuts.md | 1 + app/gui/view/graph-editor/src/lib.rs | 16 ++ app/gui/view/src/debug_mode_popup.rs | 206 +++++++++++++++++++++ app/gui/view/src/lib.rs | 1 + app/gui/view/src/project.rs | 22 +++ lib/rust/ensogl/component/label/src/lib.rs | 36 +++- 7 files changed, 280 insertions(+), 8 deletions(-) create mode 100644 app/gui/view/src/debug_mode_popup.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6daea61e831..5fc63d6f65d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ #### Visual Environment +- [Debug Mode for Graph Editor can be activated/deactivated using a + shortcut.][3264] It allows access to a set of restricted features. See + [debug-shortcuts]. - [New nodes can be created by dragging and dropping a connection on the scene.][3231] - [Node connections can be dropped by pressing the Esc key while dragging @@ -36,6 +39,8 @@ `Table.use_first_row_as_names` operations][3249] - [Implemented `Text.at` and `Text.is_digit` methods][3269] +[debug-shortcuts]: + https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug [3153]: https://github.com/enso-org/enso/pull/3153 [3166]: https://github.com/enso-org/enso/pull/3166 [3181]: https://github.com/enso-org/enso/pull/3181 @@ -51,6 +56,7 @@ [3250]: https://github.com/enso-org/enso/pull/3250 [3256]: https://github.com/enso-org/enso/pull/3256 [3249]: https://github.com/enso-org/enso/pull/3249 +[3264]: https://github.com/enso-org/enso/pull/3264 [3269]: https://github.com/enso-org/enso/pull/3269 #### Enso Compiler diff --git a/app/gui/docs/product/shortcuts.md b/app/gui/docs/product/shortcuts.md index 9c97ede1003..44d6e035103 100644 --- a/app/gui/docs/product/shortcuts.md +++ b/app/gui/docs/product/shortcuts.md @@ -120,6 +120,7 @@ broken and require further investigation. | 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. | | ctrl + alt + 0 - 10 | Switch between debug rendering modes (0 is the normal mode). | diff --git a/app/gui/view/graph-editor/src/lib.rs b/app/gui/view/graph-editor/src/lib.rs index 1fa1b6a1cda..e6c76b2538f 100644 --- a/app/gui/view/graph-editor/src/lib.rs +++ b/app/gui/view/graph-editor/src/lib.rs @@ -485,6 +485,9 @@ ensogl::define_endpoints! { // === Debug === + /// Enable or disable debug-only features. + set_debug_mode(bool), + /// Push a hardcoded breadcrumb without notifying the controller. debug_push_breadcrumb(), /// Pop a breadcrumb without notifying the controller. @@ -542,6 +545,9 @@ ensogl::define_endpoints! { } Output { + // === Debug Mode === + + debug_mode (bool), // === Edge === @@ -3374,6 +3380,16 @@ fn new_graph_editor(app: &Application) -> GraphEditor { frp.source.default_y_gap_between_nodes.emit(default_y_gap.value()); frp.source.min_x_spacing_for_new_nodes.emit(min_x_spacing.value()); + + + // ================== + // === Debug Mode === + // ================== + + frp::extend! { network + out.source.debug_mode <+ frp.set_debug_mode; + } + // Init defaults frp.edit_mode_off.emit(()); diff --git a/app/gui/view/src/debug_mode_popup.rs b/app/gui/view/src/debug_mode_popup.rs new file mode 100644 index 00000000000..22265b012cc --- /dev/null +++ b/app/gui/view/src/debug_mode_popup.rs @@ -0,0 +1,206 @@ +//! Text message on top of the screen that signals about enabling/disabling Debug Mode of Graph +//! Editor. + +use crate::prelude::*; + +use enso_frp as frp; +use ensogl::animation::delayed::DelayedAnimation; +use ensogl::application::Application; +use ensogl::display; +use ensogl::display::scene::Scene; +use ensogl::display::shape::*; +use ensogl::Animation; +use ensogl_component::label::Label; + + + +// ================= +// === Constants === +// ================= + +/// Mitigate limitations of constant strings concatenation. +macro_rules! define_debug_mode_shortcut { + ($shortcut:literal) => { + /// A keyboard shortcut used to enable/disable Debug Mode. + pub const DEBUG_MODE_SHORTCUT: &str = $shortcut; + const DEBUG_MODE_ENABLED: &str = + concat!("Debug Mode enabled. To disable, press `", $shortcut, "`."); + }; +} +define_debug_mode_shortcut!("ctrl shift d"); +const DEBUG_MODE_DISABLED: &str = "Debug Mode disabled."; + +const LABEL_VISIBILITY_DELAY_MS: f32 = 3_000.0; +const LABEL_PADDING_TOP: f32 = 50.0; + + + +// ================== +// === PopupLabel === +// ================== + +/// Text label that disappears after a predefined delay. +#[derive(Debug, Clone, CloneRef)] +struct PopupLabel { + label: Label, + network: frp::Network, + delay_animation: DelayedAnimation, + show: frp::Source, +} + +impl display::Object for PopupLabel { + fn display_object(&self) -> &display::object::Instance { + self.label.display_object() + } +} + +impl PopupLabel { + /// Constructor. + pub fn new(app: &Application) -> Self { + let network = frp::Network::new("PopupLabel"); + let label = Label::new(app); + label.set_opacity(0.0); + let background_layer = &app.display.scene().layers.panel; + let text_layer = &app.display.scene().layers.panel_text; + label.set_layers(background_layer, text_layer); + + let opacity_animation = Animation::new(&network); + network.store(&opacity_animation); + let delay_animation = DelayedAnimation::new(&network); + delay_animation.set_delay(0.0); + delay_animation.set_duration(0.0); + network.store(&delay_animation); + + frp::extend! { network + show <- source::(); + + eval show ([label, delay_animation](text) { + label.set_content(text); + delay_animation.reset(); + delay_animation.start(); + }); + + opacity_animation.target <+ show.constant(1.0); + opacity_animation.target <+ delay_animation.on_end.constant(0.0); + label.set_opacity <+ opacity_animation.value; + } + + Self { label, network, show, delay_animation } + } + + /// Set a delay in milliseconds after which the label will disappear. + pub fn set_delay(&self, delay: f32) { + self.delay_animation.set_delay(delay); + } +} + + + +// ============= +// === Model === +// ============= + +#[derive(Debug, Clone, CloneRef)] +struct Model { + display_object: display::object::Instance, + label: PopupLabel, + logger: Logger, +} + +impl Model { + /// Constructor. + pub fn new(app: &Application) -> Self { + let logger = Logger::new("DebugModePopup"); + let display_object = display::object::Instance::new(&logger); + let label = PopupLabel::new(app); + label.set_delay(LABEL_VISIBILITY_DELAY_MS); + display_object.add_child(&label); + + Self { display_object, label, logger } + } + + /// Show "Debug Mode enabled" label. + pub fn show_enabled_label(&self) { + self.label.show.emit(String::from(DEBUG_MODE_ENABLED)); + } + + /// Show "Debug Mode disabled" label. + pub fn show_disabled_label(&self) { + self.label.show.emit(String::from(DEBUG_MODE_DISABLED)); + } + + /// Return the height of the label. + pub fn label_height(&self) -> f32 { + self.label.label.size.value().y + } +} + + + +// =========== +// === FRP === +// =========== + +ensogl::define_endpoints! { + Input { + // Debug Mode was enabled. + enabled(), + // Debug Mode was disabled. + disabled(), + } + Output {} +} + + + +// ============ +// === View === +// ============ + +/// Text message on top of the screen that signals about enabling/disabling Debug Mode of Graph +/// Editor. +#[derive(Debug, Clone, CloneRef)] +pub struct View { + frp: Frp, + model: Model, +} + +impl View { + /// Constructor. + pub fn new(app: &Application) -> Self { + let frp = Frp::new(); + let model = Model::new(app); + let network = &frp.network; + + frp::extend! { network + init <- source_(); + let shape = app.display.scene().shape(); + _eval <- all_with(shape, &init, f!([model](scene_size, _init) { + let half_height = scene_size.height / 2.0; + let label_height = model.label_height(); + let pos_y = half_height - LABEL_PADDING_TOP - label_height / 2.0; + model.display_object.set_position_y(pos_y); + })); + + eval_ frp.enabled(model.show_enabled_label()); + eval_ frp.disabled(model.show_disabled_label()); + } + init.emit(()); + + Self { frp, model } + } +} + +impl display::Object for View { + fn display_object(&self) -> &display::object::Instance { + &self.model.display_object + } +} + +impl Deref for View { + type Target = Frp; + + fn deref(&self) -> &Self::Target { + &self.frp + } +} diff --git a/app/gui/view/src/lib.rs b/app/gui/view/src/lib.rs index e51d0360277..fa29741c7af 100644 --- a/app/gui/view/src/lib.rs +++ b/app/gui/view/src/lib.rs @@ -26,6 +26,7 @@ #[allow(clippy::option_map_unit_fn)] pub mod code_editor; +pub mod debug_mode_popup; pub mod documentation; pub mod open_dialog; pub mod project; diff --git a/app/gui/view/src/project.rs b/app/gui/view/src/project.rs index 2cd3c2f1c09..a50b5673d66 100644 --- a/app/gui/view/src/project.rs +++ b/app/gui/view/src/project.rs @@ -3,6 +3,8 @@ use crate::prelude::*; use crate::code_editor; +use crate::debug_mode_popup; +use crate::debug_mode_popup::DEBUG_MODE_SHORTCUT; use crate::graph_editor::component::node; use crate::graph_editor::component::node::Expression; use crate::graph_editor::component::visualization; @@ -92,6 +94,10 @@ ensogl::define_endpoints! { show_prompt(), /// Disable the prompt. It will be hidden if currently visible. disable_prompt(), + // Enable Debug Mode of Graph Editor. + enable_debug_mode(), + // Disable Debug Mode of Graph Editor. + disable_debug_mode(), } Output { @@ -106,6 +112,7 @@ ensogl::define_endpoints! { style (Theme), fullscreen_visualization_shown (bool), drop_files_enabled (bool), + debug_mode (bool), } } @@ -153,6 +160,7 @@ struct Model { prompt_background: prompt_background::View, prompt: ensogl_text::Area, open_dialog: Rc, + debug_mode_popup: debug_mode_popup::View, } impl Model { @@ -166,6 +174,7 @@ impl Model { let fullscreen_vis = default(); let prompt_background = prompt_background::View::new(&logger); let prompt = ensogl_text::Area::new(app); + let debug_mode_popup = debug_mode_popup::View::new(app); let window_control_buttons = ARGS.is_in_cloud.unwrap_or_default().as_some_from(|| { let window_control_buttons = app.new_view::(); display_object.add_child(&window_control_buttons); @@ -174,6 +183,7 @@ impl Model { }); let window_control_buttons = Immutable(window_control_buttons); let open_dialog = Rc::new(OpenDialog::new(app)); + prompt_background.add_child(&prompt); prompt.set_content("Press the tab key to search for components."); scene.layers.panel.add_exclusive(&prompt_background); @@ -184,6 +194,7 @@ impl Model { display_object.add_child(&code_editor); display_object.add_child(&searcher); display_object.add_child(&prompt_background); + display_object.add_child(&debug_mode_popup); display_object.remove_child(&searcher); let app = app.clone_ref(); @@ -200,6 +211,7 @@ impl Model { prompt_background, prompt, open_dialog, + debug_mode_popup, } } @@ -625,6 +637,14 @@ impl View { frp.source.drop_files_enabled <+ init.constant(true); frp.source.drop_files_enabled <+ frp.open_dialog_shown.map(|v| !v); + + // === Debug Mode === + + frp.source.debug_mode <+ bool(&frp.disable_debug_mode, &frp.enable_debug_mode); + graph.set_debug_mode <+ frp.source.debug_mode; + + model.debug_mode_popup.enabled <+ frp.enable_debug_mode; + model.debug_mode_popup.disabled <+ frp.disable_debug_mode; } init.emit(()); std::mem::forget(prompt_visibility); @@ -697,6 +717,8 @@ impl application::View for View { (Press, "", "cmd s", "save_module"), (Press, "", "cmd z", "undo"), (Press, "", "cmd y", "redo"), + (Press, "!debug_mode", DEBUG_MODE_SHORTCUT, "enable_debug_mode"), + (Press, "debug_mode", DEBUG_MODE_SHORTCUT, "disable_debug_mode"), ]) .iter() .map(|(a, b, c, d)| Self::self_shortcut_when(*a, *c, *d, *b)) diff --git a/lib/rust/ensogl/component/label/src/lib.rs b/lib/rust/ensogl/component/label/src/lib.rs index 4dd89bb9f50..9341e2eb8dc 100644 --- a/lib/rust/ensogl/component/label/src/lib.rs +++ b/lib/rust/ensogl/component/label/src/lib.rs @@ -18,6 +18,7 @@ use enso_frp as frp; use ensogl_core::application::Application; use ensogl_core::data::color; use ensogl_core::display; +use ensogl_core::display::scene::Layer; use ensogl_core::display::shape::*; use ensogl_shadow as shadow; use ensogl_text as text; @@ -91,25 +92,29 @@ impl Model { let label = app.new_view::(); let background = background::View::new(&logger); - // FIXME[MM/WD]: Depth sorting of labels to in front of everything else in the scene. - // Temporary solution. The depth management needs to allow defining relative position of - // the text and background and let the whole component to be set to am an arbitrary layer. - label.remove_from_scene_layer(&scene.layers.main); - label.add_to_scene_layer(&scene.layers.tooltip_text); - scene.layers.tooltip.add_exclusive(&background); - display_object.add_child(&background); display_object.add_child(&label); let style = StyleWatch::new(&app.display.scene().style_sheet); - Model { background, label, display_object, style } + let model = Model { background, label, display_object, style }; + model.set_layers(&scene.layers.tooltip, &scene.layers.tooltip_text); + model } pub fn height(&self) -> f32 { self.style.get_number(theme::height) } + /// Set scene layers for background and text respectively. + pub fn set_layers(&self, background_layer: &Layer, text_layer: &Layer) { + // FIXME[MM/WD]: Depth sorting of labels to in front of everything else in the scene. + // Temporary solution. The depth management needs to allow defining relative position of + // the text and background and let the whole component to be set to am an arbitrary layer. + background_layer.add_exclusive(&self.background); + self.label.add_to_scene_layer(text_layer); + } + fn set_width(&self, width: f32) -> Vector2 { let padding_outer = self.style.get_number(theme::padding_outer); let padding_inner_x = self.style.get_number(theme::padding_inner_x); @@ -166,6 +171,13 @@ impl Label { Label { model, frp }.init() } + /// Set layers for Label's background and text respectively. This is needed because + /// `text::Area` uses its own `add_to_scene_layer` method instead of utilizing more common + /// [`Layer::add_exclusive`]. + pub fn set_layers(&self, background_layer: &Layer, text_layer: &Layer) { + self.model.set_layers(background_layer, text_layer); + } + fn init(self) -> Self { let frp = &self.frp; let network = &frp.network; @@ -183,6 +195,14 @@ impl Label { } } +impl Deref for Label { + type Target = Frp; + + fn deref(&self) -> &Self::Target { + &self.frp + } +} + impl display::Object for Label { fn display_object(&self) -> &display::object::Instance { &self.model.display_object