Update component browser design (#3935)

A continuation and replacement for #3832

This PR updates the design of the component browser to match the latest Figma design file.


<img width="588" alt="Screenshot 2022-12-02 at 16 52 51" src="https://user-images.githubusercontent.com/6566674/205297344-d8d46e68-8c46-4e5a-b7f5-5e23014df23f.png">


https://user-images.githubusercontent.com/6566674/205297307-659c633c-a977-4c9f-9903-db72958895b7.mp4

# Important Notes
- Invalid color of the section navigator highlight is caused by a regression [#183915546](https://www.pivotaltracker.com/story/show/183915546)
This commit is contained in:
Ilya Bogdanov 2022-12-05 20:43:27 +03:00 committed by GitHub
parent 768747a55e
commit 410204a3d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1399 additions and 1002 deletions

14
Cargo.lock generated
View File

@ -1644,6 +1644,7 @@ dependencies = [
"ensogl",
"ensogl-hardcoded-theme",
"ide-view-component-list-panel-grid",
"ide-view-component-list-panel-icons",
"wasm-bindgen",
]
@ -4063,7 +4064,6 @@ dependencies = [
"ensogl-grid-view",
"ensogl-gui-component",
"ensogl-hardcoded-theme",
"ensogl-list-view",
"ensogl-scroll-area",
"ensogl-selector",
"ensogl-shadow",
@ -4071,6 +4071,7 @@ dependencies = [
"ensogl-tooltip",
"ide-view-component-list-panel-breadcrumbs",
"ide-view-component-list-panel-grid",
"ide-view-component-list-panel-icons",
"ordered-float",
]
@ -4080,6 +4081,7 @@ version = "0.1.0"
dependencies = [
"enso-frp",
"ensogl-core",
"ensogl-derive-theme",
"ensogl-grid-view",
"ensogl-hardcoded-theme",
"ensogl-text",
@ -4095,13 +4097,21 @@ dependencies = [
"ensogl-grid-view",
"ensogl-gui-component",
"ensogl-hardcoded-theme",
"ensogl-list-view",
"ensogl-shadow",
"ensogl-text",
"failure",
"ide-view-component-list-panel-icons",
"num_enum",
]
[[package]]
name = "ide-view-component-list-panel-icons"
version = "0.1.0"
dependencies = [
"ensogl-core",
"failure",
]
[[package]]
name = "ide-view-graph-editor"
version = "0.1.0"

View File

@ -12,7 +12,6 @@ enso-frp = { path = "../../../../../lib/rust/frp" }
ensogl-core = { path = "../../../../../lib/rust/ensogl/core" }
ensogl-gui-component = { path = "../../../../../lib/rust/ensogl/component/gui/" }
ensogl-grid-view = { path = "../../../../../lib/rust/ensogl/component/grid-view/" }
ensogl-list-view = { path = "../../../../../lib/rust/ensogl/component/list-view/" }
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-derive-theme = { path = "../../../../../lib/rust/ensogl/app/theme/derive" }
ensogl-scroll-area = { path = "../../../../../lib/rust/ensogl/component/scroll-area" }
@ -22,6 +21,7 @@ ensogl-text = { path = "../../../../../lib/rust/ensogl/component/text" }
ensogl-tooltip = { path = "../../../../../lib/rust/ensogl/component/tooltip/" }
ide-view-component-list-panel-breadcrumbs = { path = "breadcrumbs" }
ide-view-component-list-panel-grid = { path = "grid" }
ide-view-component-list-panel-icons = { path = "icons" }
ordered-float = "3.0.0"
[dev-dependencies]

View File

@ -7,6 +7,7 @@ edition = "2021"
[dependencies]
enso-frp = { path = "../../../../../../lib/rust/frp" }
ensogl-core = { path = "../../../../../../lib/rust/ensogl/core" }
ensogl-derive-theme = { path = "../../../../../../lib/rust/ensogl/app/theme/derive" }
ensogl-hardcoded-theme = { path = "../../../../../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-text = { path = "../../../../../../lib/rust/ensogl/component/text" }
ensogl-grid-view = { path = "../../../../../../lib/rust/ensogl/component/grid-view" }

View File

@ -10,6 +10,7 @@ use ensogl_core::data::color;
use ensogl_core::display;
use ensogl_core::display::scene::Layer;
use ensogl_core::Animation;
use ensogl_derive_theme::FromTheme;
use ensogl_grid_view::entry::Contour;
use ensogl_grid_view::entry::EntryFrp;
use ensogl_grid_view::Col;
@ -27,15 +28,20 @@ pub mod separator {
use super::*;
use std::f32::consts::PI;
pub const ICON_WIDTH: f32 = 16.0;
pub const ICON_WIDTH: f32 = 30.0;
ensogl_core::shape! {
above = [ensogl_grid_view::entry::shape];
pointer_events = false;
(style: Style, color: Vector4) {
(style: Style) {
let color = style.get_color(theme::separator::color);
let width = style.get_number(theme::separator::width);
let height = style.get_number(theme::separator::height);
let triangle = Triangle(width, height).rotate((PI/2.0).radians());
let offset_x = style.get_number(theme::separator::offset_x).px();
let offset_y = style.get_number(theme::separator::offset_y).px();
let triangle = triangle.translate_x(offset_x);
let triangle = triangle.translate_y(offset_y);
let shape = triangle.fill(color);
shape.into()
}
@ -47,21 +53,19 @@ pub mod separator {
pub mod ellipsis {
use super::*;
pub const ICON_WIDTH: f32 = 32.0;
pub const ICON_WIDTH: f32 = 28.0;
ensogl_core::shape! {
above = [ensogl_grid_view::entry::shape];
pointer_events = false;
(style: Style, alpha: f32) {
(style: Style) {
let radius = style.get_number(theme::ellipsis::circles_radius).px();
let gap = style.get_number(theme::ellipsis::circles_gap).px();
let background_width = style.get_number(theme::ellipsis::background_width);
let background_height = style.get_number(theme::ellipsis::background_height);
let background_corners_radius = style.get_number(theme::ellipsis::background_corners_radius);
let col = style.get_color(theme::ellipsis::circles_color);
let circles_color = Var::<color::Rgba>::rgba(col.red,col.green,col.blue,alpha.clone());
let bg_col = style.get_color(theme::ellipsis::background_color);
let background_color = Var::<color::Rgba>::rgba(bg_col.red,bg_col.green,bg_col.blue,alpha);
let circles_color = style.get_color(theme::ellipsis::circles_color);
let background_color = style.get_color(theme::ellipsis::background_color);
let tile_size = radius.clone() * 2.0 + gap;
let circles = Circle(radius).repeat((tile_size.clone(), tile_size.clone()));
@ -71,6 +75,10 @@ pub mod ellipsis {
let background = background.corners_radius(background_corners_radius.px());
let background = background.fill(background_color);
let shape = background + circles;
let offset_x = style.get_number(theme::ellipsis::offset_x).px();
let offset_y = style.get_number(theme::ellipsis::offset_y).px();
let shape = shape.translate_x(offset_x);
let shape = shape.translate_y(offset_y);
shape.into()
}
}
@ -78,6 +86,30 @@ pub mod ellipsis {
// =============
// === Style ===
// =============
/// Stylesheet-defined portion of the entries' parameters.
#[allow(missing_docs)]
#[derive(Clone, Debug, Default, FromTheme)]
#[base_path = "theme::entry"]
pub struct Style {
/// The margin of the entry's [`Contour`]. The [`Contour`] specifies the size of the
/// clickable area of the entry. If the margin is zero, the contour covers the entire entry.
pub margin: f32,
pub hover_color: color::Rgba,
#[theme_path = "theme::entry::font"]
pub font_name: ImString,
pub text_y_offset: f32,
pub text_padding_left: f32,
pub text_size: f32,
pub selected_color: color::Rgba,
pub highlight_corners_radius: f32,
pub greyed_out_color: color::Rgba,
}
// =============
// === Model ===
// =============
@ -178,25 +210,24 @@ impl EntryData {
}
}
fn update_layout(&self, contour: Contour, text_size: text::Size, text_padding: f32) {
fn update_layout(&self, contour: Contour, text_padding: f32, text_y_offset: f32) {
let size = contour.size;
self.text.set_xy(Vector2(text_padding - size.x / 2.0, text_size.value / 2.0));
self.separator.size.set(size);
self.ellipsis.size.set(size);
self.text.set_xy(Vector2(text_padding - size.x / 2.0, text_y_offset));
self.separator.size.set(Vector2(separator::ICON_WIDTH, size.y));
self.ellipsis.size.set(Vector2(ellipsis::ICON_WIDTH, size.y));
}
fn set_default_color(&self, color: color::Lcha) {
self.text.set_property_default(color);
self.ellipsis.alpha.set(color.alpha);
self.separator.color.set(color::Rgba::from(color).into());
}
fn set_font(&self, font: String) {
self.text.set_font(font);
}
fn set_default_text_size(&self, size: text::Size) {
self.text.set_property_default(size);
fn set_default_text_size(&self, size: f32) {
self.text.set_property_default(text::Size::new(size));
self.text.set_property_default(text::Weight::Medium);
}
fn is_state_change(&self, model: &Model) -> bool {
@ -231,26 +262,19 @@ impl EntryData {
}
}
// === Params ===
/// The style parameters of Breadcrumbs' entries. See [`ensogl_grid_view::Frp::set_entries_params`].
#[allow(missing_docs)]
#[derive(Clone, Debug, Default)]
pub struct Params {
/// The margin of the entry's [`Contour`]. The [`Contour`] specifies the size of the
/// clickable area of the entry. If the margin is zero, the contour covers the entire entry.
pub margin: f32,
pub hover_color: color::Lcha,
pub font_name: ImString,
pub text_padding: f32,
pub text_size: text::Size,
pub selected_color: color::Lcha,
pub highlight_corners_radius: f32,
pub greyed_out_color: color::Lcha,
pub style: Style,
/// The first greyed out column. All columns to the right will also be greyed out.
pub greyed_out_start: Option<Col>,
pub greyed_out_start: Option<Col>,
}
// === Entry ===
/// A Breadcrumbs entry.
@ -279,15 +303,16 @@ impl ensogl_grid_view::Entry for Entry {
enso_frp::extend! { network
init <- source_();
size <- input.set_size.on_change();
margin <- input.set_params.map(|p| p.margin).on_change();
hover_color <- input.set_params.map(|p| p.hover_color).on_change();
font <- input.set_params.map(|p| p.font_name.clone_ref()).on_change();
text_padding <- input.set_params.map(|p| p.text_padding).on_change();
text_color <- input.set_params.map(|p| p.selected_color).on_change();
text_size <- input.set_params.map(|p| p.text_size).on_change();
greyed_out_color <- input.set_params.map(|p| p.greyed_out_color).on_change();
margin <- input.set_params.map(|p| p.style.margin).on_change();
hover_color <- input.set_params.map(|p| p.style.hover_color).cloned_into().on_change();
font <- input.set_params.map(|p| p.style.font_name.clone_ref()).on_change();
text_padding <- input.set_params.map(|p| p.style.text_padding_left).on_change();
text_color <- input.set_params.map(|p| p.style.selected_color).cloned_into().on_change();
text_y_offset <- input.set_params.map(|p| p.style.text_y_offset).on_change();
text_size <- input.set_params.map(|p| p.style.text_size).on_change();
greyed_out_color <- input.set_params.map(|p| p.style.greyed_out_color).cloned_into().on_change();
highlight_corners_radius <- input.set_params.map(|p| p.style.highlight_corners_radius).on_change();
greyed_out_from <- input.set_params.map(|p| p.greyed_out_start).on_change();
highlight_corners_radius <- input.set_params.map(|p| p.highlight_corners_radius).on_change();
transparent_color <- init.constant(color::Lcha::transparent());
col <- input.set_location._1();
@ -310,8 +335,8 @@ impl ensogl_grid_view::Entry for Entry {
size: *size - Vector2(*margin, *margin) * 2.0,
corners_radius: 0.0,
});
layout <- all(contour, text_size, text_padding);
eval layout ((&(c, ts, to)) data.update_layout(c, ts, to));
layout <- all(contour, text_padding, text_y_offset);
eval layout ((&(c, to, tyo)) data.update_layout(c, to, tyo));
eval color((c) data.set_default_color(*c));
eval font((f) data.set_font(f.to_string()));
eval text_size((s) data.set_default_text_size(*s));
@ -335,8 +360,8 @@ impl ensogl_grid_view::Entry for Entry {
data.width(*text_padding)
})
);
text_width <- data.text.width.filter(f_!(data.is_text_displayed()));
// For text entries, we also listen for [`Text::width`] changes.
text_width <- data.text.width.filter(f_!(data.is_text_displayed()));
entry_width <- text_width.map2(&text_padding, f!((w, o) data.text_width(*w, *o)));
out.override_column_width <+ entry_width;
}

View File

@ -52,7 +52,6 @@ use ensogl_core::Animation;
use ensogl_grid_view as grid_view;
use ensogl_grid_view::Viewport;
use ensogl_hardcoded_theme::application::component_browser as component_browser_theme;
use ensogl_text as text;
use entry::Entry;
use grid_view::Col;
@ -175,57 +174,17 @@ impl Model {
);
grid.model_for_entry <+ requested_entry;
}
let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let params = Self::params_from_style(&style, &network, init.clone_ref());
let style_frp = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let style = entry::Style::from_theme(&network, &style_frp);
frp::extend! { network
params <- style.update.map(|s| entry::Params { style: s.clone(), greyed_out_start: None });
grid.set_entries_params <+ params;
}
init.emit(());
style.init.emit(());
Self { display_object, grid, entries, network, mask, show_ellipsis }
}
/// Prepare an [`frp::Sampler`] that emits the parameters for the grid view's entries on
/// style sheet changes.
fn params_from_style(
style: &StyleWatchFrp,
network: &frp::Network,
init: frp::Source<()>,
) -> frp::Sampler<entry::Params> {
frp::extend! { network
let margin = style.get_number(theme::entry::margin);
let hover_color = style.get_color(theme::entry::hover_color);
let font = style.get_text(theme::entry::font);
let text_padding = style.get_number(theme::entry::text_padding_left);
let text_size = style.get_number(theme::entry::text_size);
let selected_color = style.get_color(theme::entry::selected_color);
let highlight_corners_radius = style.get_number(theme::entry::highlight_corners_radius);
let greyed_out_color = style.get_color(theme::entry::greyed_out_color);
greyed_out_start <- init.constant(None);
text_params <- all4(&init, &text_padding,&text_size,&font);
colors <- all4(&init, &hover_color,&selected_color,&greyed_out_color);
params <- all_with6(&init,&margin,&text_params,&colors,&highlight_corners_radius,
&greyed_out_start,
|_,&margin,text_params,colors,&highlight_corners_radius,&greyed_out_start| {
let (_, text_padding,text_size,font) = text_params;
let (_, hover_color,selected_color,greyed_out_color) = colors;
entry::Params {
margin,
text_padding: *text_padding,
text_size: text::Size::from(*text_size),
hover_color: hover_color.into(),
font_name: font.clone(),
selected_color: selected_color.into(),
highlight_corners_radius,
greyed_out_color: greyed_out_color.into(),
greyed_out_start
}
}
);
params_sampler <- params.sampler();
}
params_sampler
}
fn set_layers(&self, layers: Layers) {
layers.mask.add(&self.mask);
@ -498,8 +457,8 @@ impl Breadcrumbs {
eval input.set_entry(((index, entry)) model.set_entry(entry, *index));
out.selected <+ selected;
scroll_anim.target <+ all_with3(&model.grid.content_size, &input.set_size, &model.grid
.entry_selected,
scroll_anim.target <+ all_with3(
&model.grid.content_size, &input.set_size, &model.grid.entry_selected,
f!((content_size, size, _) {
model.update_layout(*content_size, *size);
model.offset(*content_size, *size)

View File

@ -11,8 +11,8 @@ ensogl-derive-theme = { path = "../../../../../../lib/rust/ensogl/app/theme/deri
ensogl-hardcoded-theme = { path = "../../../../../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-text = { path = "../../../../../../lib/rust/ensogl/component/text" }
ensogl-grid-view = { path = "../../../../../../lib/rust/ensogl/component/grid-view" }
ensogl-list-view = { path = "../../../../../../lib/rust/ensogl/component/list-view" }
ensogl-gui-component = { path = "../../../../../../lib/rust/ensogl/component/gui" }
ensogl-shadow = { version = "0.1.0", path = "../../../../../../lib/rust/ensogl/component/shadow" }
ide-view-component-list-panel-icons = { path = "../icons" }
failure = "0.1.8"
num_enum = "0.5.1"

View File

@ -5,8 +5,8 @@ use ensogl_core::display::shape::*;
use crate::content::GroupId;
use crate::content::SectionId;
use crate::entry::style::ColorIntensities;
use crate::entry::style::Colors;
use crate::entry::style::ResolvedColors;
use crate::GroupColors;
use crate::Style as GridStyle;
@ -196,11 +196,11 @@ impl DimmedGroups {
#[allow(missing_docs)]
#[derive(Clone, Debug, Default)]
pub struct Params {
pub style: Style,
pub grid_style: GridStyle,
pub color_intensities: ColorIntensities,
pub group_colors: GroupColors,
pub dimmed_groups: DimmedGroups,
pub style: Style,
pub grid_style: GridStyle,
pub colors: Colors,
pub group_colors: GroupColors,
pub dimmed_groups: DimmedGroups,
}
@ -216,8 +216,7 @@ pub struct Params {
#[derive(Debug)]
struct CurrentIcon {
display_object: display::object::Instance,
vivid_color: color::Lcha,
dull_color: color::Lcha,
color: color::Lcha,
shape: Option<icon::Any>,
id: Option<icon::Id>,
}
@ -226,8 +225,7 @@ impl Default for CurrentIcon {
fn default() -> Self {
Self {
display_object: display::object::Instance::new(),
vivid_color: default(),
dull_color: default(),
color: default(),
shape: default(),
id: default(),
}
@ -240,8 +238,7 @@ impl CurrentIcon {
self.id = new_icon;
if let Some(icon_id) = new_icon {
let shape = icon_id.create_shape(Vector2(icon::SIZE, icon::SIZE));
shape.set_vivid_color(color::Rgba::from(self.vivid_color).into());
shape.set_dull_color(color::Rgba::from(self.dull_color).into());
shape.set_color(color::Rgba::from(self.color).into());
self.display_object.add_child(&shape);
self.shape = Some(shape);
} else {
@ -250,17 +247,10 @@ impl CurrentIcon {
}
}
fn set_vivid_color(&mut self, color: color::Lcha) {
self.vivid_color = color;
fn set_color(&mut self, color: color::Lcha) {
self.color = color;
if let Some(shape) = &self.shape {
shape.set_vivid_color(color::Rgba::from(color).into());
}
}
fn set_dull_color(&mut self, color: color::Lcha) {
self.dull_color = color;
if let Some(shape) = &self.shape {
shape.set_dull_color(color::Rgba::from(color).into());
shape.set_color(color::Rgba::from(color).into());
}
}
}
@ -326,26 +316,30 @@ impl Data {
Kind::LocalScopeEntry { .. } => entry_size.x,
_ => grid_style.column_width(),
};
let bg_height = entry_size.y - gap_over_header + overlap;
let bg_height = entry_size.y + overlap;
// See comment in [`Self::update_shadow`] method.
let shadow_addition = self.background.size.get().y - self.background.height.get();
let bg_sprite_height = bg_height + shadow_addition;
let bg_y = -gap_over_header / 2.0 + overlap / 2.0 + local_scope_offset;
let bg_y = -gap_over_header + overlap / 2.0 + local_scope_offset;
self.background.set_y(bg_y);
self.background.size.set(Vector2(bg_width, bg_sprite_height));
self.background.height.set(bg_height);
let left = -entry_size.x / 2.0 + style.padding;
let width = grid_style.column_width();
let left = -width / 2.0 + style.padding;
let icon_x = left + style.icon_size / 2.0;
let icon_y = local_scope_offset;
self.icon.borrow().set_xy(Vector2(icon_x, icon_y));
let text_y_offset = match kind {
Kind::Header => style.text_y_offset_header,
_ => style.text_y_offset,
};
let text_x = Self::text_x_position(kind, style, grid_style);
let text_y = style.text_size / 2.0 + local_scope_offset;
let text_y = text_y_offset + local_scope_offset;
self.label.set_xy(Vector2(text_x, text_y));
}
fn contour(kind: Kind, grid_style: &GridStyle, entry_size: Vector2) -> Contour {
let optional_gap = if kind == Kind::Header { grid_style.column_gap } else { 0.0 };
let height = entry_size.y - optional_gap;
fn contour(grid_style: &GridStyle, entry_size: Vector2) -> Contour {
let height = entry_size.y;
Contour::rectangular(Vector2(grid_style.column_width(), height))
}
@ -356,7 +350,7 @@ impl Data {
fn contour_offset(kind: Kind, grid_style: &GridStyle) -> Vector2 {
let y = match kind {
Kind::Header => -grid_style.column_gap / 2.0,
Kind::Header => -grid_style.column_gap,
Kind::LocalScopeEntry { .. } => -grid_style.column_gap,
_ => 0.0,
};
@ -372,7 +366,7 @@ impl Data {
fn text_x_position(kind: Kind, style: &Style, grid_style: &GridStyle) -> f32 {
let left = -grid_style.column_width() / 2.0 + style.padding;
if kind == Kind::Header {
left
left + style.text_x_offset_header
} else {
left + style.icon_size + style.icon_text_padding
}
@ -444,7 +438,7 @@ impl grid_view::Entry for View {
kind <- input.set_model.map(|m| m.kind).on_change();
style <- input.set_params.map(|p| p.style.clone()).on_change();
color_intensities <- input.set_params.map(|p| p.color_intensities).on_change();
colors <- input.set_params.map(|p| p.colors).on_change();
group_colors <- input.set_params.map(|p| p.group_colors).on_change();
grid_style <- input.set_params.map(|p| p.grid_style).on_change();
kind_and_style <- all(kind, style, grid_style);
@ -452,8 +446,8 @@ impl grid_view::Entry for View {
eval layout_data ((((kind, style, grid_style), entry_sz))
data.update_layout(*kind, style, grid_style, *entry_sz)
);
out.contour <+ layout_data.map(|((kind, _, grid_style), entry_sz)| {
Data::contour(*kind, grid_style, *entry_sz)
out.contour <+ all_with(&grid_style, &input.set_size, |grid_style, entry_sz| {
Data::contour(grid_style, *entry_sz)
});
out.contour_offset <+ kind_and_style.map(|(k, _, gs)| Data::contour_offset(*k, gs));
out.highlight_contour <+ out.contour.map2(&style, |c, s| Data::highlight_contour(*c, s));
@ -467,11 +461,10 @@ impl grid_view::Entry for View {
is_dimmed <- all_with(&input.set_model, &input.set_params, |m,p| {
p.dimmed_groups.is_group_dimmed(m.group_id)
});
let colors = Colors::from_main_color(network, &data.style, &color, &color_intensities, &is_dimmed);
let colors = ResolvedColors::from_main_color(network, &data.style, &color, &colors, &is_dimmed);
eval colors.background ((c) data.background.color.set(color::Rgba::from(c).into()));
data.label.set_property_default <+ colors.text.ref_into_some();
eval colors.icon_strong ((c) data.icon.borrow_mut().set_vivid_color(*c));
eval colors.icon_weak ((c) data.icon.borrow_mut().set_dull_color(*c));
eval colors.icon ((c) data.icon.borrow_mut().set_color(*c));
out.hover_highlight_color <+ colors.hover_highlight;
// We want to animate only when params changed (the different section is highlighted).
// Other case, where entry receives new model with new section means it is reused
@ -494,15 +487,20 @@ impl grid_view::Entry for View {
data.label.set_view_width <+ max_text_width.some();
content_changed <- data.label.content.constant(());
style_changed <- style.constant(());
highlight_range <= all_with3(
&input.set_model,
&content_changed,
&style_changed,
|m, (), ()| m.highlighted.deref().clone()
);
label_updated <- all3(&input.set_model, &content_changed, &style_changed);
highlight_range <= label_updated.map(|(m, (), ())| m.highlighted.deref().clone());
data.label.set_property <+ highlight_range.map2(&style, |range, s| {
(range.into(), Some(text::SdfWeight::new(s.highlight_bold).into()))
});
is_header <- label_updated.map(|(m, (), ())| m.kind == Kind::Header);
is_not_header <- is_header.on_false();
is_header <- is_header.on_true();
data.label.set_property <+ is_header.map(|_| {
((..).into(), Some(text::Weight::ExtraBold.into()))
});
data.label.set_property <+ is_not_header.map(|_| {
((..).into(), Some(text::Weight::Medium.into()))
});
data.label.set_property_default <+ style.map(|s| text::Size::new(s.text_size)).cloned_into_some();
eval icon ((&icon) data.icon.borrow_mut().update(icon));
data.label.set_font <+ style.map(|s| s.font.clone_ref()).on_change();

View File

@ -1,94 +0,0 @@
//! A module containing the list view entry type which represents an arbitrary icon.
use ensogl_core::prelude::*;
use crate::icon;
use enso_frp as frp;
use ensogl_core::application::Application;
use ensogl_core::data::color;
use ensogl_core::display;
use ensogl_core::display::scene::Layer;
use ensogl_core::display::style::Path;
use ensogl_list_view as list_view;
// =================
// === IconEntry ===
// =================
/// List view entry type which represents a single icon. We use list view with icons instead of
/// three separate buttons to simplify the implementation.
#[derive(Debug, Clone, CloneRef)]
pub struct Entry {
display_object: display::object::Instance,
icon: Rc<RefCell<Option<icon::Any>>>,
icon_id: Rc<Cell<Option<icon::Id>>>,
params: Params,
}
impl display::Object for Entry {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
}
}
impl list_view::Entry for Entry {
type Model = icon::Id;
type Params = Params;
fn new(_app: &Application, _style_prefix: &Path, params: &Self::Params) -> Self {
let display_object = display::object::Instance::new();
let icon: Rc<RefCell<Option<icon::Any>>> = default();
let icon_id = default();
let network = frp::Network::new("searcher_list_panel::navigator::Icon");
frp::extend! { network
eval params.vivid_color((&c)
icon.borrow().as_ref().map(|icon| icon.set_vivid_color(c.into()))
);
eval params.dull_color((&c)
icon.borrow().as_ref().map(|icon| icon.set_dull_color(c.into()))
);
}
Self { display_object, icon, icon_id, params: params.clone_ref() }
}
fn update(&self, model: &Self::Model) {
if !self.icon_id.get().contains(model) {
let size = Vector2(icon::SIZE, icon::SIZE);
let icon = model.create_shape(size);
icon.set_vivid_color(self.params.vivid_color.value().into());
icon.set_dull_color(self.params.dull_color.value().into());
self.display_object.add_child(&icon);
*self.icon.borrow_mut() = Some(icon);
self.icon_id.set(Some(*model));
}
}
fn set_max_width(&self, _max_width_px: f32) {}
fn set_label_layer(&self, _label_layer: &Layer) {}
}
// === IconParams ===
/// Entry parameters of the icon.
#[derive(Clone, CloneRef, Debug)]
pub struct Params {
/// Strong (darker, or more contrasting) color parameter.
pub vivid_color: frp::Sampler<color::Rgba>,
/// Weak (lighter, or less contrasting) color parameter.
pub dull_color: frp::Sampler<color::Rgba>,
}
impl Default for Params {
fn default() -> Self {
let network = frp::Network::new("searcher_list_panel::navigator::Params::default");
frp::extend! { network
default_color <- source::<color::Rgba>().sampler();
}
Self { vivid_color: default_color.clone_ref(), dull_color: default_color.clone_ref() }
}
}

View File

@ -5,65 +5,141 @@ use crate::prelude::*;
use enso_frp as frp;
use ensogl_core::data::color;
use ensogl_core::display::shape::StyleWatchFrp;
use ensogl_core::Animation;
use ensogl_core::display::style::data::DataMatch;
use ensogl_derive_theme::FromTheme;
use ensogl_hardcoded_theme::application::component_browser::component_list_panel as panel_theme;
use ensogl_hardcoded_theme::application::component_browser::component_list_panel::grid as grid_theme;
use entry_theme::highlight::selection as selection_theme;
use grid_theme::entry as entry_theme;
// =============
// === Color ===
// =============
/// Color of the different parts of the entry.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Color {
/// The final color is "main" color of the component group with the specified alpha value.
#[allow(missing_docs)]
ComponentGroup { alpha_multiplier: f32 },
/// The final color is defined as an arbitrary color in the stylesheet.
Arbitrary(color::Lcha),
}
impl Default for Color {
fn default() -> Self {
Self::Arbitrary(color::Lcha::black())
}
}
impl Color {
/// Get the final color by either taking the value from [`Self::Arbitrary`] or by applying the
/// specified transparency from [`Self::MainColorWithAlpha`] to [`main`] color.
fn resolve(&self, main: &color::Lcha) -> color::Lcha {
match self {
Self::ComponentGroup { alpha_multiplier } => main.multiply_alpha(*alpha_multiplier),
Self::Arbitrary(color) => *color,
}
}
/// A custom accessor for retrieving the color from the stylesheet using the [`FromTheme`]
/// macro. In the stylesheet, the color can be defined as either a `color::Rgba` or a `float`
/// value for the mixing coefficient. This accessor produces the corresponding variants of
/// [`Color`] or returns a default value ([`Color::MainColorWithAlpha(0.0)`]) if there is no
/// such property in the stylesheet.
fn accessor<P: Into<ensogl_core::display::style::Path>>(
network: &frp::Network,
style: &StyleWatchFrp,
path: P,
) -> frp::Sampler<Self> {
let path = path.into();
let value = style.get(path.clone());
frp::extend! { network
init <- source_();
color <- value.all_with(&init, move |data, _| {
data.color().map(|color| {
let color = color::Lcha::from(color);
Color::Arbitrary(color)
}).unwrap_or_else(|| {
let alpha_multiplier = match data.number() {
Some(number) => number,
None => {
error!("Neither color nor alpha defined for {path}.");
0.0
}
};
Color::ComponentGroup { alpha_multiplier }
})
});
sampler <- color.sampler();
}
init.emit(());
sampler
}
}
// =============
// === Style ===
// =============
// === Color Intensities ===
// === Colors ===
/// The intensities of various parts of Component Entry view. The actual color is computed by mixing
/// the main groups color with the application background - see [`Colors`] for more information.
/// The colors of various parts of the Component Entry view. The actual color can be computed by
/// modifying the transparency of the "main" color of the component group - see [`Color`] for more
/// information.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Default, PartialEq, FromTheme)]
pub struct ColorIntensities {
#[theme_path = "entry_theme::text::color_intensity"]
pub text: f32,
#[theme_path = "entry_theme::background::color_intensity"]
pub background: f32,
#[theme_path = "entry_theme::highlight::hover::color_intensity"]
pub hover_highlight: f32,
#[theme_path = "entry_theme::dimmed::color_intensity"]
pub dimmed: f32,
/// The more contrasting parts of the [icon](crate::icon::Any).
#[theme_path = "entry_theme::icon::strong_color_intensity"]
pub icon_strong: f32,
/// The less contrasting parts of the [icon](crate::icon::Any).
#[theme_path = "entry_theme::icon::weak_color_intensity"]
pub icon_weak: f32,
pub struct Colors {
#[theme_path = "entry_theme::text::color"]
#[accessor = "Color::accessor"]
pub text: Color,
#[theme_path = "entry_theme::background::intensity"]
pub background_intensity: f32,
#[theme_path = "entry_theme::highlight::hover::color"]
#[accessor = "Color::accessor"]
pub hover_highlight: Color,
#[theme_path = "entry_theme::icon::color"]
#[accessor = "Color::accessor"]
pub icon: Color,
/// The "main color" of dimmed component groups. For dimmed component groups,
/// [`ResolvedColors`] would use this value instead of "main" color of the component group.
#[theme_path = "entry_theme::dimmed"]
#[accessor = "Color::accessor"]
pub dimmed: Color,
}
/// The intensities of various parts of selected Component Entry view. A subset of
/// [`ColorIntensities`], but `FromTheme` derive takes different style's paths, plus unrelated
/// The colors of various parts of selected the Component Entry view. A subset of
/// [`StyleColors`], but `FromTheme` derive takes different style's paths, plus unrelated
/// entries are omitted.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Default, PartialEq, FromTheme)]
pub struct SelectionColorIntensities {
#[theme_path = "selection_theme::text::color_intensity"]
pub text: f32,
#[theme_path = "selection_theme::background::color_intensity"]
pub background: f32,
/// The more contrasting parts of the [icon](crate::icon::Any).
#[theme_path = "selection_theme::icon_strong::color_intensity"]
pub icon_strong: f32,
/// The less contrasting parts of the [icon](crate::icon::Any).
#[theme_path = "selection_theme::icon_weak::color_intensity"]
pub icon_weak: f32,
pub struct SelectionColors {
#[theme_path = "selection_theme::text::color"]
#[accessor = "Color::accessor"]
pub text: Color,
#[theme_path = "selection_theme::background::intensity"]
pub background_intensity: f32,
#[theme_path = "selection_theme::icon::color"]
#[accessor = "Color::accessor"]
pub icon: Color,
/// The main color of dimmed component groups. Selection is never displayed in a dimmed
/// component group. Still, we need to duplicate this parameter to avoid sudden color
/// changes while the selection shape is animated and moves through different component
/// groups.
#[theme_path = "entry_theme::dimmed"]
#[accessor = "Color::accessor"]
pub dimmed: Color,
}
impl From<SelectionColorIntensities> for ColorIntensities {
fn from(selection: SelectionColorIntensities) -> Self {
let SelectionColorIntensities { text, background, icon_strong, icon_weak } = selection;
let dimmed = 1.0;
let hover_highlight = background;
Self { text, background, icon_weak, icon_strong, dimmed, hover_highlight }
impl From<SelectionColors> for Colors {
fn from(selection: SelectionColors) -> Self {
let SelectionColors { text, background_intensity, icon, dimmed } = selection;
let hover_highlight = default();
Self { text, background_intensity, icon, dimmed, hover_highlight }
}
}
@ -82,6 +158,12 @@ pub struct Style {
pub icon_size: f32,
#[theme_path = "entry_theme::text::size"]
pub text_size: f32,
#[theme_path = "entry_theme::text::y_offset"]
pub text_y_offset: f32,
#[theme_path = "entry_theme::text::y_offset_header"]
pub text_y_offset_header: f32,
#[theme_path = "entry_theme::text::x_offset_header"]
pub text_x_offset_header: f32,
/// The distance between right edge of the icon and left edge of the caption.
#[theme_path = "entry_theme::icon::text_padding"]
pub icon_text_padding: f32,
@ -97,75 +179,73 @@ pub struct Style {
// ==============
// === Colors ===
// ==============
// ======================
// === ResolvedColors ===
// ======================
/// Colors used in the Component Group Entries.
///
/// This structure can be created from single "main color" input. Each of these colors will be
/// computed by mixing "main color" with application background.
///
/// `icon_strong` and `icon_weak` parameters represent the more/less contrasting parts of the
/// [icon](crate::icon::Any), they do not represent highlighted state of the icon.
/// This structure can be created from a single "main color" input. Each of these colors can be
/// computed by modifying the transparency of the "main color". See [`Color`] for more information.
#[allow(missing_docs)]
#[derive(Clone, CloneRef, Debug)]
pub struct Colors {
pub struct ResolvedColors {
pub background: frp::Sampler<color::Lcha>,
pub hover_highlight: frp::Sampler<color::Lcha>,
pub text: frp::Sampler<color::Lcha>,
pub icon_strong: frp::Sampler<color::Lcha>,
pub icon_weak: frp::Sampler<color::Lcha>,
pub icon: frp::Sampler<color::Lcha>,
pub skip_animations: frp::Any,
}
impl Colors {
impl ResolvedColors {
/// Create color outputs from given main color and style.
///
/// Each of these colors will be computed by mixing "main color" with application background
/// according to the proper field in [`Style::color_intensities`]. The dimming of group is
/// animated.
/// Each of these colors can be computed by modifying the transparency of the "main color". See
/// [`Color`] for more information. All color changes are animated.
pub fn from_main_color(
network: &frp::Network,
style_watch: &StyleWatchFrp,
color: &frp::Stream<color::Lcha>,
color_intensities: &frp::Stream<ColorIntensities>,
main_color: &frp::Stream<color::Lcha>,
colors: &frp::Stream<Colors>,
is_dimmed: &frp::Stream<bool>,
) -> Self {
fn mix((c1, c2): &(color::Lcha, color::Lcha), coefficient: &f32) -> color::Lcha {
color::mix(*c1, *c2, *coefficient)
}
let app_bg = style_watch.get_color(ensogl_hardcoded_theme::application::background);
let color_intensities = color_intensities.clone_ref();
let panel_background = style_watch.get_color(panel_theme::background_color);
let colors = colors.clone_ref();
let color_anim = color::Animation::new(network);
let intensity = Animation::new(network);
frp::extend! { network
init <- source_();
text_intensity <- color_intensities.map(|c| c.text);
bg_intensity <- color_intensities.map(|c| c.background);
hover_hg_intensity <- color_intensities.map(|c| c.hover_highlight);
dimmed_intensity <- color_intensities.map(|c| c.dimmed);
icon_strong_intensity <- color_intensities.map(|c| c.icon_strong);
icon_weak_intensity <- color_intensities.map(|c| c.icon_weak);
one <- init.constant(1.0);
let is_dimmed = is_dimmed.clone_ref();
intensity.target <+ is_dimmed.switch(&one, &dimmed_intensity);
app_bg <- all_with(&app_bg, &init, |col, ()| color::Lcha::from(col));
app_bg_and_input <- all(&app_bg, color);
main <- app_bg_and_input.all_with(&intensity.value, mix);
app_bg_and_main <- all(&app_bg, &main);
background <- app_bg_and_main.all_with(&bg_intensity, mix).sampler();
hover_highlight <- app_bg_and_main.all_with(&hover_hg_intensity, mix).sampler();
text <- app_bg_and_main.all_with(&text_intensity, mix).sampler();
icon_weak <- app_bg_and_main.all_with(&icon_weak_intensity, mix).sampler();
icon_strong <- app_bg_and_main.all_with(&icon_strong_intensity, mix).sampler();
dimmed <- all_with3(&init, main_color, &colors,
|_, main, colors| colors.dimmed.resolve(main)
);
color_anim.target <+ switch(&is_dimmed, main_color, &dimmed);
// We do not support the semi-transparent background of entries. Because headers share
// the same background color; therefore, the semi-transparent header's background
// reveals the underlying entries. Instead, we mix the color of the component browser's
// background and the main color of the component group.
panel_bg <- all_with(&panel_background, &init, |col, ()| color::Lcha::from(col));
panel_bg_and_main <- all(&panel_bg, &color_anim.value);
bg_intensity <- colors.map(|c| c.background_intensity);
background <- all_with(&panel_bg_and_main, &bg_intensity,
|(bg, main), intensity| color::mix(*bg, *main, *intensity)
).sampler();
hover_highlight <- all_with(&color_anim.value, &colors,
|main, colors| colors.hover_highlight.resolve(main)
).sampler();
text <- all_with(&color_anim.value, &colors,
|main, colors| colors.text.resolve(main)
).sampler();
icon <- all_with(&color_anim.value, &colors,
|main, colors| colors.icon.resolve(main)
).sampler();
skip_animations <- any(...);
intensity.skip <+ skip_animations;
color_anim.skip <+ skip_animations;
}
init.emit(());
Self { icon_weak, icon_strong, text, background, hover_highlight, skip_animations }
Self { icon, text, background, hover_highlight, skip_animations }
}
}

View File

@ -524,10 +524,10 @@ impl Model {
fn entries_params(
&self,
(style, entry_style, color_intensities, group_colors): &(
(style, entry_style, colors, group_colors): &(
Style,
entry::Style,
entry::style::ColorIntensities,
entry::style::Colors,
GroupColors,
),
dimmed_groups: entry::DimmedGroups,
@ -536,16 +536,16 @@ impl Model {
style: entry_style.clone(),
grid_style: *style,
group_colors: *group_colors,
color_intensities: *color_intensities,
colors: *colors,
dimmed_groups,
}
}
fn selection_entries_params(
&self,
(base_params, color_intensities): &(entry::Params, entry::style::SelectionColorIntensities),
(base_params, colors): &(entry::Params, entry::style::SelectionColors),
) -> entry::Params {
entry::Params { color_intensities: (*color_intensities).into(), ..base_params.clone() }
entry::Params { colors: (*colors).into(), ..base_params.clone() }
}
fn navigation_scroll_margins(
@ -599,9 +599,8 @@ impl component::Frp<Model> for Frp {
let corners_radius = style_frp.get_number(panel_theme::corners_radius);
let style = Style::from_theme(network, style_frp);
let entry_style = entry::Style::from_theme(network, style_frp);
let color_intensities = entry::style::ColorIntensities::from_theme(network, style_frp);
let selection_color_intensities =
entry::style::SelectionColorIntensities::from_theme(network, style_frp);
let colors = entry::style::Colors::from_theme(network, style_frp);
let selection_colors = entry::style::SelectionColors::from_theme(network, style_frp);
frp::extend! { network
// === Active and Hovered Entry ===
@ -637,7 +636,7 @@ impl component::Frp<Model> for Frp {
group_colors <- all_with(&groups, &local_scope_group, |&((), g0, g1, g2, g3, g4, g5), ls| {
GroupColors {
variants: [g0, g1, g2, g3, g4, g5].map(color::Lcha::from),
local_scope_group: ls.into()
local_scope_group: ls.into(),
}
});
@ -650,10 +649,10 @@ impl component::Frp<Model> for Frp {
None => entry::DimmedGroups::None,
});
entries_style <-
all4(&style.update, &entry_style.update, &color_intensities.update, &group_colors);
all4(&style.update, &entry_style.update, &colors.update, &group_colors);
entries_params <-
all_with(&entries_style, &dimmed_groups, f!((s, d) model.entries_params(s, *d)));
selection_entries_style <- all(entries_params, selection_color_intensities.update);
selection_entries_style <- all(entries_params, selection_colors.update);
selection_entries_params <-
selection_entries_style.map(f!((input) model.selection_entries_params(input)));
grid_scroll_frp.resize <+ style_and_content_size.map(Model::grid_size);
@ -732,8 +731,8 @@ impl component::Frp<Model> for Frp {
grid.resize_grid(0, column::COUNT);
style.init.emit(());
entry_style.init.emit(());
color_intensities.init.emit(());
selection_color_intensities.init.emit(());
colors.init.emit(());
selection_colors.init.emit(());
}
fn default_shortcuts() -> Vec<Shortcut> {

View File

@ -0,0 +1,9 @@
[package]
name = "ide-view-component-list-panel-icons"
version = "0.1.0"
authors = ["Enso Team <contact@enso.org>"]
edition = "2021"
[dependencies]
ensogl-core = { path = "../../../../../../lib/rust/ensogl/core" }
failure = "0.1.8"

View File

@ -36,9 +36,9 @@ pub fn grid(stroke_width: f32, cell_size: f32) -> AnyShape {
/// A cursor shape, looking roughly like a capital "I".
pub fn cursor() -> AnyShape {
let middle = Rect((1.0.px(), 15.0.px()));
let top = Rect((5.0.px(), 1.0.px())).translate_y(7.5.px());
let bottom = Rect((5.0.px(), 1.0.px())).translate_y((-7.5).px());
let middle = Rect((2.0.px(), 16.0.px()));
let top = Rect((6.0.px(), 2.0.px())).translate_y(7.0.px());
let bottom = Rect((6.0.px(), 2.0.px())).translate_y((-7.0).px());
(middle + top + bottom).into()
}
@ -57,18 +57,24 @@ pub fn arc(outer_radius: f32, stroke_width: f32, start_angle: f32, end_angle: f3
(circle * angle).into()
}
/// The shape of a table, given by a grid with size `columns` x `rows`. The stroke width is 1.0 and
/// the cell size 4.0. The origin is at the lower left corner.
pub fn table(columns: i32, rows: i32) -> AnyShape {
const STROKE_WIDTH: f32 = 1.0;
const CELL_SIZE: f32 = 4.0;
/// An 2d-array of squares of size `cell_size`.
pub fn table(columns: i32, rows: i32, cell_size: f32) -> AnyShape {
const GAP: f32 = 1.0;
let width = columns as f32 * CELL_SIZE + STROKE_WIDTH;
let height = rows as f32 * CELL_SIZE + STROKE_WIDTH;
let bounds =
Rect((width.px(), height.px())).translate(((width / 2.0).px(), (height / 2.0).px()));
let grid = grid(STROKE_WIDTH, CELL_SIZE);
(grid * bounds).into()
let cell = Rect((cell_size.px(), cell_size.px()));
let table = cell.repeat(((cell_size + GAP).px(), (cell_size + GAP).px()));
let table = table.translate_x((-cell_size / 2.0 - GAP).px());
let table = table.translate_y((-cell_size / 2.0 - GAP).px());
let width = (cell_size + GAP) * columns as f32 - GAP;
let height = (cell_size + GAP) * rows as f32 - GAP;
let bounds = Rect((width.px(), height.px()));
let bounds = bounds.translate_x((width / 2.0).px());
let bounds = bounds.translate_y((height / 2.0).px());
let shape = table * bounds;
shape.into()
}
/// A plus, consisting of two strokes of length `size` and width `stroke_width`, intersecting at the

View File

@ -17,8 +17,7 @@
/// use ensogl_core::prelude::*;
/// use ensogl_core::display::shape::*;
/// use ensogl_core::data::color;
/// use ide_view_component_list_panel_grid::entry::icon;
/// use ide_view_component_list_panel_grid::define_icons;
/// use ide_view_component_list_panel_icons::define_icons;
///
/// define_icons! {
/// /// The example of icon.
@ -28,7 +27,7 @@
/// //
/// // `use super::*` import is added silently.
/// ensogl_core::shape! {
/// (style:Style, vivid_color: Vector4, dull_color: Vector4) {
/// (style:Style, color: Vector4) {
/// Plane().into()
/// }
/// }
@ -36,8 +35,8 @@
///
/// pub mod icon2(Icon2) {
/// ensogl_core::shape! {
/// (style:Style, vivid_color: Vector4, dull_color: Vector4) {
/// Plane().fill(vivid_color).into()
/// (style:Style, color: Vector4) {
/// Plane().fill(color).into()
/// }
/// }
/// }
@ -81,30 +80,22 @@ macro_rules! define_icons {
impl Id {
/// Create icon's shape with given size.
pub fn create_shape(&self, size: Vector2) -> $crate::entry::icon::Any {
pub fn create_shape(&self, size: Vector2) -> $crate::Any {
match self {$(
Self::$variant => {
let view = $name::View::new();
view.size.set(size);
let vivid_color_fn = Box::new(f!([view]()
color::Lcha::from(color::Rgba::from(view.vivid_color.get()))
let color_fn = Box::new(f!([view]()
color::Lcha::from(color::Rgba::from(view.color.get()))
));
let dull_color_fn = Box::new(f!([view]()
color::Lcha::from(color::Rgba::from(view.dull_color.get()))
));
let set_vivid_color_fn = Box::new(f!((c)
view.vivid_color.set(color::Rgba::from(c).into())
));
let set_dull_color_fn = Box::new(f!((c)
view.dull_color.set(color::Rgba::from(c).into())
let set_color_fn = Box::new(f!((c)
view.color.set(color::Rgba::from(c).into())
));
let view = Box::new(view);
$crate::entry::icon::Any {
$crate::Any {
view,
vivid_color_fn,
dull_color_fn,
set_vivid_color_fn,
set_dull_color_fn
color_fn,
set_color_fn,
}
}
)*}
@ -124,7 +115,7 @@ macro_rules! define_icons {
}
impl FromStr for Id {
type Err = $crate::entry::icon::UnknownIcon;
type Err = $crate::UnknownIcon;
fn from_str(s: &str) -> Result<Id, Self::Err> {
match s {
$(stringify!($variant) => Ok(Self::$variant),)*

View File

@ -0,0 +1,100 @@
// === Standard Linter Configuration ===
#![deny(non_ascii_idents)]
#![warn(unsafe_code)]
#![allow(clippy::bool_to_int_with_if)]
#![allow(clippy::let_and_return)]
use prelude::*;
use ensogl_core::data::color;
use ensogl_core::display;
mod prelude {
pub use ensogl_core::application::traits::*;
pub use ensogl_core::display::shape::*;
pub use ensogl_core::prelude::*;
}
pub mod common_part;
mod define_macro;
// =================
// === Constants ===
// =================
/// The width and height of all icons.
pub const SIZE: f32 = 16.0;
/// This constant exists for development purposes only, and is published for debug scene.
/// Due to a rendering error, shapes appear too big when the camera is zoomed in very closely.
/// (Documented here: https://github.com/enso-org/ide/issues/1698)
/// In the user interface, this is not a big problem, since icon are usually shown at lower zoom
/// levels. But it is a problem during development of icon when it becomes necessary to inspect
/// them closely. In those situations, one can apply `.shrink(0.35)` to shapes to compensate for the
/// bug and make them appear at the correct size while the camera is zoomed in. But that work-around
/// will make them appear too thin on the default zoom level.
///
/// To make it easy to turn this shrinking on and off before and after working on icon, we define
/// the constant `SHRINK_AMOUNT` and apply `.shrink(SHRINK_AMOUNT.px())` to all icon. In every
/// commit, `SHRINK_AMOUNT` should be set to 0.0 to make icon look best in the user interface. But
/// during work on the icon, it can temporarily be set to 0.35.
pub const SHRINK_AMOUNT: f32 = 0.0;
// ==============
// === Errors ===
// ==============
/// Error occuring when we try parse string being an invalid icon name to icon Id.
#[derive(Clone, Debug, Fail)]
#[fail(display = "Unknown icon '{}'.", name)]
pub struct UnknownIcon {
/// The copied icon name from parsed string.
pub name: String,
}
// ===============
// === AnyIcon ===
// ===============
/// One of the icon generated from the [`define_icons`] macro. Returned from `create_shape` method.
pub struct Any {
/// The underlying icon shape.
pub view: Box<dyn display::Object>,
/// The primary color of the icon. Secondary colors are calculated by applying transparency to
/// the primary color.
pub color_fn: Box<dyn Fn() -> color::Lcha>,
/// Setter for vivid (darker, or more contrasting) color parameter.
pub set_color_fn: Box<dyn Fn(color::Lcha)>,
}
/// See docs of [`Any`] to learn more.
#[allow(missing_docs)]
impl Any {
pub fn color(&self) -> color::Lcha {
(self.color_fn)()
}
pub fn set_color(&self, color: color::Lcha) {
(self.set_color_fn)(color)
}
}
impl Debug for Any {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Any")
}
}
impl display::Object for Any {
fn display_object(&self) -> &display::object::Instance {
self.view.display_object()
}
}

View File

@ -46,7 +46,6 @@
use crate::prelude::*;
use ensogl_core::display::shape::*;
use crate::navigator::navigator_shadow;
use crate::navigator::Navigator as SectionNavigator;
use enso_frp as frp;
@ -60,9 +59,9 @@ use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::display::object::ObjectOps;
use ensogl_core::display::shape::StyleWatchFrp;
use ensogl_derive_theme::FromTheme;
use ensogl_grid_view as grid_view;
use ensogl_gui_component::component;
use ensogl_hardcoded_theme::application::component_browser::component_list_panel as theme;
use ensogl_list_view as list_view;
use ensogl_shadow as shadow;
@ -148,7 +147,7 @@ impl AllStyles {
fn breadcrumbs_pos(&self) -> Vector2 {
let crop_left = self.panel.breadcrumbs_crop_left;
let x = -self.grid.width / 2.0 + self.navigator.width / 2.0 + crop_left;
let y = self.grid.height / 2.0 + self.panel.menu_height / 2.0 - self.grid.padding;
let y = self.size().y / 2.0;
Vector2(x, y)
}
@ -179,36 +178,47 @@ mod background {
use super::*;
ensogl_core::shape! {
below = [grid::entry::background, list_view::overlay];
below = [grid::entry::background, grid_view::entry::overlay, grid_view::selectable::highlight::shape];
(style:Style,bg_color:Vector4) {
let alpha = Var::<f32>::from(format!("({0}.w)",bg_color));
let bg_color = &Var::<color::Rgba>::from(bg_color.clone());
let grid_padding = style.get_number(theme::grid::padding);
let grid_width = style.get_number(theme::grid::width);
let grid_height = style.get_number(theme::grid::height);
let corners_radius = style.get_number(theme::corners_radius);
let menu_divider_color = style.get_color(theme::menu_divider_color);
let navigator_divider_color = style.get_color(theme::navigator_divider_color);
let menu_divider_width = grid_width - grid_padding * 2.0;
let menu_divider_height = style.get_number(theme::menu_divider_height);
let navigator_divider_width = style.get_number(theme::navigator_divider_width);
let menu_height = style.get_number(theme::menu_height);
let navigator_width = style.get_number(theme::navigator::width);
let width = grid_width + navigator_width;
let height = grid_height + menu_height;
let divider_x_pos = navigator_width / 2.0;
let divider_y_pos = height / 2.0 - menu_height + menu_divider_height ;
let menu_divider_x_pos = navigator_width / 2.0;
let menu_divider_y_pos = height / 2.0 - menu_height + menu_divider_height;
let navigator_divider_x = -width / 2.0 + navigator_width - navigator_divider_width / 2.0;
let navigator_divider_y = 0.0;
let divider = Rect((grid_width.px(),menu_divider_height.px()));
let divider = divider.fill(menu_divider_color);
let divider = divider.translate_x(divider_x_pos.px());
let divider = divider.translate_y(divider_y_pos.px());
let menu_divider = Rect((menu_divider_width.px(),menu_divider_height.px()));
let menu_divider = menu_divider.fill(menu_divider_color);
let menu_divider = menu_divider.translate_x(menu_divider_x_pos.px());
let menu_divider = menu_divider.translate_y(menu_divider_y_pos.px());
let navigator_divider = Rect((navigator_divider_width.px(), height.px()));
let navigator_divider = navigator_divider.fill(navigator_divider_color);
let navigator_divider = navigator_divider.translate_x(navigator_divider_x.px());
let navigator_divider = navigator_divider.translate_y(navigator_divider_y.px());
let base_shape = Rect((width.px(), height.px()));
let base_shape = base_shape.corners_radius(corners_radius.px());
let background = base_shape.fill(bg_color);
let shadow = shadow::from_shape_with_alpha(base_shape.into(),&alpha,style);
(shadow + background + divider).into()
(shadow + background + menu_divider + navigator_divider).into()
}
}
}
@ -225,13 +235,6 @@ mod background {
pub struct Model {
display_object: display::object::Instance,
background: background::View,
// FIXME[#182593513]: This separate shape for navigator shadow can be removed and replaced
// with a shadow embedded into the [`background`] shape when the
// [issue](https://www.pivotaltracker.com/story/show/182593513) is fixed.
// To display the shadow correctly it needs to be clipped to the [`background`] shape, but
// we can't do that because of a bug in the renderer. So instead we add the shadow as a
// separate shape and clip it using `size.set(...)`.
navigator_shadow: navigator_shadow::View,
pub grid: grid::View,
pub section_navigator: SectionNavigator,
pub breadcrumbs: breadcrumbs::Breadcrumbs,
@ -246,8 +249,6 @@ impl Model {
let background = background::View::new();
display_object.add_child(&background);
let navigator_shadow = navigator_shadow::View::new();
display_object.add_child(&navigator_shadow);
let grid = app.new_view::<grid::View>();
display_object.add_child(&grid);
@ -259,15 +260,7 @@ impl Model {
breadcrumbs.set_base_layer(&app.display.default_scene.layers.node_searcher);
display_object.add_child(&breadcrumbs);
Self {
display_object,
background,
navigator_shadow,
grid,
section_navigator,
scene_navigator,
breadcrumbs,
}
Self { display_object, background, grid, section_navigator, scene_navigator, breadcrumbs }
}
fn set_initial_breadcrumbs(&self) {
@ -281,11 +274,6 @@ impl Model {
self.background.size.set(style.background_sprite_size());
self.section_navigator.update_layout(style);
let navigator_shadow_x = -style.grid.width / 2.0;
self.navigator_shadow.set_x(navigator_shadow_x);
let section_navigator_shadow_size = Vector2(style.navigator.width, style.size().y);
self.navigator_shadow.size.set(section_navigator_shadow_size);
self.breadcrumbs.set_xy(style.breadcrumbs_pos());
self.breadcrumbs.set_size(style.breadcrumbs_size());
self.grid.set_xy(style.grid_pos());
@ -397,14 +385,6 @@ impl component::Frp<Model> for Frp {
model.section_navigator.select_section <+ model.grid.active_section.on_change();
// === Navigator icons colors ===
let vivid_color = style.get_color(theme::navigator::icon_strong_color);
let dull_color = style.get_color(theme::navigator::icon_weak_color);
let params = icon::Params { vivid_color, dull_color };
model.section_navigator.set_bottom_buttons_entry_params(params);
// === Breadcrumbs ===
eval_ input.show(model.set_initial_breadcrumbs());

View File

@ -8,6 +8,7 @@ use ensogl_core::prelude::*;
use crate::AllStyles;
use crate::grid::entry::icon;
use enso_frp as frp;
use ensogl_core::animation::animation::delayed::DelayedAnimation;
use ensogl_core::application::tooltip;
@ -15,57 +16,33 @@ use ensogl_core::application::Application;
use ensogl_core::data::color;
use ensogl_core::display;
use ensogl_derive_theme::FromTheme;
use ensogl_grid_view as grid;
use ensogl_hardcoded_theme::application::component_browser::component_list_panel as list_panel_theme;
use ensogl_list_view as list_view;
use ensogl_list_view::entry::AnyModelProvider;
use ensogl_shadow as shadow;
use ensogl_tooltip::Tooltip;
use ide_view_component_list_panel_grid::entry::icon;
use grid::Col;
use grid::Row;
use ide_view_component_list_panel_grid::SectionId;
use list_panel_theme::navigator as theme;
mod entry;
type Grid = grid::selectable::GridView<entry::View>;
// =================
// === Constants ===
// =================
const MARKETPLACE_BUTTON_INDEX: usize = 1;
const MARKETPLACE_TOOLTIP_TEXT: &str = "Marketplace will be available soon.";
const MARKETPLACE_TOOLTIP_HIDE_DELAY_MS: f32 = 3000.0;
const MARKETPLACE_TOOLTIP_PLACEMENT: tooltip::Placement = tooltip::Placement::Bottom;
const TOP_BUTTONS: [icon::Id; 2] = [icon::Id::Libraries, icon::Id::Marketplace];
const MARKETPLACE_BUTTON_INDEX: usize = 1;
const BOTTOM_BUTTONS: [icon::Id; 3] = [icon::Id::SubModules, icon::Id::Star, icon::Id::LocalScope];
// ==============
// === Shadow ===
// ==============
/// A shadow between the navigator bar and the main part of the Searcher List Panel.
///
/// We should have this shape embedded into the background shape, but we use a separate object
/// because of https://www.pivotaltracker.com/story/show/182593513.
pub mod navigator_shadow {
use super::*;
ensogl_core::shape! {
above = [crate::background];
below = [list_view::overlay, list_view::selection];
pointer_events = false;
(style:Style) {
let grid_height = style.get_number(list_panel_theme::grid::height);
let menu_height = style.get_number(list_panel_theme::menu_height);
let navigator_width = style.get_number(theme::width);
let height = grid_height + menu_height;
let width = navigator_width;
let base_shape = Rect((width.px(), height.px() * 2.0)).translate_x(width.px());
shadow::from_shape(base_shape.into(), style)
}
}
}
const TOP_BUTTONS_COUNT: usize = TOP_BUTTONS.len();
const BOTTOM_BUTTONS_COUNT: usize = 3;
// =============
@ -75,39 +52,92 @@ pub mod navigator_shadow {
#[derive(Copy, Clone, Debug, Default, FromTheme)]
#[base_path = "theme"]
pub struct Style {
pub width: f32,
pub list_view_width: f32,
pub icon_strong_color: color::Rgba,
pub icon_weak_color: color::Rgba,
pub top_padding: f32,
pub bottom_padding: f32,
pub width: f32,
pub button_size: f32,
pub top_padding: f32,
pub bottom_padding: f32,
pub hover_color: color::Rgba,
#[theme_path = "theme::highlight::color"]
pub highlight_color: color::Rgba,
#[theme_path = "theme::highlight::size"]
pub highlight_size: f32,
#[theme_path = "theme::highlight::corners_radius"]
pub highlight_corners_radius: f32,
}
// =========================================================
// === Conversions Between SectionId and List View Index ===
// =========================================================
/// Convert [`SectionId`] to index on [`Navigator::bottom_buttons`].
fn section_id_to_list_index(id: SectionId) -> usize {
match id {
SectionId::Popular => 1,
SectionId::LocalScope => 2,
SectionId::SubModules => 0,
impl From<Style> for entry::Params {
fn from(style: Style) -> Self {
Self {
hover_color: style.hover_color.into(),
selection_color: style.highlight_color.into(),
selection_size: style.highlight_size,
selection_corners_radius: style.highlight_corners_radius,
}
}
}
/// Convert the index on [`Navigator::bottom_buttons`] to [`SectionId`]. Prints error on invalid
/// Colors of the buttons of the section navigator.
/// Each of the section buttons can have a different "active" color, but they all share the same
/// "inactive" color. "active" color is used when the button is highlighted, and the "inactive" is
/// used as default.
#[derive(Debug, Clone, Copy, Default, FromTheme)]
#[base_path = "theme::buttons"]
#[allow(missing_docs)]
pub struct Colors {
pub inactive: color::Rgba,
#[theme_path = "theme::buttons::active::popular"]
pub popular: color::Rgba,
#[theme_path = "theme::buttons::active::local_scope"]
pub local_scope: color::Rgba,
#[theme_path = "theme::buttons::active::submodules"]
pub submodules: color::Rgba,
}
impl Colors {
fn get(&self, section: SectionId) -> color::Rgba {
match section {
SectionId::Popular => self.popular,
SectionId::LocalScope => self.local_scope,
SectionId::SubModules => self.submodules,
}
}
}
/// Convert [`SectionId`] to the displayed icon id.
fn section_id_to_icon_id(section: SectionId) -> icon::Id {
match section {
SectionId::Popular => icon::Id::Star,
SectionId::LocalScope => icon::Id::LocalScope,
SectionId::SubModules => icon::Id::SubModules,
}
}
// ============================================================
// === Conversions Between SectionId and Grid View Location ===
// ============================================================
/// Convert [`SectionId`] to location on [`Navigator::bottom_buttons`].
fn section_id_to_grid_loc(id: SectionId) -> (Row, Col) {
const COLUMN: Col = 0;
match id {
SectionId::Popular => (1, COLUMN),
SectionId::LocalScope => (2, COLUMN),
SectionId::SubModules => (0, COLUMN),
}
}
/// Convert the location on [`Navigator::bottom_buttons`] to [`SectionId`]. Prints error on invalid
/// index and returns the id of topmost section.
fn index_to_section_id(&index: &usize) -> SectionId {
fn loc_to_section_id(&(row, _): &(Row, Col)) -> SectionId {
let highest = SectionId::SubModules;
match index {
match row {
0 => highest,
1 => SectionId::Popular,
2 => SectionId::LocalScope,
index => {
error!("Tried to create SectionId from too high Navigator List index ({}).", index);
_ => {
error!("Tried to create SectionId from too high Navigator List row ({}).", row);
highest
}
}
@ -129,8 +159,8 @@ fn index_to_section_id(&index: &usize) -> SectionId {
pub struct Navigator {
display_object: display::object::Instance,
network: frp::Network,
bottom_buttons: list_view::ListView<icon::Entry>,
top_buttons: list_view::ListView<icon::Entry>,
bottom_buttons: Grid,
top_buttons: Grid,
tooltip: Tooltip,
pub select_section: frp::Any<Option<SectionId>>,
pub chosen_section: frp::Stream<Option<SectionId>>,
@ -139,40 +169,52 @@ pub struct Navigator {
impl Navigator {
pub fn new(app: &Application) -> Self {
let display_object = display::object::Instance::new();
let top_buttons = app.new_view::<list_view::ListView<icon::Entry>>();
let bottom_buttons = app.new_view::<list_view::ListView<icon::Entry>>();
let tooltip = Tooltip::new(app);
top_buttons.set_style_prefix(list_panel_theme::navigator::list_view::HERE.str);
bottom_buttons.set_style_prefix(list_panel_theme::navigator::list_view::HERE.str);
top_buttons.show_background_shadow(false);
bottom_buttons.show_background_shadow(false);
bottom_buttons.disable_selecting_entries_with_mouse();
let top_buttons = Grid::new(app);
let bottom_buttons = Grid::new(app);
display_object.add_child(&top_buttons);
display_object.add_child(&bottom_buttons);
let tooltip = Tooltip::new(app);
app.display.default_scene.add_child(&tooltip);
top_buttons.hide_selection();
top_buttons.set_entries(AnyModelProvider::new(TOP_BUTTONS.to_vec()));
bottom_buttons.set_entries(AnyModelProvider::new(BOTTOM_BUTTONS.to_vec()));
bottom_buttons.select_entry(Some(section_id_to_list_index(SectionId::Popular)));
let network = frp::Network::new("ComponentBrowser.Navigator");
let style_frp = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let colors = Colors::from_theme(&network, &style_frp);
let tooltip_hide_timer = DelayedAnimation::new(&network);
tooltip_hide_timer.set_delay(MARKETPLACE_TOOLTIP_HIDE_DELAY_MS);
tooltip_hide_timer.set_duration(0.0);
frp::extend! { network
select_section <- any(...);
bottom_buttons.select_entry <+
select_section.map(|&s:&Option<SectionId>| s.map(section_id_to_list_index));
chosen_section <-
bottom_buttons.chosen_entry.map(|&id| id.as_ref().map(index_to_section_id));
user_selected_section <- select_section.map(|&s:&Option<SectionId>| s.map(section_id_to_grid_loc));
bottom_buttons.select_entry <+ user_selected_section;
selected_button_and_section <- all(&bottom_buttons.entry_selected, &user_selected_section);
different_section_chosen <- selected_button_and_section.filter(|(e, u)| *e != *u);
chosen_section <- different_section_chosen._0().map(|loc| loc.as_ref().map(loc_to_section_id));
model <- bottom_buttons.model_for_entry_needed.map2(&colors.update, f!([]
((row, col), colors) {
let section_id = loc_to_section_id(&(*row, *col));
let icon_id = section_id_to_icon_id(section_id);
let active_colors = colors.get(section_id).into();
let inactive_colors = colors.inactive.into();
let model = entry::Model::new(icon_id, active_colors, inactive_colors);
(*row, *col, model)
}
));
bottom_buttons.model_for_entry <+ model;
model <- top_buttons.model_for_entry_needed.map2(&colors.update, f!([]
((row, col), colors) {
let icon_id = TOP_BUTTONS.get(*row).cloned().unwrap_or_default();
let model = entry::Model::new(icon_id, colors.inactive.into(), colors.inactive.into());
(*row, *col, model)
}
));
top_buttons.model_for_entry <+ model;
// === Show tooltip when hovering the Marketplace button
let idx_of_marketplace_btn = |idx: &Option<_>| *idx == Some(MARKETPLACE_BUTTON_INDEX);
marketplace_button_selected <- top_buttons.selected_entry.map(idx_of_marketplace_btn);
marketplace_button_hovered <- marketplace_button_selected && top_buttons.is_mouse_over;
let idx_of_marketplace_btn = |loc: &Option<(Row, Col)>| matches!(loc, Some((row, _)) if *row == MARKETPLACE_BUTTON_INDEX);
marketplace_button_hovered <- top_buttons.entry_hovered.map(idx_of_marketplace_btn);
marketplace_button_hovered <- marketplace_button_hovered.on_change();
tooltip_hide_timer.start <+ marketplace_button_hovered.on_true();
tooltip_hide_timer.reset <+ marketplace_button_hovered.on_false();
@ -186,7 +228,10 @@ impl Navigator {
}
);
}
colors.init.emit(());
tooltip_hide_timer.reset();
bottom_buttons.reset_entries(BOTTOM_BUTTONS_COUNT, 1);
top_buttons.reset_entries(TOP_BUTTONS_COUNT, 1);
Self {
display_object,
@ -199,27 +244,38 @@ impl Navigator {
}
}
pub(crate) fn set_bottom_buttons_entry_params(&self, params: icon::Params) {
self.bottom_buttons.set_entry_params_and_recreate_entries(params);
}
pub(crate) fn update_layout(&self, style: &AllStyles) {
let list_view_width = style.navigator.list_view_width;
let top_buttons_height = list_view::entry::HEIGHT * TOP_BUTTONS.len() as f32;
let bottom_buttons_height = list_view::entry::HEIGHT * BOTTOM_BUTTONS.len() as f32;
self.top_buttons.resize(Vector2(list_view_width, top_buttons_height));
self.bottom_buttons.resize(Vector2(list_view_width, bottom_buttons_height));
let size = style.navigator.button_size;
let top_buttons_height = size * TOP_BUTTONS_COUNT as f32;
let bottom_buttons_height = size * BOTTOM_BUTTONS_COUNT as f32;
self.bottom_buttons.set_entries_size(Vector2(size, size));
self.top_buttons.set_entries_size(Vector2(size, size));
let (top, left, right) = (0.0, 0.0, size);
let viewport = grid::Viewport { top, bottom: 0.0, left, right };
let top_buttons_viewport = grid::Viewport { bottom: -top_buttons_height, ..viewport };
let bottom_buttons_viewport = grid::Viewport { bottom: -bottom_buttons_height, ..viewport };
self.top_buttons.set_viewport(top_buttons_viewport);
self.bottom_buttons.set_viewport(bottom_buttons_viewport);
let buttons_params = entry::Params::from(style.navigator);
self.bottom_buttons.set_entries_params(buttons_params);
let disabled_params = entry::Params {
hover_color: color::Lcha::transparent(),
selection_color: color::Lcha::transparent(),
selection_size: 0.0,
selection_corners_radius: 0.0,
};
self.top_buttons.set_entries_params(disabled_params);
let width = style.navigator.width;
let height = style.grid.height + style.panel.menu_height;
let top = height / 2.0;
let bottom = -height / 2.0;
let top_buttons_height = TOP_BUTTONS.len() as f32 * list_view::entry::HEIGHT;
let bottom_buttons_height = BOTTOM_BUTTONS.len() as f32 * list_view::entry::HEIGHT;
let bottom = (-height / 2.0).floor();
let left = -style.grid.width / 2.0 - width / 2.0;
let top_padding = style.navigator.top_padding;
let bottom_padding = style.navigator.bottom_padding;
let x_pos = -style.grid.width / 2.0;
let top_buttons_y = top - top_buttons_height / 2.0 - top_padding;
let bottom_buttons_y = bottom + bottom_buttons_height / 2.0 + bottom_padding;
let x_pos = left + (width / 2.0).floor() - size / 2.0;
let top_buttons_y = top - top_padding;
let bottom_buttons_y = bottom + bottom_buttons_height + bottom_padding;
self.top_buttons.set_xy(Vector2(x_pos, top_buttons_y));
self.bottom_buttons.set_xy(Vector2(x_pos, bottom_buttons_y));
}

View File

@ -0,0 +1,164 @@
//! A module containing the grid view entry type which represents an arbitrary icon.
use ensogl_core::prelude::*;
use crate::grid::entry::icon;
use enso_frp as frp;
use ensogl_core::application::command::FrpNetworkProvider;
use ensogl_core::application::frp::API;
use ensogl_core::application::Application;
use ensogl_core::data::color;
use ensogl_core::display;
use ensogl_core::display::scene::Layer;
use ensogl_grid_view as grid;
use ide_view_component_list_panel_icons::Any as AnyIcon;
use ide_view_component_list_panel_icons::SIZE;
// =============
// === Model ===
// =============
/// Entry's model.
#[derive(Debug, Clone, Copy, Default)]
pub struct Model {
icon_id: icon::Id,
/// Color of the icon when the entry is selected.
active: color::Lcha,
/// Color of the icon when the entry is not selected.
inactive: color::Lcha,
}
impl Model {
/// Constructor.
pub const fn new(icon_id: icon::Id, active: color::Lcha, inactive: color::Lcha) -> Self {
Self { icon_id, active, inactive }
}
}
// ==================
// === IconParams ===
// ==================
/// Entry parameters of the icon.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[allow(missing_docs)]
pub struct Params {
pub hover_color: color::Lcha,
pub selection_color: color::Lcha,
pub selection_size: f32,
pub selection_corners_radius: f32,
}
// ============
// === Data ===
// ============
/// Grid view entry type which represents a single icon. We use grid view with icons instead of
/// multiple buttons to simplify the implementation.
#[derive(Debug, Clone, CloneRef)]
pub struct Data {
display_object: display::object::Instance,
icon: Rc<RefCell<Option<AnyIcon>>>,
color: Rc<Cell<color::Lcha>>,
}
impl Data {
pub fn new() -> Self {
let display_object = display::object::Instance::new();
let icon = default();
let color = default();
Self { display_object, icon, color }
}
fn set_icon(&self, icon_id: icon::Id) {
let size = Vector2(SIZE, SIZE);
let icon = icon_id.create_shape(size);
icon.set_color(color::Rgba::from(self.color.get()).into());
self.display_object.add_child(&icon);
*self.icon.borrow_mut() = Some(icon);
}
fn set_color(&self, color: color::Lcha) {
self.color.set(color);
if let Some(icon) = self.icon.borrow().deref() {
icon.set_color(color::Rgba::from(color).into());
}
}
}
// ============
// === View ===
// ============
/// The grid view entry.
#[derive(Clone, CloneRef, Debug)]
pub struct View {
frp: grid::entry::EntryFrp<Self>,
data: Data,
}
impl grid::entry::Entry for View {
type Model = Model;
type Params = Params;
fn new(_app: &Application, _text_layer: Option<&Layer>) -> Self {
let frp = grid::entry::EntryFrp::<Self>::new();
let data = Data::new();
let network = frp.network();
let input = &frp.private().input;
let out = &frp.private().output;
let color_anim = color::Animation::new(network);
frp::extend! { network
init <- source_();
icon <- input.set_model.map(|m| m.icon_id);
eval icon((icon) data.set_icon(*icon));
active_color <- input.set_model.map(|m| m.active);
inactive_color <- input.set_model.map(|m| m.inactive);
color_anim.target <+ inactive_color;
entry_selected <- input.set_selected.on_true();
entry_deselected <- input.set_selected.on_false();
color_anim.target <+ active_color.sample(&entry_selected);
color_anim.target <+ inactive_color.sample(&entry_deselected);
eval color_anim.value((color) data.set_color(*color));
style <- input.set_params.on_change();
selection_color <- style.map(|s| s.selection_color).on_change();
hover_color <- style.map(|s| s.hover_color).on_change();
out.contour <+ input.set_size.map(|s| grid::entry::Contour::rectangular(*s));
out.highlight_contour <+ out.contour.all_with(&style,
|c,s| grid::entry::Contour { corners_radius: s.selection_corners_radius, ..*c }
);
out.hover_highlight_color <+ hover_color;
out.selection_highlight_color <+ selection_color;
}
init.emit(());
Self { frp, data }
}
fn frp(&self) -> &grid::entry::EntryFrp<Self> {
&self.frp
}
}
impl display::Object for View {
fn display_object(&self) -> &display::object::Instance {
&self.data.display_object
}
}

View File

@ -40,6 +40,8 @@ use ensogl_core::prelude::*;
use wasm_bindgen::prelude::*;
use ensogl_core::application::Application;
use ensogl_core::display;
use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::display::object::ObjectOps;
use ensogl_core::frp;
use ensogl_hardcoded_theme as theme;
@ -56,12 +58,17 @@ use ide_view_component_list_panel::grid::entry::icon;
const PREPARED_ITEMS: &[(&str, icon::Id)] = &[
// ("long sample entry with text overflowing the width", icon::Id::Star),
("convert", icon::Id::Convert),
("table input", icon::Id::DataInput),
("text input", icon::Id::TextInput),
("number input", icon::Id::NumberInput),
("table output", icon::Id::TableEdit),
("dataframe clean", icon::Id::DataframeClean),
("data input", icon::Id::DataInput),
("data output", icon::Id::DataOutput),
("table input", icon::Id::TableEdit),
("number input", icon::Id::NumberInput),
("text input", icon::Id::TextInput),
("add column", icon::Id::AddColumn),
("select column", icon::Id::SelectColumn),
("clean", icon::Id::DataframeClean),
("add row", icon::Id::AddRow),
("map row", icon::Id::DataframeMapRow),
("map column", icon::Id::DataframeMapColumn),
];
const fn make_group(section: grid::SectionId, index: usize, size: usize) -> grid::content::Group {
@ -74,6 +81,18 @@ const fn make_group(section: grid::SectionId, index: usize, size: usize) -> grid
}
}
/// Use this groups setup to compare to Figma design.
#[allow(dead_code)]
const GROUPS_AS_IN_DESIGN: &[grid::content::Group] = &[
make_group(grid::SectionId::Popular, 0, 7),
make_group(grid::SectionId::Popular, 1, 7),
make_group(grid::SectionId::Popular, 2, 5),
make_group(grid::SectionId::SubModules, 3, 10),
make_group(grid::SectionId::SubModules, 4, 3),
make_group(grid::SectionId::SubModules, 5, 10),
make_group(grid::SectionId::SubModules, 6, 10),
];
const GROUPS: &[grid::content::Group] = &[
make_group(grid::SectionId::Popular, 1, 3),
make_group(grid::SectionId::Popular, 2, 2),
@ -109,16 +128,24 @@ const LOCAL_SCOPE_GROUP_SIZE: usize = 1024;
fn content_info() -> grid::content::Info {
grid::content::Info {
groups: GROUPS.into(),
groups: GROUPS_AS_IN_DESIGN.into(),
local_scope_entry_count: LOCAL_SCOPE_GROUP_SIZE,
}
}
const GROUP_NAMES: &[&str] = &[
"Input / Output",
"Preparation",
"Join",
"Text",
"Date and Time",
"Transform",
"Machine Learning",
];
fn get_header_model(group: grid::GroupId) -> Option<(grid::GroupId, grid::HeaderModel)> {
let model = grid::HeaderModel {
caption: format!("Group {}", group.index).into(),
can_be_entered: false,
};
let caption = GROUP_NAMES.get(group.index % GROUP_NAMES.len()).copied().unwrap_or("");
let model = grid::HeaderModel { caption: caption.into(), can_be_entered: false };
Some((group, model))
}
@ -138,6 +165,17 @@ fn get_entry_model(entry: grid::GroupEntryId) -> Option<(grid::GroupEntryId, gri
Some((entry, model))
}
fn snap_to_pixel_offset(size: Vector2, scene_shape: &display::scene::Shape) -> Vector2 {
let device_size = scene_shape.device_pixels();
let origin_left_top_pos = Vector2(device_size.width, device_size.height) / 2.0;
let origin_snapped = Vector2(origin_left_top_pos.x.floor(), origin_left_top_pos.y.floor());
let origin_offset = origin_snapped - origin_left_top_pos;
let panel_left_top_pos = (size * scene_shape.pixel_ratio) / 2.0;
let panel_snapped = Vector2(panel_left_top_pos.x.floor(), panel_left_top_pos.y.floor());
let panel_offset = panel_snapped - panel_left_top_pos;
origin_offset - panel_offset
}
// ===================
@ -153,17 +191,26 @@ pub fn main() {
theme::builtin::light::register(&app);
theme::builtin::light::enable(&app);
let world = &app.display;
let scene = &world.default_scene;
let navigator = Navigator::new(scene, &scene.layers.node_searcher.camera());
let panel = app.new_view::<ide_view_component_list_panel::View>();
scene.layers.node_searcher.add(&panel);
panel.model().set_navigator(Some(navigator.clone_ref()));
panel.show();
let network = frp::Network::new("new_component_list_panel_view");
//TODO[ao] should be done by panel itself.
let grid = &panel.model().grid;
frp::extend! { network
init <- source_();
grid.model_for_header <+ grid.model_for_header_needed.filter_map(|&id| get_header_model(id));
grid.model_for_entry <+ grid.model_for_entry_needed.filter_map(|&id| get_entry_model(id));
size <- all_with(&init, &panel.size, |(), panel_size| *panel_size);
snap <- all_with(&size, &scene.frp.shape, |sz, sh| snap_to_pixel_offset(*sz, sh));
eval snap((snap) panel.set_xy(*snap));
}
init.emit(());
grid.reset(content_info());
scene.add_child(&panel);
@ -171,5 +218,6 @@ pub fn main() {
mem::forget(app);
mem::forget(panel);
mem::forget(network);
mem::forget(navigator);
})
}

View File

@ -11,4 +11,5 @@ crate-type = ["cdylib", "rlib"]
ensogl = { path = "../../../../../lib/rust/ensogl" }
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
ide-view-component-list-panel-grid = { path = "../../component-browser/component-list-panel/grid" }
ide-view-component-list-panel-icons = { path = "../../component-browser/component-list-panel/icons" }
wasm-bindgen = { version = "=0.2.78" }

View File

@ -14,7 +14,8 @@ use ensogl::display::navigation::navigator::Navigator;
use ensogl::display::DomSymbol;
use ensogl::system::web;
use ide_view_component_list_panel_grid::entry::icon;
use ide_view_component_list_panel_grid::entry::icon::SHRINK_AMOUNT;
use ide_view_component_list_panel_icons::SHRINK_AMOUNT;
use ide_view_component_list_panel_icons::SIZE;
@ -29,7 +30,7 @@ mod frame {
ensogl::shape! {
(style:Style) {
let inner = Rect((icon::SIZE.px(), icon::SIZE.px()));
let inner = Rect((SIZE.px(), SIZE.px()));
let outer = inner.grow(0.2.px());
let shape = (outer - inner).fill(color::Rgba::black());
shape.shrink(SHRINK_AMOUNT.px()).into()
@ -72,7 +73,7 @@ pub fn entry_point_searcher_icons() {
let grid = DomSymbol::new(&grid_div);
scene.dom.layers.back.manage(&grid);
world.add_child(&grid);
grid.set_size(Vector2(1000.0, icon::SIZE));
grid.set_size(Vector2(1000.0, SIZE));
mem::forget(grid);
@ -80,9 +81,8 @@ pub fn entry_point_searcher_icons() {
let mut x = -300.0;
icon::Id::for_each(|id| {
let shape = id.create_shape(Vector2(icon::SIZE, icon::SIZE));
shape.set_vivid_color(color::Rgba(0.243, 0.541, 0.160, 1.0).into());
shape.set_dull_color(color::Rgba(0.655, 0.788, 0.624, 1.0).into());
let shape = id.create_shape(Vector2(SIZE, SIZE));
shape.set_color(color::Rgba(0.243, 0.541, 0.160, 1.0).into());
shape.set_x(x);
x += 20.0;
world.add_child(&shape);

View File

@ -1,6 +1,6 @@
# Options intended to be common for all developers.
wasm-size-limit: 15.05 MiB
wasm-size-limit: 15.09 MiB
required-versions:
cargo-watch: ^8.1.1

View File

@ -20,6 +20,7 @@ use syn::Type;
const BASE_PATH_ATTRIBUTE_NAME: &str = "base_path";
// Sadly we can't use `path` here, because it conflicts with Rust's builtin attribute.
const PATH_ATTRIBUTE_NAME: &str = "theme_path";
const ACCESSOR_ATTRIBUTE_NAME: &str = "accessor";
// ==================
@ -31,11 +32,13 @@ enum ThemeTypes {
Color,
String,
Number,
/// The type is not directly supported by the macro, but can be accessed using a custom
/// accessor.
Unknown,
}
impl ThemeTypes {
/// Return the corresponging [`ThemeType`] for the given [`Type`]. Panics with an error message
/// indicating the wrong type if this is not a valid theme type.
/// Return the corresponging [`ThemeType`] for the given [`Type`].
fn from_ty(ty: &Type) -> Self {
match ty {
Type::Path(type_path)
@ -46,11 +49,7 @@ impl ThemeTypes {
Type::Path(type_path)
if type_path.clone().into_token_stream().to_string().contains("Rgba") =>
Self::Color,
_ => panic!(
"The type `{:?}` cannot be derived from a theme. Only `ImString`,\
`color::Rgba` and `f32` are supported.",
ty
),
_ => Self::Unknown,
}
}
}
@ -108,8 +107,7 @@ fn build_frp(
}
}
/// Create the field initializers for the struct holding the theme values. They differ between
/// types that implement copy or clone.
/// Create the field initializers for the struct holding the theme values.
fn make_struct_inits(
fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
) -> TokenStream {
@ -120,13 +118,9 @@ fn make_struct_inits(
.ident
.as_ref()
.expect("Encountered unnamed struct field. This cannot not happen.");
let init = match ThemeTypes::from_ty(&field.ty) {
ThemeTypes::Color | ThemeTypes::Number => quote! {
#field_name:*#field_name,
},
ThemeTypes::String => quote! {
#field_name:#field_name.clone(),
},
// Keep in mind that all [`Copy`] types also implement [`Clone`].
let init = quote! {
#field_name : #field_name.clone(),
};
combined.extend(init);
}
@ -202,19 +196,28 @@ fn make_theme_getters(
(None, None) => panic!("Neither `base_path` nor `path` attributes were set."),
};
let accessor = match ThemeTypes::from_ty(&f.ty) {
ThemeTypes::Color => quote! {
get_color
},
ThemeTypes::String => quote! {
get_text
},
ThemeTypes::Number => quote! {
get_number
},
};
let accessor = get_path_from_metadata(&f.attrs, ACCESSOR_ATTRIBUTE_NAME)
.map(|accessor| {
quote! { #accessor(&network, style, #field_path) }
})
.unwrap_or_else(|| match ThemeTypes::from_ty(&f.ty) {
ThemeTypes::Color => quote! {
StyleWatchFrp::get_color(style, #field_path)
},
ThemeTypes::String => quote! {
StyleWatchFrp::get_text(style, #field_path)
},
ThemeTypes::Number => quote! {
StyleWatchFrp::get_number(style, #field_path)
},
ThemeTypes::Unknown => panic!(
"Unknown type for theme value, but no accessor was provided. \
Use the `#[accessor]` attribute to provide a custom accessor function"
),
});
quote! {
let #field_name = style.#accessor(#field_path);
let #field_name = #accessor;
}
})
.collect()

View File

@ -7,6 +7,20 @@
//! appended to `base_path` to get the full path to the value in the theme. It can be overridden
//! using the `theme_path` attribute on the field.
//!
//! A custom accessor function can be provided for each field using the `accessor` attribute. This
//! function should have a following signature:
//!
//! ```ignore
//! fn accessor<P: Into<ensogl_core::display::style::Path>>(
//! network: &frp::Network,
//! style: &StyleWatchFrp,
//! path: P,
//! ) -> frp::Sampler<T>
//! ```
//! where `T` is the type of the field. This accessor will be used to retrieve the value from the
//! stylesheet. If no accessor is provided, a standard getters of the [`StyleWatchFrp`] are used
//! instead.
//!
//! Example usage
//!```no_compile
//! use ensogl_core::data::color;
@ -19,6 +33,8 @@
//! some_color: color::Rgba,
//! #[theme_path = "ensogl_hardcoded_theme::some_path::label"]
//! some_label: String,
//! #[accessor = "my_custom_accessor"]
//! some_custom_value: CustomType,
//! }
//! ```
@ -69,7 +85,7 @@ mod from_theme;
/// Implements the `FromTheme` derive macro. See thr crate docs for more information.
#[proc_macro_derive(FromTheme, attributes(base_path, theme_path))]
#[proc_macro_derive(FromTheme, attributes(base_path, theme_path, accessor))]
pub fn derive_from_thee(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
from_theme::expand(input).into()

View File

@ -175,8 +175,8 @@ impl Default for Theme {
define_themes! { [light:0, dark:1]
application {
// Original RGB values (for reference after fixing color-conversion issues)
// light: rgb(249,250,251), old-dark: Lcha(0.13,0.014,0.18,1.0), dark: rgb(32,34,36)
background = Rgba(0.976,0.98,0.984,1.0) , Rgba(0.125,0.133,0.141,1.0);
// light: rgb(231,235,238), old-dark: Lcha(0.13,0.014,0.18,1.0), dark: rgb(32,34,36)
background = Rgb::from_base_255(231.0, 235.0, 238.0) , Rgba(0.125,0.133,0.141,1.0);
tooltip {
hide_delay_duration_ms = 150.0, 150.0;
show_delay_duration_ms = 150.0, 150.0;
@ -184,43 +184,70 @@ define_themes! { [light:0, dark:1]
component_browser {
panels_gap = 3.0, 3.0;
documentation {
width = 369.0, 369.0;
width = 400.0, 400.0;
}
component_list_panel {
background_color = Rgba::new(252.0 / 256.0, 254.0 / 255.0, 1.0, 1.0),Rgba::new(252.0 / 256.0, 254.0 / 255.0, 1.0, 1.0);
corners_radius = 15.0, 15.0;
background_color = Rgb::from_base_255(236.0, 240.0, 242.0),Rgb::from_base_255(236.0, 240.0, 242.0);
corners_radius = 16.0, 16.0;
grid {
width = 400.0, 400.0;
height = 398.0, 398.0;
padding = 3.0, 3.0;
width = 413.0, 413.0;
height = 414.0, 414.0;
padding = 4.0, 4.0;
column_gap = 3.0, 3.0;
entry_height = 30.0, 30.0;
// The `color` values support color types (like `color::Rgba`)
// and floating-point numbers. This is possible due to a custom stylesheet
// accessor defined for
// [`ide_view_component_list_panel_grid::entry::style::Color`] type.
// Floating-point numbers mean the alpha multiplier for the "main" color of the
// component group.
entry {
background.color_intensity = 0.2, 0.2;
dimmed.color_intensity = 0.5, 0.5;
padding = 17.0, 17.0;
background.intensity = 0.08, 0.08;
dimmed = Rgb::from_base_255(160.0, 163.0, 165.0), Rgb::from_base_255(160.0, 163.0, 165.0);
padding = 16.0, 16.0;
text {
font = "default", "default";
size = 12.0, 12.0;
color_intensity = 1.0, 1.0;
highlight_bold = 0.04, 0.04;
font = "mplus1p", "mplus1p";
y_offset = 8.0, 8.0;
y_offset_header = 5.0, 5.0;
x_offset_header = 0.0, 0.0;
size = 11.5, 11.5;
color = 1.0, 1.0;
highlight_bold = 0.01, 0.01;
}
icon {
size = 16.0, 16.0;
text_padding = 8.0, 8.0;
strong_color_intensity = 1.0, 1.0;
weak_color_intensity = 0.5, 0.5;
text_padding = 9.0, 9.0;
color = 1.0, 1.0;
dull_color_alpha = 0.25, 0.25;
}
special_icons {
join.intersection_alpha = 0.7, 0.7;
libraries {
dull_alpha = 1.0, 1.0;
secondary_alpha = 1.0, 1.0;
tertiary_alpha = 1.0, 1.0;
}
marketplace {
dull_alpha = 1.0, 1.0;
secondary_alpha = 1.0, 1.0;
tertiary_alpha = 1.0, 1.0;
}
computer_vision {
highlight = Rgba(0.872,0.267,0.255,1.0) , Rgba(0.872,0.267,0.255,1.0);
}
system {
background = Rgba(0.306,0.306,0.306,1.0) , Rgba(0.306,0.306,0.306,1.0);
content = Rgba(0.988,0.996,1.0,1.0) , Rgba(0.988,0.996,1.0,1.0);
}
}
highlight {
corners_radius = 12.0, 12.0;
hover.color_intensity = 0.4, 0.4;
hover.color = 0.4, 0.4;
selection {
background.color_intensity = 1.0, 1.0;
dimmed.color_intensity = 0.5, 0.5;
text.color_intensity = 0.2, 0.2;
icon_strong.color_intensity = 0.5, 0.5;
icon_weak.color_intensity = 0.2, 0.2;
background.intensity = 0.75, 0.75;
text.color = Rgba::white(), Rgba::white();
icon.color = Lcha(1.0, 0.0, 0.0, 1.0), Lcha(1.0, 0.0, 0.0, 1.0);
}
}
shadow = shadow , shadow;
@ -235,66 +262,80 @@ define_themes! { [light:0, dark:1]
}
group_colors {
group_0 = Rgba(43.0 / 255.0, 117.0 / 255.0, 239.0 / 255.0, 1.0),Rgba(43.0 / 255.0, 117.0 / 255.0, 239.0 / 255.0, 1.0);
group_1 = Rgba(62.0 / 255.0, 139.0 / 255.0, 41.0 / 255.0, 1.0),Rgba(62.0 / 255.0, 139.0 / 255.0, 41.0 / 255.0, 1.0);
group_2 = Rgba(192.0 / 255.0, 71.0 / 255.0, 171.0 / 255.0, 1.0),Rgba(192.0 / 255.0, 71.0 / 255.0, 171.0 / 255.0, 1.0);
group_3 = Rgba(121.0 / 255.0, 126.0 / 255.0, 37.0 / 255.0, 1.0),Rgba(121.0 / 255.0, 126.0 / 255.0, 37.0 / 255.0, 1.0);
group_4 = Rgba(181.0 / 255.0, 97.0 / 255.0, 35.0 / 255.0, 1.0),Rgba(181.0 / 255.0, 97.0 / 255.0, 35.0 / 255.0, 1.0);
group_5 = Rgba(61.0 / 255.0, 146.0 / 255.0, 206.0 / 255.0, 1.0),Rgba(61.0 / 255.0, 146.0 / 255.0, 206.0 / 255.0, 1.0);
// Yellow
group_0 = Rgb::from_base_255(134.0, 135.0, 43.0), Rgb::from_base_255(134.0, 135.0, 43.0);
// Green
group_1 = Rgb::from_base_255(63.0, 139.0, 41.0), Rgb::from_base_255(63.0, 139.0, 41.0);
// Light Blue
group_2 = Rgb::from_base_255(54.0, 122.0, 185.0), Rgb::from_base_255(54.0, 122.0, 185.0);
// Pink
group_3 = Rgb::from_base_255(193.0, 71.0, 171.0), Rgb::from_base_255(193.0, 71.0, 171.0);
// Blue
group_4 = Rgb::from_base_255(43.0, 117.0, 239.0), Rgb::from_base_255(43.0, 117.0, 239.0);
// Orange
group_5 = Rgb::from_base_255(181.0, 97.0, 35.0), Rgb::from_base_255(181.0, 97.0, 35.0);
local_scope_group = Rgba::new(0.0, 0.42, 0.64, 1.0),Rgba::new(0.0, 0.42, 0.64, 1.0);
}
}
menu_height = 35.0, 35.0;
menu_height = 45.0, 45.0;
menu_divider_color = Rgb(0.7804, 0.7804, 0.7804), Rgb(0.7804, 0.7804, 0.7804);
navigator_divider_color = Rgb(0.7804, 0.7804, 0.7804), Rgb(0.7804, 0.7804, 0.7804);
menu_divider_height = 0.5,0.5;
navigator_divider_width = 0.5,0.5;
menu {
breadcrumbs {
crop_left = 9.0, 9.0;
crop_left = 8.0, 8.0;
crop_right = 3.0, 3.0;
height = 28.0, 28.0;
height = 44.0, 44.0;
separator {
width = 8.0, 8.0;
height = 6.0, 6.0;
color = Rgba(0.0, 0.0, 0.0, 0.15), Rgba(0.0, 0.0, 0.0, 0.15);
offset_x = 1.0, 1.0;
offset_y = -2.0, -2.0;
}
ellipsis {
background_width = 24.0, 24.0;
background_height = 10.0, 10.0;
background_corners_radius = 16.0, 16.0;
background_color = Rgb(0.89, 0.89, 0.9), Rgb(0.89, 0.89, 0.9);
circles_color = Rgb(0.74, 0.74, 0.75), Rgb(0.74, 0.74, 0.75);
background_width = 28.0, 28.0;
background_height = 16.0, 16.0;
background_corners_radius = 100.0, 100.0;
background_color = Rgba(0.0, 0.0, 0.0, 0.04), Rgba(0.0, 0.0, 0.0, 0.04);
circles_color = Rgba(0.0, 0.0, 0.0, 0.17), Rgba(0.0, 0.0, 0.0, 0.17);
circles_radius = 2.0, 2.0;
circles_gap = 2.0, 2.0;
offset_x = 0.0, 0.0;
offset_y = -2.0, -2.0;
}
entry {
margin = 1.0, 1.0;
hover_color = Rgba(0.0, 0.0, 0.0, 0.0), Rgba(0.0, 0.0, 0.0, 0.0);
font = "default", "default";
text_padding_left = 7.0, 7.0;
text_size = 12.0, 12.0;
selected_color = Rgba(0.5, 0.5, 0.51, 1.0), Rgba(0.5, 0.5, 0.51, 1.0);
font = "mplus1p", "mplus1p";
text_y_offset = 6.0, 6.0;
text_padding_left = 0.0, 0.0;
text_size = 11.5, 11.5;
selected_color = Rgba(0.0, 0.0, 0.0, 0.46), Rgba(0.0, 0.0, 0.0, 0.46);
highlight_corners_radius = 15.0, 15.0;
greyed_out_color = Rgba(0.79, 0.79, 0.8, 1.0), Rgba(0.79, 0.79, 0.8, 1.0);
greyed_out_color = Rgba(0.0, 0.0, 0.0, 0.15), Rgba(0.0, 0.0, 0.0, 0.15);
}
}
}
navigator {
width = 37.0, 37.0;
icon_strong_color = Rgba(0.569,0.584,0.612,1.0), Rgba(0.569,0.584,0.612,1.0);
icon_weak_color = Rgba(0.569,0.584,0.612,1.0), Rgba(0.569,0.584,0.612,1.0);
top_padding = -3.0, -3.0;
bottom_padding = 7.0, 7.0;
list_view_width = 39.0, 39.0;
list_view {
background = Rgba::transparent() , Rgba::transparent();
highlight = Rgb(0.96,0.85,0.725) , Rgb(0.96,0.85,0.725); // rgb(245,217,185)
highlight {
height = 29.0, 29.0;
corner_radius = 10.0, 10.0;
width = 41.0, 41.0;
button_size = 32.0, 32.0;
top_padding = 8.0, 8.0;
bottom_padding = 4.0, 4.0;
hover_color = Rgba::transparent(), Rgba::transparent();
highlight {
color = Rgb(0.96,0.85,0.725) , Rgb(0.96,0.85,0.725); // rgb(245,217,185)
size = 29.0, 29.0;
corners_radius = 12.0, 12.0;
}
buttons {
active {
local_scope = Rgb::from_base_255(250.0, 149.0, 31.0), Rgb::from_base_255(250.0, 149.0, 31.0);
submodules = Rgb::from_base_255(250.0, 149.0, 31.0), Rgb::from_base_255(250.0, 149.0, 31.0);
popular = Rgb::from_base_255(250.0, 149.0, 31.0), Rgb::from_base_255(250.0, 149.0, 31.0);
}
entry {
padding = 14.5, 14.5;
}
padding = 5.0, 5.0;
inactive = Rgba(0.0, 0.0, 0.0, 0.15), Rgba(0.0, 0.0, 0.0, 0.15);
}
}
}

View File

@ -169,7 +169,8 @@ ensogl_core::define_endpoints_2! {
set_column_width((Col, f32)),
/// Set the entries parameters.
set_entries_params(EntryParams),
/// Set the entries size. All entries have the same size.
/// Set the entry size. All entries have the same height, but the width can be changed
/// using [`set_column_width`] input or [`EntryFrp::override_column_width`] output.
set_entries_size(Vector2),
/// Set the layer for any texts rendered by entries. The layer will be passed to entries'
/// constructors. **Performance note**: This will re-instantiate all entries.