Resizing visualizations (#7164)

Closes #7047

Adds an ability to resize visualizations by dragging a special (invisible) shape along the bottom and right borders of visualizations.

- Visualizations are aligned to the left border of the node now.
- Default visualization width now equals to the node's width (default height is the same)
- Changing the width of the node also changes visualization width, but only if no manual drag-resizing was applied
- Visualization size is preserved when reopening visualization (but it is not saved in project metadata)
- No visual indication that resizing is possible exist, it will be implemented in #7049



https://github.com/enso-org/enso/assets/6566674/2f2525e8-cf10-4c92-953a-b69eb97a954a
This commit is contained in:
Ilya Bogdanov 2023-07-09 18:18:45 +03:00 committed by GitHub
parent 9dcf48a3e0
commit 8020916f58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 279 additions and 148 deletions

View File

@ -193,6 +193,10 @@
in the text. This is fixed now.
- [Added prototype AI Searcher that can be used to create new nodes from
natural language input][7146]
- [Allow visualization resizing][7164]. Now the user can adjust the
visualization size by dragging its right and bottom borders. Visualization
width also follows the node's width, and visualizations are aligned to the
left side of the node.
[5910]: https://github.com/enso-org/enso/pull/5910
[6279]: https://github.com/enso-org/enso/pull/6279
@ -211,6 +215,7 @@
[7028]: https://github.com/enso-org/enso/pull/7028
[7014]: https://github.com/enso-org/enso/pull/7014
[7146]: https://github.com/enso-org/enso/pull/7146
[7164]: https://github.com/enso-org/enso/pull/7164
#### EnsoGL (rendering engine)

1
Cargo.lock generated
View File

@ -4416,6 +4416,7 @@ dependencies = [
"enso-text",
"ensogl",
"ensogl-component",
"ensogl-derive-theme",
"ensogl-drop-manager",
"ensogl-hardcoded-theme",
"ensogl-text-msdf",

View File

@ -71,7 +71,7 @@ impl component::Model for Model {
let scene = &app.display.default_scene;
shapes_order_dependencies! {
scene => {
component_list_panel::background -> documentation::overlay;
component_list_panel::background -> display::shape::compound::rectangle;
}
}

View File

@ -19,6 +19,7 @@
#![warn(unused_import_braces)]
#![warn(unused_qualifications)]
use ensogl::display::shape::*;
use ensogl::prelude::*;
use ensogl::system::web::traits::*;
@ -31,7 +32,6 @@ use ensogl::data::color;
use ensogl::display;
use ensogl::display::scene::Scene;
use ensogl::display::shape::primitive::StyleWatch;
use ensogl::display::shape::StyleWatchFrp;
use ensogl::display::DomSymbol;
use ensogl::system::web;
use ensogl::Animation;
@ -48,8 +48,6 @@ use ide_view_graph_editor as graph_editor;
pub mod html;
pub use visualization::container::overlay;
// =================
@ -91,7 +89,7 @@ pub struct Model {
inner_dom: DomSymbol,
/// The purpose of this overlay is stop propagating mouse events under the documentation panel
/// to EnsoGL shapes, and pass them to the DOM instead.
overlay: overlay::View,
overlay: Rectangle,
display_object: display::object::Instance,
event_handlers: Rc<RefCell<Vec<web::EventListenerHandle>>>,
}
@ -104,7 +102,9 @@ impl Model {
let outer_dom = DomSymbol::new(&outer_div);
let inner_div = web::document.create_div_or_panic();
let inner_dom = DomSymbol::new(&inner_div);
let overlay = overlay::View::new();
let overlay = Rectangle::new().build(|r| {
r.set_color(INVISIBLE_HOVER_COLOR);
});
let caption_div = web::document.create_div_or_panic();
let caption_dom = DomSymbol::new(&caption_div);
caption_dom.set_inner_html(&html::caption_html());
@ -125,7 +125,6 @@ impl Model {
inner_dom.dom().set_style_or_warn("overflow-x", "auto");
inner_dom.dom().set_style_or_warn("pointer-events", "auto");
overlay.roundness.set(1.0);
display_object.add_child(&outer_dom);
outer_dom.add_child(&caption_dom);
outer_dom.add_child(&inner_dom);
@ -161,6 +160,7 @@ impl Model {
/// Set size of the documentation view.
fn set_size(&self, size: Vector2) {
self.overlay.set_size(size);
self.overlay.set_xy(Vector2(-size.x / 2.0, -size.y / 2.0));
self.outer_dom.set_dom_size(Vector2(size.x, size.y));
}
@ -204,7 +204,7 @@ impl Model {
fn update_style(&self, style: Style) {
self.set_size(Vector2(style.width, style.height));
self.overlay.radius.set(style.corner_radius);
self.overlay.set_corner_radius(style.corner_radius);
self.outer_dom.set_style_or_warn("border-radius", format!("{}px", style.corner_radius));
self.inner_dom.set_style_or_warn("border-radius", format!("{}px", style.corner_radius));
let bg_color = style.background.to_javascript_string();

View File

@ -21,6 +21,7 @@ enso-shapely = { path = "../../../../lib/rust/shapely" }
enso-text = { path = "../../../../lib/rust/text" }
ensogl = { path = "../../../../lib/rust/ensogl" }
ensogl-component = { path = "../../../../lib/rust/ensogl/component" }
ensogl-derive-theme = { path = "../../../../lib/rust/ensogl/app/theme/derive" }
ensogl-drop-manager = { path = "../../../../lib/rust/ensogl/component/drop-manager" }
ensogl-hardcoded-theme = { path = "../../../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-text-msdf = { path = "../../../../lib/rust/ensogl/component/text/src/font/msdf" }

View File

@ -71,9 +71,10 @@ pub const RADIUS: f32 = 14.0;
pub const COMMENT_MARGIN: f32 = 10.0;
const INFINITE: f32 = 99999.0;
const ERROR_VISUALIZATION_SIZE: (f32, f32) = visualization::container::DEFAULT_SIZE;
const ERROR_VISUALIZATION_SIZE: Vector2 = visualization::container::DEFAULT_SIZE;
const VISUALIZATION_OFFSET_Y: f32 = -120.0;
const VISUALIZATION_OFFSET_Y: f32 = -20.0;
const VISUALIZATION_OFFSET: Vector2 = Vector2(0.0, VISUALIZATION_OFFSET_Y);
const ENABLE_VIS_PREVIEW: bool = false;
const VIS_PREVIEW_ONSET_MS: f32 = 4000.0;
@ -410,8 +411,7 @@ impl NodeModel {
display_object.add_child(&input);
let error_visualization = error::Container::new(app);
let (x, y) = ERROR_VISUALIZATION_SIZE;
error_visualization.frp.set_size.emit(Vector2(x, y));
error_visualization.frp.set_size.emit(ERROR_VISUALIZATION_SIZE);
let action_bar = action_bar::ActionBar::new(app);
display_object.add_child(&action_bar);
@ -545,9 +545,9 @@ impl NodeModel {
.set_x(x_offset_to_node_center + width / 2.0 + CORNER_RADIUS + action_bar_width / 2.0);
self.action_bar.frp.set_size(Vector2::new(action_bar_width, ACTION_BAR_HEIGHT));
let visualization_offset = visualization_offset(width);
self.error_visualization.set_xy(visualization_offset);
self.visualization.set_xy(visualization_offset);
self.error_visualization.set_xy(VISUALIZATION_OFFSET);
self.visualization.set_xy(VISUALIZATION_OFFSET);
self.visualization.frp.set_width(width);
size
}
@ -955,12 +955,6 @@ fn x_offset_to_node_center(node_width: f32) -> f32 {
node_width / 2.0
}
/// Calculate a position where to render the [`visualization::Container`] of a node, relative to
/// the node's origin.
fn visualization_offset(node_width: f32) -> Vector2 {
Vector2(x_offset_to_node_center(node_width), VISUALIZATION_OFFSET_Y)
}
#[profile(Debug)]
fn bounding_box(
node_position: Vector2,
@ -971,8 +965,7 @@ fn bounding_box(
let node_bbox_pos = node_position + Vector2(x_offset_to_node_center, 0.0) - node_size / 2.0;
let node_bbox = BoundingBox::from_position_and_size(node_bbox_pos, node_size);
if let Some(visualization_size) = visualization_size {
let visualization_offset = visualization_offset(node_size.x);
let visualization_pos = node_position + visualization_offset;
let visualization_pos = node_position + VISUALIZATION_OFFSET;
let visualization_bbox_pos = visualization_pos - visualization_size / 2.0;
let visualization_bbox =
BoundingBox::from_position_and_size(visualization_bbox_pos, visualization_size);

View File

@ -67,7 +67,7 @@ impl Error {
// === Constants ===
const SIZE: (f32, f32) = super::super::visualization::container::DEFAULT_SIZE;
const SIZE: Vector2 = super::super::visualization::container::DEFAULT_SIZE;
const Z_INDEX: usize = 1;
const BORDER_RADIUS: f32 = 14.0;
@ -121,7 +121,7 @@ impl Container {
let div = web::document.create_div_or_panic();
let background_dom = DomSymbol::new(&div);
let (width, height) = SIZE;
let (width, height) = (SIZE.x, SIZE.y);
let width = format!("{width}.px");
let height = format!("{height}.px");
let z_index = Z_INDEX.to_string();

View File

@ -28,15 +28,20 @@ use crate::visualization;
use action_bar::ActionBar;
use enso_frp as frp;
use ensogl::application::Application;
use ensogl::control::io::mouse;
use ensogl::data::color::Rgba;
use ensogl::display;
use ensogl::display::scene;
use ensogl::display::scene::Scene;
use ensogl::display::scene::Shape;
use ensogl::display::shape::StyleWatchFrp;
use ensogl::display::DomScene;
use ensogl::display::DomSymbol;
use ensogl::system::web;
use ensogl::Animation;
use ensogl_component::shadow;
use ensogl_derive_theme::FromTheme;
use ensogl_hardcoded_theme::graph_editor::visualization as theme;
// ==============
@ -54,78 +59,32 @@ pub mod visualization_chooser;
// =================
/// Default width and height of the visualization container.
pub const DEFAULT_SIZE: (f32, f32) = (200.0, 200.0);
const PADDING: f32 = 20.0;
pub const DEFAULT_SIZE: Vector2 = Vector2(200.0, 200.0);
/// Minimal allowed size of the visualization container.
const MIN_SIZE: Vector2 = Vector2(200.0, 200.0);
/// Maximal allowed size of the visualization container, as percentage of the screen size.
const MAX_PORTION_OF_SCREEN: Vector2 = Vector2(0.8, 0.8);
const CORNER_RADIUS: f32 = super::super::node::CORNER_RADIUS;
const ACTION_BAR_HEIGHT: f32 = 2.0 * CORNER_RADIUS;
/// Whether to reset the manually-resized visualization on opening or not.
const RESET_RESIZING_ON_OPEN: bool = false;
// =============
// === Shape ===
// =============
// ======================
// === SelectionStyle ===
// ======================
/// Container overlay shape definition. Used to capture events over the visualization within the
/// container.
pub mod overlay {
use super::*;
ensogl::shape! {
alignment = center;
(style: Style, radius: f32, roundness: f32, selection: f32) {
let width = Var::<Pixels>::from("input_size.x");
let height = Var::<Pixels>::from("input_size.y");
let radius = 1.px() * &radius;
let corner_radius = &radius * &roundness;
let color_overlay = color::Rgba::new(1.0,0.0,0.0,0.000_000_1);
let overlay = Rect((&width,&height)).corners_radius(corner_radius);
let overlay = overlay.fill(color_overlay);
let out = overlay;
out.into()
}
}
}
/// Container's background, including selection.
// TODO[ao] : Currently it does not contain the real background, which is rendered in HTML instead.
// This should be fixed in https://github.com/enso-org/ide/issues/526
pub mod background {
use super::*;
use ensogl_hardcoded_theme::graph_editor::visualization as theme;
ensogl::shape! {
alignment = center;
(style:Style, radius:f32, roundness:f32, selection:f32) {
let width = Var::<Pixels>::from("input_size.x");
let height = Var::<Pixels>::from("input_size.y");
let width = width - PADDING.px() * 2.0;
let height = height - PADDING.px() * 2.0;
let radius = 1.px() * &radius;
let corner_radius = &radius * &roundness;
// === Selection ===
let sel_color = style.get_color(theme::selection);
let sel_size = style.get_number(theme::selection::size);
let sel_offset = style.get_number(theme::selection::offset);
let sel_width = &width - 1.px() + &sel_offset.px() * 2.0 * &selection;
let sel_height = &height - 1.px() + &sel_offset.px() * 2.0 * &selection;
let sel_radius = &corner_radius + &sel_offset.px();
let select = Rect((&sel_width,&sel_height)).corners_radius(sel_radius);
let sel2_width = &width - 2.px() + &(sel_size + sel_offset).px() * 2.0 * &selection;
let sel2_height = &height - 2.px() + &(sel_size + sel_offset).px() * 2.0 * &selection;
let sel2_radius = &corner_radius + &sel_offset.px() + &sel_size.px() * &selection;
let select2 = Rect((&sel2_width,&sel2_height)).corners_radius(sel2_radius);
let select = select2 - select;
let select = select.fill(sel_color);
select.into()
}
}
/// The style parameters of the selected node highlight.
///
/// The highlight looks like a narrow border around the node.
#[derive(Debug, Clone, Copy, Default, FromTheme)]
#[base_path = "theme::selection"]
pub struct SelectionStyle {
/// Width of the border.
width: f32,
/// Color of the border.
color: Rgba,
}
@ -176,6 +135,8 @@ ensogl::define_endpoints_2! {
deselect (),
set_size (Vector2),
set_vis_input_type (Option<enso::Type>),
// Set width of the container, preserving the current height.
set_width (f32),
}
Output {
preprocessor (PreprocessorConfiguration),
@ -197,12 +158,22 @@ ensogl::define_endpoints_2! {
// ============
/// View of the visualization container.
///
/// Container has its origin in the top left corner.
#[derive(Debug)]
#[allow(missing_docs)]
pub struct View {
display_object: display::object::Instance,
background: background::View,
overlay: overlay::View,
selection: Rectangle,
overlay: Rectangle,
/// Resize grip is a rectangle with the size of the container but with a slight offset from the
/// overlay shape so that it extends beyond the container at the bottom and right sides.
/// The ordering of `overlay`, `selection`, and `resize_grip` is controlled by partition layers
/// (see [`View::init`]).
resize_grip: Rectangle,
// TODO : We added a HTML background to the `View`, because "shape" background was
// overlapping the JS visualization. This should be further investigated
// while fixing rust visualization displaying. (#796)
background_dom: DomSymbol,
scene: Scene,
loading_spinner: ensogl_component::spinner::View,
@ -212,23 +183,33 @@ impl View {
/// Constructor.
pub fn new(scene: Scene) -> Self {
let display_object = display::object::Instance::new();
let background = background::View::new();
let overlay = overlay::View::new();
display_object.add_child(&background);
display_object.add_child(&overlay);
let selection = Rectangle::default().build(|r| {
r.set_color(Rgba::transparent());
});
let overlay = Rectangle::default().build(|r| {
r.set_color(INVISIBLE_HOVER_COLOR).set_border_color(INVISIBLE_HOVER_COLOR);
});
let resize_grip = Rectangle::default().build(|r| {
r.set_color(INVISIBLE_HOVER_COLOR).set_border_color(INVISIBLE_HOVER_COLOR);
});
display_object.add_child(&selection);
selection.add_child(&overlay);
overlay.add_child(&resize_grip);
let div = web::document.create_div_or_panic();
let background_dom = DomSymbol::new(&div);
display_object.add_child(&background_dom);
let loading_spinner = ensogl_component::spinner::View::new();
ensogl::shapes_order_dependencies! {
scene => {
background -> overlay;
background -> ensogl_component::spinner;
}
};
Self { display_object, background, overlay, background_dom, scene, loading_spinner }.init()
Self {
display_object,
selection,
overlay,
resize_grip,
background_dom,
scene,
loading_spinner,
}
.init()
}
fn set_layer(&self, layer: visualization::Layer) {
@ -243,31 +224,38 @@ impl View {
self.loading_spinner.unset_parent();
}
fn set_resize_grip_offset(&self, offset: Vector2) {
self.resize_grip.set_xy(offset);
}
fn set_corner_radius(&self, radius: f32) {
self.overlay.set_corner_radius(radius);
self.selection.set_corner_radius(radius);
let radius = format!("{radius}px");
self.background_dom.dom().set_style_or_warn("border-radius", radius);
}
fn set_background_color(&self, color: Rgba) {
let bg_color = format!(
"rgba({},{},{},{})",
color.red * 255.0,
color.green * 255.0,
color.blue * 255.0,
color.alpha
);
self.background_dom.dom().set_style_or_warn("background", bg_color);
}
fn init_background(&self) {
let background = &self.background_dom;
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
// system (#795)
let styles = StyleWatch::new(&self.scene.style_sheet);
let bg_color =
styles.get_color(ensogl_hardcoded_theme::graph_editor::visualization::background);
let bg_hex = format!(
"rgba({},{},{},{})",
bg_color.red * 255.0,
bg_color.green * 255.0,
bg_color.blue * 255.0,
bg_color.alpha
);
// TODO : We added a HTML background to the `View`, because "shape" background was
// overlapping the JS visualization. This should be further investigated
// while fixing rust visualization displaying. (#796)
let background = &self.background_dom;
background.dom().set_style_or_warn("width", "0");
background.dom().set_style_or_warn("height", "0");
background.dom().set_style_or_warn("z-index", "1");
background.dom().set_style_or_warn("overflow-y", "auto");
background.dom().set_style_or_warn("overflow-x", "auto");
background.dom().set_style_or_warn("background", bg_hex);
background.dom().set_style_or_warn("border-radius", "14px");
shadow::add_to_dom_element(background, &styles);
}
@ -283,6 +271,9 @@ impl View {
self.show_waiting_screen();
self.set_layer(visualization::Layer::Default);
self.scene.layers.viz.add(&self);
self.scene.layers.viz_selection.add(&self.selection);
self.scene.layers.viz_resize_grip.add(&self.resize_grip);
self.scene.layers.viz_overlay.add(&self.overlay);
self
}
}
@ -355,7 +346,6 @@ impl ContainerModel {
self.scene.layers.above_nodes.add(&self.action_bar);
self.scene.layers.panel.add(&self.fullscreen_view);
self.update_shape_sizes(ViewState::default());
self.init_corner_roundness();
self.view.show_waiting_screen();
self
}
@ -371,6 +361,42 @@ impl ContainerModel {
// === Private API ===
impl ContainerModel {
/// Resize the container to the given size. The size is clamped between [`MIN_SIZE`] and
/// screen_shape * [`MAX_PORTION_OF_SCREEN`].
fn resize(
&self,
mut new_size: Vector2,
view_state: ViewState,
screen_shape: &Shape,
) -> Vector2 {
let max_size = Vector2::from(screen_shape).component_mul(&MAX_PORTION_OF_SCREEN);
new_size.x = new_size.x.clamp(MIN_SIZE.x, max_size.x);
new_size.y = new_size.y.clamp(MIN_SIZE.y, max_size.y);
self.update_layout(new_size, view_state);
new_size
}
/// Convert the given position from screen space to object space of the container.
fn screen_to_object_space(&self, screen_pos: Vector2) -> Vector2 {
let object = &self.display_object;
let pos = scene().screen_to_object_space(object, screen_pos);
pos
}
/// Update the selection shape. `value` is a selection width factor in range `[0, 1]`.
fn set_selection(&self, container_size: Vector2, value: f32, style: &SelectionStyle) {
let border_width = style.width * value;
let overall_size = container_size + Vector2(border_width * 2.0, border_width * 2.0);
if value > 0.0 {
self.view.selection.set_border_color(style.color);
} else {
self.view.selection.set_border_color(Rgba::transparent());
}
self.view.selection.set_border_and_inset(border_width);
self.view.selection.set_size(overall_size);
self.view.selection.set_xy(-overall_size / 2.0);
}
fn apply_view_state(&self, view_state: ViewState) {
// This is a workaround for #6600. It ensures the action bar is removed
// and receive no further mouse events.
@ -452,11 +478,10 @@ impl ContainerModel {
self.update_layout(size, view_state);
}
fn update_layout(&self, size: impl Into<Vector2>, view_state: ViewState) {
fn update_layout(&self, size: Vector2, view_state: ViewState) {
self.size.set(size);
let dom = self.view.background_dom.dom();
let bg_dom = self.fullscreen_view.background_dom.dom();
let size = size.into();
self.size.set(size);
if view_state.is_fullscreen() {
self.view.overlay.set_size(Vector2(0.0, 0.0));
dom.set_style_or_warn("width", "0");
@ -464,15 +489,15 @@ impl ContainerModel {
bg_dom.set_style_or_warn("width", format!("{}px", size[0]));
bg_dom.set_style_or_warn("height", format!("{}px", size[1]));
} else {
self.view.overlay.radius.set(CORNER_RADIUS);
self.view.background.radius.set(CORNER_RADIUS);
self.view.overlay.set_size(size);
self.view.background.set_size(size + 2.0 * Vector2(PADDING, PADDING));
self.view.loading_spinner.set_size(size + 2.0 * Vector2(PADDING, PADDING));
self.view.resize_grip.set_size(size);
self.view.selection.set_size(size);
self.view.loading_spinner.set_size(size);
dom.set_style_or_warn("width", format!("{}px", size[0]));
dom.set_style_or_warn("height", format!("{}px", size[1]));
bg_dom.set_style_or_warn("width", "0");
bg_dom.set_style_or_warn("height", "0");
self.drag_root.set_xy(Vector2(size.x / 2.0, -size.y / 2.0));
}
let action_bar_size = if matches!(view_state, ViewState::Enabled) {
Vector2::new(size.x, ACTION_BAR_HEIGHT)
@ -487,15 +512,6 @@ impl ContainerModel {
}
}
fn init_corner_roundness(&self) {
self.set_corner_roundness(1.0)
}
fn set_corner_roundness(&self, value: f32) {
self.view.overlay.roundness.set(value);
self.view.background.roundness.set(value);
}
/// Check if given mouse-event-target means this visualization.
fn is_this_target(&self, target: scene::PointerTargetId) -> bool {
self.view.overlay.is_this_target(target)
@ -562,13 +578,16 @@ impl Container {
let action_bar = &model.action_bar.frp;
let registry = &model.registry;
let selection = Animation::new(network);
let width_anim = Animation::new(network);
let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let selection_style = SelectionStyle::from_theme(network, &style);
frp::extend! { network
init <- source_();
eval input.set_view_state((state) model.apply_view_state(*state));
output.view_state <+ input.set_view_state.on_change();
output.fullscreen <+ output.view_state.map(|state| state.is_fullscreen()).on_change();
output.visible <+ output.view_state.map(|state| state.is_visible()).on_change();
output.size <+ input.set_size.on_change();
visualization_not_selected <- input.set_visualization.map(|t| t.is_none());
input_type_not_set <- input.set_vis_input_type.is_some().not();
@ -585,6 +604,61 @@ impl Container {
set_default_visualization <- any(
&set_default_visualization, &set_default_visualization_for_type);
// === Styles ===
let corner_radius = style.get_number(theme::corner_radius);
let background_color = style.get_color(theme::background);
let grip_offset_x = style.get_number(theme::resize_grip::offset_x);
let grip_offset_y = style.get_number(theme::resize_grip::offset_y);
_eval <- corner_radius.all_with(&init, f!((radius, _) model.view.set_corner_radius(*radius)));
_eval <- background_color.all_with(&init, f!((color, _) model.view.set_background_color(*color)));
grip_offset <- all_with3(&init, &grip_offset_x, &grip_offset_y, |_, x, y| Vector2(*x, *y));
eval grip_offset((offset) model.view.set_resize_grip_offset(*offset));
// === Drag-resize ===
let on_down = model.view.resize_grip.on_event::<mouse::Down>();
let on_up = scene.on_event::<mouse::Up>();
let on_move = scene.on_event::<mouse::Move>();
on_down <- on_down.gate(&output.visible);
on_up <- on_up.gate(&output.visible);
is_down <- bool(&on_up, &on_down);
on_move_down <- on_move.gate(&is_down);
glob_pos_on_down <- on_down.map(|event| event.client_centered());
glob_pos_on_move_down <- on_move_down.map(|event| event.client_centered());
pos_on_down <- glob_pos_on_down.map(f!((p) model.screen_to_object_space(*p)));
pos_on_move_down <- glob_pos_on_move_down.map(f!((p) model.screen_to_object_space(*p)));
pos_diff <- pos_on_move_down.map2(&pos_on_down, |a, b| a - b);
size_on_drag_start <- output.size.sample(&on_down);
output.size <+ pos_diff.map4(&size_on_drag_start, &output.view_state, scene_shape,
f!([model](diff, size, view_state, scene_shape) {
let diff = Vector2(diff.x, -diff.y);
let new_size = size + diff;
model.resize(new_size, *view_state, scene_shape)
}
));
// === Adjust width to the width of the node ===
size_was_not_changed_manually <- any(...);
size_was_not_changed_manually <+ init.constant(true);
size_was_not_changed_manually <+ pos_diff.filter(|d| *d != Vector2::default()).constant(false);
size_was_not_changed_manually <+ output.visible.on_change().constant(true);
width_target <- input.set_width.identity();
on_visible <- output.visible.on_true();
width_change_after_open <- width_target.sample(&on_visible);
width_anim.target <+ width_target.gate(&size_was_not_changed_manually);
new_size <- width_anim.value.map2(&output.size, |w, s| Vector2(*w, s.y));
reset_resizing_on_open <- init.constant(RESET_RESIZING_ON_OPEN);
size_reset <- width_change_after_open.gate(&reset_resizing_on_open).map(|w| Vector2(*w, DEFAULT_SIZE.x));
size_change <- any3(&new_size, &input.set_size, &size_reset);
size_change_and_scene_shape <- all(&size_change, scene_shape);
output.size <+ size_change_and_scene_shape.map2(&output.view_state,
f!(((new_size, scene_shape), view_state) model.resize(*new_size, *view_state, scene_shape))
);
}
@ -674,7 +748,11 @@ impl Container {
}));
selection_after_click <- selected_by_click.map(|sel| if *sel {1.0} else {0.0});
selection.target <+ selection_after_click;
eval selection.value ((selection) model.view.background.selection.set(*selection));
_eval <- selection.value.all_with3(&output.size, &selection_style.update,
f!((value, size, style) {
model.set_selection(*size, *value, style);
}
));
is_selected <- selected_by_click || output.fullscreen;
output.is_selected <+ is_selected.on_change();
}
@ -695,7 +773,6 @@ impl Container {
let weight_inv = 1.0 - weight;
let scene_size : Vector2 = scene_size.into();
let current_size = viz_size * weight_inv + scene_size * *weight;
model.set_corner_roundness(weight_inv);
model.update_layout(current_size,*view_state);
let m1 = model.scene.layers.panel.camera().inversed_view_matrix();
@ -743,8 +820,10 @@ impl Container {
//
// This is not optimal the optimal solution to this problem, as it also means that we have
// an animation on an invisible component running.
self.frp.public.set_size(Vector2(DEFAULT_SIZE.0, DEFAULT_SIZE.1));
self.frp.public.set_size(DEFAULT_SIZE);
self.frp.public.set_visualization(None);
init.emit(());
selection_style.init.emit(());
self
}

View File

@ -509,6 +509,7 @@ define_themes! { [light:0, dark:1]
background = Rgba(0.992,0.996,1.0,1.0), Rgba(0.182,0.188,0.196,1.0);
background.skipped = graph_editor::node::background , graph_editor::node::background;
selection = selection, selection;
corner_radius = 14.0, 14.0;
selection {
size = 3.5 , 3.5;
offset = 3.75 , 3.75;
@ -550,7 +551,8 @@ define_themes! { [light:0, dark:1]
}
}
visualization {
background = graph_editor::node::background , graph_editor::node::background;
background = graph_editor::node::background, graph_editor::node::background;
corner_radius = graph_editor::node::corner_radius, graph_editor::node::corner_radius;
text = Lcha(0.0,0.0,0.0,0.7) , Lcha(1.0,0.0,0.0,0.7);
text.selection = Lcha(0.7,0.0,0.125,0.7) , Lcha(0.7,0.0,0.125,0.7);
error {
@ -565,12 +567,13 @@ define_themes! { [light:0, dark:1]
icon = Lcha(0.0,0.0,0.0,0.7) , Lcha(1.0,0.0,0.0,0.7);
text = Lcha(0.0,0.0,0.0,0.7) , Lcha(1.0,0.0,0.0,0.7);
}
// Original RGB values (for reference after fixing color-conversion issues)
// ... , rgb(35 41 47)
selection = Rgba(0.306,0.647,0.992,0.14) , Rgba(0.137,0.16,0.184,1.0);
selection {
size = 8.0 , 8.0;
offset = 0.0 , 0.0;
color = Rgba(0.306,0.647,0.992,0.14) , Rgba(0.137,0.16,0.184,1.0);
width = 7.0, 7.0;
}
resize_grip {
offset_x = 10.0, 10.0;
offset_y = -10.0, -10.0;
}
text_grid {
font = "DejaVu Sans Mono" , "DejaVu Sans Mono";

View File

@ -673,6 +673,43 @@ fn partition_layer<S: display::shape::primitive::system::Shape>(
/// Please note that currently the `Layers` structure is implemented in a hacky way. It assumes the
/// existence of several layers, which are needed for the GUI to display shapes properly. This
/// should be abstracted away in the future.
///
/// Scene layers hierarchy:
///
/// ```plaintext
/// - root
/// ├── viz
/// │ ├── viz_selection
/// │ ├── viz_resize_grip
/// │ ├── viz_overlay
/// ├── below_main
/// ├── main
/// │ ├── edges
/// │ ├── nodes
/// │ ├── above_inactive_nodes
/// │ ├── active_nodes
/// │ └── above_all_nodes
/// ├── widget
/// ├── port
/// ├── port_selection (Camera: port_selection_cam)
/// ├── label
/// ├── port_hover
/// ├── above_nodes
/// ├── above_nodes_text
/// ├── panel_background (Camera: panel_cam)
/// │ ├── bottom
/// │ └── top
/// ├── panel (Camera: panel_cam)
/// ├── panel_text (Camera: panel_cam)
/// ├── node_searcher (Camera: node_searcher_cam)
/// ├── node_searcher_text (Camera: node_searcher_cam)
/// ├── edited_node (Camera: edited_node_cam)
/// ├── edited_node_text (Camera: edited_node_cam)
/// ├── tooltip
/// ├── tooltip_text
/// └── cursor (Camera: cursor_cam)
/// - DETACHED
/// ```
#[derive(Clone, CloneRef, Debug)]
#[allow(non_snake_case)]
pub struct HardcodedLayers {
@ -681,6 +718,9 @@ pub struct HardcodedLayers {
pub DETACHED: Layer,
pub root: Layer,
pub viz: Layer,
pub viz_selection: RectLayerPartition,
pub viz_resize_grip: RectLayerPartition,
pub viz_overlay: RectLayerPartition,
pub below_main: Layer,
pub main: Layer,
pub main_edges_level: RectLayerPartition,
@ -732,6 +772,9 @@ impl HardcodedLayers {
let root = Layer::new_with_camera("root", &main_cam);
let viz = root.create_sublayer("viz");
let viz_selection = partition_layer(&viz, "viz_selection");
let viz_resize_grip = partition_layer(&viz, "viz_resize_grip");
let viz_overlay = partition_layer(&viz, "viz_overlay");
let below_main = root.create_sublayer("below_main");
let main = root.create_sublayer("main");
let main_edges_level = partition_layer(&main, "edges");
@ -768,6 +811,9 @@ impl HardcodedLayers {
DETACHED,
root,
viz,
viz_selection,
viz_resize_grip,
viz_overlay,
below_main,
main,
main_edges_level,

View File

@ -333,6 +333,9 @@ impl Rectangle {
}
set_property!(corner_radius: number);
set_property!(color: color);
set_property!(border_color: color);
set_property!(inset: number);
set_property!(border: number);
}
}