Add dashboard button (#6474)

Closes #6399: Adding a button to the top bar in the project view to return to the dashboard.

Note that this just fires a DOM event (see #6399). To test it, you can add an event listener: `document.addEventListener('show-dashboard', console.log)`

https://user-images.githubusercontent.com/607786/235687669-ab04339f-0f07-439a-9cd3-59d96815edaa.mp4
This commit is contained in:
Stijn ("stain") Seghers 2023-05-23 16:23:23 +02:00 committed by GitHub
parent cbc3568cf3
commit 8e62ed60e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 503 additions and 405 deletions

View File

@ -172,6 +172,8 @@
it is now referenced via the `Main` namespace instead of the project
namespace,][6719] e.g. `Main.func1` instead of `MyProject.func1`. This makes
it robust against project name changes.
- [Added a button to return from an opened project back to the project
dashboard.][6474]
[6279]: https://github.com/enso-org/enso/pull/6279
[6421]: https://github.com/enso-org/enso/pull/6421
@ -181,6 +183,7 @@
[6663]: https://github.com/enso-org/enso/pull/6663
[6752]: https://github.com/enso-org/enso/pull/6752
[6719]: https://github.com/enso-org/enso/pull/6719
[6474]: https://github.com/enso-org/enso/pull/6474
#### EnsoGL (rendering engine)

1
Cargo.lock generated
View File

@ -3265,6 +3265,7 @@ version = "0.1.0"
dependencies = [
"enso-frp",
"ensogl-core",
"ensogl-hardcoded-theme",
]
[[package]]

View File

@ -276,7 +276,8 @@ pub fn register_views(app: &Application) {
app.views.register::<ensogl_component::list_view::ListView<PlaceholderEntryType>>();
if enso_config::ARGS.groups.startup.options.platform.value == "web" {
app.views.register::<ide_view::window_control_buttons::View>();
app.views
.register::<ide_view::project::project_view_top_bar::window_control_buttons::View>();
}
}

View File

@ -329,6 +329,16 @@ impl Model {
}
});
}
fn show_dashboard(&self) {
match enso_web::Event::new("show-dashboard") {
Ok(event) =>
if let Err(error) = enso_web::document.dispatch_event(&event) {
error!("Failed to dispatch event to show the dashboard. {error:?}");
},
Err(error) => error!("Failed to create event to show the dashboard. {error:?}"),
}
}
}
@ -429,6 +439,8 @@ impl Project {
view.set_read_only <+ view.toggle_read_only.map(f_!(model.toggle_read_only()));
eval graph_view.execution_environment((env) model.execution_environment_changed(*env));
eval_ graph_view.execution_environment_play_button_pressed( model.trigger_clean_live_execution());
eval_ view.go_to_dashboard_button_pressed (model.show_dashboard());
}
let graph_controller = self.model.graph_controller.clone_ref();

View File

@ -12,7 +12,8 @@ use ensogl::display;
use ensogl_component::toggle_button;
use ensogl_component::toggle_button::ColorableShape;
use ensogl_component::toggle_button::ToggleButton;
use ensogl_hardcoded_theme as theme;
use ensogl_hardcoded_theme::graph_editor::node::actions as theme;
// ==============
@ -135,13 +136,6 @@ impl Icons {
self.freeze.frp.set_read_only(read_only);
self.skip.frp.set_read_only(read_only);
}
fn set_color_scheme(&self, color_scheme: &toggle_button::ColorScheme) {
self.visibility.frp.set_color_scheme(color_scheme);
self.context_switch.set_color_scheme(color_scheme);
self.freeze.frp.set_color_scheme(color_scheme);
self.skip.frp.set_color_scheme(color_scheme);
}
}
impl display::Object for Icons {
@ -378,10 +372,10 @@ impl ActionBar {
pub fn new(app: &Application) -> Self {
let model = Rc::new(Model::new(app));
let frp = Frp::new();
ActionBar { frp, model }.init_frp()
ActionBar { frp, model }.init_frp(app)
}
fn init_frp(self) -> Self {
fn init_frp(self, app: &Application) -> Self {
let network = &self.frp.network;
let frp = &self.frp;
let model = &self.model;
@ -459,20 +453,11 @@ impl ActionBar {
);
}
use theme::graph_editor::node::actions;
let color_scheme = toggle_button::ColorScheme {
non_toggled: Some(model.styles.get_color(actions::button::non_toggled).into()),
toggled: Some(model.styles.get_color(actions::button::toggled).into()),
hovered: Some(model.styles.get_color(actions::button::hovered).into()),
..default()
};
let scene = &app.display.default_scene;
let context_switch_color_scheme = toggle_button::ColorScheme {
non_toggled: Some(model.styles.get_color(actions::context_switch::non_toggled).into()),
toggled: Some(model.styles.get_color(actions::context_switch::toggled).into()),
hovered: Some(model.styles.get_color(actions::context_switch::hovered).into()),
..default()
toggled: Some(model.styles.get_color(theme::context_switch::toggled).into()),
..toggle_button::default_color_scheme(&scene.style_sheet)
};
model.icons.set_color_scheme(&color_scheme);
model.icons.context_switch.set_color_scheme(&context_switch_color_scheme);
frp.show_on_hover.emit(true);

View File

@ -146,7 +146,8 @@ impl Button {
/// Constructs a new button for toggling the editor's view mode.
pub fn new(app: &Application) -> Button {
let scene = &app.display.default_scene;
let styles = StyleWatchFrp::new(&scene.style_sheet);
let style_sheet = &scene.style_sheet;
let styles = StyleWatchFrp::new(style_sheet);
let frp = Frp::new();
let network = &frp.network;
@ -154,7 +155,6 @@ impl Button {
.with_placement(tooltip::Placement::Left);
let button = ToggleButton::<icon::Shape>::new(app, tooltip_style);
scene.layers.panel.add(&button);
button.set_visibility(true);
button.frp.set_size(Vector2(32.0, 32.0));
frp::extend! { network
@ -179,21 +179,20 @@ impl Button {
// === Color ===
use ensogl_hardcoded_theme::graph_editor::profiling_button as button_theme;
let non_toggled_color = styles.get_color(button_theme::non_toggled);
let toggled_color = styles.get_color(button_theme::toggled);
let hovered_color = styles.get_color(button_theme::hovered);
let toggled_hovered_color = styles.get_color(button_theme::toggled_hovered);
init_color_scheme <- source::<()>();
button.set_color_scheme <+ all_with5(&non_toggled_color,&toggled_color,&hovered_color
,&toggled_hovered_color,&init_color_scheme
,|&non_toggled,&toggled,&hovered,&toggled_hovered,_|
button.set_color_scheme <+ all_with3(
&toggled_color,
&toggled_hovered_color,
&init_color_scheme,
f!([style_sheet] (&toggled, &toggled_hovered, _)
toggle_button::ColorScheme {
non_toggled : Some(non_toggled.into()),
toggled : Some(toggled.into()),
hovered : Some(hovered.into()),
toggled_hovered : Some(toggled_hovered.into()),
..default()
..toggle_button::default_color_scheme(&style_sheet)
}
)
);
}

View File

@ -41,12 +41,13 @@ pub fn init_frp(frp: &Frp, model: &GraphEditorModelWithNetwork) {
// === Layout ===
init <- source::<()>();
size_update <- all(init,selector.size,inputs.space_for_window_buttons);
eval size_update ([model]((_,size,gap_size)) {
size_update <- all(init, selector.size, inputs.graph_editor_top_bar_offset_x);
eval size_update ([model] ((_, size, graph_editor_top_bar_offset_x)) {
let y_offset = MACOS_TRAFFIC_LIGHTS_VERTICAL_CENTER;
let traffic_light_width = traffic_lights_gap_width();
let execution_environment_selector_x = gap_size.x + traffic_light_width;
let execution_environment_selector_x =
graph_editor_top_bar_offset_x + traffic_light_width;
model.execution_environment_selector.set_x(execution_environment_selector_x);
let breadcrumb_gap_width =
execution_environment_selector_x + size.x + TOP_BAR_ITEM_MARGIN;

View File

@ -111,7 +111,8 @@ const MACOS_TRAFFIC_LIGHTS_CONTENT_WIDTH: f32 = 52.0;
const MACOS_TRAFFIC_LIGHTS_CONTENT_HEIGHT: f32 = 12.0;
/// Horizontal and vertical offset between traffic lights and window border
const MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET: f32 = 13.0;
const MACOS_TRAFFIC_LIGHTS_VERTICAL_CENTER: f32 =
/// The vertical center of the traffic lights, relative to the window border.
pub const MACOS_TRAFFIC_LIGHTS_VERTICAL_CENTER: f32 =
-MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET - MACOS_TRAFFIC_LIGHTS_CONTENT_HEIGHT / 2.0;
const MAX_ZOOM: f32 = 1.0;
/// Space between items in the top bar.
@ -124,7 +125,7 @@ fn traffic_lights_gap_width() -> f32 {
if is_macos && !ARGS.groups.window.options.frame.value {
MACOS_TRAFFIC_LIGHTS_CONTENT_WIDTH + MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET
} else {
TOP_BAR_ITEM_MARGIN
0.0
}
}
@ -451,7 +452,10 @@ ensogl::define_endpoints_2! {
// === Layout ===
space_for_window_buttons (Vector2<f32>),
/// The offset in the x-axis at which the part of the top bar of the graph editor should
/// start.
graph_editor_top_bar_offset_x (f32),
// === Read-only mode ===

View File

@ -38,7 +38,6 @@ pub mod project_list;
pub mod root;
pub mod searcher;
pub mod status_bar;
pub mod window_control_buttons;
pub use ide_view_component_browser as component_browser;
pub use ide_view_documentation as documentation;

View File

@ -24,12 +24,19 @@ use ensogl::application::shortcut;
use ensogl::application::Application;
use ensogl::display;
use ensogl::system::web;
use ensogl::system::web::dom;
use ensogl::DEPRECATED_Animation;
use ensogl_component::text;
use ensogl_component::text::selection::Selection;
use ensogl_hardcoded_theme::Theme;
use ide_view_graph_editor::NodeSource;
use project_view_top_bar::ProjectViewTopBar;
// ==============
// === Export ===
// ==============
pub mod project_view_top_bar;
@ -127,6 +134,7 @@ ensogl::define_endpoints! {
fullscreen_visualization_shown (bool),
drop_files_enabled (bool),
debug_mode (bool),
go_to_dashboard_button_pressed (),
}
}
@ -138,22 +146,20 @@ ensogl::define_endpoints! {
#[derive(Clone, CloneRef, Debug)]
struct Model {
app: Application,
display_object: display::object::Instance,
/// These buttons are present only in a cloud environment.
window_control_buttons: Immutable<Option<crate::window_control_buttons::View>>,
graph_editor: Rc<GraphEditor>,
searcher: component_browser::View,
code_editor: code_editor::View,
fullscreen_vis: Rc<RefCell<Option<visualization::fullscreen::Panel>>>,
project_list: Rc<ProjectList>,
debug_mode_popup: debug_mode_popup::View,
popup: popup::View,
app: Application,
display_object: display::object::Instance,
project_view_top_bar: ProjectViewTopBar,
graph_editor: Rc<GraphEditor>,
searcher: component_browser::View,
code_editor: code_editor::View,
fullscreen_vis: Rc<RefCell<Option<visualization::fullscreen::Panel>>>,
project_list: Rc<ProjectList>,
debug_mode_popup: debug_mode_popup::View,
popup: popup::View,
}
impl Model {
fn new(app: &Application) -> Self {
let scene = &app.display.default_scene;
let display_object = display::object::Instance::new();
let searcher = app.new_view::<component_browser::View>();
let graph_editor = app.new_view::<GraphEditor>();
@ -161,14 +167,7 @@ impl Model {
let fullscreen_vis = default();
let debug_mode_popup = debug_mode_popup::View::new(app);
let popup = popup::View::new(app);
let runs_in_web = ARGS.groups.startup.options.platform.value == "web";
let window_control_buttons = runs_in_web.as_some_from(|| {
let window_control_buttons = app.new_view::<crate::window_control_buttons::View>();
display_object.add_child(&window_control_buttons);
scene.layers.panel.add(&window_control_buttons);
window_control_buttons
});
let window_control_buttons = Immutable(window_control_buttons);
let project_view_top_bar = ProjectViewTopBar::new(app);
let project_list = Rc::new(ProjectList::new(app));
display_object.add_child(&graph_editor);
@ -176,6 +175,7 @@ impl Model {
display_object.add_child(&searcher);
display_object.add_child(&debug_mode_popup);
display_object.add_child(&popup);
display_object.add_child(&project_view_top_bar);
display_object.remove_child(&searcher);
let app = app.clone_ref();
@ -183,7 +183,7 @@ impl Model {
Self {
app,
display_object,
window_control_buttons,
project_view_top_bar,
graph_editor,
searcher,
code_editor,
@ -263,12 +263,18 @@ impl Model {
}
}
fn on_dom_shape_changed(&self, shape: &dom::shape::Shape) {
// Top buttons must always stay in top-left corner.
if let Some(window_control_buttons) = &*self.window_control_buttons {
let pos = Vector2(-shape.width, shape.height) / 2.0;
window_control_buttons.set_xy(pos);
}
fn position_project_view_top_bar(
&self,
scene_shape: &display::scene::Shape,
project_view_top_bar_size: Vector2,
) {
let top_left = Vector2(-scene_shape.width, scene_shape.height) / 2.0;
let project_view_top_bar_origin = Vector2(
0.0,
crate::graph_editor::MACOS_TRAFFIC_LIGHTS_VERTICAL_CENTER
- project_view_top_bar_size.y / 2.0,
);
self.project_view_top_bar.set_xy(top_left + project_view_top_bar_origin);
}
fn on_close_clicked(&self) {
@ -368,6 +374,7 @@ impl View {
let frp = Frp::new();
let network = &frp.network;
let searcher = &model.searcher.frp();
let project_view_top_bar = &model.project_view_top_bar;
let graph = &model.graph_editor.frp;
let code_editor = &model.code_editor;
let project_list = &model.project_list;
@ -378,27 +385,36 @@ impl View {
model.set_style(theme);
let input_change_delay = frp::io::timer::Timeout::new(network);
if let Some(window_control_buttons) = &*model.window_control_buttons {
let initial_size = &window_control_buttons.size.value();
model.graph_editor.input.space_for_window_buttons(initial_size);
frp::extend! { network
graph.space_for_window_buttons <+ window_control_buttons.size;
eval_ window_control_buttons.close (model.on_close_clicked());
eval_ window_control_buttons.fullscreen (model.on_fullscreen_clicked());
}
}
let shape = scene.shape().clone_ref();
frp::extend! { network
init <- source::<()>();
shape <- all(shape, init)._0();
eval shape ((shape) model.on_dom_shape_changed(shape));
init <- source_();
eval_ frp.show_graph_editor(model.show_graph_editor());
eval_ frp.hide_graph_editor(model.hide_graph_editor());
// === Project View Top Bar ===
let window_control_buttons = &project_view_top_bar.window_control_buttons;
eval_ window_control_buttons.close (model.on_close_clicked());
eval_ window_control_buttons.fullscreen (model.on_fullscreen_clicked());
let go_to_dashboard_button = &project_view_top_bar.go_to_dashboard_button;
frp.source.go_to_dashboard_button_pressed <+
go_to_dashboard_button.is_pressed.on_true();
let project_view_top_bar_display_object = project_view_top_bar.display_object();
_eval <- all_with3(
&init,
scene.shape(),
&project_view_top_bar_display_object.on_resized,
f!((_, scene_shape, project_view_top_bar_size)
model.position_project_view_top_bar(scene_shape, *project_view_top_bar_size)
)
);
project_view_top_bar_width <-
project_view_top_bar_display_object.on_resized.map(|new_size| new_size.x);
graph.graph_editor_top_bar_offset_x <+ project_view_top_bar_width;
// === Read-only mode ===
graph.set_read_only <+ frp.set_read_only;

View File

@ -0,0 +1,72 @@
//! Defines a UI container for the window control buttons and the "go to dashboard" button. This is
//! merely here to make use of the auto-layout functionality.
use ensogl::prelude::*;
use enso_config::ARGS;
use ensogl::application::Application;
use ensogl::display;
mod go_to_dashboard_button;
pub mod window_control_buttons;
// =================
// === Constants ===
// =================
/// The gap in pixels between the various components of the project view top bar.
const GAP: f32 = 16.0;
/// The padding left of the project view top bar.
const PADDING_LEFT: f32 = 19.0;
// ============================
// === Project View Top Bar ===
// ============================
/// Defines a UI container for the window control buttons and the "go to dashboard" button. This is
/// merely here to make use of the auto-layout functionality.
#[derive(Clone, CloneRef, Debug)]
#[allow(missing_docs)]
pub struct ProjectViewTopBar {
root: display::object::Instance,
/// These buttons are only visible in a cloud environment.
pub window_control_buttons: window_control_buttons::View,
pub go_to_dashboard_button: go_to_dashboard_button::View,
}
impl ProjectViewTopBar {
/// Constructor.
pub fn new(app: &Application) -> Self {
let root = display::object::Instance::new_named("ProjectViewTopBar");
let window_control_buttons = app.new_view::<window_control_buttons::View>();
let go_to_dashboard_button = go_to_dashboard_button::View::new(app);
if ARGS.groups.startup.options.platform.value == "web" {
root.add_child(&window_control_buttons);
}
root.add_child(&go_to_dashboard_button);
root.use_auto_layout()
.set_gap((GAP, 0.0))
.set_padding_left(PADDING_LEFT)
// We use `GAP` as the right padding since it delimits the space to the part of the top
// bar that's defined in the graph editor.
.set_padding_right(GAP)
.set_children_alignment_center();
app.display.default_scene.layers.panel.add(&root);
Self { root, window_control_buttons, go_to_dashboard_button }
}
}
impl display::Object for ProjectViewTopBar {
fn display_object(&self) -> &display::object::Instance {
&self.root
}
}

View File

@ -0,0 +1,101 @@
//! Provides a button to switch back from the project view to the dashboard.
use crate::prelude::*;
use ensogl::application::tooltip;
use ensogl::application::Application;
use ensogl::display;
use ensogl::display::style;
use ensogl_component::toggle_button;
use ensogl_component::toggle_button::ToggleButton;
// =================
// === Constants ===
// =================
/// The width and height of the button.
pub const SIZE: f32 = 16.0;
// ============
// === Icon ===
// ============
/// Defines an icon for returning to the dashboard. It looks like a hamburger button.
mod icon {
use super::*;
use ensogl::data::color;
use ensogl_component::toggle_button::ColorableShape;
ensogl::shape! {
(style: Style, color_rgba: Vector4<f32>) {
let fill_color = Var::<color::Rgba>::from(color_rgba);
let width = Var::<Pixels>::from("input_size.x");
let height = Var::<Pixels>::from("input_size.y");
let unit = &width / SIZE;
let mid_bar = Rect((&unit * 12.0, &unit * 3.0)).corners_radius(&unit);
let top_bar = mid_bar.translate_y(&unit * -5.0);
let bottom_bar = mid_bar.translate_y(&unit * 5.0);
let all_bars = top_bar + mid_bar + bottom_bar;
let shape = all_bars.fill(fill_color);
let hover_area = Rect((&width, &height)).fill(INVISIBLE_HOVER_COLOR);
(shape + hover_area).into()
}
}
impl ColorableShape for Shape {
fn set_color(&self, color: color::Rgba) {
self.color_rgba.set(Vector4::new(color.red, color.green, color.blue, color.alpha));
}
}
}
// ============
// === View ===
// ============
/// Provides a button to switch back from the project view to the dashboard.
#[derive(Debug, Clone, CloneRef, Deref)]
pub struct View {
button: ToggleButton<icon::Shape>,
}
impl View {
/// Constructor.
pub fn new(app: &Application) -> Self {
let scene = &app.display.default_scene;
let tooltip_style = tooltip::Style::set_label("Dashboard".to_owned())
.with_placement(tooltip::Placement::Right);
let button = ToggleButton::<icon::Shape>::new(app, tooltip_style);
scene.layers.panel.add(&button);
button.set_color_scheme(Self::color_scheme(&scene.style_sheet));
button.set_size(Vector2(SIZE, SIZE));
Self { button }
}
fn color_scheme(style_sheet: &style::Sheet) -> toggle_button::ColorScheme {
let default_color_scheme = toggle_button::default_color_scheme(style_sheet);
toggle_button::ColorScheme {
// Make it look like a normal button (as opposed to a toggle button) by not having a
// toggled state visually.
toggled: default_color_scheme.non_toggled,
toggled_hovered: default_color_scheme.hovered,
..default_color_scheme
}
}
}
impl display::Object for View {
fn display_object(&self) -> &display::object::Instance {
self.button.display_object()
}
}

View File

@ -0,0 +1,199 @@
//! The component with buttons in the top left corner. See [[View]].
use ensogl::display::shape::*;
use ensogl::prelude::*;
use enso_frp as frp;
use ensogl::application;
use ensogl::application::Application;
use ensogl::display;
use ensogl::display::object::ObjectOps;
use ensogl::shape;
use ensogl_hardcoded_theme::application::window_control_buttons as theme;
// ==============
// === Export ===
// ==============
pub mod close;
pub mod fullscreen;
// ==============
// === Shapes ===
// ==============
mod shape {
use super::*;
shape! {
alignment = center;
(style: Style) {
Plane().fill(INVISIBLE_HOVER_COLOR).into()
}
}
}
// =============
// === Model ===
// =============
/// An internal model of Status Bar component
#[derive(Clone, CloneRef, Debug)]
pub struct Model {
app: Application,
display_object: display::object::Instance,
shape: shape::View,
close: close::View,
fullscreen: fullscreen::View,
}
impl Model {
/// Constructor.
pub fn new(app: &Application) -> Self {
let app = app.clone_ref();
let display_object = display::object::Instance::new_named("WindowControlButtons");
ensogl::shapes_order_dependencies! {
app.display.default_scene => {
shape -> close::shape;
shape -> fullscreen::shape;
}
};
let close = close::View::new(&app);
display_object.add_child(&close);
let fullscreen = fullscreen::View::new(&app);
display_object.add_child(&fullscreen);
let shape = shape::View::new();
display_object.add_child(&shape);
app.display.default_scene.layers.panel.add(&display_object);
Self { app, display_object, shape, close, fullscreen }
}
/// Updates positions of the buttons and sizes of the mouse area.
pub fn set_layout(&self, spacing: f32) {
let close_size = self.close.size.value();
let fullscreen_size = self.fullscreen.size.value();
let fullscreen_offset = Vector2(close_size.x + spacing, 0.0);
self.fullscreen.set_xy(fullscreen_offset);
let width = fullscreen_offset.x + fullscreen_size.x;
let height = max(close_size.y, fullscreen_size.y);
let size = Vector2(width, height);
self.shape.set_size(size);
self.display_object.set_size(size);
}
}
// ===========
// === FRP ===
// ===========
ensogl::define_endpoints! {
Input {
enabled (bool),
}
Output {
close(),
fullscreen(),
}
}
// ============
// === View ===
// ============
/// The Top Buttons Panel component.
///
/// The panel contains two buttons: one for closing IDE and one for toggling the fullscreen mode.
/// The panel is meant to be displayed only when IDE runs in a cloud environment.
#[derive(Clone, CloneRef, Debug)]
pub struct View {
#[allow(missing_docs)]
pub frp: Frp,
model: Model,
style: StyleWatchFrp,
}
impl View {
/// Constructor.
pub fn new(app: &Application) -> Self {
let frp = Frp::new();
let model = Model::new(app);
let network = &frp.network;
let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let radius = style.get_number(theme::radius);
let spacing = style.get_number(theme::spacing);
frp::extend! { network
// Layout
button_size <- radius.map(|&r| Vector2(2.0 * r, 2.0 * r));
model.close.set_size <+ button_size;
model.fullscreen.set_size <+ button_size;
button_resized <- any_(&model.close.size, &model.fullscreen.size);
_eval <- all_with(&button_resized, &spacing,
f!((_, spacing) model.set_layout(*spacing))
);
// Handle the panel-wide hover
mouse_near_buttons <- bool(
&model.shape.events_deprecated.mouse_out,
&model.shape.events_deprecated.mouse_over
);
mouse_on_any_buttton <- model.close.is_hovered.or(&model.fullscreen.is_hovered);
mouse_nearby <- mouse_near_buttons.or(&mouse_on_any_buttton);
model.close.mouse_nearby <+ mouse_nearby;
model.fullscreen.mouse_nearby <+ mouse_nearby;
// === Handle buttons' clicked events ===
frp.source.close <+ model.close.clicked;
frp.source.fullscreen <+ model.fullscreen.clicked;
}
model.set_layout(spacing.value());
Self { frp, model, style }
}
}
impl display::Object for View {
fn display_object(&self) -> &display::object::Instance {
&self.model.display_object
}
}
impl Deref for View {
type Target = Frp;
fn deref(&self) -> &Self::Target {
&self.frp
}
}
impl FrpNetworkProvider for View {
fn network(&self) -> &frp::Network {
&self.frp.network
}
}
impl application::View for View {
fn label() -> &'static str {
"TopButtons"
}
fn new(app: &Application) -> Self {
View::new(app)
}
fn app(&self) -> &Application {
&self.model.app
}
}

View File

@ -20,7 +20,6 @@ pub mod shape {
use super::*;
ensogl::shape! {
alignment = center;
(style: Style, background_color: Vector4<f32>, icon_color: Vector4<f32>) {
let size = Var::canvas_size();
let radius = Min::min(size.x(),size.y()) / 2.0;

View File

@ -20,7 +20,6 @@ pub use ensogl_hardcoded_theme::application::window_control_buttons::fullscreen
pub mod shape {
use super::*;
ensogl::shape! {
alignment = center;
(style: Style, background_color: Vector4<f32>, icon_color: Vector4<f32>) {
let size = Var::canvas_size();
let radius = Min::min(size.x(),size.y()) / 2.0;

View File

@ -1,301 +0,0 @@
//! The component with buttons in the top left corner. See [[View]].
use ensogl::display::shape::*;
use ensogl::prelude::*;
use enso_frp as frp;
use ensogl::application;
use ensogl::application::Application;
use ensogl::display;
use ensogl::display::object::ObjectOps;
use ensogl::shape;
use ensogl_hardcoded_theme::application::window_control_buttons as theme;
// ==============
// === Export ===
// ==============
pub mod close;
pub mod fullscreen;
// ==============
// === Shapes ===
// ==============
mod shape {
use super::*;
shape! {
alignment = center;
(style: Style) {
Plane().fill(INVISIBLE_HOVER_COLOR).into()
}
}
}
// ==============
// === Layout ===
// ==============
/// Information on how to layout shapes in the top buttons panel, as defined in the theme.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub struct LayoutParams<T> {
pub spacing: T,
pub padding_left: T,
pub padding_top: T,
pub padding_right: T,
pub padding_bottom: T,
}
impl Default for LayoutParams<f32> {
fn default() -> Self {
Self {
spacing: 8.0,
padding_left: 13.0,
padding_top: 13.0,
padding_right: 13.0,
padding_bottom: 13.0,
}
}
}
impl<T> LayoutParams<T> {
/// Applies a given function over all stored values and return layout with resulting values.
pub fn map<U>(&self, f: impl Fn(&T) -> U) -> LayoutParams<U> {
LayoutParams {
spacing: f(&self.spacing),
padding_left: f(&self.padding_left),
padding_top: f(&self.padding_top),
padding_right: f(&self.padding_right),
padding_bottom: f(&self.padding_bottom),
}
}
}
impl LayoutParams<frp::Sampler<f32>> {
/// Get layout from theme. Each layout parameter will be an frp sampler.
pub fn from_theme(style: &StyleWatchFrp) -> Self {
let default = LayoutParams::default();
let spacing = style.get_number_or(theme::spacing, default.spacing);
let padding_left = style.get_number_or(theme::padding::left, default.padding_left);
let padding_top = style.get_number_or(theme::padding::top, default.padding_top);
let padding_right = style.get_number_or(theme::padding::right, default.padding_right);
let padding_bottom = style.get_number_or(theme::padding::bottom, default.padding_bottom);
Self { spacing, padding_left, padding_top, padding_right, padding_bottom }
}
/// Take values from the parameters' samplers.
pub fn value(&self) -> LayoutParams<f32> {
self.map(|sampler| sampler.value())
}
/// Join all member frp streams into a single stream with aggregated values.
pub fn flatten(&self, network: &frp::Network) -> frp::Stream<LayoutParams<f32>> {
/// Helper method that puts back LayoutParams from its fields.
/// Be careful, as the arguments must be in the same order as they are in `all_with5`
/// invocation below.
// We intentionally take references to f32 for seamless usage in FRP.
#[allow(clippy::trivially_copy_pass_by_ref)]
fn to_layout(
spacing: &f32,
padding_left: &f32,
padding_top: &f32,
padding_right: &f32,
padding_bottom: &f32,
) -> LayoutParams<f32> {
let ret =
LayoutParams { spacing, padding_left, padding_top, padding_right, padding_bottom };
ret.map(|v| **v)
}
network.all_with5(
"TopButtonsLayoutStyle",
&self.spacing,
&self.padding_left,
&self.padding_top,
&self.padding_right,
&self.padding_bottom,
to_layout,
)
}
}
// =============
// === Model ===
// =============
/// An internal model of Status Bar component
#[derive(Clone, CloneRef, Debug)]
pub struct Model {
app: Application,
display_object: display::object::Instance,
shape: shape::View,
close: close::View,
fullscreen: fullscreen::View,
}
impl Model {
/// Constructor.
pub fn new(app: &Application) -> Self {
let app = app.clone_ref();
let display_object = display::object::Instance::new();
ensogl::shapes_order_dependencies! {
app.display.default_scene => {
shape -> close::shape;
shape -> fullscreen::shape;
}
};
let close = close::View::new(&app);
display_object.add_child(&close);
let fullscreen = fullscreen::View::new(&app);
display_object.add_child(&fullscreen);
let shape = shape::View::new();
display_object.add_child(&shape);
Self { app, display_object, shape, close, fullscreen }
}
/// Updates positions of the buttons and sizes of the mouse area.
/// Returns the new size of the panel (being also the size of mouse area).
pub fn set_layout(&self, layout: LayoutParams<f32>) -> Vector2 {
let LayoutParams { spacing, padding_left, padding_top, padding_right, padding_bottom } =
layout;
let close_size = self.close.size.value();
let fullscreen_size = self.fullscreen.size.value();
let padding_offset = Vector2(padding_left, -padding_top);
let origin_offset = |size: Vector2| Vector2(size.x / 2.0, -size.y / 2.0);
self.close.set_xy(padding_offset + origin_offset(close_size));
let fullscreen_x = padding_left + close_size.x + spacing;
self.fullscreen
.set_xy(Vector2(fullscreen_x, -padding_top) + origin_offset(fullscreen_size));
let width = fullscreen_x + fullscreen_size.x + padding_right;
let height = padding_top + max(close_size.y, fullscreen_size.y) + padding_bottom;
let size = Vector2(width, height);
self.shape.set_xy(Vector2(size.x, -size.y) / 2.0);
self.shape.set_size(size);
size
}
}
// ===========
// === FRP ===
// ===========
ensogl::define_endpoints! {
Input {
enabled (bool),
}
Output {
close(),
fullscreen(),
size(Vector2<f32>),
}
}
// ============
// === View ===
// ============
/// The Top Buttons Panel component.
///
/// The panel contains two buttons: one for closing IDE and one for toggling the fullscreen mode.
/// The panel is meant to be displayed only when IDE runs in a cloud environment.
#[derive(Clone, CloneRef, Debug)]
pub struct View {
frp: Frp,
model: Model,
style: StyleWatchFrp,
}
impl View {
/// Constructor.
pub fn new(app: &Application) -> Self {
let frp = Frp::new();
let model = Model::new(app);
let network = &frp.network;
let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let style_frp = LayoutParams::from_theme(&style);
let layout_style = style_frp.flatten(network);
let radius = style.get_number(theme::radius);
frp::extend! { network
// Layout
button_size <- radius.map(|&r| Vector2(2.0 * r, 2.0 * r));
model.close.set_size <+ button_size;
model.fullscreen.set_size <+ button_size;
button_resized <- any_(&model.close.size,&model.fullscreen.size);
layout_on_button_change <- sample(&layout_style,&button_resized);
need_relayout <- any(&layout_style,&layout_on_button_change);
frp.source.size <+ need_relayout.map(f!((layout) model.set_layout(*layout)));
// Handle the panel-wide hover
mouse_near_buttons <- bool(
&model.shape.events_deprecated.mouse_out,
&model.shape.events_deprecated.mouse_over
);
mouse_on_any_buttton <- model.close.is_hovered.or(&model.fullscreen.is_hovered);
mouse_nearby <- mouse_near_buttons.or(&mouse_on_any_buttton);
model.close.mouse_nearby <+ mouse_nearby;
model.fullscreen.mouse_nearby <+ mouse_nearby;
// === Handle buttons' clicked events ===
frp.source.close <+ model.close.clicked;
frp.source.fullscreen <+ model.fullscreen.clicked;
}
let initial_style = style_frp.value();
let initial_size = model.set_layout(initial_style);
frp.source.size.emit(initial_size);
Self { frp, model, style }
}
}
impl display::Object for View {
fn display_object(&self) -> &display::object::Instance {
&self.model.display_object
}
}
impl Deref for View {
type Target = Frp;
fn deref(&self) -> &Self::Target {
&self.frp
}
}
impl FrpNetworkProvider for View {
fn network(&self) -> &frp::Network {
&self.frp.network
}
}
impl application::View for View {
fn label() -> &'static str {
"TopButtons"
}
fn new(app: &Application) -> Self {
View::new(app)
}
fn app(&self) -> &Application {
&self.model.app
}
}

View File

@ -1,6 +1,6 @@
# Options intended to be common for all developers.
wasm-size-limit: 15.87 MiB
wasm-size-limit: 15.88 MiB
required-versions:
# NB. The Rust version is pinned in rust-toolchain.toml.

View File

@ -405,12 +405,6 @@ define_themes! { [light:0, dark:1]
window_control_buttons {
radius = 6.5, 6.5;
spacing = application::window_control_buttons::radius, application::window_control_buttons::radius;
padding {
left = 13.0, 13.0;
top = 13.0, 13.0;
right = 13.0, 13.0;
bottom = 13.0, 13.0;
}
close {
normal {
@ -526,15 +520,8 @@ define_themes! { [light:0, dark:1]
selection = Lcha(0.7,0.0,0.125,0.7) , Lcha(0.7,0.0,0.125,0.7);
}
actions {
button {
non_toggled = Lcha(0.0,0.0,0.0,0.3) , Lcha(0.4,0.0,0.0,1.0);
toggled = Lcha(0.0,0.0,0.0,0.7) , Lcha(1.0,0.0,0.0,0.7);
hovered = Lcha(0.0,0.0,0.0,0.45) , Lcha(1.0,0.0,0.0,0.7);
}
context_switch {
non_toggled = Lcha(0.0,0.0,0.0,0.3) , Lcha(0.4,0.0,0.0,1.0);
toggled = Lcha(0.58, 0.67, 0.0825, 1.0), Lcha(0.58, 0.67, 0.0825, 1.0);
hovered = Lcha(0.0,0.0,0.0,0.45) , Lcha(1.0,0.0,0.0,0.7);
}
}
vcs {
@ -620,11 +607,7 @@ define_themes! { [light:0, dark:1]
}
}
profiling_button {
non_toggled = graph_editor::node::actions::button::non_toggled
,graph_editor::node::actions::button::non_toggled;
toggled = Lcha(0.7,0.5,0.12,1.0) , Lcha(0.7,0.5,0.12,1.0);
hovered = graph_editor::node::actions::button::hovered
,graph_editor::node::actions::button::hovered;
toggled_hovered = Lcha(0.55,0.5,0.12,1.0) , Lcha(0.85,0.5,0.12,1.0);
}
add_node_button {
@ -726,6 +709,11 @@ define_themes! { [light:0, dark:1]
scale = 1.0, 1.0;
}
}
toggle_button {
non_toggled = Lcha(0.0,0.0,0.0,0.3), Lcha(0.4,0.0,0.0,1.0);
toggled = Lcha(0.0,0.0,0.0,0.7), Lcha(1.0,0.0,0.0,0.7);
hovered = Lcha(0.0,0.0,0.0,0.45), Lcha(1.0,0.0,0.0,0.7);
}
}

View File

@ -7,3 +7,4 @@ edition = "2021"
[dependencies]
enso-frp = { path = "../../../frp" }
ensogl-core = { path = "../../core" }
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }

View File

@ -18,6 +18,7 @@
#![warn(unused_import_braces)]
#![warn(unused_qualifications)]
use ensogl_core::display::shape::*;
use ensogl_core::prelude::*;
use enso_frp as frp;
@ -27,7 +28,9 @@ use ensogl_core::data::color;
use ensogl_core::display;
use ensogl_core::display::shape::system::Shape;
use ensogl_core::display::shape::system::ShapeWithDefaultableData;
use ensogl_core::display::style;
use ensogl_core::gui::component::ShapeView;
use ensogl_hardcoded_theme::component::toggle_button as theme;
@ -159,6 +162,18 @@ impl ColorScheme {
}
}
/// Return the default color scheme for the given style sheet.
pub fn default_color_scheme(style_sheet: &style::Sheet) -> ColorScheme {
let styles = StyleWatch::new(style_sheet);
ColorScheme {
non_toggled: Some(styles.get_color(theme::non_toggled).into()),
toggled: Some(styles.get_color(theme::toggled).into()),
hovered: Some(styles.get_color(theme::hovered).into()),
..default()
}
}
// === Getters ===
@ -283,7 +298,9 @@ impl<Shape: ColorableShape + 'static> ToggleButton<Shape> {
}
frp.set_state.emit(false);
color.target_alpha.emit(0.0);
frp.set_visibility.emit(true);
let color_scheme = default_color_scheme(&app.display.default_scene.style_sheet);
frp.set_color_scheme.emit(color_scheme);
self
}

View File

@ -532,6 +532,7 @@ mock_data! { Document => EventTarget
fn create_element(&self, local_name: &str) -> Result<Element, JsValue>;
fn get_element_by_id(&self, element_id: &str) -> Option<Element>;
fn create_text_node(&self, data: &str) -> Text;
fn dispatch_event(&self, event: &Event) -> Result<bool, JsValue>;
}
@ -575,6 +576,7 @@ impl AddEventListenerOptions {
// === Event ===
mock_data! { Event => Object
fn new(type_: &str) -> Result<Event, JsValue>;
fn prevent_default(&self);
fn stop_propagation(&self);
fn current_target(&self) -> Option<EventTarget>;