mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 22:10:15 +03:00
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:
parent
9dcf48a3e0
commit
8020916f58
@ -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
1
Cargo.lock
generated
@ -4416,6 +4416,7 @@ dependencies = [
|
||||
"enso-text",
|
||||
"ensogl",
|
||||
"ensogl-component",
|
||||
"ensogl-derive-theme",
|
||||
"ensogl-drop-manager",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-text-msdf",
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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" }
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user