A drop-down that allows changing the execution mode. (#6130)

Implements https://github.com/enso-org/enso/issues/5931.

https://user-images.githubusercontent.com/1428930/228532453-2032b376-1aa5-4140-8331-be37e4e675d4.mp4

# Important Notes
Not functional yet, as it needs integration with the engine.
This commit is contained in:
Michael Mauderer 2023-04-18 12:26:17 +02:00 committed by GitHub
parent e5a96b9782
commit 64043323e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 631 additions and 70 deletions

View File

@ -126,6 +126,8 @@
shortened labels for entries with long module paths. When an option is
selected from the dropdown, the necessary module imports are inserted,
eliminating the need for fully qualified names.
- [The IDE now has a new UI element for selecting the execution mode of the
project][6130].
- [Added tooltips to icon buttons][6035] for improved usability. Users can now
quickly understand each button's function.
- [File associations are created on Windows and macOS][6077]. This allows
@ -189,6 +191,7 @@
[4047]: https://github.com/enso-org/enso/pull/4047
[4003]: https://github.com/enso-org/enso/pull/4003
[5895]: https://github.com/enso-org/enso/pull/5895
[5895]: https://github.com/enso-org/enso/pull/6130
[6035]: https://github.com/enso-org/enso/pull/6035
[6097]: https://github.com/enso-org/enso/pull/6097

30
Cargo.lock generated
View File

@ -1613,6 +1613,18 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "debug-scene-execution-mode-dropdown"
version = "0.1.0"
dependencies = [
"ensogl",
"ensogl-drop-down-menu",
"ensogl-hardcoded-theme",
"ensogl-list-view",
"ensogl-text-msdf",
"ide-view-execution-mode-selector",
]
[[package]]
name = "debug-scene-icons"
version = "0.1.0"
@ -1635,6 +1647,7 @@ dependencies = [
"ensogl-hardcoded-theme",
"ensogl-text-msdf",
"ide-view",
"ide-view-execution-mode-selector",
"parser",
"span-tree",
"uuid 0.8.2",
@ -2096,6 +2109,7 @@ version = "0.1.0"
dependencies = [
"debug-scene-component-list-panel-view",
"debug-scene-documentation",
"debug-scene-execution-mode-dropdown",
"debug-scene-icons",
"debug-scene-interface",
"debug-scene-text-grid-visualization",
@ -4273,6 +4287,7 @@ dependencies = [
"ensogl-text-msdf",
"ide-view-component-browser",
"ide-view-documentation",
"ide-view-execution-mode-selector",
"ide-view-graph-editor",
"js-sys",
"multi-map",
@ -4386,6 +4401,20 @@ dependencies = [
"web-sys",
]
[[package]]
name = "ide-view-execution-mode-selector"
version = "0.1.0"
dependencies = [
"enso-frp",
"enso-prelude",
"ensogl",
"ensogl-derive-theme",
"ensogl-drop-down-menu",
"ensogl-gui-component",
"ensogl-hardcoded-theme",
"ensogl-list-view",
]
[[package]]
name = "ide-view-graph-editor"
version = "0.1.0"
@ -4406,6 +4435,7 @@ dependencies = [
"ensogl-hardcoded-theme",
"ensogl-text-msdf",
"failure",
"ide-view-execution-mode-selector",
"indexmap",
"js-sys",
"nalgebra",

View File

@ -379,10 +379,20 @@ impl Project {
let graph_controller = self.model.graph_controller.clone_ref();
self.init_analytics()
.init_execution_modes()
.setup_notification_handler()
.attach_frp_to_values_computed_notifications(graph_controller, values_computed)
}
/// Initialises execution modes. Currently a dummy implementqation to be replaced during
/// 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()]);
graph.set_available_execution_modes(entries);
self
}
fn init_analytics(self) -> Self {
let network = &self.network;
let project = &self.model.view;

View File

@ -23,6 +23,7 @@ ensogl-text = { path = "../../../lib/rust/ensogl/component/text" }
ensogl-text-msdf = { path = "../../../lib/rust/ensogl/component/text/src/font/msdf" }
ensogl-hardcoded-theme = { path = "../../../lib/rust/ensogl/app/theme/hardcoded" }
ide-view-component-browser = { path = "component-browser" }
ide-view-execution-mode-selector = { path = "execution-mode-selector" }
ide-view-documentation = { path = "documentation" }
ide-view-graph-editor = { path = "graph-editor" }
span-tree = { path = "../language/span-tree" }

View File

@ -14,6 +14,7 @@ debug-scene-icons = { path = "icons" }
debug-scene-interface = { path = "interface" }
debug-scene-text-grid-visualization = { path = "text-grid-visualization" }
debug-scene-visualization = { path = "visualization" }
debug-scene-execution-mode-dropdown = { path = "execution-mode-dropdown" }
# Stop wasm-pack from running wasm-opt, because we run it from our build scripts in order to customize options.
[package.metadata.wasm-pack.profile.release]

View File

@ -0,0 +1,16 @@
[package]
name = "debug-scene-execution-mode-dropdown"
version = "0.1.0"
authors = ["Enso Team <contact@enso.org>"]
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
ensogl = { path = "../../../../../lib/rust/ensogl" }
ensogl-drop-down-menu = { path = "../../../../../lib/rust/ensogl/component/drop-down-menu" }
ensogl-list-view = { path = "../../../../../lib/rust/ensogl/component/list-view" }
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-text-msdf = { path = "../../../../../lib/rust/ensogl/component/text/src/font/msdf" }
ide-view-execution-mode-selector = { path = "../../execution-mode-selector" }

View File

@ -0,0 +1,66 @@
//! This is a visualization example scene which creates a sinusoidal graph.
// === Standard Linter Configuration ===
#![deny(non_ascii_idents)]
#![warn(unsafe_code)]
#![allow(clippy::bool_to_int_with_if)]
#![allow(clippy::let_and_return)]
// === Non-Standard Linter Configuration ===
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![warn(trivial_casts)]
#![warn(trivial_numeric_casts)]
#![warn(unused_import_braces)]
#![warn(unused_qualifications)]
use ensogl::prelude::*;
use ensogl::animation;
use ensogl::application::Application;
use ensogl_text_msdf::run_once_initialized;
use ide_view_execution_mode_selector as execution_mode_selector;
// ======================
// === Initialisation ===
// ======================
fn make_entries() -> execution_mode_selector::ExecutionModes {
Rc::new(vec!["development".to_string(), "production".to_string()])
}
fn init(app: &Application) {
let app = app.clone_ref();
let world = &app.display;
let _scene = &world.default_scene;
let execution_mode_selector = execution_mode_selector::ExecutionModeSelector::new(&app);
world.add_child(&execution_mode_selector);
execution_mode_selector.set_available_execution_modes(make_entries());
world
.on
.before_frame
.add(move |_time_info: animation::TimeInfo| {
let _keep_alive = &execution_mode_selector;
})
.forget();
}
// ===================
// === Entry Point ===
// ===================
/// Entry point for the demo scene.
#[entry_point]
#[allow(dead_code)]
pub fn main() {
run_once_initialized(|| {
let app = Application::new("root");
init(&app);
mem::forget(app);
});
}

View File

@ -9,12 +9,13 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
ast = { path = "../../../language/ast/impl" }
parser = { path = "../../../language/parser" }
enso-frp = { path = "../../../../../lib/rust/frp" }
ensogl = { path = "../../../../../lib/rust/ensogl" }
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-text-msdf = { path = "../../../../../lib/rust/ensogl/component/text/src/font/msdf" }
ide-view = { path = "../.." }
ide-view-execution-mode-selector = { path = "../../execution-mode-selector" }
parser = { path = "../../../language/parser" }
span-tree = { path = "../../../language/span-tree" }
uuid = { version = "0.8", features = ["v4", "wasm-bindgen"] }
wasm-bindgen = { workspace = true }

View File

@ -254,6 +254,14 @@ fn init(app: &Application) {
graph_editor.set_node_profiling_status(node3_id, node3_status);
// === Execution Modes ===
graph_editor
.set_available_execution_modes(vec!["development".to_string(), "production".to_string()]);
// === Rendering ===
// let tgt_type = dummy_type_generator.get_dummy_type();
let mut was_rendered = false;
let mut loader_hidden = false;

View File

@ -24,6 +24,7 @@
pub use debug_scene_component_list_panel_view as new_component_list_panel_view;
pub use debug_scene_documentation as documentation;
pub use debug_scene_execution_mode_dropdown as execution_mode_dropdown;
pub use debug_scene_icons as icons;
pub use debug_scene_interface as interface;
pub use debug_scene_text_grid_visualization as text_grid_visualization;

View File

@ -0,0 +1,18 @@
[package]
name = "ide-view-execution-mode-selector"
version = "0.1.0"
authors = ["Enso Team <contact@enso.org>"]
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
enso-frp = { path = "../../../../lib/rust/frp" }
enso-prelude = { path = "../../../../lib/rust/prelude" }
ensogl = { path = "../../../../lib/rust/ensogl" }
ensogl-derive-theme = { path = "../../../../lib/rust/ensogl/app/theme/derive" }
ensogl-drop-down-menu = { path = "../../../../lib/rust/ensogl/component/drop-down-menu" }
ensogl-gui-component = { path = "../../../../lib/rust/ensogl/component/gui" }
ensogl-hardcoded-theme = { path = "../../../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-list-view = { path = "../../../../lib/rust/ensogl/component/list-view" }

View File

@ -0,0 +1,274 @@
//! UI component that allows selecting a execution mode.
#![recursion_limit = "512"]
// === Features ===
#![feature(option_result_contains)]
#![feature(trait_alias)]
// === Standard Linter Configuration ===
#![deny(non_ascii_idents)]
#![warn(unsafe_code)]
#![allow(clippy::bool_to_int_with_if)]
#![allow(clippy::let_and_return)]
// === Non-Standard Linter Configuration ===
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![warn(trivial_casts)]
#![warn(trivial_numeric_casts)]
#![warn(unused_import_braces)]
#![warn(unused_qualifications)]
use enso_prelude::*;
use ensogl::prelude::*;
use enso_frp as frp;
use ensogl::application::Application;
use ensogl::data::color::Rgba;
use ensogl::display;
use ensogl::display::camera::Camera2d;
use ensogl::display::shape::StyleWatchFrp;
use ensogl_derive_theme::FromTheme;
use ensogl_gui_component::component;
use ensogl_hardcoded_theme::graph_editor::execution_mode_selector as theme;
// =============
// === Style ===
// ==============
/// Theme specification for the execution mode selector.
#[derive(Debug, Clone, Copy, Default, FromTheme)]
#[base_path = "ensogl_hardcoded_theme::graph_editor::execution_mode_selector"]
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,
}
impl Style {
fn overall_width(&self) -> f32 {
self.dropdown_width
+ 2.0 * self.divider_padding
+ self.play_button_size
+ self.play_button_padding
}
}
// ==============
// === 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 ===
// ===========
/// An identifier of a execution mode.
pub type ExecutionMode = String;
/// A list of execution modes.
pub type ExecutionModes = Rc<Vec<ExecutionMode>>;
ensogl::define_endpoints_2! {
Input {
set_available_execution_modes (ExecutionModes),
}
Output {
selected_execution_mode (ExecutionMode),
play_press(),
size (Vector2),
}
}
// =============
// === Model ===
// =============
/// The model of the execution mode selector.
#[derive(Debug, Clone, CloneRef)]
pub struct Model {
/// Main root object for the execution mode selector exposed for external positioning.
display_object: display::object::Instance,
/// Inner root that will be used for positioning the execution mode selector relative to the
/// window
inner_root: display::object::Instance,
background: display::shape::compound::rectangle::Rectangle,
divider: display::shape::compound::rectangle::Rectangle,
play_icon: play_icon::View,
dropdown: ensogl_drop_down_menu::DropDownMenu,
}
impl Model {
fn update_dropdown_style(&self, style: &Style) {
self.dropdown.set_menu_offset_y(style.menu_offset);
self.dropdown.set_x(style.overall_width() / 2.0 - style.divider_offset);
self.dropdown.set_label_color(Rgba::white());
self.dropdown.set_icon_size(Vector2::new(1.0, 1.0));
self.dropdown.set_menu_alignment(ensogl_drop_down_menu::Alignment::Right);
self.dropdown.set_label_alignment(ensogl_drop_down_menu::Alignment::Left);
}
fn update_background_style(&self, style: &Style) {
let width = style.overall_width();
let Style { height, background, .. } = *style;
let size = Vector2::new(width, height);
self.background.set_size(size);
self.background.set_xy(-size / 2.0);
self.background.set_corner_radius(height / 2.0);
self.background.set_color(background);
self.divider.set_size(Vector2::new(1.0, height));
self.divider.set_xy(Vector2::new(width / 2.0 - style.divider_offset, -height / 2.0));
self.divider.set_color(style.divider);
}
fn update_play_icon_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);
}
fn update_position(&self, style: &Style, camera: &Camera2d) {
let screen = camera.screen();
let x = -screen.width / 2.0 + style.overall_width() / 2.0;
let y = screen.height / 2.0 - style.height / 2.0;
self.inner_root.set_x(x.round());
self.inner_root.set_y(y.round());
}
fn set_entries(&self, entries: Rc<Vec<ExecutionMode>>) {
let provider = ensogl_list_view::entry::AnyModelProvider::from(entries.clone_ref());
self.dropdown.set_entries(provider);
self.dropdown.set_selected(0);
}
}
impl display::Object for Model {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
}
}
// =============================
// === ExecutionModeDropdown ===
// =============================
impl component::Model for Model {
fn label() -> &'static str {
"ExecutionModeDropdown"
}
fn new(app: &Application) -> Self {
let scene = &app.display.default_scene;
let display_object = display::object::Instance::new();
let inner_root = display::object::Instance::new();
let background = default();
let divider = default();
let play_icon = play_icon::View::new();
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(&background);
inner_root.add_child(&divider);
scene.layers.panel.add(&inner_root);
scene.layers.panel.add(&dropdown);
scene.layers.panel.add(&divider);
dropdown.set_label_layer(&scene.layers.panel_text);
dropdown.restore_shape_constraints(app);
Self { display_object, background, play_icon, dropdown, inner_root, divider }
}
}
impl component::Frp<Model> for Frp {
fn init(
network: &enso_frp::Network,
frp: &<Self as ensogl::application::frp::API>::Private,
app: &Application,
model: &Model,
style_watch: &StyleWatchFrp,
) {
let scene = &app.display.default_scene;
let camera = scene.camera();
let dropdown = &model.dropdown;
let play_icon = &model.play_icon;
let input = &frp.input;
let output = &frp.output;
let style = Style::from_theme(network, style_watch);
let style_update = style.update;
frp::extend! { network
// == Layout ==
let camera_changed = scene.frp.camera_changed.clone_ref();
update_position <- all(camera_changed, style_update)._1();
eval update_position ([model, camera] (style){
model.update_position(style, &camera);
});
eval style_update((style) {
model.update_dropdown_style(style);
model.update_background_style(style);
model.update_play_icon_style(style);
});
// == Inputs ==
eval input.set_available_execution_modes ((entries) model.set_entries(entries.clone()));
selected_id <- dropdown.frp.chosen_entry.unwrap();
selection <- all(input.set_available_execution_modes, selected_id);
selected_entry <- selection.map(|(entries, entry_id)| entries[*entry_id].clone());
output.selected_execution_mode <+ selected_entry;
// == Outputs ==
output.play_press <+ play_icon.events_deprecated.mouse_down.constant(());
output.size <+ style_update.map(|style| {
Vector2::new(style.overall_width(),style.height)
}).on_change();
}
style.init.emit(());
}
}
/// ExecutionModeSelector is a component that allows the user to select the execution mode of the
/// graph.
pub type ExecutionModeSelector = component::ComponentView<Model, Frp>;

View File

@ -24,6 +24,7 @@ ensogl-drop-manager = { path = "../../../../lib/rust/ensogl/component/drop-manag
ensogl-hardcoded-theme = { path = "../../../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-text-msdf = { path = "../../../../lib/rust/ensogl/component/text/src/font/msdf" }
failure = { workspace = true }
ide-view-execution-mode-selector = { path = "../execution-mode-selector" }
indexmap = "1.9.2"
js-sys = { workspace = true }
nalgebra = { workspace = true }

View File

@ -51,8 +51,9 @@ pub const HEIGHT: f32 = VERTICAL_MARGIN
+ VERTICAL_MARGIN;
// This should be as large as the shadow around the background.
const MAGIC_SHADOW_MARGIN: f32 = 40.0;
const MAGIC_SHADOW_MARGIN: f32 = 25.0;
/// Text offset to make the text appear more centered.
const TEXT_Y_OFFSET: f32 = 2.0;
// ========================
@ -207,7 +208,7 @@ impl BreadcrumbsModel {
let background = background::View::new();
let gap_width = default();
scene.layers.panel.add(&background);
scene.layers.panel_background.add(&background);
Self {
display_object,
@ -267,6 +268,8 @@ impl BreadcrumbsModel {
self.project_name.set_x(gap_width);
self.breadcrumbs_container.set_x(gap_width + project_name_width);
self.project_name.set_y(TEXT_Y_OFFSET);
self.breadcrumbs_container.set_y(TEXT_Y_OFFSET);
let width = gap_width + project_name_width + self.breadcrumbs_container_width();
let background_width = width + 2.0 * BACKGROUND_PADDING;

View File

@ -4,7 +4,8 @@
//!
//! TODO: If similar things are needed elsewhere, refactor this to a
//! Chooser<T:Eq+Display> (or similar) which would represent a `DropDownMenu` for specific owned
//! values.
//! values. It should also be refactored to use `drop_down_menu` from `ensogl_components` instead
//! of the old list view.
use crate::prelude::*;
@ -60,7 +61,9 @@ struct Model {
impl Model {
pub fn new(app: &Application, registry: visualization::Registry) -> Self {
let selection_menu = drop_down_menu::DropDownMenu::new(app);
selection_menu.set_label_alignment(drop_down_menu::Alignment::Right);
app.display.default_scene.layers.above_nodes.add(&selection_menu);
selection_menu.set_label_layer(&app.display.default_scene.layers.above_nodes_text);
Self { selection_menu, registry }
}

View File

@ -76,6 +76,7 @@ use ensogl_component::text;
use ensogl_component::text::buffer::selection::Selection;
use ensogl_component::tooltip::Tooltip;
use ensogl_hardcoded_theme as theme;
use ide_view_execution_mode_selector as execution_mode_selector;
// ===============
@ -109,6 +110,8 @@ const MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET: f32 = 13.0;
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.
const TOP_BAR_ITEM_MARGIN: f32 = 10.0;
fn traffic_lights_gap_width() -> f32 {
let platform_str = ARGS.groups.startup.options.platform.value.as_str();
@ -117,12 +120,11 @@ 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 {
0.0
TOP_BAR_ITEM_MARGIN
}
}
// =================
// === SharedVec ===
// =================
@ -650,6 +652,9 @@ ensogl::define_endpoints_2! {
/// Drop an edge that is being dragged.
drop_dragged_edge (),
/// Set the execution modes available to the graph.
set_available_execution_modes (Rc<Vec<execution_mode_selector::ExecutionMode>>),
}
Output {
@ -749,6 +754,11 @@ ensogl::define_endpoints_2! {
default_x_gap_between_nodes (f32),
default_y_gap_between_nodes (f32),
min_x_spacing_for_new_nodes (f32),
/// The selected execution mode.
execution_mode (execution_mode_selector::ExecutionMode),
/// A press of the execution mode selector play button.
execution_mode_play_button_pressed (),
}
}
@ -1752,24 +1762,25 @@ impl GraphEditorModelWithNetwork {
#[derive(Debug, Clone, CloneRef)]
#[allow(missing_docs)] // FIXME[everyone] Public-facing API should be documented.
pub struct GraphEditorModel {
pub display_object: display::object::Instance,
pub app: Application,
pub breadcrumbs: component::Breadcrumbs,
pub cursor: cursor::Cursor,
pub nodes: Nodes,
pub edges: Edges,
pub vis_registry: visualization::Registry,
pub drop_manager: ensogl_drop_manager::Manager,
pub navigator: Navigator,
pub add_node_button: Rc<component::add_node_button::AddNodeButton>,
tooltip: Tooltip,
touch_state: TouchState,
visualisations: Visualisations,
frp: Frp,
profiling_statuses: profiling::Statuses,
profiling_button: component::profiling::Button,
styles_frp: StyleWatchFrp,
selection_controller: selection::Controller,
pub display_object: display::object::Instance,
pub app: Application,
pub breadcrumbs: component::Breadcrumbs,
pub cursor: cursor::Cursor,
pub nodes: Nodes,
pub edges: Edges,
pub vis_registry: visualization::Registry,
pub drop_manager: ensogl_drop_manager::Manager,
pub navigator: Navigator,
pub add_node_button: Rc<component::add_node_button::AddNodeButton>,
tooltip: Tooltip,
touch_state: TouchState,
visualisations: Visualisations,
frp: Frp,
profiling_statuses: profiling::Statuses,
profiling_button: component::profiling::Button,
styles_frp: StyleWatchFrp,
selection_controller: selection::Controller,
execution_mode_selector: execution_mode_selector::ExecutionModeSelector,
}
@ -1787,6 +1798,8 @@ impl GraphEditorModel {
let visualisations = default();
let touch_state = TouchState::new(network, &scene.mouse.frp_deprecated);
let breadcrumbs = component::Breadcrumbs::new(app.clone_ref());
let execution_mode_selector = execution_mode_selector::ExecutionModeSelector::new(app);
let app = app.clone_ref();
let frp = frp.clone_ref();
let navigator = Navigator::new(scene, &scene.camera());
@ -1824,17 +1837,19 @@ impl GraphEditorModel {
add_node_button,
styles_frp,
selection_controller,
execution_mode_selector,
}
.init()
}
fn init(self) -> Self {
self.add_child(&self.breadcrumbs);
let x_offset = MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET;
let y_offset = MACOS_TRAFFIC_LIGHTS_VERTICAL_CENTER + component::breadcrumbs::HEIGHT / 2.0;
self.add_child(&self.execution_mode_selector);
self.add_child(&self.breadcrumbs);
self.breadcrumbs.set_x(x_offset);
self.breadcrumbs.set_y(y_offset);
self.breadcrumbs.gap_width(traffic_lights_gap_width());
self.scene().add_child(&self.tooltip);
self.add_child(&self.profiling_button);
self.add_child(&*self.add_node_button);
@ -2746,12 +2761,6 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
let out = &frp.private.output;
let selection_controller = &model.selection_controller;
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
// system (#795)
let styles = StyleWatch::new(&scene.style_sheet);
// ========================
// === Scene Navigation ===
// ========================
@ -2778,17 +2787,9 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
// ===================
frp::extend! { network
// === Layout ===
eval inputs.space_for_window_buttons([model](size) {
// The breadcrumbs apply their own spacing next to the gap, so we need to omit padding.
let width = size.x;
let path = theme::application::window_control_buttons::padding::right;
let right_padding = styles.get_number(path);
model.breadcrumbs.gap_width.emit(width - right_padding)
});
// === Debugging ===
eval_ inputs.debug_push_breadcrumb(model.breadcrumbs.debug_push_breadcrumb.emit(None));
eval_ inputs.debug_pop_breadcrumb (model.breadcrumbs.debug_pop_breadcrumb.emit(()));
}
@ -3875,6 +3876,35 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
frp.private.output.min_x_spacing_for_new_nodes.emit(min_x_spacing.value());
// ================================
// === Execution Mode Selection ===
// ================================
let execution_mode_selector = &model.execution_mode_selector;
frp::extend! { network
execution_mode_selector.set_available_execution_modes <+ frp.set_available_execution_modes;
out.execution_mode <+ execution_mode_selector.selected_execution_mode;
out.execution_mode_play_button_pressed <+ execution_mode_selector.play_press;
// === Layout ===
init <- source::<()>();
size_update <- all(init,execution_mode_selector.size,inputs.space_for_window_buttons);
eval size_update ([model]((_,size,gap_size)) {
let y_offset = MACOS_TRAFFIC_LIGHTS_VERTICAL_CENTER;
let traffic_light_width = traffic_lights_gap_width();
let execution_mode_selector_x = gap_size.x + traffic_light_width;
model.execution_mode_selector.set_x(execution_mode_selector_x);
let breadcrumb_gap_width = execution_mode_selector_x + size.x + TOP_BAR_ITEM_MARGIN;
model.breadcrumbs.gap_width(breadcrumb_gap_width);
model.execution_mode_selector.set_y(y_offset + size.y / 2.0);
model.breadcrumbs.set_y(y_offset + component::breadcrumbs::HEIGHT / 2.0);
});
}
init.emit(());
// ==================
// === Debug Mode ===

View File

@ -41,6 +41,7 @@ pub mod window_control_buttons;
pub use ide_view_component_browser as component_browser;
pub use ide_view_documentation as documentation;
pub use ide_view_execution_mode_selector as execution_mode_selector;
pub use ide_view_graph_editor as graph_editor;
pub use welcome_screen;

View File

@ -10,6 +10,7 @@ use enso_frp as frp;
use ensogl::application;
use ensogl::application::Application;
use ensogl::display;
use ensogl::display::shape::StyleWatchFrp;
use std::rc::Rc;
@ -130,10 +131,18 @@ impl View {
let model = Model::new(app);
let frp = Frp::new();
let network = &frp.network;
let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let offset_y = style.get_number(ensogl_hardcoded_theme::application::status_bar::offset_y);
frp::extend! { network
init <- source::<()>();
eval_ frp.switch_view_to_project(model.switch_view_to_project());
eval_ frp.switch_view_to_welcome_screen(model.switch_view_to_welcome_screen());
offset_y <- all(&init,&offset_y)._1();
eval offset_y ((offset_y) model.status_bar.set_y(*offset_y));
}
init.emit(());
Self { model, frp }
}

View File

@ -442,6 +442,7 @@ define_themes! { [light:0, dark:1]
}
}
status_bar {
offset_y = -30.0, -30.0;
text = text, text;
background = graph_editor::node::background , graph_editor::node::background;
background {
@ -653,6 +654,19 @@ define_themes! { [light:0, dark:1]
color = Rgba(0.0, 0.451, 0.859, 1.0), Rgba(0.0, 0.451, 0.859, 1.0);
}
}
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;
}
}
widget {
list_view {

View File

@ -75,6 +75,7 @@ pub mod chooser_hover_area {
use super::*;
ensogl_core::shape! {
above = [arrow];
alignment = center;
(style: Style) {
let width : Var<Pixels> = "input_size.x".into();
@ -87,6 +88,25 @@ pub mod chooser_hover_area {
}
// =================
// === Alignment ===
// =================
/// Indicates the alignment of the selection menu.
#[derive(Clone, Copy, Debug)]
pub enum Alignment {
/// Align the menu to the left side of the selection menu.
Left,
/// Align the menu to the right side of the selection menu.
Right,
}
impl Default for Alignment {
fn default() -> Self {
Self::Left
}
}
// ===========
// === FRP ===
@ -100,6 +120,8 @@ ensogl_core::define_endpoints! {
hide_selection_menu (),
set_selected (Option<list_view::entry::Id>),
set_menu_offset_y (f32),
set_menu_alignment (Alignment),
set_label_alignment (Alignment),
}
Output {
menu_visible (bool),
@ -123,8 +145,8 @@ pub type Entry = list_view::entry::Label;
struct Model {
display_object: display::object::Instance,
icon: arrow::View,
icon_overlay: chooser_hover_area::View,
icon: arrow::View,
click_overlay: chooser_hover_area::View,
label: text::Text,
selection_menu: list_view::ListView<Entry>,
@ -137,17 +159,17 @@ impl Model {
fn new(app: &Application) -> Self {
let display_object = display::object::Instance::new();
let icon = arrow::View::new();
let icon_overlay = chooser_hover_area::View::new();
let click_overlay = chooser_hover_area::View::new();
let selection_menu = list_view::ListView::new(app);
let label = app.new_view::<text::Text>();
let content = default();
Self { display_object, icon, icon_overlay, label, selection_menu, content }.init()
Self { display_object, icon, click_overlay, label, selection_menu, content }.init()
}
fn init(self) -> Self {
self.add_child(&self.icon);
self.add_child(&self.icon_overlay);
self.add_child(&self.click_overlay);
self.add_child(&self.label);
// Clear default parent and hide again.
self.show_selection_menu();
@ -249,7 +271,6 @@ impl DropDownMenu {
let menu_height = DEPRECATED_Animation::<f32>::new(network);
eval menu_height.value ([model](height) {
model.selection_menu.frp.resize.emit(Vector2::new(MENU_WIDTH,*height));
if *height <= 0.0 {
@ -264,27 +285,42 @@ impl DropDownMenu {
model.icon.set_size(size-2.0*padding);
});
resize_menu <- all(model.selection_menu.size,frp.input.set_icon_size,frp.input.set_menu_offset_y);
eval resize_menu (((menu_size,icon_size,menu_offset_y)) {
resize_menu <- all(model.selection_menu.size,frp.input.set_icon_size,
frp.input.set_menu_offset_y,frp.input.set_menu_alignment);
eval resize_menu (((menu_size,icon_size,menu_offset_y,alignment)) {
// Align the top of the menu to the bottom of the icon.
model.selection_menu.set_y(-menu_size.y/2.0-icon_size.y/2.0-menu_offset_y);
// Align the right of the menu to the right of the icon.
let offfset_y = -menu_size.x/2.0+icon_size.x/2.0-list_view::SHADOW_PX/2.0;
model.selection_menu.set_x(offfset_y);
let x_offset = match alignment {
Alignment::Left => -menu_size.x/2.0+icon_size.x/2.0-list_view::SHADOW_PX/2.0,
Alignment::Right => 0.0,
};
model.selection_menu.set_x(x_offset);
});
label_position <- all(model.label.frp.width,frp.input.set_icon_size);
eval label_position (((text_width,icon_size)) {
model.label.set_x(-text_width-icon_size.x/2.0);
label_position <- all(model.label.frp.width,frp.input.set_icon_size,model.label.frp.height,
frp.input.set_label_alignment);
eval label_position ([model]((text_width,icon_size,text_height,alignment)) {
let base_offset = match alignment {
Alignment::Left => -MENU_WIDTH/2.0+icon_size.x/2.0,
Alignment::Right => -text_width-icon_size.x/2.0,
};
model.label.set_x(base_offset);
// Adjust for text offset, so this appears more centered.
model.label.set_y(0.25 * icon_size.y);
model.label.set_y(0.5 * text_height);
});
overlay_size <- all(model.label.frp.width,frp.input.set_icon_size);
eval overlay_size ([model]((text_width,icon_size)) {
let size = Vector2::new(text_width + icon_size.x,icon_size.y);
model.icon_overlay.set_size(size);
model.icon_overlay.set_x(-text_width/2.0);
overlay_size <- all(
model.label.frp.width,
model.label.frp.height,
frp.input.set_icon_size,
frp.input.set_icon_padding);
eval overlay_size ([model]((text_width,text_height,icon_size,icon_padding)) {
let height = icon_size.y.max(*text_height);
let width = text_width + icon_size.x + icon_padding.x;
let size = Vector2::new(width,height);
model.click_overlay.set_size(size);
model.click_overlay.set_x(-width/2.0 + icon_size.x/2.0 - icon_padding.x);
});
@ -349,14 +385,14 @@ impl DropDownMenu {
// === Menu Toggle Through Mouse Interaction ===
icon_hovered <- source::<bool>();
eval_ model.icon_overlay.events_deprecated.mouse_over ( icon_hovered.emit(true) );
eval_ model.icon_overlay.events_deprecated.mouse_out ( icon_hovered.emit(false) );
eval_ model.click_overlay.events_deprecated.mouse_over ( icon_hovered.emit(true) );
eval_ model.click_overlay.events_deprecated.mouse_out ( icon_hovered.emit(false) );
frp.source.icon_mouse_over <+ model.icon_overlay.events_deprecated.mouse_over;
frp.source.icon_mouse_out <+ model.icon_overlay.events_deprecated.mouse_out;
frp.source.icon_mouse_over <+ model.click_overlay.events_deprecated.mouse_over;
frp.source.icon_mouse_out <+ model.click_overlay.events_deprecated.mouse_out;
let icon_mouse_down = model.icon_overlay.events_deprecated.mouse_down_primary.clone_ref();
let icon_mouse_down = model.click_overlay.events_deprecated.mouse_down_primary.clone_ref();
visibility_on_mouse_down <- frp.source.menu_visible.sample(&icon_mouse_down) ;
eval visibility_on_mouse_down ([show_menu,hide_menu](is_visible){
@ -384,6 +420,28 @@ impl DropDownMenu {
self
}
/// Set the label of the dropdown menu.
pub fn set_label_color(&self, color: color::Rgba) {
self.model.label.set_property_default(color);
}
/// Set the layer of all text labels.
pub fn set_label_layer(&self, layer: &display::scene::Layer) {
self.model.selection_menu.set_label_layer(layer);
self.model.label.add_to_scene_layer(layer);
}
/// Set the correct order for the shapes. To be sued after moving the component to a different
/// layer. Workaround for #6241.
pub fn restore_shape_constraints(&self, app: &Application) {
let scene = &app.display.default_scene;
shapes_order_dependencies! {
scene => {
arrow -> chooser_hover_area;
}
}
}
}
impl display::Object for DropDownMenu {

View File

@ -240,7 +240,7 @@ impl<E: Entry> Model<E> {
self.entries.set_x(-view.size.x / 2.0 + entry_padding);
self.background.set_size(view.size + padding + shadow + margin);
self.overlay.set_size(view.size + padding + shadow + margin);
self.scrolled_area.set_y(view.size.y / 2.0 - view.position_y);
self.scrolled_area.set_y(view.size.y / 2.0 - view.position_y + SHAPE_MARGIN / 2.0);
self.entries.update_entries(visible_entries, entry_width, style_prefix);
}

View File

@ -494,6 +494,15 @@ impl Rgba {
Self::new(0.0, 0.0, 0.0, 0.0)
}
/// Constructor.
pub fn black_with_alpha(alpha: f32) -> Self {
Self::new(0.0, 0.0, 0.0, alpha)
}
/// Constructor.
pub fn white_with_alpha(alpha: f32) -> Self {
Self::new(1.0, 1.0, 1.0, alpha)
}
/// Convert the color to `LinearRgba` representation.
pub fn into_linear(self) -> LinearRgba {
self.into()

View File

@ -601,6 +601,7 @@ pub struct HardcodedLayers {
pub above_nodes_text: Layer,
/// Layer containing all panels with fixed position (not moving with the panned scene)
/// like status bar, breadcrumbs or similar.
pub panel_background: Layer,
pub panel: Layer,
pub panel_text: Layer,
pub node_searcher: Layer,
@ -640,6 +641,7 @@ impl HardcodedLayers {
let label = root.create_sublayer("label");
let above_nodes = root.create_sublayer("above_nodes");
let above_nodes_text = root.create_sublayer("above_nodes_text");
let panel_background = root.create_sublayer_with_camera("panel_background", &panel_cam);
let panel = root.create_sublayer_with_camera("panel", &panel_cam);
let panel_text = root.create_sublayer_with_camera("panel_text", &panel_cam);
let node_searcher = root.create_sublayer_with_camera("node_searcher", &node_searcher_cam);
@ -662,6 +664,7 @@ impl HardcodedLayers {
label,
above_nodes,
above_nodes_text,
panel_background,
panel,
panel_text,
node_searcher,

View File

@ -21,7 +21,8 @@ pub use shape::Shape;
// === Shape ===
// =============
mod shape {
/// Shape definition.
pub mod shape {
use super::*;
crate::shape! {
(