diff --git a/CHANGELOG.md b/CHANGELOG.md index b5087cd5807..d439a551df3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -172,6 +172,8 @@ it is now referenced via the `Main` namespace instead of the project namespace,][6719] e.g. `Main.func1` instead of `MyProject.func1`. This makes it robust against project name changes. +- [Added a button to return from an opened project back to the project + dashboard.][6474] [6279]: https://github.com/enso-org/enso/pull/6279 [6421]: https://github.com/enso-org/enso/pull/6421 @@ -181,6 +183,7 @@ [6663]: https://github.com/enso-org/enso/pull/6663 [6752]: https://github.com/enso-org/enso/pull/6752 [6719]: https://github.com/enso-org/enso/pull/6719 +[6474]: https://github.com/enso-org/enso/pull/6474 #### EnsoGL (rendering engine) diff --git a/Cargo.lock b/Cargo.lock index 187cc016e90..7fe5ea81762 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3265,6 +3265,7 @@ version = "0.1.0" dependencies = [ "enso-frp", "ensogl-core", + "ensogl-hardcoded-theme", ] [[package]] diff --git a/app/gui/src/ide/initializer.rs b/app/gui/src/ide/initializer.rs index 9251c4d3d8a..fb3290323e1 100644 --- a/app/gui/src/ide/initializer.rs +++ b/app/gui/src/ide/initializer.rs @@ -276,7 +276,8 @@ pub fn register_views(app: &Application) { app.views.register::>(); if enso_config::ARGS.groups.startup.options.platform.value == "web" { - app.views.register::(); + app.views + .register::(); } } diff --git a/app/gui/src/presenter/project.rs b/app/gui/src/presenter/project.rs index 01ed7c3efdd..7cc0b216c97 100644 --- a/app/gui/src/presenter/project.rs +++ b/app/gui/src/presenter/project.rs @@ -329,6 +329,16 @@ impl Model { } }); } + + fn show_dashboard(&self) { + match enso_web::Event::new("show-dashboard") { + Ok(event) => + if let Err(error) = enso_web::document.dispatch_event(&event) { + error!("Failed to dispatch event to show the dashboard. {error:?}"); + }, + Err(error) => error!("Failed to create event to show the dashboard. {error:?}"), + } + } } @@ -429,6 +439,8 @@ impl Project { view.set_read_only <+ view.toggle_read_only.map(f_!(model.toggle_read_only())); eval graph_view.execution_environment((env) model.execution_environment_changed(*env)); eval_ graph_view.execution_environment_play_button_pressed( model.trigger_clean_live_execution()); + + eval_ view.go_to_dashboard_button_pressed (model.show_dashboard()); } let graph_controller = self.model.graph_controller.clone_ref(); 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 30662563950..dd4e14f535a 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 @@ -12,7 +12,8 @@ use ensogl::display; use ensogl_component::toggle_button; use ensogl_component::toggle_button::ColorableShape; use ensogl_component::toggle_button::ToggleButton; -use ensogl_hardcoded_theme as theme; +use ensogl_hardcoded_theme::graph_editor::node::actions as theme; + // ============== @@ -135,13 +136,6 @@ impl Icons { 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); - self.freeze.frp.set_color_scheme(color_scheme); - self.skip.frp.set_color_scheme(color_scheme); - } } impl display::Object for Icons { @@ -378,10 +372,10 @@ impl ActionBar { pub fn new(app: &Application) -> Self { let model = Rc::new(Model::new(app)); let frp = Frp::new(); - ActionBar { frp, model }.init_frp() + ActionBar { frp, model }.init_frp(app) } - fn init_frp(self) -> Self { + fn init_frp(self, app: &Application) -> Self { let network = &self.frp.network; let frp = &self.frp; let model = &self.model; @@ -459,20 +453,11 @@ impl ActionBar { ); } - use theme::graph_editor::node::actions; - let color_scheme = toggle_button::ColorScheme { - non_toggled: Some(model.styles.get_color(actions::button::non_toggled).into()), - toggled: Some(model.styles.get_color(actions::button::toggled).into()), - hovered: Some(model.styles.get_color(actions::button::hovered).into()), - ..default() - }; + let scene = &app.display.default_scene; let context_switch_color_scheme = toggle_button::ColorScheme { - non_toggled: Some(model.styles.get_color(actions::context_switch::non_toggled).into()), - toggled: Some(model.styles.get_color(actions::context_switch::toggled).into()), - hovered: Some(model.styles.get_color(actions::context_switch::hovered).into()), - ..default() + toggled: Some(model.styles.get_color(theme::context_switch::toggled).into()), + ..toggle_button::default_color_scheme(&scene.style_sheet) }; - model.icons.set_color_scheme(&color_scheme); model.icons.context_switch.set_color_scheme(&context_switch_color_scheme); frp.show_on_hover.emit(true); diff --git a/app/gui/view/graph-editor/src/component/profiling.rs b/app/gui/view/graph-editor/src/component/profiling.rs index b32cfba3bd0..0fe4388f942 100644 --- a/app/gui/view/graph-editor/src/component/profiling.rs +++ b/app/gui/view/graph-editor/src/component/profiling.rs @@ -146,7 +146,8 @@ 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 styles = StyleWatchFrp::new(&scene.style_sheet); + let style_sheet = &scene.style_sheet; + let styles = StyleWatchFrp::new(style_sheet); let frp = Frp::new(); let network = &frp.network; @@ -154,7 +155,6 @@ impl Button { .with_placement(tooltip::Placement::Left); let button = ToggleButton::::new(app, tooltip_style); scene.layers.panel.add(&button); - button.set_visibility(true); button.frp.set_size(Vector2(32.0, 32.0)); frp::extend! { network @@ -179,21 +179,20 @@ impl Button { // === Color === use ensogl_hardcoded_theme::graph_editor::profiling_button as button_theme; - let non_toggled_color = styles.get_color(button_theme::non_toggled); let toggled_color = styles.get_color(button_theme::toggled); - let hovered_color = styles.get_color(button_theme::hovered); let toggled_hovered_color = styles.get_color(button_theme::toggled_hovered); init_color_scheme <- source::<()>(); - button.set_color_scheme <+ all_with5(&non_toggled_color,&toggled_color,&hovered_color - ,&toggled_hovered_color,&init_color_scheme - ,|&non_toggled,&toggled,&hovered,&toggled_hovered,_| + button.set_color_scheme <+ all_with3( + &toggled_color, + &toggled_hovered_color, + &init_color_scheme, + f!([style_sheet] (&toggled, &toggled_hovered, _) toggle_button::ColorScheme { - non_toggled : Some(non_toggled.into()), toggled : Some(toggled.into()), - hovered : Some(hovered.into()), toggled_hovered : Some(toggled_hovered.into()), - ..default() + ..toggle_button::default_color_scheme(&style_sheet) } + ) ); } diff --git a/app/gui/view/graph-editor/src/execution_environment.rs b/app/gui/view/graph-editor/src/execution_environment.rs index 119d2baa015..9fdf0acef52 100644 --- a/app/gui/view/graph-editor/src/execution_environment.rs +++ b/app/gui/view/graph-editor/src/execution_environment.rs @@ -41,12 +41,13 @@ pub fn init_frp(frp: &Frp, model: &GraphEditorModelWithNetwork) { // === Layout === init <- source::<()>(); - size_update <- all(init,selector.size,inputs.space_for_window_buttons); - eval size_update ([model]((_,size,gap_size)) { + size_update <- all(init, selector.size, inputs.graph_editor_top_bar_offset_x); + eval size_update ([model] ((_, size, graph_editor_top_bar_offset_x)) { let y_offset = MACOS_TRAFFIC_LIGHTS_VERTICAL_CENTER; let traffic_light_width = traffic_lights_gap_width(); - let execution_environment_selector_x = gap_size.x + traffic_light_width; + let execution_environment_selector_x = + graph_editor_top_bar_offset_x + traffic_light_width; model.execution_environment_selector.set_x(execution_environment_selector_x); let breadcrumb_gap_width = execution_environment_selector_x + size.x + TOP_BAR_ITEM_MARGIN; diff --git a/app/gui/view/graph-editor/src/lib.rs b/app/gui/view/graph-editor/src/lib.rs index 90a25dd43d3..43751bcf2b8 100644 --- a/app/gui/view/graph-editor/src/lib.rs +++ b/app/gui/view/graph-editor/src/lib.rs @@ -111,7 +111,8 @@ const MACOS_TRAFFIC_LIGHTS_CONTENT_WIDTH: f32 = 52.0; const MACOS_TRAFFIC_LIGHTS_CONTENT_HEIGHT: f32 = 12.0; /// Horizontal and vertical offset between traffic lights and window border const MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET: f32 = 13.0; -const MACOS_TRAFFIC_LIGHTS_VERTICAL_CENTER: f32 = +/// The vertical center of the traffic lights, relative to the window border. +pub const MACOS_TRAFFIC_LIGHTS_VERTICAL_CENTER: f32 = -MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET - MACOS_TRAFFIC_LIGHTS_CONTENT_HEIGHT / 2.0; const MAX_ZOOM: f32 = 1.0; /// Space between items in the top bar. @@ -124,7 +125,7 @@ fn traffic_lights_gap_width() -> f32 { if is_macos && !ARGS.groups.window.options.frame.value { MACOS_TRAFFIC_LIGHTS_CONTENT_WIDTH + MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET } else { - TOP_BAR_ITEM_MARGIN + 0.0 } } @@ -451,7 +452,10 @@ ensogl::define_endpoints_2! { // === Layout === - space_for_window_buttons (Vector2), + + /// The offset in the x-axis at which the part of the top bar of the graph editor should + /// start. + graph_editor_top_bar_offset_x (f32), // === Read-only mode === diff --git a/app/gui/view/src/lib.rs b/app/gui/view/src/lib.rs index 96853628a87..c704eaac39d 100644 --- a/app/gui/view/src/lib.rs +++ b/app/gui/view/src/lib.rs @@ -38,7 +38,6 @@ pub mod project_list; pub mod root; pub mod searcher; pub mod status_bar; -pub mod window_control_buttons; pub use ide_view_component_browser as component_browser; pub use ide_view_documentation as documentation; diff --git a/app/gui/view/src/project.rs b/app/gui/view/src/project.rs index e1c4123c3e9..b8fd8b32697 100644 --- a/app/gui/view/src/project.rs +++ b/app/gui/view/src/project.rs @@ -24,12 +24,19 @@ use ensogl::application::shortcut; use ensogl::application::Application; use ensogl::display; use ensogl::system::web; -use ensogl::system::web::dom; use ensogl::DEPRECATED_Animation; use ensogl_component::text; use ensogl_component::text::selection::Selection; use ensogl_hardcoded_theme::Theme; use ide_view_graph_editor::NodeSource; +use project_view_top_bar::ProjectViewTopBar; + + +// ============== +// === Export === +// ============== + +pub mod project_view_top_bar; @@ -127,6 +134,7 @@ ensogl::define_endpoints! { fullscreen_visualization_shown (bool), drop_files_enabled (bool), debug_mode (bool), + go_to_dashboard_button_pressed (), } } @@ -138,22 +146,20 @@ ensogl::define_endpoints! { #[derive(Clone, CloneRef, Debug)] struct Model { - app: Application, - display_object: display::object::Instance, - /// These buttons are present only in a cloud environment. - window_control_buttons: Immutable>, - graph_editor: Rc, - searcher: component_browser::View, - code_editor: code_editor::View, - fullscreen_vis: Rc>>, - project_list: Rc, - debug_mode_popup: debug_mode_popup::View, - popup: popup::View, + app: Application, + display_object: display::object::Instance, + project_view_top_bar: ProjectViewTopBar, + graph_editor: Rc, + searcher: component_browser::View, + code_editor: code_editor::View, + fullscreen_vis: Rc>>, + project_list: Rc, + debug_mode_popup: debug_mode_popup::View, + popup: popup::View, } impl Model { fn new(app: &Application) -> Self { - let scene = &app.display.default_scene; let display_object = display::object::Instance::new(); let searcher = app.new_view::(); let graph_editor = app.new_view::(); @@ -161,14 +167,7 @@ impl Model { let fullscreen_vis = default(); let debug_mode_popup = debug_mode_popup::View::new(app); let popup = popup::View::new(app); - let runs_in_web = ARGS.groups.startup.options.platform.value == "web"; - let window_control_buttons = runs_in_web.as_some_from(|| { - let window_control_buttons = app.new_view::(); - display_object.add_child(&window_control_buttons); - scene.layers.panel.add(&window_control_buttons); - window_control_buttons - }); - let window_control_buttons = Immutable(window_control_buttons); + let project_view_top_bar = ProjectViewTopBar::new(app); let project_list = Rc::new(ProjectList::new(app)); display_object.add_child(&graph_editor); @@ -176,6 +175,7 @@ impl Model { display_object.add_child(&searcher); display_object.add_child(&debug_mode_popup); display_object.add_child(&popup); + display_object.add_child(&project_view_top_bar); display_object.remove_child(&searcher); let app = app.clone_ref(); @@ -183,7 +183,7 @@ impl Model { Self { app, display_object, - window_control_buttons, + project_view_top_bar, graph_editor, searcher, code_editor, @@ -263,12 +263,18 @@ impl Model { } } - fn on_dom_shape_changed(&self, shape: &dom::shape::Shape) { - // Top buttons must always stay in top-left corner. - if let Some(window_control_buttons) = &*self.window_control_buttons { - let pos = Vector2(-shape.width, shape.height) / 2.0; - window_control_buttons.set_xy(pos); - } + fn position_project_view_top_bar( + &self, + scene_shape: &display::scene::Shape, + project_view_top_bar_size: Vector2, + ) { + let top_left = Vector2(-scene_shape.width, scene_shape.height) / 2.0; + let project_view_top_bar_origin = Vector2( + 0.0, + crate::graph_editor::MACOS_TRAFFIC_LIGHTS_VERTICAL_CENTER + - project_view_top_bar_size.y / 2.0, + ); + self.project_view_top_bar.set_xy(top_left + project_view_top_bar_origin); } fn on_close_clicked(&self) { @@ -368,6 +374,7 @@ impl View { let frp = Frp::new(); let network = &frp.network; let searcher = &model.searcher.frp(); + let project_view_top_bar = &model.project_view_top_bar; let graph = &model.graph_editor.frp; let code_editor = &model.code_editor; let project_list = &model.project_list; @@ -378,27 +385,36 @@ impl View { model.set_style(theme); let input_change_delay = frp::io::timer::Timeout::new(network); - if let Some(window_control_buttons) = &*model.window_control_buttons { - let initial_size = &window_control_buttons.size.value(); - model.graph_editor.input.space_for_window_buttons(initial_size); - frp::extend! { network - graph.space_for_window_buttons <+ window_control_buttons.size; - eval_ window_control_buttons.close (model.on_close_clicked()); - eval_ window_control_buttons.fullscreen (model.on_fullscreen_clicked()); - } - } - - let shape = scene.shape().clone_ref(); - frp::extend! { network - init <- source::<()>(); - shape <- all(shape, init)._0(); - eval shape ((shape) model.on_dom_shape_changed(shape)); + init <- source_(); eval_ frp.show_graph_editor(model.show_graph_editor()); eval_ frp.hide_graph_editor(model.hide_graph_editor()); + // === Project View Top Bar === + + let window_control_buttons = &project_view_top_bar.window_control_buttons; + eval_ window_control_buttons.close (model.on_close_clicked()); + eval_ window_control_buttons.fullscreen (model.on_fullscreen_clicked()); + let go_to_dashboard_button = &project_view_top_bar.go_to_dashboard_button; + frp.source.go_to_dashboard_button_pressed <+ + go_to_dashboard_button.is_pressed.on_true(); + + let project_view_top_bar_display_object = project_view_top_bar.display_object(); + _eval <- all_with3( + &init, + scene.shape(), + &project_view_top_bar_display_object.on_resized, + f!((_, scene_shape, project_view_top_bar_size) + model.position_project_view_top_bar(scene_shape, *project_view_top_bar_size) + ) + ); + project_view_top_bar_width <- + project_view_top_bar_display_object.on_resized.map(|new_size| new_size.x); + graph.graph_editor_top_bar_offset_x <+ project_view_top_bar_width; + + // === Read-only mode === graph.set_read_only <+ frp.set_read_only; diff --git a/app/gui/view/src/project/project_view_top_bar.rs b/app/gui/view/src/project/project_view_top_bar.rs new file mode 100644 index 00000000000..e56732381af --- /dev/null +++ b/app/gui/view/src/project/project_view_top_bar.rs @@ -0,0 +1,72 @@ +//! Defines a UI container for the window control buttons and the "go to dashboard" button. This is +//! merely here to make use of the auto-layout functionality. + +use ensogl::prelude::*; + +use enso_config::ARGS; +use ensogl::application::Application; +use ensogl::display; + + + +mod go_to_dashboard_button; +pub mod window_control_buttons; + + + +// ================= +// === Constants === +// ================= + +/// The gap in pixels between the various components of the project view top bar. +const GAP: f32 = 16.0; +/// The padding left of the project view top bar. +const PADDING_LEFT: f32 = 19.0; + + + +// ============================ +// === Project View Top Bar === +// ============================ + +/// Defines a UI container for the window control buttons and the "go to dashboard" button. This is +/// merely here to make use of the auto-layout functionality. +#[derive(Clone, CloneRef, Debug)] +#[allow(missing_docs)] +pub struct ProjectViewTopBar { + root: display::object::Instance, + /// These buttons are only visible in a cloud environment. + pub window_control_buttons: window_control_buttons::View, + pub go_to_dashboard_button: go_to_dashboard_button::View, +} + +impl ProjectViewTopBar { + /// Constructor. + pub fn new(app: &Application) -> Self { + let root = display::object::Instance::new_named("ProjectViewTopBar"); + let window_control_buttons = app.new_view::(); + let go_to_dashboard_button = go_to_dashboard_button::View::new(app); + + if ARGS.groups.startup.options.platform.value == "web" { + root.add_child(&window_control_buttons); + } + root.add_child(&go_to_dashboard_button); + root.use_auto_layout() + .set_gap((GAP, 0.0)) + .set_padding_left(PADDING_LEFT) + // We use `GAP` as the right padding since it delimits the space to the part of the top + // bar that's defined in the graph editor. + .set_padding_right(GAP) + .set_children_alignment_center(); + + app.display.default_scene.layers.panel.add(&root); + + Self { root, window_control_buttons, go_to_dashboard_button } + } +} + +impl display::Object for ProjectViewTopBar { + fn display_object(&self) -> &display::object::Instance { + &self.root + } +} diff --git a/app/gui/view/src/project/project_view_top_bar/go_to_dashboard_button.rs b/app/gui/view/src/project/project_view_top_bar/go_to_dashboard_button.rs new file mode 100644 index 00000000000..196fb97b00d --- /dev/null +++ b/app/gui/view/src/project/project_view_top_bar/go_to_dashboard_button.rs @@ -0,0 +1,101 @@ +//! Provides a button to switch back from the project view to the dashboard. + +use crate::prelude::*; + +use ensogl::application::tooltip; +use ensogl::application::Application; +use ensogl::display; +use ensogl::display::style; +use ensogl_component::toggle_button; +use ensogl_component::toggle_button::ToggleButton; + + + +// ================= +// === Constants === +// ================= + +/// The width and height of the button. +pub const SIZE: f32 = 16.0; + + + +// ============ +// === Icon === +// ============ + +/// Defines an icon for returning to the dashboard. It looks like a hamburger button. +mod icon { + use super::*; + + use ensogl::data::color; + use ensogl_component::toggle_button::ColorableShape; + + ensogl::shape! { + (style: Style, color_rgba: Vector4) { + let fill_color = Var::::from(color_rgba); + let width = Var::::from("input_size.x"); + let height = Var::::from("input_size.y"); + let unit = &width / SIZE; + let mid_bar = Rect((&unit * 12.0, &unit * 3.0)).corners_radius(&unit); + let top_bar = mid_bar.translate_y(&unit * -5.0); + let bottom_bar = mid_bar.translate_y(&unit * 5.0); + let all_bars = top_bar + mid_bar + bottom_bar; + let shape = all_bars.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)); + } + } +} + + + +// ============ +// === View === +// ============ + +/// Provides a button to switch back from the project view to the dashboard. +#[derive(Debug, Clone, CloneRef, Deref)] +pub struct View { + button: ToggleButton, +} + +impl View { + /// Constructor. + pub fn new(app: &Application) -> Self { + let scene = &app.display.default_scene; + + let tooltip_style = tooltip::Style::set_label("Dashboard".to_owned()) + .with_placement(tooltip::Placement::Right); + let button = ToggleButton::::new(app, tooltip_style); + scene.layers.panel.add(&button); + + button.set_color_scheme(Self::color_scheme(&scene.style_sheet)); + button.set_size(Vector2(SIZE, SIZE)); + + Self { button } + } + + fn color_scheme(style_sheet: &style::Sheet) -> toggle_button::ColorScheme { + let default_color_scheme = toggle_button::default_color_scheme(style_sheet); + toggle_button::ColorScheme { + // Make it look like a normal button (as opposed to a toggle button) by not having a + // toggled state visually. + toggled: default_color_scheme.non_toggled, + toggled_hovered: default_color_scheme.hovered, + ..default_color_scheme + } + } +} + +impl display::Object for View { + fn display_object(&self) -> &display::object::Instance { + self.button.display_object() + } +} diff --git a/app/gui/view/src/project/project_view_top_bar/window_control_buttons.rs b/app/gui/view/src/project/project_view_top_bar/window_control_buttons.rs new file mode 100644 index 00000000000..a2ce8b6cfd0 --- /dev/null +++ b/app/gui/view/src/project/project_view_top_bar/window_control_buttons.rs @@ -0,0 +1,199 @@ +//! The component with buttons in the top left corner. See [[View]]. + +use ensogl::display::shape::*; +use ensogl::prelude::*; + +use enso_frp as frp; +use ensogl::application; +use ensogl::application::Application; +use ensogl::display; +use ensogl::display::object::ObjectOps; +use ensogl::shape; +use ensogl_hardcoded_theme::application::window_control_buttons as theme; + + +// ============== +// === Export === +// ============== + +pub mod close; +pub mod fullscreen; + + + +// ============== +// === Shapes === +// ============== + +mod shape { + use super::*; + shape! { + alignment = center; + (style: Style) { + Plane().fill(INVISIBLE_HOVER_COLOR).into() + } + } +} + + + +// ============= +// === Model === +// ============= + +/// An internal model of Status Bar component +#[derive(Clone, CloneRef, Debug)] +pub struct Model { + app: Application, + display_object: display::object::Instance, + shape: shape::View, + close: close::View, + fullscreen: fullscreen::View, +} + +impl Model { + /// Constructor. + pub fn new(app: &Application) -> Self { + let app = app.clone_ref(); + let display_object = display::object::Instance::new_named("WindowControlButtons"); + + ensogl::shapes_order_dependencies! { + app.display.default_scene => { + shape -> close::shape; + shape -> fullscreen::shape; + } + }; + let close = close::View::new(&app); + display_object.add_child(&close); + + let fullscreen = fullscreen::View::new(&app); + display_object.add_child(&fullscreen); + + let shape = shape::View::new(); + display_object.add_child(&shape); + + app.display.default_scene.layers.panel.add(&display_object); + + Self { app, display_object, shape, close, fullscreen } + } + + /// Updates positions of the buttons and sizes of the mouse area. + pub fn set_layout(&self, spacing: f32) { + let close_size = self.close.size.value(); + let fullscreen_size = self.fullscreen.size.value(); + let fullscreen_offset = Vector2(close_size.x + spacing, 0.0); + self.fullscreen.set_xy(fullscreen_offset); + let width = fullscreen_offset.x + fullscreen_size.x; + let height = max(close_size.y, fullscreen_size.y); + let size = Vector2(width, height); + self.shape.set_size(size); + self.display_object.set_size(size); + } +} + + + +// =========== +// === FRP === +// =========== + +ensogl::define_endpoints! { + Input { + enabled (bool), + } + Output { + close(), + fullscreen(), + } +} + + + +// ============ +// === View === +// ============ + +/// The Top Buttons Panel component. +/// +/// The panel contains two buttons: one for closing IDE and one for toggling the fullscreen mode. +/// The panel is meant to be displayed only when IDE runs in a cloud environment. +#[derive(Clone, CloneRef, Debug)] +pub struct View { + #[allow(missing_docs)] + pub frp: Frp, + model: Model, + style: StyleWatchFrp, +} + +impl View { + /// Constructor. + pub fn new(app: &Application) -> Self { + let frp = Frp::new(); + let model = Model::new(app); + let network = &frp.network; + + let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet); + let radius = style.get_number(theme::radius); + let spacing = style.get_number(theme::spacing); + + frp::extend! { network + // Layout + button_size <- radius.map(|&r| Vector2(2.0 * r, 2.0 * r)); + model.close.set_size <+ button_size; + model.fullscreen.set_size <+ button_size; + button_resized <- any_(&model.close.size, &model.fullscreen.size); + _eval <- all_with(&button_resized, &spacing, + f!((_, spacing) model.set_layout(*spacing)) + ); + + // Handle the panel-wide hover + mouse_near_buttons <- bool( + &model.shape.events_deprecated.mouse_out, + &model.shape.events_deprecated.mouse_over + ); + mouse_on_any_buttton <- model.close.is_hovered.or(&model.fullscreen.is_hovered); + mouse_nearby <- mouse_near_buttons.or(&mouse_on_any_buttton); + model.close.mouse_nearby <+ mouse_nearby; + model.fullscreen.mouse_nearby <+ mouse_nearby; + + // === Handle buttons' clicked events === + frp.source.close <+ model.close.clicked; + frp.source.fullscreen <+ model.fullscreen.clicked; + } + + model.set_layout(spacing.value()); + + Self { frp, model, style } + } +} + +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 + } +} + +impl FrpNetworkProvider for View { + fn network(&self) -> &frp::Network { + &self.frp.network + } +} + +impl application::View for View { + fn label() -> &'static str { + "TopButtons" + } + fn new(app: &Application) -> Self { + View::new(app) + } + fn app(&self) -> &Application { + &self.model.app + } +} diff --git a/app/gui/view/src/window_control_buttons/close.rs b/app/gui/view/src/project/project_view_top_bar/window_control_buttons/close.rs similarity index 98% rename from app/gui/view/src/window_control_buttons/close.rs rename to app/gui/view/src/project/project_view_top_bar/window_control_buttons/close.rs index 1c436930945..15cd29c2f0a 100644 --- a/app/gui/view/src/window_control_buttons/close.rs +++ b/app/gui/view/src/project/project_view_top_bar/window_control_buttons/close.rs @@ -20,7 +20,6 @@ pub mod shape { use super::*; ensogl::shape! { - alignment = center; (style: Style, background_color: Vector4, icon_color: Vector4) { let size = Var::canvas_size(); let radius = Min::min(size.x(),size.y()) / 2.0; diff --git a/app/gui/view/src/window_control_buttons/fullscreen.rs b/app/gui/view/src/project/project_view_top_bar/window_control_buttons/fullscreen.rs similarity index 98% rename from app/gui/view/src/window_control_buttons/fullscreen.rs rename to app/gui/view/src/project/project_view_top_bar/window_control_buttons/fullscreen.rs index 5688cffcfb5..4a52c2e4a30 100644 --- a/app/gui/view/src/window_control_buttons/fullscreen.rs +++ b/app/gui/view/src/project/project_view_top_bar/window_control_buttons/fullscreen.rs @@ -20,7 +20,6 @@ pub use ensogl_hardcoded_theme::application::window_control_buttons::fullscreen pub mod shape { use super::*; ensogl::shape! { - alignment = center; (style: Style, background_color: Vector4, icon_color: Vector4) { let size = Var::canvas_size(); let radius = Min::min(size.x(),size.y()) / 2.0; diff --git a/app/gui/view/src/window_control_buttons.rs b/app/gui/view/src/window_control_buttons.rs deleted file mode 100644 index a4f017b4df9..00000000000 --- a/app/gui/view/src/window_control_buttons.rs +++ /dev/null @@ -1,301 +0,0 @@ -//! The component with buttons in the top left corner. See [[View]]. - -use ensogl::display::shape::*; -use ensogl::prelude::*; - -use enso_frp as frp; -use ensogl::application; -use ensogl::application::Application; -use ensogl::display; -use ensogl::display::object::ObjectOps; -use ensogl::shape; -use ensogl_hardcoded_theme::application::window_control_buttons as theme; - - -// ============== -// === Export === -// ============== - -pub mod close; -pub mod fullscreen; - - - -// ============== -// === Shapes === -// ============== - -mod shape { - use super::*; - shape! { - alignment = center; - (style: Style) { - Plane().fill(INVISIBLE_HOVER_COLOR).into() - } - } -} - - - -// ============== -// === Layout === -// ============== - -/// Information on how to layout shapes in the top buttons panel, as defined in the theme. -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug)] -pub struct LayoutParams { - pub spacing: T, - pub padding_left: T, - pub padding_top: T, - pub padding_right: T, - pub padding_bottom: T, -} - -impl Default for LayoutParams { - fn default() -> Self { - Self { - spacing: 8.0, - padding_left: 13.0, - padding_top: 13.0, - padding_right: 13.0, - padding_bottom: 13.0, - } - } -} - -impl LayoutParams { - /// Applies a given function over all stored values and return layout with resulting values. - pub fn map(&self, f: impl Fn(&T) -> U) -> LayoutParams { - LayoutParams { - spacing: f(&self.spacing), - padding_left: f(&self.padding_left), - padding_top: f(&self.padding_top), - padding_right: f(&self.padding_right), - padding_bottom: f(&self.padding_bottom), - } - } -} - -impl LayoutParams> { - /// Get layout from theme. Each layout parameter will be an frp sampler. - pub fn from_theme(style: &StyleWatchFrp) -> Self { - let default = LayoutParams::default(); - let spacing = style.get_number_or(theme::spacing, default.spacing); - let padding_left = style.get_number_or(theme::padding::left, default.padding_left); - let padding_top = style.get_number_or(theme::padding::top, default.padding_top); - let padding_right = style.get_number_or(theme::padding::right, default.padding_right); - let padding_bottom = style.get_number_or(theme::padding::bottom, default.padding_bottom); - Self { spacing, padding_left, padding_top, padding_right, padding_bottom } - } - - /// Take values from the parameters' samplers. - pub fn value(&self) -> LayoutParams { - self.map(|sampler| sampler.value()) - } - - /// Join all member frp streams into a single stream with aggregated values. - pub fn flatten(&self, network: &frp::Network) -> frp::Stream> { - /// Helper method that puts back LayoutParams from its fields. - /// Be careful, as the arguments must be in the same order as they are in `all_with5` - /// invocation below. - // We intentionally take references to f32 for seamless usage in FRP. - #[allow(clippy::trivially_copy_pass_by_ref)] - fn to_layout( - spacing: &f32, - padding_left: &f32, - padding_top: &f32, - padding_right: &f32, - padding_bottom: &f32, - ) -> LayoutParams { - let ret = - LayoutParams { spacing, padding_left, padding_top, padding_right, padding_bottom }; - ret.map(|v| **v) - } - - network.all_with5( - "TopButtonsLayoutStyle", - &self.spacing, - &self.padding_left, - &self.padding_top, - &self.padding_right, - &self.padding_bottom, - to_layout, - ) - } -} - - - -// ============= -// === Model === -// ============= - -/// An internal model of Status Bar component -#[derive(Clone, CloneRef, Debug)] -pub struct Model { - app: Application, - display_object: display::object::Instance, - shape: shape::View, - close: close::View, - fullscreen: fullscreen::View, -} - -impl Model { - /// Constructor. - pub fn new(app: &Application) -> Self { - let app = app.clone_ref(); - let display_object = display::object::Instance::new(); - - ensogl::shapes_order_dependencies! { - app.display.default_scene => { - shape -> close::shape; - shape -> fullscreen::shape; - } - }; - let close = close::View::new(&app); - display_object.add_child(&close); - - let fullscreen = fullscreen::View::new(&app); - display_object.add_child(&fullscreen); - - let shape = shape::View::new(); - display_object.add_child(&shape); - - Self { app, display_object, shape, close, fullscreen } - } - - /// Updates positions of the buttons and sizes of the mouse area. - /// Returns the new size of the panel (being also the size of mouse area). - pub fn set_layout(&self, layout: LayoutParams) -> Vector2 { - let LayoutParams { spacing, padding_left, padding_top, padding_right, padding_bottom } = - layout; - let close_size = self.close.size.value(); - let fullscreen_size = self.fullscreen.size.value(); - let padding_offset = Vector2(padding_left, -padding_top); - let origin_offset = |size: Vector2| Vector2(size.x / 2.0, -size.y / 2.0); - - self.close.set_xy(padding_offset + origin_offset(close_size)); - let fullscreen_x = padding_left + close_size.x + spacing; - self.fullscreen - .set_xy(Vector2(fullscreen_x, -padding_top) + origin_offset(fullscreen_size)); - - let width = fullscreen_x + fullscreen_size.x + padding_right; - let height = padding_top + max(close_size.y, fullscreen_size.y) + padding_bottom; - - let size = Vector2(width, height); - self.shape.set_xy(Vector2(size.x, -size.y) / 2.0); - self.shape.set_size(size); - size - } -} - - - -// =========== -// === FRP === -// =========== - -ensogl::define_endpoints! { - Input { - enabled (bool), - } - Output { - close(), - fullscreen(), - size(Vector2), - } -} - - - -// ============ -// === View === -// ============ - -/// The Top Buttons Panel component. -/// -/// The panel contains two buttons: one for closing IDE and one for toggling the fullscreen mode. -/// The panel is meant to be displayed only when IDE runs in a cloud environment. -#[derive(Clone, CloneRef, Debug)] -pub struct View { - frp: Frp, - model: Model, - style: StyleWatchFrp, -} - -impl View { - /// Constructor. - pub fn new(app: &Application) -> Self { - let frp = Frp::new(); - let model = Model::new(app); - let network = &frp.network; - - let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet); - let style_frp = LayoutParams::from_theme(&style); - let layout_style = style_frp.flatten(network); - let radius = style.get_number(theme::radius); - - frp::extend! { network - // Layout - button_size <- radius.map(|&r| Vector2(2.0 * r, 2.0 * r)); - model.close.set_size <+ button_size; - model.fullscreen.set_size <+ button_size; - button_resized <- any_(&model.close.size,&model.fullscreen.size); - layout_on_button_change <- sample(&layout_style,&button_resized); - need_relayout <- any(&layout_style,&layout_on_button_change); - frp.source.size <+ need_relayout.map(f!((layout) model.set_layout(*layout))); - - // Handle the panel-wide hover - mouse_near_buttons <- bool( - &model.shape.events_deprecated.mouse_out, - &model.shape.events_deprecated.mouse_over - ); - mouse_on_any_buttton <- model.close.is_hovered.or(&model.fullscreen.is_hovered); - mouse_nearby <- mouse_near_buttons.or(&mouse_on_any_buttton); - model.close.mouse_nearby <+ mouse_nearby; - model.fullscreen.mouse_nearby <+ mouse_nearby; - - // === Handle buttons' clicked events === - frp.source.close <+ model.close.clicked; - frp.source.fullscreen <+ model.fullscreen.clicked; - } - - let initial_style = style_frp.value(); - let initial_size = model.set_layout(initial_style); - frp.source.size.emit(initial_size); - - Self { frp, model, style } - } -} - -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 - } -} - -impl FrpNetworkProvider for View { - fn network(&self) -> &frp::Network { - &self.frp.network - } -} - -impl application::View for View { - fn label() -> &'static str { - "TopButtons" - } - fn new(app: &Application) -> Self { - View::new(app) - } - fn app(&self) -> &Application { - &self.model.app - } -} diff --git a/build-config.yaml b/build-config.yaml index ca035692c78..ee61dd8c81d 100644 --- a/build-config.yaml +++ b/build-config.yaml @@ -1,6 +1,6 @@ # Options intended to be common for all developers. -wasm-size-limit: 15.87 MiB +wasm-size-limit: 15.88 MiB required-versions: # NB. The Rust version is pinned in rust-toolchain.toml. diff --git a/lib/rust/ensogl/app/theme/hardcoded/src/lib.rs b/lib/rust/ensogl/app/theme/hardcoded/src/lib.rs index 92ee6f3b850..0ba6f85d033 100644 --- a/lib/rust/ensogl/app/theme/hardcoded/src/lib.rs +++ b/lib/rust/ensogl/app/theme/hardcoded/src/lib.rs @@ -405,12 +405,6 @@ define_themes! { [light:0, dark:1] window_control_buttons { radius = 6.5, 6.5; spacing = application::window_control_buttons::radius, application::window_control_buttons::radius; - padding { - left = 13.0, 13.0; - top = 13.0, 13.0; - right = 13.0, 13.0; - bottom = 13.0, 13.0; - } close { normal { @@ -526,15 +520,8 @@ define_themes! { [light:0, dark:1] selection = Lcha(0.7,0.0,0.125,0.7) , Lcha(0.7,0.0,0.125,0.7); } actions { - button { - non_toggled = Lcha(0.0,0.0,0.0,0.3) , Lcha(0.4,0.0,0.0,1.0); - toggled = Lcha(0.0,0.0,0.0,0.7) , Lcha(1.0,0.0,0.0,0.7); - hovered = Lcha(0.0,0.0,0.0,0.45) , Lcha(1.0,0.0,0.0,0.7); - } context_switch { - non_toggled = Lcha(0.0,0.0,0.0,0.3) , Lcha(0.4,0.0,0.0,1.0); toggled = Lcha(0.58, 0.67, 0.0825, 1.0), Lcha(0.58, 0.67, 0.0825, 1.0); - hovered = Lcha(0.0,0.0,0.0,0.45) , Lcha(1.0,0.0,0.0,0.7); } } vcs { @@ -620,11 +607,7 @@ define_themes! { [light:0, dark:1] } } profiling_button { - non_toggled = graph_editor::node::actions::button::non_toggled - ,graph_editor::node::actions::button::non_toggled; toggled = Lcha(0.7,0.5,0.12,1.0) , Lcha(0.7,0.5,0.12,1.0); - hovered = graph_editor::node::actions::button::hovered - ,graph_editor::node::actions::button::hovered; toggled_hovered = Lcha(0.55,0.5,0.12,1.0) , Lcha(0.85,0.5,0.12,1.0); } add_node_button { @@ -726,6 +709,11 @@ define_themes! { [light:0, dark:1] scale = 1.0, 1.0; } } + toggle_button { + non_toggled = Lcha(0.0,0.0,0.0,0.3), Lcha(0.4,0.0,0.0,1.0); + toggled = Lcha(0.0,0.0,0.0,0.7), Lcha(1.0,0.0,0.0,0.7); + hovered = Lcha(0.0,0.0,0.0,0.45), Lcha(1.0,0.0,0.0,0.7); + } } diff --git a/lib/rust/ensogl/component/toggle-button/Cargo.toml b/lib/rust/ensogl/component/toggle-button/Cargo.toml index ebce49e8e29..ce3612a8994 100644 --- a/lib/rust/ensogl/component/toggle-button/Cargo.toml +++ b/lib/rust/ensogl/component/toggle-button/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] enso-frp = { path = "../../../frp" } ensogl-core = { path = "../../core" } +ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" } diff --git a/lib/rust/ensogl/component/toggle-button/src/lib.rs b/lib/rust/ensogl/component/toggle-button/src/lib.rs index 21f3efabd47..2859b58b674 100644 --- a/lib/rust/ensogl/component/toggle-button/src/lib.rs +++ b/lib/rust/ensogl/component/toggle-button/src/lib.rs @@ -18,6 +18,7 @@ #![warn(unused_import_braces)] #![warn(unused_qualifications)] +use ensogl_core::display::shape::*; use ensogl_core::prelude::*; use enso_frp as frp; @@ -27,7 +28,9 @@ use ensogl_core::data::color; use ensogl_core::display; use ensogl_core::display::shape::system::Shape; use ensogl_core::display::shape::system::ShapeWithDefaultableData; +use ensogl_core::display::style; use ensogl_core::gui::component::ShapeView; +use ensogl_hardcoded_theme::component::toggle_button as theme; @@ -159,6 +162,18 @@ impl ColorScheme { } } +/// Return the default color scheme for the given style sheet. +pub fn default_color_scheme(style_sheet: &style::Sheet) -> ColorScheme { + let styles = StyleWatch::new(style_sheet); + ColorScheme { + non_toggled: Some(styles.get_color(theme::non_toggled).into()), + toggled: Some(styles.get_color(theme::toggled).into()), + hovered: Some(styles.get_color(theme::hovered).into()), + ..default() + } +} + + // === Getters === @@ -283,7 +298,9 @@ impl ToggleButton { } frp.set_state.emit(false); - color.target_alpha.emit(0.0); + frp.set_visibility.emit(true); + let color_scheme = default_color_scheme(&app.display.default_scene.style_sheet); + frp.set_color_scheme.emit(color_scheme); self } diff --git a/lib/rust/web/src/binding/mock.rs b/lib/rust/web/src/binding/mock.rs index 0225f5b8f8d..db643555c53 100644 --- a/lib/rust/web/src/binding/mock.rs +++ b/lib/rust/web/src/binding/mock.rs @@ -532,6 +532,7 @@ mock_data! { Document => EventTarget fn create_element(&self, local_name: &str) -> Result; fn get_element_by_id(&self, element_id: &str) -> Option; fn create_text_node(&self, data: &str) -> Text; + fn dispatch_event(&self, event: &Event) -> Result; } @@ -575,6 +576,7 @@ impl AddEventListenerOptions { // === Event === mock_data! { Event => Object + fn new(type_: &str) -> Result; fn prevent_default(&self); fn stop_propagation(&self); fn current_target(&self) -> Option;