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 shortened labels for entries with long module paths. When an option is
selected from the dropdown, the necessary module imports are inserted, selected from the dropdown, the necessary module imports are inserted,
eliminating the need for fully qualified names. 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 - [Added tooltips to icon buttons][6035] for improved usability. Users can now
quickly understand each button's function. quickly understand each button's function.
- [File associations are created on Windows and macOS][6077]. This allows - [File associations are created on Windows and macOS][6077]. This allows
@ -189,6 +191,7 @@
[4047]: https://github.com/enso-org/enso/pull/4047 [4047]: https://github.com/enso-org/enso/pull/4047
[4003]: https://github.com/enso-org/enso/pull/4003 [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/5895
[5895]: https://github.com/enso-org/enso/pull/6130
[6035]: https://github.com/enso-org/enso/pull/6035 [6035]: https://github.com/enso-org/enso/pull/6035
[6097]: https://github.com/enso-org/enso/pull/6097 [6097]: https://github.com/enso-org/enso/pull/6097

30
Cargo.lock generated
View File

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

View File

@ -379,10 +379,20 @@ impl Project {
let graph_controller = self.model.graph_controller.clone_ref(); let graph_controller = self.model.graph_controller.clone_ref();
self.init_analytics() self.init_analytics()
.init_execution_modes()
.setup_notification_handler() .setup_notification_handler()
.attach_frp_to_values_computed_notifications(graph_controller, values_computed) .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 { fn init_analytics(self) -> Self {
let network = &self.network; let network = &self.network;
let project = &self.model.view; 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-text-msdf = { path = "../../../lib/rust/ensogl/component/text/src/font/msdf" }
ensogl-hardcoded-theme = { path = "../../../lib/rust/ensogl/app/theme/hardcoded" } ensogl-hardcoded-theme = { path = "../../../lib/rust/ensogl/app/theme/hardcoded" }
ide-view-component-browser = { path = "component-browser" } ide-view-component-browser = { path = "component-browser" }
ide-view-execution-mode-selector = { path = "execution-mode-selector" }
ide-view-documentation = { path = "documentation" } ide-view-documentation = { path = "documentation" }
ide-view-graph-editor = { path = "graph-editor" } ide-view-graph-editor = { path = "graph-editor" }
span-tree = { path = "../language/span-tree" } span-tree = { path = "../language/span-tree" }

View File

@ -14,6 +14,7 @@ debug-scene-icons = { path = "icons" }
debug-scene-interface = { path = "interface" } debug-scene-interface = { path = "interface" }
debug-scene-text-grid-visualization = { path = "text-grid-visualization" } debug-scene-text-grid-visualization = { path = "text-grid-visualization" }
debug-scene-visualization = { path = "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. # 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] [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] [dependencies]
ast = { path = "../../../language/ast/impl" } ast = { path = "../../../language/ast/impl" }
parser = { path = "../../../language/parser" }
enso-frp = { path = "../../../../../lib/rust/frp" } enso-frp = { path = "../../../../../lib/rust/frp" }
ensogl = { path = "../../../../../lib/rust/ensogl" } ensogl = { path = "../../../../../lib/rust/ensogl" }
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" } ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-text-msdf = { path = "../../../../../lib/rust/ensogl/component/text/src/font/msdf" } ensogl-text-msdf = { path = "../../../../../lib/rust/ensogl/component/text/src/font/msdf" }
ide-view = { path = "../.." } ide-view = { path = "../.." }
ide-view-execution-mode-selector = { path = "../../execution-mode-selector" }
parser = { path = "../../../language/parser" }
span-tree = { path = "../../../language/span-tree" } span-tree = { path = "../../../language/span-tree" }
uuid = { version = "0.8", features = ["v4", "wasm-bindgen"] } uuid = { version = "0.8", features = ["v4", "wasm-bindgen"] }
wasm-bindgen = { workspace = true } wasm-bindgen = { workspace = true }

View File

@ -254,6 +254,14 @@ fn init(app: &Application) {
graph_editor.set_node_profiling_status(node3_id, node3_status); 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 tgt_type = dummy_type_generator.get_dummy_type();
let mut was_rendered = false; let mut was_rendered = false;
let mut loader_hidden = 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_component_list_panel_view as new_component_list_panel_view;
pub use debug_scene_documentation as documentation; 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_icons as icons;
pub use debug_scene_interface as interface; pub use debug_scene_interface as interface;
pub use debug_scene_text_grid_visualization as text_grid_visualization; 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-hardcoded-theme = { path = "../../../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-text-msdf = { path = "../../../../lib/rust/ensogl/component/text/src/font/msdf" } ensogl-text-msdf = { path = "../../../../lib/rust/ensogl/component/text/src/font/msdf" }
failure = { workspace = true } failure = { workspace = true }
ide-view-execution-mode-selector = { path = "../execution-mode-selector" }
indexmap = "1.9.2" indexmap = "1.9.2"
js-sys = { workspace = true } js-sys = { workspace = true }
nalgebra = { workspace = true } nalgebra = { workspace = true }

View File

@ -51,8 +51,9 @@ pub const HEIGHT: f32 = VERTICAL_MARGIN
+ VERTICAL_MARGIN; + VERTICAL_MARGIN;
// This should be as large as the shadow around the background. // 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 background = background::View::new();
let gap_width = default(); let gap_width = default();
scene.layers.panel.add(&background); scene.layers.panel_background.add(&background);
Self { Self {
display_object, display_object,
@ -267,6 +268,8 @@ impl BreadcrumbsModel {
self.project_name.set_x(gap_width); self.project_name.set_x(gap_width);
self.breadcrumbs_container.set_x(gap_width + project_name_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 width = gap_width + project_name_width + self.breadcrumbs_container_width();
let background_width = width + 2.0 * BACKGROUND_PADDING; 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 //! 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 //! 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::*; use crate::prelude::*;
@ -60,7 +61,9 @@ struct Model {
impl Model { impl Model {
pub fn new(app: &Application, registry: visualization::Registry) -> Self { pub fn new(app: &Application, registry: visualization::Registry) -> Self {
let selection_menu = drop_down_menu::DropDownMenu::new(app); 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); 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 } Self { selection_menu, registry }
} }

View File

@ -76,6 +76,7 @@ use ensogl_component::text;
use ensogl_component::text::buffer::selection::Selection; use ensogl_component::text::buffer::selection::Selection;
use ensogl_component::tooltip::Tooltip; use ensogl_component::tooltip::Tooltip;
use ensogl_hardcoded_theme as theme; 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 = const MACOS_TRAFFIC_LIGHTS_VERTICAL_CENTER: f32 =
-MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET - MACOS_TRAFFIC_LIGHTS_CONTENT_HEIGHT / 2.0; -MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET - MACOS_TRAFFIC_LIGHTS_CONTENT_HEIGHT / 2.0;
const MAX_ZOOM: f32 = 1.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 { fn traffic_lights_gap_width() -> f32 {
let platform_str = ARGS.groups.startup.options.platform.value.as_str(); 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 { if is_macos && !ARGS.groups.window.options.frame.value {
MACOS_TRAFFIC_LIGHTS_CONTENT_WIDTH + MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET MACOS_TRAFFIC_LIGHTS_CONTENT_WIDTH + MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET
} else { } else {
0.0 TOP_BAR_ITEM_MARGIN
} }
} }
// ================= // =================
// === SharedVec === // === SharedVec ===
// ================= // =================
@ -650,6 +652,9 @@ ensogl::define_endpoints_2! {
/// Drop an edge that is being dragged. /// Drop an edge that is being dragged.
drop_dragged_edge (), drop_dragged_edge (),
/// Set the execution modes available to the graph.
set_available_execution_modes (Rc<Vec<execution_mode_selector::ExecutionMode>>),
} }
Output { Output {
@ -749,6 +754,11 @@ ensogl::define_endpoints_2! {
default_x_gap_between_nodes (f32), default_x_gap_between_nodes (f32),
default_y_gap_between_nodes (f32), default_y_gap_between_nodes (f32),
min_x_spacing_for_new_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)] #[derive(Debug, Clone, CloneRef)]
#[allow(missing_docs)] // FIXME[everyone] Public-facing API should be documented. #[allow(missing_docs)] // FIXME[everyone] Public-facing API should be documented.
pub struct GraphEditorModel { pub struct GraphEditorModel {
pub display_object: display::object::Instance, pub display_object: display::object::Instance,
pub app: Application, pub app: Application,
pub breadcrumbs: component::Breadcrumbs, pub breadcrumbs: component::Breadcrumbs,
pub cursor: cursor::Cursor, pub cursor: cursor::Cursor,
pub nodes: Nodes, pub nodes: Nodes,
pub edges: Edges, pub edges: Edges,
pub vis_registry: visualization::Registry, pub vis_registry: visualization::Registry,
pub drop_manager: ensogl_drop_manager::Manager, pub drop_manager: ensogl_drop_manager::Manager,
pub navigator: Navigator, pub navigator: Navigator,
pub add_node_button: Rc<component::add_node_button::AddNodeButton>, pub add_node_button: Rc<component::add_node_button::AddNodeButton>,
tooltip: Tooltip, tooltip: Tooltip,
touch_state: TouchState, touch_state: TouchState,
visualisations: Visualisations, visualisations: Visualisations,
frp: Frp, frp: Frp,
profiling_statuses: profiling::Statuses, profiling_statuses: profiling::Statuses,
profiling_button: component::profiling::Button, profiling_button: component::profiling::Button,
styles_frp: StyleWatchFrp, styles_frp: StyleWatchFrp,
selection_controller: selection::Controller, selection_controller: selection::Controller,
execution_mode_selector: execution_mode_selector::ExecutionModeSelector,
} }
@ -1787,6 +1798,8 @@ impl GraphEditorModel {
let visualisations = default(); let visualisations = default();
let touch_state = TouchState::new(network, &scene.mouse.frp_deprecated); let touch_state = TouchState::new(network, &scene.mouse.frp_deprecated);
let breadcrumbs = component::Breadcrumbs::new(app.clone_ref()); let breadcrumbs = component::Breadcrumbs::new(app.clone_ref());
let execution_mode_selector = execution_mode_selector::ExecutionModeSelector::new(app);
let app = app.clone_ref(); let app = app.clone_ref();
let frp = frp.clone_ref(); let frp = frp.clone_ref();
let navigator = Navigator::new(scene, &scene.camera()); let navigator = Navigator::new(scene, &scene.camera());
@ -1824,17 +1837,19 @@ impl GraphEditorModel {
add_node_button, add_node_button,
styles_frp, styles_frp,
selection_controller, selection_controller,
execution_mode_selector,
} }
.init() .init()
} }
fn init(self) -> Self { fn init(self) -> Self {
self.add_child(&self.breadcrumbs);
let x_offset = MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET; 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_x(x_offset);
self.breadcrumbs.set_y(y_offset);
self.breadcrumbs.gap_width(traffic_lights_gap_width());
self.scene().add_child(&self.tooltip); self.scene().add_child(&self.tooltip);
self.add_child(&self.profiling_button); self.add_child(&self.profiling_button);
self.add_child(&*self.add_node_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 out = &frp.private.output;
let selection_controller = &model.selection_controller; 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 === // === Scene Navigation ===
// ======================== // ========================
@ -2778,17 +2787,9 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
// =================== // ===================
frp::extend! { network 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 === // === Debugging ===
eval_ inputs.debug_push_breadcrumb(model.breadcrumbs.debug_push_breadcrumb.emit(None)); eval_ inputs.debug_push_breadcrumb(model.breadcrumbs.debug_push_breadcrumb.emit(None));
eval_ inputs.debug_pop_breadcrumb (model.breadcrumbs.debug_pop_breadcrumb.emit(())); 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()); 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 === // === 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_component_browser as component_browser;
pub use ide_view_documentation as documentation; 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 ide_view_graph_editor as graph_editor;
pub use welcome_screen; pub use welcome_screen;

View File

@ -10,6 +10,7 @@ use enso_frp as frp;
use ensogl::application; use ensogl::application;
use ensogl::application::Application; use ensogl::application::Application;
use ensogl::display; use ensogl::display;
use ensogl::display::shape::StyleWatchFrp;
use std::rc::Rc; use std::rc::Rc;
@ -130,10 +131,18 @@ impl View {
let model = Model::new(app); let model = Model::new(app);
let frp = Frp::new(); let frp = Frp::new();
let network = &frp.network; 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 frp::extend! { network
init <- source::<()>();
eval_ frp.switch_view_to_project(model.switch_view_to_project()); eval_ frp.switch_view_to_project(model.switch_view_to_project());
eval_ frp.switch_view_to_welcome_screen(model.switch_view_to_welcome_screen()); 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 } Self { model, frp }
} }

View File

@ -442,6 +442,7 @@ define_themes! { [light:0, dark:1]
} }
} }
status_bar { status_bar {
offset_y = -30.0, -30.0;
text = text, text; text = text, text;
background = graph_editor::node::background , graph_editor::node::background; background = graph_editor::node::background , graph_editor::node::background;
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); 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 { widget {
list_view { list_view {

View File

@ -75,6 +75,7 @@ pub mod chooser_hover_area {
use super::*; use super::*;
ensogl_core::shape! { ensogl_core::shape! {
above = [arrow];
alignment = center; alignment = center;
(style: Style) { (style: Style) {
let width : Var<Pixels> = "input_size.x".into(); 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 === // === FRP ===
@ -100,6 +120,8 @@ ensogl_core::define_endpoints! {
hide_selection_menu (), hide_selection_menu (),
set_selected (Option<list_view::entry::Id>), set_selected (Option<list_view::entry::Id>),
set_menu_offset_y (f32), set_menu_offset_y (f32),
set_menu_alignment (Alignment),
set_label_alignment (Alignment),
} }
Output { Output {
menu_visible (bool), menu_visible (bool),
@ -123,8 +145,8 @@ pub type Entry = list_view::entry::Label;
struct Model { struct Model {
display_object: display::object::Instance, display_object: display::object::Instance,
icon: arrow::View, icon: arrow::View,
icon_overlay: chooser_hover_area::View, click_overlay: chooser_hover_area::View,
label: text::Text, label: text::Text,
selection_menu: list_view::ListView<Entry>, selection_menu: list_view::ListView<Entry>,
@ -137,17 +159,17 @@ impl Model {
fn new(app: &Application) -> Self { fn new(app: &Application) -> Self {
let display_object = display::object::Instance::new(); let display_object = display::object::Instance::new();
let icon = arrow::View::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 selection_menu = list_view::ListView::new(app);
let label = app.new_view::<text::Text>(); let label = app.new_view::<text::Text>();
let content = default(); 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 { fn init(self) -> Self {
self.add_child(&self.icon); self.add_child(&self.icon);
self.add_child(&self.icon_overlay); self.add_child(&self.click_overlay);
self.add_child(&self.label); self.add_child(&self.label);
// Clear default parent and hide again. // Clear default parent and hide again.
self.show_selection_menu(); self.show_selection_menu();
@ -249,7 +271,6 @@ impl DropDownMenu {
let menu_height = DEPRECATED_Animation::<f32>::new(network); let menu_height = DEPRECATED_Animation::<f32>::new(network);
eval menu_height.value ([model](height) { eval menu_height.value ([model](height) {
model.selection_menu.frp.resize.emit(Vector2::new(MENU_WIDTH,*height)); model.selection_menu.frp.resize.emit(Vector2::new(MENU_WIDTH,*height));
if *height <= 0.0 { if *height <= 0.0 {
@ -264,27 +285,42 @@ impl DropDownMenu {
model.icon.set_size(size-2.0*padding); 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); resize_menu <- all(model.selection_menu.size,frp.input.set_icon_size,
eval resize_menu (((menu_size,icon_size,menu_offset_y)) { 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. // 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); 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. // 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; let x_offset = match alignment {
model.selection_menu.set_x(offfset_y); 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); label_position <- all(model.label.frp.width,frp.input.set_icon_size,model.label.frp.height,
eval label_position (((text_width,icon_size)) { frp.input.set_label_alignment);
model.label.set_x(-text_width-icon_size.x/2.0); 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. // 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); overlay_size <- all(
eval overlay_size ([model]((text_width,icon_size)) { model.label.frp.width,
let size = Vector2::new(text_width + icon_size.x,icon_size.y); model.label.frp.height,
model.icon_overlay.set_size(size); frp.input.set_icon_size,
model.icon_overlay.set_x(-text_width/2.0); 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 === // === Menu Toggle Through Mouse Interaction ===
icon_hovered <- source::<bool>(); icon_hovered <- source::<bool>();
eval_ model.icon_overlay.events_deprecated.mouse_over ( icon_hovered.emit(true) ); eval_ model.click_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_out ( icon_hovered.emit(false) );
frp.source.icon_mouse_over <+ model.icon_overlay.events_deprecated.mouse_over; frp.source.icon_mouse_over <+ model.click_overlay.events_deprecated.mouse_over;
frp.source.icon_mouse_out <+ model.icon_overlay.events_deprecated.mouse_out; 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) ; visibility_on_mouse_down <- frp.source.menu_visible.sample(&icon_mouse_down) ;
eval visibility_on_mouse_down ([show_menu,hide_menu](is_visible){ eval visibility_on_mouse_down ([show_menu,hide_menu](is_visible){
@ -384,6 +420,28 @@ impl DropDownMenu {
self 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 { 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.entries.set_x(-view.size.x / 2.0 + entry_padding);
self.background.set_size(view.size + padding + shadow + margin); self.background.set_size(view.size + padding + shadow + margin);
self.overlay.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); 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) 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. /// Convert the color to `LinearRgba` representation.
pub fn into_linear(self) -> LinearRgba { pub fn into_linear(self) -> LinearRgba {
self.into() self.into()

View File

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

View File

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