From 285959835f8c8432092d40ba52dfd8c87d17a1e6 Mon Sep 17 00:00:00 2001 From: Ilya Bogdanov Date: Wed, 14 Dec 2022 19:37:04 +0300 Subject: [PATCH] Scroll the documentation panel when it is hovered by the mouse (#3968) [Task link](https://www.pivotaltracker.com/story/show/183970810). Now documentation panel is being scrolled when only hovered by the cursor (you don't need to click on it beforehand). Tested on macOS with both the touchpad and the mouse. https://user-images.githubusercontent.com/6566674/206667769-04aae6b2-91ff-4877-bf10-8c0f0c4c5873.mp4 --- .../component-list-panel/src/lib.rs | 32 ++---------------- .../component-list-panel-view/src/lib.rs | 6 +++- app/gui/view/graph-editor/src/lib.rs | 6 ++-- app/gui/view/src/component_browser.rs | 4 +++ app/gui/view/src/documentation.rs | 15 +++++++++ app/gui/view/src/project.rs | 15 ++++----- .../core/src/display/navigation/navigator.rs | 33 +++++++++++++++++-- 7 files changed, 66 insertions(+), 45 deletions(-) diff --git a/app/gui/view/component-browser/component-list-panel/src/lib.rs b/app/gui/view/component-browser/component-list-panel/src/lib.rs index 868abb2c66..5f40bef921 100644 --- a/app/gui/view/component-browser/component-list-panel/src/lib.rs +++ b/app/gui/view/component-browser/component-list-panel/src/lib.rs @@ -55,7 +55,6 @@ use ensogl_core::data::bounding_box::BoundingBox; use ensogl_core::data::color; use ensogl_core::define_endpoints_2; use ensogl_core::display; -use ensogl_core::display::navigation::navigator::Navigator; use ensogl_core::display::object::ObjectOps; use ensogl_core::display::shape::StyleWatchFrp; use ensogl_derive_theme::FromTheme; @@ -238,14 +237,12 @@ pub struct Model { pub grid: grid::View, pub section_navigator: SectionNavigator, pub breadcrumbs: breadcrumbs::Breadcrumbs, - scene_navigator: Rc>>, } impl Model { fn new(app: &Application) -> Self { let app = app.clone_ref(); let display_object = display::object::Instance::new(); - let scene_navigator = default(); let background = background::View::new(); display_object.add_child(&background); @@ -260,7 +257,7 @@ impl Model { breadcrumbs.set_base_layer(&app.display.default_scene.layers.node_searcher); display_object.add_child(&breadcrumbs); - Self { display_object, background, grid, section_navigator, scene_navigator, breadcrumbs } + Self { display_object, background, grid, section_navigator, breadcrumbs } } fn set_initial_breadcrumbs(&self) { @@ -279,11 +276,6 @@ impl Model { self.grid.set_xy(style.grid_pos()); } - /// Set the navigator so it can be disabled on hover. - pub fn set_navigator(&self, navigator: Option) { - *self.scene_navigator.borrow_mut() = navigator - } - // Note that this is a workaround for lack of hierarchical mouse over events. // We need to know if the mouse is over the panel, but cannot do it via a shape, as // sub-components still need to receive all of the mouse events, too. @@ -295,23 +287,6 @@ impl Model { let viewport = BoundingBox::from_center_and_size(default(), size); viewport.contains(pos) } - - fn on_hover(&self) { - if let Some(navigator) = self.scene_navigator.borrow().as_ref() { - navigator.disable() - } else { - warn!( - "Navigator was not initialised on ComponentBrowserPanel. \ - Scroll events will not be handled correctly." - ) - } - } - - fn on_hover_end(&self) { - if let Some(navigator) = self.scene_navigator.borrow().as_ref() { - navigator.enable() - } - } } impl display::Object for Model { @@ -345,6 +320,7 @@ define_endpoints_2! { } Output{ size(Vector2), + is_hovered(bool), } } @@ -367,15 +343,13 @@ impl component::Frp for Frp { let pos = scene.screen_to_object_space(&model, pos.xy()); model.is_hovered(pos) })).gate(&is_visible).on_change(); + output.is_hovered <+ is_hovered; // TODO[ib] Temporary solution for focus, we grab keyboard events if the // component browser is visible. The proper implementation is tracked in // https://www.pivotaltracker.com/story/show/180872763 model.grid.deprecated_set_focus <+ is_visible; - on_hover <- is_hovered.on_true(); on_hover_end <- is_hovered.on_false(); - eval_ on_hover ( model.on_hover() ); - eval_ on_hover_end ( model.on_hover_end() ); model.grid.unhover_element <+ on_hover_end; diff --git a/app/gui/view/debug_scene/component-list-panel-view/src/lib.rs b/app/gui/view/debug_scene/component-list-panel-view/src/lib.rs index 8f7a85e4db..8a32f1023a 100644 --- a/app/gui/view/debug_scene/component-list-panel-view/src/lib.rs +++ b/app/gui/view/debug_scene/component-list-panel-view/src/lib.rs @@ -197,7 +197,6 @@ pub fn main() { let navigator = Navigator::new(scene, &scene.layers.node_searcher.camera()); let panel = app.new_view::(); scene.layers.node_searcher.add(&panel); - panel.model().set_navigator(Some(navigator.clone_ref())); panel.show(); let network = frp::Network::new("new_component_list_panel_view"); //TODO[ao] should be done by panel itself. @@ -209,6 +208,11 @@ pub fn main() { size <- all_with(&init, &panel.size, |(), panel_size| *panel_size); snap <- all_with(&size, &scene.frp.shape, |sz, sh| snap_to_pixel_offset(*sz, sh)); eval snap((snap) panel.set_xy(*snap)); + + + // === Disable navigator on hover === + + navigator.frp.set_enabled <+ panel.is_hovered.not(); } init.emit(()); diff --git a/app/gui/view/graph-editor/src/lib.rs b/app/gui/view/graph-editor/src/lib.rs index 64fc381ff3..7d1fb68377 100644 --- a/app/gui/view/graph-editor/src/lib.rs +++ b/app/gui/view/graph-editor/src/lib.rs @@ -2648,11 +2648,9 @@ fn new_graph_editor(app: &Application) -> GraphEditor { disable_navigator <- any_(&set_navigator_false,&some_vis_selected); enable_navigator <- any_(&set_navigator_true,&no_vis_selected); - eval_ disable_navigator ( model.navigator.disable() ); - eval_ enable_navigator ( model.navigator.enable() ); + model.navigator.frp.set_enabled <+ bool(&disable_navigator,&enable_navigator); - out.navigator_active <+ inputs.set_navigator_disabled - || out.some_visualisation_selected; + out.navigator_active <+ model.navigator.frp.enabled; } diff --git a/app/gui/view/src/component_browser.rs b/app/gui/view/src/component_browser.rs index 1a2cd02f2f..a8a694664c 100644 --- a/app/gui/view/src/component_browser.rs +++ b/app/gui/view/src/component_browser.rs @@ -91,6 +91,7 @@ ensogl::define_endpoints_2! { is_visible(bool), size(Vector2), expression_input_position(Vector2), + is_hovered(bool), } } @@ -138,6 +139,9 @@ impl component::Frp for Frp { &snap, Model::expression_input_position ); + + out.is_hovered <+ list_panel.is_hovered || documentation.frp.is_hovered; + out.is_hovered <+ input.hide.constant(false); } init.emit(()); } diff --git a/app/gui/view/src/documentation.rs b/app/gui/view/src/documentation.rs index cba4cbc928..f4e01becec 100644 --- a/app/gui/view/src/documentation.rs +++ b/app/gui/view/src/documentation.rs @@ -238,6 +238,8 @@ ensogl::define_endpoints! { /// Indicates whether the documentation panel has been selected through clicking into /// it, or deselected by clicking somewhere else. is_selected(bool), + /// Indicates whether the documentation panel has been hovered. + is_hovered(bool), } } @@ -328,6 +330,19 @@ impl View { app.frp.show_system_cursor <+ overlay.events.mouse_over; app.frp.hide_system_cursor <+ overlay.events.mouse_out; + + + // === Hover === + + frp.source.is_hovered <+ model.overlay.events.mouse_over.constant(true); + frp.source.is_hovered <+ model.overlay.events.mouse_out.constant(false); + let mouse_up = scene.mouse.frp.up.clone_ref(); + let mouse_down = scene.mouse.frp.down.clone_ref(); + let mouse_wheel = scene.mouse.frp.wheel.clone_ref(); + let mouse_position = scene.mouse.frp.position.clone_ref(); + caught_mouse <- any_(mouse_up,mouse_down,mouse_wheel,mouse_position); + pass_to_dom <- caught_mouse.gate(&frp.source.is_hovered); + eval_ pass_to_dom(scene.current_js_event.pass_to_dom.emit(())); } visualization.pass_events_to_dom_if_active(scene, network); self diff --git a/app/gui/view/src/project.rs b/app/gui/view/src/project.rs index 08caae4682..fe78380c61 100644 --- a/app/gui/view/src/project.rs +++ b/app/gui/view/src/project.rs @@ -24,7 +24,6 @@ use ensogl::application; use ensogl::application::shortcut; use ensogl::application::Application; use ensogl::display; -use ensogl::display::navigation::navigator::Navigator; use ensogl::system::web; use ensogl::system::web::dom; use ensogl::Animation; @@ -143,6 +142,7 @@ struct SearcherFrp { editing_committed: frp::Stream, is_visible: frp::Stream, is_empty: frp::Stream, + is_hovered: frp::Stream, } /// A structure containing the Searcher View: the old Node Searcher of a new Component Browser. @@ -166,12 +166,6 @@ impl SearcherVariant { } } - fn set_navigator(&self, navigator: Navigator) { - if let Self::ComponentBrowser(browser) = self { - browser.model().list.model().set_navigator(Some(navigator)) - } - } - fn frp(&self, project_view_network: &frp::Network) -> SearcherFrp { match self { SearcherVariant::ComponentBrowser(view) => { @@ -185,9 +179,11 @@ impl SearcherVariant { editing_committed, is_visible: view.output.is_visible.clone_ref().into(), is_empty: is_empty.into(), + is_hovered: view.output.is_hovered.clone_ref().into(), } } SearcherVariant::OldNodeSearcher(view) => { + let documentation = view.documentation(); frp::extend! {project_view_network editing_committed <- view.editing_committed.constant(()); } @@ -195,6 +191,7 @@ impl SearcherVariant { editing_committed, is_visible: view.output.is_visible.clone_ref().into(), is_empty: view.output.is_empty.clone_ref().into(), + is_hovered: documentation.frp.is_hovered.clone_ref().into(), } } } @@ -276,7 +273,6 @@ impl Model { let display_object = display::object::Instance::new(); let searcher = SearcherVariant::new(app); let graph_editor = app.new_view::(); - searcher.set_navigator(graph_editor.model.navigator.clone_ref()); let code_editor = app.new_view::(); let fullscreen_vis = default(); let prompt_background = prompt_background::View::new(); @@ -749,7 +745,8 @@ impl View { // === Disabling Navigation === let documentation = model.searcher.documentation(); - disable_navigation <- documentation.frp.is_selected || frp.open_dialog_shown; + searcher_active <- searcher.is_hovered || documentation.frp.is_selected; + disable_navigation <- searcher_active || frp.open_dialog_shown; graph.set_navigator_disabled <+ disable_navigation; // === Disabling Dropping === diff --git a/lib/rust/ensogl/core/src/display/navigation/navigator.rs b/lib/rust/ensogl/core/src/display/navigation/navigator.rs index 6dbb683b44..e1f238d797 100644 --- a/lib/rust/ensogl/core/src/display/navigation/navigator.rs +++ b/lib/rust/ensogl/core/src/display/navigation/navigator.rs @@ -7,6 +7,7 @@ use crate::prelude::*; use crate::animation::physics; use crate::control::callback; +use crate::define_endpoints_2; use crate::display::camera::Camera2d; use crate::display::navigation::navigator::events::NavigatorEvents; use crate::display::object::traits::*; @@ -238,6 +239,20 @@ impl NavigatorModel { +// =========== +// === FRP === +// =========== + +define_endpoints_2! { + Input { + set_enabled(bool), + } + Output { + enabled(bool), + } +} + + // ================= // === Navigator === // ================= @@ -245,14 +260,28 @@ impl NavigatorModel { /// Navigator enables camera navigation with mouse interactions. #[derive(Clone, CloneRef, Debug, Shrinkwrap)] pub struct Navigator { + #[allow(missing_docs)] + pub frp: Frp, #[shrinkwrap(main_field)] - model: Rc, + model: Rc, } impl Navigator { pub fn new(scene: &Scene, camera: &Camera2d) -> Self { let model = Rc::new(NavigatorModel::new(scene, camera)); - Navigator { model } + let frp = Frp::new(); + let out = &frp.private.output; + + let network = frp.network(); + frp::extend! { network + enable <- frp.set_enabled.on_true(); + disable <- frp.set_enabled.on_false(); + eval_ enable(model.enable()); + eval_ disable(model.disable()); + out.enabled <+ frp.set_enabled; + } + + Navigator { model, frp } } }