From 370c0ce4435a58946c18d329e68cbb4df9b7402f Mon Sep 17 00:00:00 2001 From: Stijn Seghers Date: Fri, 7 Apr 2023 15:49:03 +0200 Subject: [PATCH 1/2] Make play button clickable and hide it when irrelevant --- app/gui/src/presenter/project.rs | 2 +- .../execution-mode-dropdown/src/lib.rs | 2 +- app/gui/view/examples/interface/src/lib.rs | 3 +- .../view/execution-mode-selector/src/lib.rs | 101 +++++----- .../src/play_button.rs | 180 ++++++++++++++++++ .../ensogl/app/theme/hardcoded/src/lib.rs | 16 +- 6 files changed, 250 insertions(+), 54 deletions(-) create mode 100644 app/gui/view/execution-mode-selector/src/play_button.rs diff --git a/app/gui/src/presenter/project.rs b/app/gui/src/presenter/project.rs index edee5de83639..dfb5569b68bf 100644 --- a/app/gui/src/presenter/project.rs +++ b/app/gui/src/presenter/project.rs @@ -388,7 +388,7 @@ impl Project { /// implementation of #5930. fn init_execution_modes(self) -> Self { let graph = &self.model.view.graph(); - let entries = Rc::new(vec!["development".to_string(), "production".to_string()]); + let entries = Rc::new(vec!["design".to_string(), "live".to_string()]); graph.set_available_execution_modes(entries); self } diff --git a/app/gui/view/examples/execution-mode-dropdown/src/lib.rs b/app/gui/view/examples/execution-mode-dropdown/src/lib.rs index c2c9b7087589..e455ceb2ec1c 100644 --- a/app/gui/view/examples/execution-mode-dropdown/src/lib.rs +++ b/app/gui/view/examples/execution-mode-dropdown/src/lib.rs @@ -28,7 +28,7 @@ use ide_view_execution_mode_selector as execution_mode_selector; // ====================== fn make_entries() -> execution_mode_selector::ExecutionModes { - Rc::new(vec!["development".to_string(), "production".to_string()]) + Rc::new(vec!["design".to_string(), "live".to_string()]) } fn init(app: &Application) { diff --git a/app/gui/view/examples/interface/src/lib.rs b/app/gui/view/examples/interface/src/lib.rs index 4a7752a42b10..ea20b7e01206 100644 --- a/app/gui/view/examples/interface/src/lib.rs +++ b/app/gui/view/examples/interface/src/lib.rs @@ -256,8 +256,7 @@ fn init(app: &Application) { // === Execution Modes === - graph_editor - .set_available_execution_modes(vec!["development".to_string(), "production".to_string()]); + graph_editor.set_available_execution_modes(vec!["design".to_string(), "live".to_string()]); // === Rendering === diff --git a/app/gui/view/execution-mode-selector/src/lib.rs b/app/gui/view/execution-mode-selector/src/lib.rs index a5a4caa9bb10..07c88143f57d 100644 --- a/app/gui/view/execution-mode-selector/src/lib.rs +++ b/app/gui/view/execution-mode-selector/src/lib.rs @@ -18,6 +18,10 @@ #![warn(unused_import_braces)] #![warn(unused_qualifications)] + + +mod play_button; + use enso_prelude::*; use ensogl::prelude::*; @@ -39,51 +43,31 @@ use ensogl_hardcoded_theme::graph_editor::execution_mode_selector as theme; /// Theme specification for the execution mode selector. #[derive(Debug, Clone, Copy, Default, FromTheme)] -#[base_path = "ensogl_hardcoded_theme::graph_editor::execution_mode_selector"] +#[base_path = "theme"] pub struct Style { - play_button_size: f32, - play_button_offset: f32, - play_button_padding: f32, - divider_offset: f32, - divider_padding: f32, - dropdown_width: f32, - height: f32, - background: Rgba, - divider: Rgba, - menu_offset: f32, + divider_offset: f32, + divider_padding: f32, + dropdown_width: f32, + height: f32, + background: Rgba, + divider: Rgba, + menu_offset: f32, + #[theme_path = "theme::play_button::triangle_size"] + play_button_triangle_size: f32, + #[theme_path = "theme::play_button::padding_x"] + play_button_padding_x: f32, } impl Style { fn overall_width(&self) -> f32 { self.dropdown_width + 2.0 * self.divider_padding - + self.play_button_size - + self.play_button_padding + + self.play_button_triangle_size + + 2.0 * self.play_button_padding_x } } -// ============== -// === Shapes === -// ============== - -mod play_icon { - use super::*; - - use std::f32::consts::PI; - ensogl::shape! { - above = [display::shape::compound::rectangle::shape]; - (style:Style) { - let triangle_size = style.get_number(theme::play_button_size); - let color = style.get_color(theme::triangle); - let triangle = Triangle(triangle_size, triangle_size).rotate((PI/2.0).radians()); - let triangle = triangle.fill(color); - let bg_size = Var::canvas_size(); - let bg = Rect(bg_size).fill(INVISIBLE_HOVER_COLOR); - (bg + triangle).into() - } - } -} // =========== // === FRP === @@ -106,6 +90,7 @@ ensogl::define_endpoints_2! { } + // ============= // === Model === // ============= @@ -120,7 +105,7 @@ pub struct Model { inner_root: display::object::Instance, background: display::shape::compound::rectangle::Rectangle, divider: display::shape::compound::rectangle::Rectangle, - play_icon: play_icon::View, + play_button: play_button::PlayButton, dropdown: ensogl_drop_down_menu::DropDownMenu, } @@ -148,12 +133,11 @@ impl Model { self.divider.set_color(style.divider); } - fn update_play_icon_style(&self, style: &Style) { + fn update_play_button_style(&self, style: &Style) { let width = style.overall_width(); - let size = Vector2::new(style.play_button_size + style.play_button_padding, style.height); - self.play_icon.set_size(size); - self.play_icon.set_x(width / 2.0 - style.play_button_offset - size.x / 2.0); - self.play_icon.set_y(-size.y / 2.0); + let Style { height, .. } = *style; + self.play_button.set_x(width / 2.0); + self.play_button.set_y(-height / 2.0); } fn update_position(&self, style: &Style, camera: &Camera2d) { @@ -169,6 +153,16 @@ impl Model { self.dropdown.set_entries(provider); self.dropdown.set_selected(0); } + + fn set_play_button_visibility(&self, visible: bool) { + if visible { + self.inner_root.add_child(&self.play_button); + self.inner_root.add_child(&self.divider); + } else { + self.inner_root.remove_child(&self.play_button); + self.inner_root.remove_child(&self.divider); + } + } } impl display::Object for Model { @@ -178,6 +172,7 @@ impl display::Object for Model { } + // ============================= // === ExecutionModeDropdown === // ============================= @@ -194,12 +189,12 @@ impl component::Model for Model { let inner_root = display::object::Instance::new(); let background = default(); let divider = default(); - let play_icon = play_icon::View::new(); + let play_button = play_button::PlayButton::new(app); let dropdown = ensogl_drop_down_menu::DropDownMenu::new(app); display_object.add_child(&inner_root); inner_root.add_child(&dropdown); - inner_root.add_child(&play_icon); + inner_root.add_child(&play_button); inner_root.add_child(&background); inner_root.add_child(÷r); @@ -211,7 +206,7 @@ impl component::Model for Model { dropdown.restore_shape_constraints(app); - Self { display_object, background, play_icon, dropdown, inner_root, divider } + Self { display_object, background, play_button, dropdown, inner_root, divider } } } @@ -226,7 +221,7 @@ impl component::Frp for Frp { let scene = &app.display.default_scene; let camera = scene.camera(); let dropdown = &model.dropdown; - let play_icon = &model.play_icon; + let play_button = &model.play_button; let input = &frp.input; let output = &frp.output; @@ -246,7 +241,7 @@ impl component::Frp for Frp { eval style_update((style) { model.update_dropdown_style(style); model.update_background_style(style); - model.update_play_icon_style(style); + model.update_play_button_style(style); }); // == Inputs == @@ -258,9 +253,23 @@ impl component::Frp for Frp { selected_entry <- selection.map(|(entries, entry_id)| entries[*entry_id].clone()); output.selected_execution_mode <+ selected_entry; + eval selected_entry ([model] (execution_mode) { + // TODO(#5930): Revisit when connecting with externally set execution mode + let play_button_visibility = match execution_mode.as_str() { + "design" => true, + "live" => false, + _ => { + error!("Play button: invalid execution mode"); + false + } + }; + model.set_play_button_visibility(play_button_visibility); + }); + play_button.reset <+ selected_entry.constant(()); + // == Outputs == - output.play_press <+ play_icon.events_deprecated.mouse_down.constant(()); + output.play_press <+ play_button.pressed; output.size <+ style_update.map(|style| { Vector2::new(style.overall_width(),style.height) }).on_change(); diff --git a/app/gui/view/execution-mode-selector/src/play_button.rs b/app/gui/view/execution-mode-selector/src/play_button.rs new file mode 100644 index 000000000000..d94be9875f9b --- /dev/null +++ b/app/gui/view/execution-mode-selector/src/play_button.rs @@ -0,0 +1,180 @@ +use enso_prelude::*; +use ensogl::prelude::*; + +use enso_frp as frp; +use ensogl::application::Application; +use ensogl::display; +use ensogl::display::shape::StyleWatchFrp; +use ensogl_derive_theme::FromTheme; +use ensogl_gui_component::component; +use ensogl_hardcoded_theme::graph_editor::execution_mode_selector::play_button as theme; + + + +// ============= +// === Style === +// ============== + +#[derive(Debug, Clone, Copy, Default, FromTheme)] +#[base_path = "theme"] +pub struct Style { + triangle_size: f32, + offset: f32, + padding_x: f32, + padding_y: f32, +} + + + +// ============== +// === Shapes === +// ============== + +mod play_icon { + use super::*; + + use std::f32::consts::PI; + + ensogl::shape! { + above = [display::shape::compound::rectangle::shape]; + (style: Style) { + let triangle_size = style.get_number(theme::triangle_size); + let color = style.get_color(theme::color); + let triangle = Triangle(triangle_size, triangle_size).rotate((PI / 2.0).radians()); + let triangle = triangle.fill(color); + let bg_size = Var::canvas_size(); + let bg = Rect(bg_size).fill(INVISIBLE_HOVER_COLOR); + (bg + triangle).into() + } + } +} + +mod spinner_icon { + use super::*; + + use std::f32::consts::FRAC_PI_3; + + ensogl::shape! { + above = [display::shape::compound::rectangle::shape]; + (style: Style) { + let color = style.get_color(theme::spinner::color); + let speed = style.get_number(theme::spinner::speed); + let width = Var::::from("input_size.x"); + let time = Var::::from("input_time"); + let unit = &width / 16.0; + let arc = RoundedArc(&unit * 5.0, (4.0 * FRAC_PI_3).radians(), &unit * 2.0); + let rotated_arc = arc.rotate(time * speed); + rotated_arc.fill(color).into() + } + } +} + + + +// =========== +// === FRP === +// =========== + +ensogl::define_endpoints_2! { + Input { + reset (), + } + Output { + pressed (), + } +} + + + +// ============= +// === Model === +// ============= + +#[derive(Debug, Clone, CloneRef)] +pub struct Model { + display_object: display::object::Instance, + play_icon: play_icon::View, + spinner_icon: spinner_icon::View, +} + +impl Model { + fn update_style(&self, style: &Style) { + let triangle_size = Vector2::new(style.triangle_size, style.triangle_size); + let padding = Vector2::new(style.padding_x, style.padding_y); + let size = triangle_size + 2.0 * padding; + self.play_icon.set_size(size); + self.spinner_icon.set_size(size); + self.play_icon.set_x(-size.x / 2.0 - style.offset); + self.spinner_icon.set_x(-size.x / 2.0 - style.offset); + } + + fn set_playing(&self, playing: bool) { + if playing { + self.display_object.remove_child(&self.play_icon); + self.display_object.add_child(&self.spinner_icon); + } else { + self.display_object.remove_child(&self.spinner_icon); + self.display_object.add_child(&self.play_icon); + } + } +} + +impl display::Object for Model { + fn display_object(&self) -> &display::object::Instance { + &self.display_object + } +} + + + +// =================== +// === Play Button === +// =================== + +impl component::Model for Model { + fn label() -> &'static str { + "PlayButton" + } + + fn new(_app: &Application) -> Self { + let display_object = display::object::Instance::new(); + let play_icon = play_icon::View::new(); + let spinner_icon = spinner_icon::View::new(); + + display_object.add_child(&play_icon); + + Self { display_object, play_icon, spinner_icon } + } +} + +impl component::Frp for Frp { + fn init( + network: &enso_frp::Network, + frp: &::Private, + _app: &Application, + model: &Model, + style_watch: &StyleWatchFrp, + ) { + let play_icon = &model.play_icon; + let input = &frp.input; + let output = &frp.output; + + let style = Style::from_theme(network, style_watch); + + frp::extend! { network + eval style.update ((style) model.update_style(style)); + + eval_ input.reset (model.set_playing(false)); + + output.pressed <+ play_icon.events_deprecated.mouse_down.constant(()); + + eval_ output.pressed (model.set_playing(true)); + } + style.init.emit(()); + } +} + +/// A button to execute the workflow in a fully enabled way within the current execution +/// environment. The button should be visible in any execution environment where one or more +/// contexts are disabled. +pub type PlayButton = component::ComponentView; diff --git a/lib/rust/ensogl/app/theme/hardcoded/src/lib.rs b/lib/rust/ensogl/app/theme/hardcoded/src/lib.rs index c2cfb6673942..12568043e1a3 100644 --- a/lib/rust/ensogl/app/theme/hardcoded/src/lib.rs +++ b/lib/rust/ensogl/app/theme/hardcoded/src/lib.rs @@ -110,6 +110,7 @@ macro_rules! _define_theme_wrapper_and_literals { pub mod $name { use super::*; use ensogl_core::application::Application; + use ensogl_core::data::color::Lch; use ensogl_core::data::color::Lcha; use ensogl_core::data::color::Rgb; use ensogl_core::data::color::Rgba; @@ -657,15 +658,22 @@ define_themes! { [light:0, dark:1] execution_mode_selector { background = Rgb::from_base_255(100.0, 181.0, 38.0), Rgb::from_base_255(100.0, 181.0, 38.0); divider = Rgba::black_with_alpha(0.12), Rgba::black_with_alpha(0.12); - triangle = Rgba::white_with_alpha(0.75), Rgba::white_with_alpha(0.75); - play_button_size = 10.0, 10.0; - play_button_offset = 15.0, 15.0; - play_button_padding = 10.0, 10.0; divider_offset = 32.5, 32.5; divider_padding = 10.0, 10.0; dropdown_width = 95.0, 95.0; height = 24.0, 24.0; menu_offset = 20.0, 20.0; + play_button { + color = Rgba::white_with_alpha(0.75), Rgba::white_with_alpha(0.75); + triangle_size = 10.0, 10.0; + offset = 15.0, 15.0; + padding_x = 5.0, 5.0; + padding_y = 7.0, 7.0; + spinner { + color = Lch(0.8, 0.0, 0.0), Lch(0.8, 0.0, 0.0); + speed = 0.003, 0.003; // Radians/ms + } + } } } widget { From 707cb9815a26e9d43500884966b2a8b2f83d622d Mon Sep 17 00:00:00 2001 From: Stijn Seghers Date: Thu, 13 Apr 2023 14:53:00 +0200 Subject: [PATCH 2/2] Fixups for PR comments --- app/gui/view/execution-mode-selector/src/play_button.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/gui/view/execution-mode-selector/src/play_button.rs b/app/gui/view/execution-mode-selector/src/play_button.rs index d94be9875f9b..406b2e6a6189 100644 --- a/app/gui/view/execution-mode-selector/src/play_button.rs +++ b/app/gui/view/execution-mode-selector/src/play_button.rs @@ -3,6 +3,7 @@ use ensogl::prelude::*; use enso_frp as frp; use ensogl::application::Application; +use ensogl::control::io::mouse; use ensogl::display; use ensogl::display::shape::StyleWatchFrp; use ensogl_derive_theme::FromTheme; @@ -166,7 +167,8 @@ impl component::Frp for Frp { eval_ input.reset (model.set_playing(false)); - output.pressed <+ play_icon.events_deprecated.mouse_down.constant(()); + let play_icon_mouse_down = play_icon.on_event::(); + output.pressed <+ play_icon_mouse_down.constant(()); eval_ output.pressed (model.set_playing(true)); }