mirror of
https://github.com/enso-org/enso.git
synced 2024-11-29 17:22:57 +03:00
Fully visible group name in partially scrolled Component Group View (#3447)
[ci no changelog needed] [Task link](https://www.pivotaltracker.com/story/show/181725003) This PR implements a fully visible component group header while scrolling the group (using the ScrollArea). The header moves in sync with scrolling movements (using new `set_header_pos` FRP input), so it looks like the component group is scrolled. ScrollArea masks the "scrolled" entries above the header. This design allows a fully visible header even though our renderer doesn't support nested layers masking yet. The screencast: https://user-images.githubusercontent.com/6566674/168320360-2c2017b2-0ef5-42ce-9c79-82b9641c1d73.mp4 The most recent one, with the updated demo scene from develop: https://user-images.githubusercontent.com/6566674/168555268-8552c4b0-f887-4388-89a1-e65ddf668be6.mp4 # Important Notes - I fixed the API of the list view so now it supports non-hardcoded scene layers (previously it did not). I also believe it was implemented incorrectly. - I've found a [pretty weird bug](https://www.pivotaltracker.com/story/show/182193824): the component group inside the ScrollArea is invisible unless I add some arbitrary shape to the scroll area content. I use a `transparent_circle` for this purpose in the demo scene. The bug is probably related to masking the sublayers, though I wasn't able to reproduce it properly on a simpler example. - The selection box is removed from the demo scene as agreed with @farmaazon . The correct implementation has proven to be much harder than I expected, and we will implement another approach in a separate PR. - I also modified the `shadow::Parameters` so that it uses `Var`s instead of plain values.
This commit is contained in:
parent
6b6b1430bc
commit
4ebf637fd4
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -757,6 +757,7 @@ dependencies = [
|
||||
"ensogl-core",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-list-view",
|
||||
"ensogl-scroll-area",
|
||||
"ensogl-selector",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ide-view-component-group",
|
||||
@ -2355,6 +2356,7 @@ dependencies = [
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-label",
|
||||
"ensogl-list-view",
|
||||
"ensogl-shadow",
|
||||
"ensogl-text",
|
||||
]
|
||||
|
||||
|
@ -11,6 +11,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
enso-frp = { version = "0.1.0", path = "../../../../../lib/rust/frp" }
|
||||
ensogl-core = { version = "0.1.0", path = "../../../../../lib/rust/ensogl/core" }
|
||||
ensogl-gui-component = { version = "0.1.0", path = "../../../../../lib/rust/ensogl/component/gui" }
|
||||
ensogl-shadow = { version = "0.1.0", path = "../../../../../lib/rust/ensogl/component/shadow" }
|
||||
ensogl-hardcoded-theme = { version = "0.1.0", path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||
ensogl-list-view = { version = "0.1.0", path = "../../../../../lib/rust/ensogl/component/list-view" }
|
||||
ensogl-text = { version = "0.1.0", path = "../../../../../lib/rust/ensogl/component/text" }
|
||||
|
@ -5,6 +5,16 @@
|
||||
//!
|
||||
//! To learn more about component groups, see the [Component Browser Design
|
||||
//! Document](https://github.com/enso-org/design/blob/e6cffec2dd6d16688164f04a4ef0d9dff998c3e7/epics/component-browser/design.md).
|
||||
//!
|
||||
//! # Header and its shadow
|
||||
//!
|
||||
//! To simulate scrolling of the component group entries we move the header of the component group
|
||||
//! down while moving the whole component group up (see [`Frp::set_header_pos`]). When the header
|
||||
//! is moved down the shadow appears below it. The shadow changes its intensity smoothly before
|
||||
//! the header reaches the [`HEADER_SHADOW_PEAK`] distance from the top of the component group.
|
||||
//! After that the shadow is unchanged. When the header approaches the bottom of the component group
|
||||
//! we gradually reduce the size of the shadow so that it will never be rendered outside the
|
||||
//! component group boundaries. See `Header Backgound` section in the [`Model::resize`] method.
|
||||
|
||||
#![recursion_limit = "512"]
|
||||
// === Standard Linter Configuration ===
|
||||
@ -28,9 +38,12 @@ use ensogl_core::application::shortcut::Shortcut;
|
||||
use ensogl_core::application::Application;
|
||||
use ensogl_core::data::color;
|
||||
use ensogl_core::display;
|
||||
use ensogl_core::display::camera::Camera2d;
|
||||
use ensogl_core::display::scene::layer;
|
||||
use ensogl_gui_component::component;
|
||||
use ensogl_hardcoded_theme::application::component_browser::component_group as theme;
|
||||
use ensogl_list_view as list_view;
|
||||
use ensogl_shadow as shadow;
|
||||
use ensogl_text as text;
|
||||
|
||||
|
||||
@ -45,6 +58,23 @@ pub use entry::View as Entry;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/// The distance (in pixels) from the top border of the component group at which
|
||||
/// the header shadow reaches its maximum intensity.
|
||||
///
|
||||
/// When the header is on top of the component group (default position) the shadow is invisible.
|
||||
/// Once the header moves down (see [`Frp::set_header_pos`]) we start to linearly increase the
|
||||
/// intensity of the shadow until it reaches its maximum at [`HEADER_SHADOW_PEAK`] distance from
|
||||
/// the top. The intensity does not change all the way down from there, but the shadow is clipped at
|
||||
/// the bottom of the component group so that it will never escape the borders of the group and
|
||||
/// will not cover any neighboring elements of the scene. (see [`Model::resize`] method)
|
||||
const HEADER_SHADOW_PEAK: f32 = list_view::entry::HEIGHT / 2.0;
|
||||
|
||||
|
||||
|
||||
// ==========================
|
||||
// === Shapes Definitions ===
|
||||
// ==========================
|
||||
@ -66,6 +96,37 @@ pub mod background {
|
||||
}
|
||||
|
||||
|
||||
// === Header Background ===
|
||||
|
||||
/// The background of the header. It consists of a rectangle that matches the [`background`] in
|
||||
/// color and a shadow underneath it.
|
||||
pub mod header_background {
|
||||
use super::*;
|
||||
|
||||
ensogl_core::define_shape_system! {
|
||||
above = [background, list_view::background];
|
||||
(style:Style, color:Vector4, height: f32, shadow_height_multiplier: f32) {
|
||||
let color = Var::<color::Rgba>::from(color);
|
||||
let width: Var<Pixels> = "input_size.x".into();
|
||||
let height: Var<Pixels> = height.into();
|
||||
let bg = Rect((width.clone(), height.clone())).fill(color);
|
||||
// We use wider and shorter rect for the shadow because of the visual artifacts that
|
||||
// will appear otherwise:
|
||||
// 1. Rounded corners of the shadow are visible if the rect is too narrow. By widening
|
||||
// it we keep the shadow sharp and flat for the whole width of the header.
|
||||
// 2. Visual glitching similar to z-fighting occurs on the border of the elements
|
||||
// when the shadow rect has the exact same size as the background. We shrink the
|
||||
// height by 1 pixel to avoid it.
|
||||
let shadow_rect = Rect((width * 2.0, height - 1.0.px()));
|
||||
let mut shadow_parameters = shadow::parameters_from_style_path(style, theme::header::shadow);
|
||||
shadow_parameters.size = shadow_parameters.size * shadow_height_multiplier;
|
||||
let shadow = shadow::from_shape_with_parameters(shadow_rect.into(), shadow_parameters);
|
||||
(shadow + bg).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Header Overlay ===
|
||||
|
||||
/// The transparent overlay over Component Group View Header, used for capturing mouse events.
|
||||
@ -95,6 +156,7 @@ struct HeaderGeometry {
|
||||
padding_left: f32,
|
||||
padding_right: f32,
|
||||
padding_bottom: f32,
|
||||
shadow_size: f32,
|
||||
}
|
||||
|
||||
impl HeaderGeometry {
|
||||
@ -103,12 +165,13 @@ impl HeaderGeometry {
|
||||
let padding_left = style.get_number(theme::header::padding::left);
|
||||
let padding_right = style.get_number(theme::header::padding::right);
|
||||
let padding_bottom = style.get_number(theme::header::padding::bottom);
|
||||
let shadow_size = style.get_number(theme::header::shadow::size);
|
||||
|
||||
frp::extend! { network
|
||||
init <- source_();
|
||||
theme <- all_with5(&init,&height,&padding_left,&padding_right,&padding_bottom,
|
||||
|_,&height,&padding_left,&padding_right,&padding_bottom|
|
||||
Self{height,padding_left,padding_right,padding_bottom}
|
||||
theme <- all_with6(&init,&height,&padding_left,&padding_right,&padding_bottom,&shadow_size,
|
||||
|_,&height,&padding_left,&padding_right,&padding_bottom,&shadow_size|
|
||||
Self{height,padding_left,padding_right,padding_bottom,shadow_size}
|
||||
);
|
||||
theme_sampler <- theme.sampler();
|
||||
}
|
||||
@ -134,6 +197,13 @@ ensogl_core::define_endpoints_2! {
|
||||
set_color(color::Rgba),
|
||||
set_dimmed(bool),
|
||||
set_width(f32),
|
||||
/// Sets the y-position of the header from the top of the component group.
|
||||
///
|
||||
/// It can't move past the top and bottom borders of the component group.
|
||||
///
|
||||
/// We use it to simulate entries scrolling with a fixed-position header. Though in fact we
|
||||
/// move the header down while moving the whole component group up.
|
||||
set_header_pos(f32),
|
||||
}
|
||||
Output {
|
||||
selected_entry(Option<entry::Id>),
|
||||
@ -165,8 +235,16 @@ impl component::Frp<Model> for Frp {
|
||||
entries.entry_count() as f32 * list_view::entry::HEIGHT + header_geom.height
|
||||
});
|
||||
out.size <+ all_with(&input.set_width, &height, |w, h| Vector2(*w, *h));
|
||||
size_and_header_geometry <- all(&out.size, &header_geometry);
|
||||
eval size_and_header_geometry(((size, hdr_geom)) model.resize(*size, *hdr_geom));
|
||||
size_and_header_geometry <- all3(&out.size, &header_geometry, &input.set_header_pos);
|
||||
eval size_and_header_geometry(((size, hdr_geom, hdr_pos))
|
||||
model.resize(*size, *hdr_geom, *hdr_pos)
|
||||
);
|
||||
|
||||
|
||||
// === Show/hide shadow ===
|
||||
|
||||
shadow <- input.set_header_pos.map(|p| (*p / HEADER_SHADOW_PEAK).min(1.0));
|
||||
eval shadow((v) model.header_background.shadow_height_multiplier.set(*v));
|
||||
}
|
||||
|
||||
|
||||
@ -206,13 +284,14 @@ impl component::Frp<Model> for Frp {
|
||||
header_text_size <- all(&header_text_size, &init)._0();
|
||||
model.header.set_default_text_size <+ header_text_size.map(|v| text::Size(*v));
|
||||
_set_header <- input.set_header.map2(&size_and_header_geometry, f!(
|
||||
(text, (size, hdr_geom)) {
|
||||
(text, (size, hdr_geom, _)) {
|
||||
model.header_text.replace(text.clone());
|
||||
model.update_header_width(*size, *hdr_geom);
|
||||
})
|
||||
);
|
||||
model.header.set_default_color <+ header_color;
|
||||
eval bg_color((c) model.background.color.set(c.into()));
|
||||
eval bg_color((c) model.header_background.color.set(c.into()));
|
||||
}
|
||||
|
||||
|
||||
@ -274,6 +353,45 @@ impl component::Frp<Model> for Frp {
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Layers ===
|
||||
// ==============
|
||||
|
||||
/// A set of scene layers shared by every component group.
|
||||
///
|
||||
/// A component group consists of a several shapes with a strict rendering order. The order of the
|
||||
/// fields of this struct represents the rendering order of layers, with `background` being the
|
||||
/// bottom-most and `header_text` being the top-most.
|
||||
#[derive(Debug, Clone, CloneRef)]
|
||||
pub struct Layers {
|
||||
background: layer::Layer,
|
||||
text: layer::Layer,
|
||||
header: layer::Layer,
|
||||
header_text: layer::Layer,
|
||||
}
|
||||
|
||||
impl Layers {
|
||||
/// Constructor.
|
||||
///
|
||||
/// A `camera` will be used to render all layers. Layers will be attached to a `parent_layer` as
|
||||
/// sublayers.
|
||||
pub fn new(logger: &Logger, camera: &Camera2d, parent_layer: &layer::Layer) -> Self {
|
||||
let background = layer::Layer::new_with_cam(logger.clone_ref(), camera);
|
||||
let text = layer::Layer::new_with_cam(logger.clone_ref(), camera);
|
||||
let header = layer::Layer::new_with_cam(logger.clone_ref(), camera);
|
||||
let header_text = layer::Layer::new_with_cam(logger.clone_ref(), camera);
|
||||
|
||||
background.add_sublayer(&text);
|
||||
background.add_sublayer(&header);
|
||||
header.add_sublayer(&header_text);
|
||||
|
||||
parent_layer.add_sublayer(&background);
|
||||
|
||||
Self { background, header, text, header_text }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =============
|
||||
// === Model ===
|
||||
// =============
|
||||
@ -281,12 +399,13 @@ impl component::Frp<Model> for Frp {
|
||||
/// The Model of the [`View`] component.
|
||||
#[derive(Clone, CloneRef, Debug)]
|
||||
pub struct Model {
|
||||
display_object: display::object::Instance,
|
||||
header: text::Area,
|
||||
header_text: Rc<RefCell<String>>,
|
||||
header_overlay: header_overlay::View,
|
||||
background: background::View,
|
||||
entries: list_view::ListView<Entry>,
|
||||
display_object: display::object::Instance,
|
||||
header: text::Area,
|
||||
header_background: header_background::View,
|
||||
header_text: Rc<RefCell<String>>,
|
||||
header_overlay: header_overlay::View,
|
||||
background: background::View,
|
||||
entries: list_view::ListView<Entry>,
|
||||
}
|
||||
|
||||
impl display::Object for Model {
|
||||
@ -303,30 +422,47 @@ impl component::Model for Model {
|
||||
fn new(app: &Application, logger: &Logger) -> Self {
|
||||
let header_text = default();
|
||||
let display_object = display::object::Instance::new(&logger);
|
||||
let background = background::View::new(&logger);
|
||||
let header = text::Area::new(app);
|
||||
let header_overlay = header_overlay::View::new(&logger);
|
||||
let background = background::View::new(&logger);
|
||||
let header_background = header_background::View::new(&logger);
|
||||
let header = text::Area::new(app);
|
||||
let entries = app.new_view::<list_view::ListView<Entry>>();
|
||||
entries.set_style_prefix(entry::STYLE_PATH);
|
||||
entries.set_background_color(HOVER_COLOR);
|
||||
entries.show_background_shadow(false);
|
||||
entries.set_background_corners_radius(0.0);
|
||||
entries.hide_selection();
|
||||
display_object.add_child(&background);
|
||||
display_object.add_child(&header_background);
|
||||
display_object.add_child(&header);
|
||||
display_object.add_child(&header_overlay);
|
||||
display_object.add_child(&entries);
|
||||
|
||||
let label_layer = &app.display.default_scene.layers.label;
|
||||
header.add_to_scene_layer(label_layer);
|
||||
|
||||
entries.hide_selection();
|
||||
|
||||
Model { display_object, header, header_text, header_overlay, background, entries }
|
||||
Model {
|
||||
display_object,
|
||||
header_overlay,
|
||||
header,
|
||||
header_text,
|
||||
background,
|
||||
header_background,
|
||||
entries,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn resize(&self, size: Vector2, header_geometry: HeaderGeometry) {
|
||||
/// Assign a set of layers to render the component group in. Must be called after constructing
|
||||
/// the [`View`].
|
||||
pub fn set_layers(&self, layers: &Layers) {
|
||||
layers.background.add_exclusive(&self.background);
|
||||
layers.header_text.add_exclusive(&self.header_overlay);
|
||||
layers.background.add_exclusive(&self.entries);
|
||||
self.entries.set_label_layer(&layers.text);
|
||||
layers.header.add_exclusive(&self.header_background);
|
||||
self.header.add_to_scene_layer(&layers.header_text);
|
||||
}
|
||||
|
||||
fn resize(&self, size: Vector2, header_geometry: HeaderGeometry, header_pos: f32) {
|
||||
// === Background ===
|
||||
|
||||
self.background.size.set(size);
|
||||
@ -339,13 +475,34 @@ impl Model {
|
||||
let header_text_height = self.header.height.value();
|
||||
let header_padding_bottom = header_geometry.padding_bottom;
|
||||
let header_height = header_geometry.height;
|
||||
let header_center_y = size.y / 2.0 - header_height / 2.0;
|
||||
let header_bottom_y = header_center_y - header_height / 2.0;
|
||||
let half_header_height = header_height / 2.0;
|
||||
let header_center_y = size.y / 2.0 - half_header_height;
|
||||
let header_center_y = header_center_y - header_pos;
|
||||
let header_center_y = header_center_y.max(-size.y / 2.0 + half_header_height);
|
||||
let header_center_y = header_center_y.min(size.y / 2.0 - half_header_height);
|
||||
let header_bottom_y = header_center_y - half_header_height;
|
||||
let header_text_y = header_bottom_y + header_text_height + header_padding_bottom;
|
||||
self.header.set_position_xy(Vector2(header_text_x, header_text_y));
|
||||
self.update_header_width(size, header_geometry);
|
||||
|
||||
|
||||
// === Header Background ===
|
||||
|
||||
self.header_background.height.set(header_height);
|
||||
let shadow_size = header_geometry.shadow_size;
|
||||
let distance_to_bottom = (-size.y / 2.0 - header_bottom_y).abs();
|
||||
// We need to render both the header background and the shadow below it, so we add
|
||||
// `shadow_size` and `header_height` to calculate the final `size` of the
|
||||
// `header_background` shape. We use `shadow_size * 2.0`, because the shadow extends by
|
||||
// `shadow_size` in each direction around the base shape, not only down. We cap the
|
||||
// `shadow_size` by the distance to the bottom of the component group so that the shadow is
|
||||
// not visible outside the component group background.
|
||||
let shadow_size = shadow_size.min(distance_to_bottom);
|
||||
let header_background_height = header_height + shadow_size * 2.0;
|
||||
self.header_background.size.set(Vector2(size.x, header_background_height));
|
||||
self.header_background.set_position_y(header_center_y);
|
||||
|
||||
|
||||
// === Header Overlay ===
|
||||
|
||||
self.header_overlay.set_position_y(header_center_y);
|
||||
|
@ -12,6 +12,7 @@ enso-frp = { path = "../../../../../lib/rust/frp" }
|
||||
ensogl-core = { path = "../../../../../lib/rust/ensogl/core" }
|
||||
ensogl-selector = { path = "../../../../../lib/rust/ensogl/component/selector" }
|
||||
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||
ensogl-scroll-area = { path = "../../../../../lib/rust/ensogl/component/scroll-area" }
|
||||
ensogl-list-view = { path = "../../../../../lib/rust/ensogl/component/list-view" }
|
||||
ensogl-text-msdf-sys = { path = "../../../../../lib/rust/ensogl/component/text/msdf-sys" }
|
||||
ide-view-component-group = { path = "../../component-browser/component-group" }
|
||||
|
@ -12,6 +12,7 @@
|
||||
#![warn(unused_import_braces)]
|
||||
#![warn(unused_qualifications)]
|
||||
|
||||
use ensogl_core::display::shape::*;
|
||||
use ensogl_core::prelude::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
@ -19,11 +20,10 @@ use ensogl_core::application::Application;
|
||||
use ensogl_core::data::color;
|
||||
use ensogl_core::display::object::ObjectOps;
|
||||
use ensogl_core::frp;
|
||||
use ensogl_core::Animation;
|
||||
use ensogl_hardcoded_theme as theme;
|
||||
use ensogl_list_view as list_view;
|
||||
use ensogl_scroll_area::ScrollArea;
|
||||
use ensogl_selector as selector;
|
||||
use ensogl_selector::Bounds;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
use ide_view_component_group as component_group;
|
||||
use list_view::entry::AnyModelProvider;
|
||||
@ -75,12 +75,6 @@ impl MockEntries {
|
||||
})
|
||||
}
|
||||
|
||||
fn set_count(&self, count: usize) {
|
||||
if self.entries.len() >= count {
|
||||
self.count.set(count);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_entry(&self, id: list_view::entry::Id) -> Option<component_group::entry::Model> {
|
||||
self.entries.get(id).cloned()
|
||||
}
|
||||
@ -98,6 +92,56 @@ impl list_view::entry::ModelProvider<component_group::Entry> for MockEntries {
|
||||
|
||||
|
||||
|
||||
// ==================================
|
||||
// === Component Group Controller ===
|
||||
// ==================================
|
||||
|
||||
/// An example abstraction to arrange component groups vertically inside the scroll area.
|
||||
///
|
||||
/// It arranges `component_groups` on top of each other, with the first one being the top most.
|
||||
/// It also calculates the header positions for every component group to simulate the scrolling.
|
||||
/// While the [`ScrollArea`] moves every component group up we move headers of the partially
|
||||
/// visible component groups down. That makes the headers of the partially scrolled component
|
||||
/// groups visible at all times.
|
||||
#[derive(Debug, Clone, CloneRef)]
|
||||
struct ComponentGroupController {
|
||||
component_groups: Rc<Vec<component_group::View>>,
|
||||
}
|
||||
|
||||
impl ComponentGroupController {
|
||||
fn init(
|
||||
component_groups: &[component_group::View],
|
||||
network: &frp::Network,
|
||||
scroll_area: &ScrollArea,
|
||||
) {
|
||||
Self { component_groups: Rc::new(component_groups.to_vec()) }
|
||||
.init_inner(network, scroll_area)
|
||||
}
|
||||
|
||||
fn init_inner(&self, network: &frp::Network, scroll_area: &ScrollArea) {
|
||||
for (i, group) in self.component_groups.iter().enumerate() {
|
||||
let this = self.clone_ref();
|
||||
frp::extend! { network
|
||||
eval group.size([group, this](size)
|
||||
group.set_position_y(-size.y / 2.0 - this.heights_sum(i))
|
||||
);
|
||||
is_scrolled <- scroll_area.scroll_position_y.map(f!([this] (s) *s > this.heights_sum(i)));
|
||||
change_hdr_pos <- scroll_area.scroll_position_y.gate(&is_scrolled);
|
||||
reset_hdr_pos <- is_scrolled.on_false();
|
||||
eval change_hdr_pos([group, this](y) group.set_header_pos(*y - this.heights_sum(i)));
|
||||
eval_ reset_hdr_pos(group.set_header_pos(0.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a sum of heights of the first `n` component groups, counting from the top.
|
||||
fn heights_sum(&self, n: usize) -> f32 {
|
||||
self.component_groups.iter().take(n).map(|g| g.size.value().y).sum()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ========================
|
||||
// === Init Application ===
|
||||
// ========================
|
||||
@ -105,41 +149,19 @@ impl list_view::entry::ModelProvider<component_group::Entry> for MockEntries {
|
||||
|
||||
// === Helpers ====
|
||||
|
||||
fn create_selection() -> list_view::selection::View {
|
||||
let selection = list_view::selection::View::new(Logger::new("Selection"));
|
||||
selection.size.set(Vector2(150.0, list_view::entry::HEIGHT));
|
||||
selection.corner_radius.set(5.0);
|
||||
selection
|
||||
}
|
||||
|
||||
fn create_component_group(app: &Application) -> component_group::View {
|
||||
fn create_component_group(
|
||||
app: &Application,
|
||||
header: &str,
|
||||
layers: &component_group::Layers,
|
||||
) -> component_group::View {
|
||||
let component_group = app.new_view::<component_group::View>();
|
||||
app.display.add_child(&component_group);
|
||||
component_group.model().set_layers(layers);
|
||||
component_group.set_header(header.to_string());
|
||||
component_group.set_width(150.0);
|
||||
component_group.set_position_x(75.0);
|
||||
component_group
|
||||
}
|
||||
|
||||
fn wide_component_group(app: &Application) -> component_group::wide::View {
|
||||
let wide_component_group = app.new_view::<component_group::wide::View>();
|
||||
app.display.add_child(&wide_component_group);
|
||||
wide_component_group.set_position_x(250.0);
|
||||
wide_component_group.set_width(450.0);
|
||||
wide_component_group.set_background_color(color::Rgba(0.927, 0.937, 0.913, 1.0));
|
||||
wide_component_group.set_no_items_label_text("No local variables.");
|
||||
wide_component_group
|
||||
}
|
||||
|
||||
fn items_slider(app: &Application) -> selector::NumberPicker {
|
||||
let slider = app.new_view::<selector::NumberPicker>();
|
||||
app.display.add_child(&slider);
|
||||
slider.frp.resize(Vector2(400.0, 50.0));
|
||||
slider.frp.allow_click_selection(true);
|
||||
slider.frp.set_bounds(Bounds::new(0.0, 15.0));
|
||||
slider.set_position_y(-250.0);
|
||||
slider.frp.set_caption(Some("Items count:".to_string()));
|
||||
slider
|
||||
}
|
||||
|
||||
fn color_component_slider(app: &Application, caption: &str) -> selector::NumberPicker {
|
||||
let slider = app.new_view::<selector::NumberPicker>();
|
||||
app.display.add_child(&slider);
|
||||
@ -153,83 +175,76 @@ fn color_component_slider(app: &Application, caption: &str) -> selector::NumberP
|
||||
|
||||
// === init ===
|
||||
|
||||
/// This is a workaround for the bug [#182193824](https://www.pivotaltracker.com/story/show/182193824).
|
||||
///
|
||||
/// We add a tranparent shape to the [`ScrollArea`] content to make component groups visible.
|
||||
mod transparent_circle {
|
||||
use super::*;
|
||||
ensogl_core::define_shape_system! {
|
||||
(style:Style) {
|
||||
// As you can see even a zero-radius circle works as a workaround.
|
||||
let radius = 0.px();
|
||||
Circle(radius).fill(color::Rgba::transparent()).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn init(app: &Application) {
|
||||
theme::builtin::dark::register(&app);
|
||||
theme::builtin::light::register(&app);
|
||||
theme::builtin::light::enable(&app);
|
||||
|
||||
let items_slider = items_slider(app);
|
||||
let network = frp::Network::new("Component Group Debug Scene");
|
||||
let selection = create_selection();
|
||||
let selection_animation = Animation::<Vector2>::new(&network);
|
||||
let wide_selection = create_selection();
|
||||
let wide_selection_animation = Animation::<Vector2>::new(&network);
|
||||
|
||||
let component_group = create_component_group(app);
|
||||
component_group.add_child(&selection);
|
||||
let scroll_area = ScrollArea::new(app);
|
||||
scroll_area.set_position_xy(Vector2(0.0, 100.0));
|
||||
scroll_area.resize(Vector2(170.0, 400.0));
|
||||
scroll_area.set_content_width(150.0);
|
||||
scroll_area.set_content_height(2000.0);
|
||||
app.display.add_child(&scroll_area);
|
||||
|
||||
let camera = &scroll_area.content_layer().camera();
|
||||
let parent_layer = scroll_area.content_layer();
|
||||
let layers = component_group::Layers::new(&app.logger, camera, parent_layer);
|
||||
|
||||
let group_name = "Long group name with text overflowing the width";
|
||||
component_group.set_header(group_name.to_string());
|
||||
component_group.set_position_x(-150.0);
|
||||
let dimmed_component_group = create_component_group(app);
|
||||
dimmed_component_group.set_dimmed(true);
|
||||
dimmed_component_group.set_header("Input / Output".to_string());
|
||||
dimmed_component_group.set_position_x(-350.0);
|
||||
let wide_component_group = wide_component_group(app);
|
||||
wide_component_group.add_child(&wide_selection);
|
||||
let first_component_group = create_component_group(app, group_name, &layers);
|
||||
let group_name = "Second component group";
|
||||
let second_component_group = create_component_group(app, group_name, &layers);
|
||||
second_component_group.set_dimmed(true);
|
||||
|
||||
scroll_area.content().add_child(&first_component_group);
|
||||
scroll_area.content().add_child(&second_component_group);
|
||||
|
||||
// FIXME(#182193824): This is a workaround for a bug. See the docs of the
|
||||
// [`transparent_circle`].
|
||||
{
|
||||
let transparent_circle = transparent_circle::View::new(&app.logger);
|
||||
transparent_circle.size.set(Vector2(150.0, 150.0));
|
||||
transparent_circle.set_position_xy(Vector2(200.0, -150.0));
|
||||
scroll_area.content().add_child(&transparent_circle);
|
||||
std::mem::forget(transparent_circle);
|
||||
}
|
||||
|
||||
// === Regular Component Group ===
|
||||
|
||||
frp::extend! { network
|
||||
selection_animation.target <+ component_group.selection_position_target;
|
||||
eval selection_animation.value ((pos) selection.set_position_xy(*pos));
|
||||
|
||||
eval component_group.suggestion_accepted ([](id) DEBUG!("Accepted Suggestion {id}"));
|
||||
eval component_group.expression_accepted ([](id) DEBUG!("Accepted Expression {id}"));
|
||||
eval_ component_group.header_accepted ([] DEBUG!("Accepted Header"));
|
||||
eval first_component_group.suggestion_accepted ([](id) DEBUG!("Accepted Suggestion {id}"));
|
||||
eval first_component_group.expression_accepted ([](id) DEBUG!("Accepted Expression {id}"));
|
||||
eval_ first_component_group.header_accepted ([] DEBUG!("Accepted Header"));
|
||||
}
|
||||
selection_animation.target.emit(component_group.selection_position_target.value());
|
||||
selection_animation.skip.emit(());
|
||||
|
||||
ComponentGroupController::init(
|
||||
&[first_component_group.clone_ref(), second_component_group.clone_ref()],
|
||||
&network,
|
||||
&scroll_area,
|
||||
);
|
||||
|
||||
// === Wide Component Group ===
|
||||
|
||||
wide_selection.color.set(color::Rgba(0.527, 0.554, 0.18, 1.0).into());
|
||||
frp::extend! { network
|
||||
wide_selection_animation.target <+ wide_component_group.selection_position_target;
|
||||
eval wide_selection_animation.value ((pos) wide_selection.set_position_xy(*pos));
|
||||
|
||||
eval wide_component_group.suggestion_accepted ([](id) DEBUG!("[Wide] Accepted Suggestion {id}"));
|
||||
eval wide_component_group.expression_accepted ([](id) DEBUG!("[Wide] Accepted Expression {id}"));
|
||||
|
||||
no_entries <- wide_component_group.entry_count.map(|count| *count == 0);
|
||||
hide_selection <- no_entries.on_true();
|
||||
show_selection <- no_entries.on_false();
|
||||
eval_ hide_selection (wide_selection.color.set(color::Rgba::transparent().into()));
|
||||
eval_ show_selection (wide_selection.color.set(color::Rgba(0.527, 0.554, 0.18, 1.0).into()));
|
||||
}
|
||||
wide_selection_animation.target.emit(wide_component_group.selection_position_target.value());
|
||||
wide_selection_animation.skip.emit(());
|
||||
|
||||
|
||||
// === Setup slider to change entry count ===
|
||||
|
||||
let mock_entries = MockEntries::new(25);
|
||||
let mock_entries = MockEntries::new(15);
|
||||
let model_provider = AnyModelProvider::from(mock_entries.clone_ref());
|
||||
frp::extend! { network
|
||||
int_value <- items_slider.frp.output.value.map(|v| *v as usize);
|
||||
eval int_value([component_group, dimmed_component_group, wide_component_group](i) {
|
||||
mock_entries.set_count(*i);
|
||||
component_group.set_entries(model_provider.clone_ref());
|
||||
dimmed_component_group.set_entries(model_provider.clone_ref());
|
||||
wide_component_group.set_entries(model_provider.clone_ref());
|
||||
});
|
||||
}
|
||||
items_slider.frp.set_value(10.0);
|
||||
// Select the bottom left entry at the start.
|
||||
let first_column = component_group::wide::ColumnId::new(0);
|
||||
wide_component_group.select_entry(first_column, 0);
|
||||
|
||||
first_component_group.set_entries(model_provider.clone_ref());
|
||||
second_component_group.set_entries(model_provider);
|
||||
|
||||
// === Color sliders ===
|
||||
|
||||
@ -259,9 +274,8 @@ fn init(app: &Application) {
|
||||
let blue_slider_value = &blue_slider_frp.value;
|
||||
color <- all_with3(red_slider_value, green_slider_value, blue_slider_value,
|
||||
|r,g,b| color::Rgba(*r, *g, *b, 1.0));
|
||||
component_group.set_color <+ color;
|
||||
dimmed_component_group.set_color <+ color;
|
||||
eval color((c) selection.color.set(c.into()));
|
||||
first_component_group.set_color <+ color;
|
||||
second_component_group.set_color <+ color;
|
||||
}
|
||||
init.emit(());
|
||||
|
||||
@ -271,11 +285,9 @@ fn init(app: &Application) {
|
||||
std::mem::forget(red_slider);
|
||||
std::mem::forget(green_slider);
|
||||
std::mem::forget(blue_slider);
|
||||
std::mem::forget(items_slider);
|
||||
std::mem::forget(scroll_area);
|
||||
std::mem::forget(network);
|
||||
std::mem::forget(selection);
|
||||
std::mem::forget(component_group);
|
||||
std::mem::forget(dimmed_component_group);
|
||||
std::mem::forget(wide_component_group);
|
||||
std::mem::forget(wide_selection);
|
||||
std::mem::forget(first_component_group);
|
||||
std::mem::forget(second_component_group);
|
||||
std::mem::forget(layers);
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ use ensogl_component::text;
|
||||
use ensogl_hardcoded_theme as theme;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
@ -97,7 +97,7 @@ impl ProjectList {
|
||||
app.display.default_scene.layers.panel.add_exclusive(&display_object);
|
||||
caption.set_content("Open Project");
|
||||
caption.add_to_scene_layer(&app.display.default_scene.layers.panel_text);
|
||||
list.set_label_layer(app.display.default_scene.layers.panel_text.id());
|
||||
list.set_label_layer(&app.display.default_scene.layers.panel_text);
|
||||
|
||||
ensogl::shapes_order_dependencies! {
|
||||
app.display.default_scene => {
|
||||
|
@ -135,7 +135,7 @@ impl Model {
|
||||
let style = StyleWatch::new(&app.display.default_scene.style_sheet);
|
||||
let action_list_gap_path = ensogl_hardcoded_theme::application::searcher::action_list_gap;
|
||||
let action_list_gap = style.get_number_or(action_list_gap_path, 0.0);
|
||||
list.set_label_layer(scene.layers.node_searcher_text.id());
|
||||
list.set_label_layer(&scene.layers.node_searcher_text);
|
||||
list.set_position_y(-action_list_gap);
|
||||
list.set_position_x(ACTION_LIST_X);
|
||||
documentation.set_position_x(DOCUMENTATION_X);
|
||||
|
@ -193,6 +193,15 @@ define_themes! { [light:0, dark:1]
|
||||
right = 2.5, 2.5;
|
||||
bottom = 5.0, 5.0;
|
||||
}
|
||||
shadow = shadow , shadow;
|
||||
shadow {
|
||||
size = 27.0 , 27.0;
|
||||
spread = shadow::spread , shadow::spread;
|
||||
fading = shadow::fading , shadow::fading;
|
||||
exponent = shadow::exponent , shadow::exponent;
|
||||
offset_x = shadow::offset_x , shadow::offset_x;
|
||||
offset_y = shadow::offset_y , shadow::offset_y;
|
||||
}
|
||||
}
|
||||
entries {
|
||||
text {
|
||||
|
@ -7,7 +7,8 @@ use crate::entry::Entry;
|
||||
|
||||
use ensogl_core::application::Application;
|
||||
use ensogl_core::display;
|
||||
use ensogl_core::display::scene::layer::LayerId;
|
||||
use ensogl_core::display::scene::layer::Layer;
|
||||
use ensogl_core::display::scene::layer::WeakLayer;
|
||||
use ensogl_core::display::style;
|
||||
|
||||
|
||||
@ -72,7 +73,7 @@ pub struct ListData<E, P> {
|
||||
entries_range: Rc<CloneCell<Range<entry::Id>>>,
|
||||
entry_params: Rc<RefCell<P>>,
|
||||
provider: Rc<CloneRefCell<entry::AnyModelProvider<E>>>,
|
||||
label_layer: Rc<Cell<LayerId>>,
|
||||
label_layer: Rc<RefCell<WeakLayer>>,
|
||||
}
|
||||
|
||||
impl<E, P: Default> ListData<E, P> {
|
||||
@ -85,7 +86,7 @@ impl<E, P: Default> ListData<E, P> {
|
||||
let entry_params = default();
|
||||
let display_object = display::object::Instance::new(&logger);
|
||||
let provider = default();
|
||||
let label_layer = Rc::new(Cell::new(app.display.default_scene.layers.label.id()));
|
||||
let label_layer = Rc::new(RefCell::new(app.display.default_scene.layers.label.downgrade()));
|
||||
Self {
|
||||
logger,
|
||||
app,
|
||||
@ -148,20 +149,11 @@ impl<E, P> ListData<E, P> {
|
||||
|
||||
impl<E: Entry, P> ListData<E, P> {
|
||||
/// Sets the scene layer where the labels will be placed.
|
||||
pub fn set_label_layer(&self, label_layer: LayerId) {
|
||||
let layers = &self.app.display.default_scene.layers;
|
||||
if let Some(layer) = layers.get_sublayer(self.label_layer.get()) {
|
||||
for entry in &*self.entries.borrow() {
|
||||
entry.entry.set_label_layer(&layer);
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
self.logger,
|
||||
"Cannot set layer {label_layer:?} for labels: the layer does not \
|
||||
exist in the scene"
|
||||
);
|
||||
pub fn set_label_layer(&self, label_layer: &Layer) {
|
||||
for entry in &*self.entries.borrow() {
|
||||
entry.entry.set_label_layer(label_layer);
|
||||
}
|
||||
self.label_layer.set(label_layer);
|
||||
self.label_layer.replace(label_layer.downgrade());
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,7 +260,7 @@ impl<E: Entry> ListData<E, E::Params> {
|
||||
|
||||
fn create_new_entry(&self, style_prefix: &style::Path) -> DisplayedEntry<E> {
|
||||
let layers = &self.app.display.default_scene.layers;
|
||||
let layer = layers.get_sublayer(self.label_layer.get()).unwrap_or_else(|| {
|
||||
let layer = self.label_layer.borrow().upgrade().unwrap_or_else(|| {
|
||||
error!(
|
||||
self.logger,
|
||||
"Cannot set layer {self.label_layer:?} for labels: the layer does \
|
||||
|
@ -41,7 +41,7 @@ use ensogl_core::application::shortcut;
|
||||
use ensogl_core::application::Application;
|
||||
use ensogl_core::data::color;
|
||||
use ensogl_core::display;
|
||||
use ensogl_core::display::scene::layer::LayerId;
|
||||
use ensogl_core::display::scene::layer::Layer;
|
||||
use ensogl_core::display::shape::*;
|
||||
use ensogl_core::Animation;
|
||||
use ensogl_hardcoded_theme as theme;
|
||||
@ -581,7 +581,7 @@ where E::Model: Default
|
||||
}
|
||||
|
||||
/// Sets the scene layer where the labels will be placed.
|
||||
pub fn set_label_layer(&self, layer: LayerId) {
|
||||
pub fn set_label_layer(&self, layer: &Layer) {
|
||||
self.model.entries.set_label_layer(layer);
|
||||
}
|
||||
|
||||
|
@ -229,4 +229,9 @@ impl ScrollArea {
|
||||
pub fn content(&self) -> &display::object::Instance {
|
||||
&self.model.content
|
||||
}
|
||||
|
||||
/// A scene layer containing the content of the ScrollArea.
|
||||
pub fn content_layer(&self) -> &layer::Layer {
|
||||
&self.model.display_object.layer.masked_layer
|
||||
}
|
||||
}
|
||||
|
@ -30,15 +30,16 @@ use ensogl_hardcoded_theme as theme;
|
||||
|
||||
|
||||
/// Defines the appearance of a shadow
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct Parameters {
|
||||
base_color: color::Rgba,
|
||||
fading: color::Rgba,
|
||||
size: f32,
|
||||
spread: f32,
|
||||
exponent: f32,
|
||||
offset_x: f32,
|
||||
offset_y: f32,
|
||||
pub base_color: Var<color::Rgba>,
|
||||
pub fading: Var<color::Rgba>,
|
||||
pub size: Var<f32>,
|
||||
pub spread: Var<f32>,
|
||||
pub exponent: Var<f32>,
|
||||
pub offset_x: Var<f32>,
|
||||
pub offset_y: Var<f32>,
|
||||
}
|
||||
|
||||
/// Loads shadow parameters from the given style, at the given path. The structure of the style
|
||||
@ -46,18 +47,18 @@ pub struct Parameters {
|
||||
pub fn parameters_from_style_path(style: &StyleWatch, path: impl Into<style::Path>) -> Parameters {
|
||||
let path: style::Path = path.into();
|
||||
Parameters {
|
||||
base_color: style.get_color(&path),
|
||||
fading: style.get_color(&path.sub("fading")),
|
||||
size: style.get_number(&path.sub("size")),
|
||||
spread: style.get_number(&path.sub("spread")),
|
||||
exponent: style.get_number(&path.sub("exponent")),
|
||||
offset_x: style.get_number(&path.sub("offset_x")),
|
||||
offset_y: style.get_number(&path.sub("offset_y")),
|
||||
base_color: style.get_color(&path).into(),
|
||||
fading: style.get_color(&path.sub("fading")).into(),
|
||||
size: style.get_number(&path.sub("size")).into(),
|
||||
spread: style.get_number(&path.sub("spread")).into(),
|
||||
exponent: style.get_number(&path.sub("exponent")).into(),
|
||||
offset_x: style.get_number(&path.sub("offset_x")).into(),
|
||||
offset_y: style.get_number(&path.sub("offset_y")).into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility method to retrieve the size of the shadow. can be used to determine shape padding etc.
|
||||
pub fn size(style: &StyleWatch) -> f32 {
|
||||
pub fn size(style: &StyleWatch) -> Var<f32> {
|
||||
let parameters = parameters_from_style_path(style, theme::shadow);
|
||||
parameters.size
|
||||
}
|
||||
@ -93,15 +94,13 @@ pub fn from_shape_with_parameters_and_alpha(
|
||||
parameters: Parameters,
|
||||
alpha: &Var<f32>,
|
||||
) -> AnyShape {
|
||||
let grow = Var::<f32>::from(parameters.size);
|
||||
let grow = parameters.size.clone();
|
||||
let shadow = base_shape.grow(grow);
|
||||
let shadow = shadow.translate((parameters.offset_x.px(), parameters.offset_y.px()));
|
||||
|
||||
let base_color = Var::<color::Rgba>::from(parameters.base_color);
|
||||
let base_color = base_color.multiply_alpha(alpha);
|
||||
let base_color = parameters.base_color.multiply_alpha(alpha);
|
||||
|
||||
let fading_color = Var::<color::Rgba>::from(parameters.fading);
|
||||
let fading_color = fading_color.multiply_alpha(alpha);
|
||||
let fading_color = parameters.fading.multiply_alpha(alpha);
|
||||
|
||||
let shadow_color = color::gradient::Linear::<Var<color::LinearRgba>>::new(
|
||||
fading_color.into_linear(),
|
||||
|
@ -3,6 +3,7 @@
|
||||
use crate::prelude::*;
|
||||
use crate::system::gpu::shader::glsl::traits::*;
|
||||
|
||||
use crate::display::shape::Var;
|
||||
use crate::system::gpu::shader::glsl::Glsl;
|
||||
|
||||
|
||||
@ -96,9 +97,9 @@ pub const DEFAULT_DISTANCE_GRADIENT_SIZE: f32 = 10.0;
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SdfSampler<Gradient> {
|
||||
/// The distance from the shape border at which the gradient should start.
|
||||
pub spread: f32,
|
||||
pub spread: Var<f32>,
|
||||
/// The size of the gradient in the SDF space.
|
||||
pub size: f32,
|
||||
pub size: Var<f32>,
|
||||
/// The gradient slope modifier. Defines how fast the gradient values change.
|
||||
pub slope: Slope,
|
||||
/// The underlying gradient.
|
||||
@ -109,21 +110,21 @@ impl<Gradient> SdfSampler<Gradient> {
|
||||
/// Constructs a new gradient with `spread` and `size` set to
|
||||
/// `DEFAULT_DISTANCE_GRADIENT_SPREAD` and `DEFAULT_DISTANCE_GRADIENT_SIZE` respectively.
|
||||
pub fn new(gradient: Gradient) -> Self {
|
||||
let spread = DEFAULT_DISTANCE_GRADIENT_SPREAD;
|
||||
let size = DEFAULT_DISTANCE_GRADIENT_SIZE;
|
||||
let spread = DEFAULT_DISTANCE_GRADIENT_SPREAD.into();
|
||||
let size = DEFAULT_DISTANCE_GRADIENT_SIZE.into();
|
||||
let slope = Slope::Smooth;
|
||||
Self { spread, size, slope, gradient }
|
||||
}
|
||||
|
||||
/// Constructor setter for the `spread` field.
|
||||
pub fn spread(mut self, t: f32) -> Self {
|
||||
self.spread = t;
|
||||
pub fn spread(mut self, t: impl Into<Var<f32>>) -> Self {
|
||||
self.spread = t.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Constructor setter for the `size` field.
|
||||
pub fn size(mut self, t: f32) -> Self {
|
||||
self.size = t;
|
||||
pub fn size(mut self, t: impl Into<Var<f32>>) -> Self {
|
||||
self.size = t.into();
|
||||
self
|
||||
}
|
||||
|
||||
@ -153,7 +154,7 @@ impls! {[G:RefInto<Glsl>] From<&SdfSampler<G>> for Glsl {
|
||||
let size = iformat!("{g.size.glsl()}");
|
||||
let offset = iformat!("-shape.sdf.distance + {g.spread.glsl()}");
|
||||
let norm = iformat!("clamp(({offset}) / ({size}))");
|
||||
let t = match g.slope {
|
||||
let t = match &g.slope {
|
||||
Slope::Linear => norm,
|
||||
Slope::Smooth => iformat!("smoothstep(0.0,1.0,{norm})"),
|
||||
Slope::Exponent(exp) => iformat!("pow({norm},{exp.glsl()})"),
|
||||
@ -167,7 +168,7 @@ impls! {[G:RefInto<Glsl>] From<&SdfSampler<G>> for Glsl {
|
||||
macro_rules! define_slope {
|
||||
($($(#$docs:tt)* $name:ident $(($($arg:ident : $arg_type:ty),*))? $fn_name:ident),* $(,)?) => {
|
||||
/// Defines how fast gradient values change.
|
||||
#[derive(Copy,Clone,Debug)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum Slope {
|
||||
$($(#$docs)* $name $(($($arg_type),*))? ),*
|
||||
}
|
||||
@ -191,8 +192,8 @@ define_slope! {
|
||||
Smooth smooth,
|
||||
/// Raises the normalized gradient offset to the given power and uses it as the interpolation
|
||||
/// step.
|
||||
Exponent(exp:f32) exponent,
|
||||
Exponent(exp:Var<f32>) exponent,
|
||||
/// Raises the normalized gradient offset to the given power and uses it as the interpolation
|
||||
/// step.
|
||||
InvExponent(exp:f32) inv_exponent,
|
||||
InvExponent(exp:Var<f32>) inv_exponent,
|
||||
}
|
||||
|
@ -40,6 +40,16 @@ impl PixelDistance for f32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelDistance for Var<f32> {
|
||||
type Output = Var<Pixels>;
|
||||
fn px(&self) -> Self::Output {
|
||||
match self {
|
||||
Var::Static(v) => v.px(),
|
||||
Var::Dynamic(v) => Var::Dynamic(v.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
|
@ -33,6 +33,7 @@ use enso_profiler_data as data;
|
||||
use std::collections;
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === main ===
|
||||
// ============
|
||||
|
Loading…
Reference in New Issue
Block a user