Wide Componet Group List (#3409)

[ci no changelog needed]

[Task link](https://www.pivotaltracker.com/story/show/181414466)

This PR brings a new UI component: Wide Component Group. This is a three-column headerless container similar to Component Group. See the updated `component-group` demo scene:


https://user-images.githubusercontent.com/6566674/166933866-e5bee142-5176-4a02-bc18-a5bfd96ccbe2.mp4
This commit is contained in:
Ilya Bogdanov 2022-05-12 12:30:00 +03:00 committed by GitHub
parent a2dae60aa9
commit 1c8aa26f90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 631 additions and 53 deletions

2
Cargo.lock generated
View File

@ -747,6 +747,7 @@ dependencies = [
"ensogl-core",
"ensogl-hardcoded-theme",
"ensogl-list-view",
"ensogl-selector",
"ensogl-text-msdf-sys",
"ide-view-component-group",
"wasm-bindgen",
@ -2331,6 +2332,7 @@ dependencies = [
"ensogl-core",
"ensogl-gui-component",
"ensogl-hardcoded-theme",
"ensogl-label",
"ensogl-list-view",
"ensogl-text",
]

View File

@ -5,15 +5,11 @@
//! and many other data complexities); as a result, it is better suited to display of large numbers
//! of events spread unevenly over a long time range than any UML renderers I[KW] am aware of.
// ===============
// === Diagram ===
// ===============
use crate::backend::Direction;
use crate::Metadata;
/// The data necessary to create a diagram of message timings.
#[derive(Debug, Default)]
pub struct Diagram<'a> {

View File

@ -14,14 +14,19 @@
#![warn(trivial_numeric_casts)]
#![warn(unused_import_braces)]
pub mod backend;
pub mod beanpole;
use serde::Serializer;
use std::fmt::Display;
use std::fmt::Formatter;
// ==============
// === Export ===
// ==============
pub mod backend;
pub mod beanpole;
// ================
// === Metadata ===

View File

@ -14,4 +14,5 @@ ensogl-gui-component = { version = "0.1.0", path = "../../../../../lib/rust/enso
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" }
ensogl-label = { path = "../../../../../lib/rust/ensogl/component/label/" }

View File

@ -6,6 +6,7 @@
//! 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).
#![recursion_limit = "512"]
// === Standard Linter Configuration ===
#![deny(non_ascii_idents)]
#![warn(unsafe_code)]
@ -17,14 +18,13 @@
#![warn(trivial_numeric_casts)]
#![warn(unused_import_braces)]
#![warn(unused_qualifications)]
#![recursion_limit = "512"]
use ensogl_core::application::traits::*;
use ensogl_core::display::shape::*;
use ensogl_core::prelude::*;
use enso_frp as frp;
use ensogl_core::application::shortcut::Shortcut;
use ensogl_core::application::traits::*;
use ensogl_core::application::Application;
use ensogl_core::data::color::Rgba;
use ensogl_core::display;
@ -34,6 +34,13 @@ use ensogl_list_view as list_view;
use ensogl_text as text;
// ==============
// === Export ===
// ==============
pub mod wide;
// =================
// === Constants ===
@ -67,13 +74,8 @@ pub mod background {
ensogl_core::define_shape_system! {
below = [list_view::background];
(style:Style, color:Vector4) {
let sprite_width: Var<Pixels> = "input_size.x".into();
let sprite_height: Var<Pixels> = "input_size.y".into();
let color = Var::<Rgba>::from(color);
// TODO[MC,WD]: We should use Plane here, but it has a bug - renders wrong color. See:
// https://github.com/enso-org/enso/pull/3373#discussion_r849054476
let shape = Rect((&sprite_width, &sprite_height)).fill(color);
shape.into()
Plane().fill(color).into()
}
}
}

View File

@ -0,0 +1,447 @@
//! A multi-column [Component Group] without header.
//!
//! Almost every type in this module is parametrized with `COLUMNS` const generic, that represents
//! the count of columns the widget will have. The default value is `3`. (see
//! [`DEFAULT_COLUMNS_COUNT`])
//!
//! The widget is defined by the [`View`].
//!
//! 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).
//!
//! [Component Group]: crate::component_group::View
use ensogl_core::application::traits::*;
use ensogl_core::display::shape::*;
use ensogl_core::prelude::*;
use crate::EntryId;
use enso_frp as frp;
use ensogl_core::application::shortcut::Shortcut;
use ensogl_core::application::Application;
use ensogl_core::data::color::Rgba;
use ensogl_core::display;
use ensogl_gui_component::component;
use ensogl_label::Label;
use ensogl_list_view as list_view;
use list_view::entry::AnyModelProvider;
// =================
// === Constants ===
// =================
/// The default count of columns to display.
pub const DEFAULT_COLUMNS_COUNT: usize = 3;
const ENTRY_HEIGHT: f32 = list_view::entry::HEIGHT;
const MINIMAL_HEIGHT: f32 = ENTRY_HEIGHT;
// ===============
// === Aliases ===
// ===============
/// Type of the component group items.
type Entry = list_view::entry::Label;
newtype_prim! {
/// An index of the column.
ColumnId(usize);
}
// ========================
// === Background Shape ===
// ========================
/// The background of the Wide Component Group.
pub mod background {
use super::*;
ensogl_core::define_shape_system! {
below = [list_view::background];
(style:Style, color:Vector4) {
let color = Var::<Rgba>::from(color);
Plane().fill(color).into()
}
}
}
// =====================
// === ModelProvider ===
// =====================
/// A [`list_view::entry::ModelProvider`] wrapper that splits entries into `COLUMNS` lists.
///
/// Entries are distributed evenly between lists. If the entry count is not divisible by `COLUMNS` -
/// the lists with lower indices will have more entries.
#[derive(Debug, Clone, CloneRef, Default)]
pub struct ModelProvider<const COLUMNS: usize> {
inner: AnyModelProvider<Entry>,
column_id: Immutable<ColumnId>,
}
impl<const COLUMNS: usize> ModelProvider<COLUMNS> {
/// Wrap [`AnyModelProvider`] and split its entries into `COLUMNS` lists. The returned instance
/// provides entries for the column with `column_id`.
fn wrap(inner: &AnyModelProvider<Entry>, column_id: ColumnId) -> AnyModelProvider<Entry> {
AnyModelProvider::new(Self {
inner: inner.clone_ref(),
column_id: Immutable(column_id),
})
}
}
impl<const COLUMNS: usize> list_view::entry::ModelProvider<Entry> for ModelProvider<COLUMNS> {
fn entry_count(&self) -> usize {
let total_entry_count = self.inner.entry_count();
entry_count_in_column::<COLUMNS>(*self.column_id, total_entry_count)
}
fn get(&self, id: EntryId) -> Option<String> {
let total_entry_count = self.inner.entry_count();
let idx = local_idx_to_global::<COLUMNS>(*self.column_id, id, total_entry_count);
self.inner.get(idx)
}
}
// ===========
// === FRP ===
// ===========
ensogl_core::define_endpoints_2! {
Input {
/// Accept the currently selected suggestion. Should be bound to "Suggestion Acceptance Key"
/// described in
/// [Component Browser Design Doc](https://github.com/enso-org/design/blob/main/epics/component-browser/design.md#key-binding-dictionary)
accept_suggestion(),
select_entry(ColumnId, EntryId),
set_entries(AnyModelProvider<Entry>),
set_background_color(Rgba),
set_width(f32),
set_no_items_label_text(String),
}
Output {
selected_entry(Option<EntryId>),
suggestion_accepted(EntryId),
expression_accepted(EntryId),
/// While resizing the list of entries, the selection will follow the selected entry if
/// possible. If the entry disappears, the selection will move to some visible entry in the same
/// column if possible. If there are no more entries in this column, the selection will move to
/// the next non-empty column to the left.
selection_position_target(Vector2<f32>),
entry_count(usize),
size(Vector2<f32>),
}
}
impl<const COLUMNS: usize> component::Frp<Model<COLUMNS>> for Frp {
fn init(
api: &Self::Private,
_app: &Application,
model: &Model<COLUMNS>,
_style: &StyleWatchFrp,
) {
let network = &api.network;
let input = &api.input;
let out = &api.output;
frp::extend! { network
entry_count <- input.set_entries.map(|p| p.entry_count());
out.entry_count <+ entry_count;
selected_column_and_entry <- any(...);
update_selected_entry <- selected_column_and_entry.sample(&out.size);
select_entry <- any(&input.select_entry, &update_selected_entry);
eval select_entry([model]((column, entry)) {
let column = model.non_empty_column(*column);
if let Some(column) = column {
let real_entry_id = column.reverse_index(*entry);
column.select_entry(real_entry_id);
}
});
eval input.set_background_color((c) model.background.color.set(c.into()));
eval input.set_no_items_label_text((text) model.set_no_items_label_text(text));
// === Background size ===
background_height <- any(...);
let background_width = input.set_width.clone_ref();
size <- all_with(&background_width, &background_height,
|width, height| Vector2(*width, *height));
eval size((size) model.background.size.set(*size));
out.size <+ size;
// === "No items" label ===
no_entries_provided <- entry_count.map(|c| *c == 0);
show_no_items_label <- no_entries_provided.on_true();
hide_no_items_label <- no_entries_provided.on_false();
eval_ show_no_items_label(model.show_no_items_label());
eval_ hide_no_items_label(model.hide_no_items_label());
}
for column in model.columns.iter() {
let col_id = column.id.clone_ref();
frp::extend! { network
// === Accepting suggestions ===
accepted_entry <- column.selected_entry.sample(&input.accept_suggestion).filter_map(|e| *e);
chosen_entry <- column.chosen_entry.filter_map(|e| *e);
out.suggestion_accepted <+ accepted_entry.map2(&entry_count, f!(
[](&e, &total) local_idx_to_global::<COLUMNS>(col_id, e, total)
));
out.expression_accepted <+ chosen_entry.map2(&entry_count, f!(
[](&e, &total) local_idx_to_global::<COLUMNS>(col_id, e, total)
));
// === Selection position ===
entry_selected <- column.selected_entry.filter_map(|e| *e);
selected_column_and_entry <+ entry_selected.map(f!([column](e) (col_id, column.reverse_index(*e))));
on_column_selected <- column.selected_entry.map(|e| e.is_some()).on_true();
eval_ on_column_selected(model.on_column_selected(col_id));
selection_pos <- column.selection_position_target.sample(&on_column_selected);
out.selection_position_target <+ selection_pos.map(f!((pos) column.selection_position(*pos)));
// === set_entries ===
out.selected_entry <+ column.selected_entry.map2(&entry_count, move |entry, total| {
entry.map(|e| local_idx_to_global::<COLUMNS>(col_id, e, *total))
});
entries <- input.set_entries.map(move |p| ModelProvider::<COLUMNS>::wrap(p, col_id));
background_height <+ entries.map(f_!(model.background_height()));
eval entries((e) column.set_entries(e));
_eval <- all_with(&entries, &out.size, f!((_, size) column.resize_and_place(*size)));
}
}
}
fn default_shortcuts() -> Vec<Shortcut> {
use ensogl_core::application::shortcut::ActionType::*;
(&[(Press, "tab", "accept_suggestion")])
.iter()
.map(|(a, b, c)| View::<COLUMNS>::self_shortcut(*a, *b, *c))
.collect()
}
}
// ==============
// === Column ===
// ==============
/// An internal representation of the column.
///
/// `COLUMNS` is the total count of columns in the widget.
#[derive(Debug, Clone, CloneRef, Deref)]
struct Column<const COLUMNS: usize> {
id: ColumnId,
provider: Rc<CloneRefCell<AnyModelProvider<Entry>>>,
#[deref]
list_view: list_view::ListView<Entry>,
}
impl<const COLUMNS: usize> Column<COLUMNS> {
/// Constructor.
fn new(app: &Application, id: ColumnId) -> Self {
Self { id, provider: default(), list_view: app.new_view::<list_view::ListView<Entry>>() }
}
/// An entry count for this column.
fn len(&self) -> usize {
self.provider.get().entry_count()
}
/// Transforms `entry_id` into the actual [`EntryId`] for the underlying
/// [`list_view::ListView`].
///
/// [`EntryId`] of the Wide Component Group counts from the bottom (the bottom most entry has an
/// id of 0), but the underlying [`list_view::ListView`] starts its ids from the top (so
/// that the top most entry has an id of 0). This function converts the former to the
/// latter.
fn reverse_index(&self, entry_id: EntryId) -> EntryId {
reverse_index(entry_id, self.len())
}
/// Update the entries list, a setter for [`list_view::ListView::set_entries`].
fn set_entries(&self, provider: &AnyModelProvider<Entry>) {
self.provider.set(provider.clone_ref());
self.list_view.set_entries(provider);
}
/// Resize the column and update its position.
fn resize_and_place(&self, size: Vector2) {
let width = size.x / COLUMNS as f32;
let bg_height = size.y;
let height = self.len() as f32 * ENTRY_HEIGHT;
self.list_view.resize(Vector2(width, height));
let left_border = -(COLUMNS as f32 * width / 2.0) + width / 2.0;
let pos_x = left_border + width * *self.id as f32;
let half_height = height / 2.0;
let background_bottom = -bg_height / 2.0;
let pos_y = background_bottom + half_height;
self.list_view.set_position_x(pos_x);
self.list_view.set_position_y(pos_y);
}
/// Transform the position relative to the column into the position relative to the whole
/// widget.
fn selection_position(&self, pos: Vector2) -> Vector2 {
self.position().xy() + pos
}
}
// =============
// === Model ===
// =============
/// The Model of the [`View`] component. Consists of `COLUMNS` columns.
#[derive(Clone, CloneRef, Debug)]
pub struct Model<const COLUMNS: usize> {
display_object: display::object::Instance,
background: background::View,
columns: Rc<Vec<Column<COLUMNS>>>,
no_items_label: Label,
}
impl<const COLUMNS: usize> display::Object for Model<COLUMNS> {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
}
}
impl<const COLUMNS: usize> component::Model for Model<COLUMNS> {
fn label() -> &'static str {
"WideComponentGroupView"
}
fn new(app: &Application, logger: &Logger) -> Self {
let display_object = display::object::Instance::new(&logger);
let background = background::View::new(&logger);
display_object.add_child(&background);
let columns: Vec<_> = (0..COLUMNS).map(|i| Column::new(app, ColumnId::new(i))).collect();
let columns = Rc::new(columns);
for column in columns.iter() {
column.hide_selection();
column.set_background_color(Rgba::transparent());
column.show_background_shadow(false);
column.set_background_corners_radius(0.0);
display_object.add_child(&**column);
}
let no_items_label = Label::new(app);
Model { no_items_label, display_object, background, columns }
}
}
impl<const COLUMNS: usize> Model<COLUMNS> {
/// Set the text content of the "no items" label.
fn set_no_items_label_text(&self, text: &str) {
self.no_items_label.set_content(text);
}
/// Make the "no items" label visible.
fn show_no_items_label(&self) {
self.display_object.add_child(&self.no_items_label);
}
/// Hide the "no items" label.
fn hide_no_items_label(&self) {
self.display_object.remove_child(&self.no_items_label);
}
/// Returns the rightmost non-empty column with index less or equal to `index`.
fn non_empty_column(&self, index: ColumnId) -> Option<&Column<COLUMNS>> {
let indexes_to_the_right = (0..=*index).rev();
let mut columns_to_the_right = indexes_to_the_right.flat_map(|i| self.columns.get(i));
columns_to_the_right.find(|col| col.len() > 0)
}
/// Deselect entries in all columns except the one with provided `column_index`. We ensure that
/// at all times only a single entry across all columns is selected.
fn on_column_selected(&self, column_id: ColumnId) {
let other_columns = self.columns.iter().enumerate().filter(|(i, _)| *i != *column_id);
for (_, column) in other_columns {
column.deselect_entries();
}
}
/// Calculate the height of the component. It can't be less than [`MINIMAL_HEIGHT`].
fn background_height(&self) -> f32 {
if let Some(largest_column) = self.columns.first() {
let entry_count_in_largest_column = largest_column.len();
let background_height = entry_count_in_largest_column as f32 * ENTRY_HEIGHT;
background_height.max(MINIMAL_HEIGHT)
} else {
MINIMAL_HEIGHT
}
}
}
// ============
// === View ===
// ============
/// The implementation of the visual component described in the module's documentation.
pub type View<const COLUMNS: usize = DEFAULT_COLUMNS_COUNT> =
component::ComponentView<Model<COLUMNS>, Frp>;
// ===============
// === Helpers ===
// ===============
/// Return the number of entries in the column with `index`.
fn entry_count_in_column<const COLUMNS: usize>(
column_id: ColumnId,
total_entry_count: usize,
) -> usize {
let evenly_distributed_count = total_entry_count / COLUMNS;
let remainder = total_entry_count % COLUMNS;
let has_remainder = remainder > 0;
let column_contains_remaining_entries = *column_id < remainder;
if has_remainder && column_contains_remaining_entries {
evenly_distributed_count + 1
} else {
evenly_distributed_count
}
}
/// Transform the index inside column to the "global" index inside the Wide Component Group.
///
/// The entry #1 in column with index 2 is the second item from the bottom in the third column and
/// has a "global" index of `COLUMNS + 2 = 5`.
fn local_idx_to_global<const COLUMNS: usize>(
column: ColumnId,
entry: EntryId,
total_entry_count: usize,
) -> EntryId {
let reversed_index =
reverse_index(entry, entry_count_in_column::<COLUMNS>(column, total_entry_count));
COLUMNS * reversed_index + *column
}
/// "Reverse" the index in such a way that the first entry becomes the last, and the last becomes
/// the first.
fn reverse_index(index: EntryId, entries_count: usize) -> EntryId {
entries_count.saturating_sub(index).saturating_sub(1)
}

View File

@ -10,6 +10,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
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-list-view = { path = "../../../../../lib/rust/ensogl/component/list-view" }
ensogl-text-msdf-sys = { path = "../../../../../lib/rust/ensogl/component/text/msdf-sys" }

View File

@ -22,8 +22,11 @@ use ensogl_core::frp;
use ensogl_core::Animation;
use ensogl_hardcoded_theme as theme;
use ensogl_list_view as list_view;
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;
@ -47,27 +50,48 @@ pub fn main() {
// === Mock Entries ===
// ====================
#[derive(Clone, Debug)]
const PREPARED_ITEMS: &[&str; 8] = &[
"long sample entry with text overflowing the width",
"convert",
"table input",
"text input",
"number input",
"table output",
"data output",
"data input",
];
#[derive(Debug)]
struct MockEntries {
entries: Vec<String>,
count: Cell<usize>,
}
impl MockEntries {
fn new(entries: Vec<String>) -> Self {
Self { entries }
fn new(count: usize) -> Rc<Self> {
Rc::new(Self {
entries: PREPARED_ITEMS.iter().cycle().take(count).map(ToString::to_string).collect(),
count: Cell::new(count),
})
}
fn get_entry(&self, i: usize) -> Option<String> {
self.entries.get(i).cloned()
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<String> {
self.entries.get(id).cloned()
}
}
impl list_view::entry::ModelProvider<list_view::entry::Label> for MockEntries {
fn entry_count(&self) -> usize {
self.entries.len()
self.count.get()
}
fn get(&self, id: usize) -> Option<String> {
fn get(&self, id: list_view::entry::Id) -> Option<String> {
self.get_entry(id)
}
}
@ -78,37 +102,71 @@ impl list_view::entry::ModelProvider<list_view::entry::Label> for MockEntries {
// === Init Application ===
// ========================
// === Helpers ====
fn create_selection() -> list_view::selection::View {
let selection = list_view::selection::View::new(Logger::new("Selection"));
selection.color.set(color::Rgba(0.527, 0.554, 0.18, 1.0).into());
selection.size.set(Vector2(150.0, list_view::entry::HEIGHT));
selection.corner_radius.set(5.0);
selection
}
fn component_group(app: &Application) -> component_group::View {
let component_group = app.new_view::<component_group::View>();
let group_name = "Long group name with text overflowing the width";
component_group.set_header(group_name.to_string());
component_group.set_width(150.0);
component_group.set_position_x(-300.0);
component_group.set_background_color(color::Rgba(0.927, 0.937, 0.913, 1.0));
component_group
}
fn wide_component_group(app: &Application) -> component_group::wide::View {
let wide_component_group = app.new_view::<component_group::wide::View>();
wide_component_group.set_position_x(100.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 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
}
// === init ===
fn init(app: &Application) {
theme::builtin::dark::register(&app);
theme::builtin::light::register(&app);
theme::builtin::light::enable(&app);
let mock_entries = MockEntries::new(vec![
"long sample entry with text overflowing the width".into(),
"convert".into(),
"table input".into(),
"text input".into(),
"number input".into(),
"table input".into(),
"data output".into(),
"data input".into(),
]);
let slider = slider(app);
let network = frp::Network::new("Component Group Debug Scene");
let selection = list_view::selection::View::new(Logger::new("Selection"));
selection.color.set(color::Rgba(0.527, 0.554, 0.18, 1.0).into());
selection.size.set(Vector2(150.0, list_view::entry::HEIGHT));
selection.corner_radius.set(5.0);
let selection = create_selection();
let selection_animation = Animation::<Vector2>::new(&network);
let component_group = app.new_view::<component_group::View>();
let provider = list_view::entry::AnyModelProvider::new(mock_entries);
let group_name = "Long group name with text overflowing the width";
component_group.set_header(group_name.to_string());
component_group.set_entries(provider);
component_group.set_width(150.0);
component_group.set_background_color(color::Rgba(0.927, 0.937, 0.913, 1.0));
let wide_selection = create_selection();
let wide_selection_animation = Animation::<Vector2>::new(&network);
let component_group = component_group(app);
app.display.add_child(&component_group);
app.display.add_child(&selection);
component_group.add_child(&selection);
let wide_component_group = wide_component_group(app);
app.display.add_child(&wide_component_group);
wide_component_group.add_child(&wide_selection);
// === Regular Component Group ===
frp::extend! { network
selection_animation.target <+ component_group.selection_position_target;
@ -121,7 +179,47 @@ fn init(app: &Application) {
selection_animation.target.emit(component_group.selection_position_target.value());
selection_animation.skip.emit(());
// === Wide Component Group ===
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 model_provider = AnyModelProvider::from(mock_entries.clone_ref());
frp::extend! { network
int_value <- slider.frp.output.value.map(|v| *v as usize);
eval int_value([component_group, wide_component_group](i) {
mock_entries.set_count(*i);
component_group.set_entries(model_provider.clone_ref());
wide_component_group.set_entries(model_provider.clone_ref());
});
}
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);
std::mem::forget(slider);
std::mem::forget(network);
std::mem::forget(selection);
std::mem::forget(component_group);
std::mem::forget(wide_component_group);
std::mem::forget(wide_selection);
}

View File

@ -1,10 +1,12 @@
//! A visual line capped with an arrow, that shows a tooltip on mouse hover.
use ensogl_core::display::shape::*;
use ensogl_core::prelude::*;
use crate::shape;
use crate::shape::CAP_WIDTH;
use crate::shape::HOVER_PADDING;
use ensogl::frp;
use ensogl_core::application::tooltip;
use ensogl_core::application::Application;

View File

@ -13,13 +13,11 @@
#![warn(unused_import_braces)]
#![warn(unused_qualifications)]
pub mod labeled_line;
pub mod shape;
use ensogl_core::prelude::*;
use crate::labeled_line::Cap;
use crate::labeled_line::LabeledLine;
use enso_profiler_data::Profile;
use enso_profiler_enso_data::Metadata;
use ensogl::frp;
@ -29,6 +27,14 @@ use ensogl_core::display::shape::StyleWatchFrp;
use ensogl_gui_component::component;
// ==============
// === Export ===
// ==============
pub mod labeled_line;
pub mod shape;
// =================
// === Constants ===

View File

@ -1,8 +1,9 @@
//! Shape used for the LabeledLine.
use ensogl_core::display::shape::*;
use ensogl_core::prelude::*;
use ensogl_core::data::color;
use ensogl_core::display::shape::*;

View File

@ -1,6 +1,9 @@
//! The `Tooltip` shows extra information for UI components. It is pegged to the cursor location
//! and appears when it receives information to show.
use ensogl_core as ensogl;
// === Standard Linter Configuration ===
#![deny(non_ascii_idents)]
#![warn(unsafe_code)]
use ensogl::prelude::*;
@ -9,13 +12,20 @@ use ensogl::animation::hysteretic::HystereticAnimation;
use ensogl::application::Application;
use ensogl::display;
use ensogl::display::shape::StyleWatch;
use ensogl_core as ensogl;
use ensogl_core::application::tooltip::Placement;
use ensogl_core::application::tooltip::Style;
use ensogl_label::Label;
// ==============
// === Export ===
// ==============
pub use ensogl::application::tooltip;
// =================
// === Constants ===
// =================

View File

@ -13,7 +13,6 @@ use crate::gui::cursor::Cursor;
use crate::system::web;
// ==============
// === Export ===
// ==============
@ -24,8 +23,11 @@ pub mod frp;
pub mod shortcut;
pub mod tooltip;
pub mod view;
pub use view::View;
/// A module with commonly used traits to mass import.
pub mod traits {
pub use crate::application::view::View as TRAIT_View;

View File

@ -7,11 +7,16 @@ use serde::Deserialize;
use serde::Serialize;
// ==============
// === Export ===
// ==============
pub mod builder;
pub use builder::Builder;
/// Metadata of any type.
pub type AnyMetadata = Box<serde_json::value::RawValue>;