Show section name in breadcrumbs (#3778)

This PR implements displaying a currently active section name as a first crumb in the breadcrumbs panel. Sections are called `Popular`, `Modules` and `Local`.


https://user-images.githubusercontent.com/6566674/194551276-90bd7d6b-8509-43ec-b3c0-11c35fda9063.mp4

# Important Notes
This PR also contains a fix for [this bug](https://www.pivotaltracker.com/story/show/183499312). It was caused by mistake in the FRP implementation of the breadcrumbs. You could only trigger this bug after code changes in the `animation/loop.rs` module. It is not possible to see it otherwise. Still, the code was not correct, and now it is fixed.
This commit is contained in:
Ilya Bogdanov 2022-10-17 16:26:40 +03:00 committed by GitHub
parent 701c644d0e
commit 78b8f43b96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 24 deletions

View File

@ -21,6 +21,7 @@ use enso_frp as frp;
use ide_view as view; use ide_view as view;
use ide_view::component_browser::component_list_panel::grid as component_grid; use ide_view::component_browser::component_list_panel::grid as component_grid;
use ide_view::component_browser::component_list_panel::BreadcrumbId; use ide_view::component_browser::component_list_panel::BreadcrumbId;
use ide_view::component_browser::component_list_panel::SECTION_NAME_CRUMB_INDEX;
use ide_view::graph_editor::component::node as node_view; use ide_view::graph_editor::component::node as node_view;
use ide_view::graph_editor::GraphEditor; use ide_view::graph_editor::GraphEditor;
use ide_view::project::SearcherParams; use ide_view::project::SearcherParams;
@ -221,6 +222,17 @@ impl Model {
} }
} }
fn set_section_name_crumb(&self, text: &str) {
if let SearcherVariant::ComponentBrowser(browser) = self.view.searcher() {
let breadcrumbs = &browser.model().list.model().breadcrumbs;
breadcrumbs.set_entry((SECTION_NAME_CRUMB_INDEX, ImString::new(text).into()));
}
}
fn on_active_section_change(&self, section_id: component_grid::SectionId) {
self.set_section_name_crumb(section_id.as_str());
}
fn module_entered(&self, module: component_grid::ElementId) { fn module_entered(&self, module: component_grid::ElementId) {
self.enter_module(module); self.enter_module(module);
} }
@ -380,6 +392,8 @@ impl Searcher {
eval entry_selected((entry) model.suggestion_selected(*entry)); eval entry_selected((entry) model.suggestion_selected(*entry));
eval grid.module_entered((id) model.module_entered(*id)); eval grid.module_entered((id) model.module_entered(*id));
eval breadcrumbs.selected((id) model.breadcrumb_selected(*id)); eval breadcrumbs.selected((id) model.breadcrumb_selected(*id));
active_section <- grid.active_section.filter_map(|s| *s);
eval active_section((section) model.on_active_section_change(*section));
} }
} }
SearcherVariant::OldNodeSearcher(searcher) => { SearcherVariant::OldNodeSearcher(searcher) => {

View File

@ -178,9 +178,9 @@ impl EntryData {
} }
} }
fn update_layout(&self, contour: Contour, text_size: text::Size, text_offset: f32) { fn update_layout(&self, contour: Contour, text_size: text::Size, text_padding: f32) {
let size = contour.size; let size = contour.size;
self.text.set_position_xy(Vector2(text_offset - size.x / 2.0, text_size.value / 2.0)); self.text.set_position_xy(Vector2(text_padding - size.x / 2.0, text_size.value / 2.0));
self.separator.size.set(size); self.separator.size.set(size);
self.ellipsis.size.set(size); self.ellipsis.size.set(size);
} }
@ -216,15 +216,19 @@ impl EntryData {
self.state.get() == State::Text self.state.get() == State::Text
} }
/// Return the width of the entry if it is known. If the entry displays text, the width needs to fn width(&self, text_padding: f32) -> f32 {
/// be calculated separately.
fn fixed_width(&self) -> Option<f32> {
match self.state.get() { match self.state.get() {
State::Text => None, State::Text => self.text_width(self.text.width.value(), text_padding),
State::Separator => Some(separator::ICON_WIDTH), State::Separator => separator::ICON_WIDTH,
State::Ellipsis => Some(ellipsis::ICON_WIDTH), State::Ellipsis => ellipsis::ICON_WIDTH,
} }
} }
/// Width of the breadcrumb column filled with text of width [`text_width`] and with margin
/// [`text_padding`].
fn text_width(&self, text_width: f32, text_padding: f32) -> f32 {
text_width + text_padding * 2.0
}
} }
// === Params === // === Params ===
@ -238,7 +242,7 @@ pub struct Params {
pub margin: f32, pub margin: f32,
pub hover_color: color::Lcha, pub hover_color: color::Lcha,
pub font_name: ImString, pub font_name: ImString,
pub text_padding_left: f32, pub text_padding: f32,
pub text_size: text::Size, pub text_size: text::Size,
pub selected_color: color::Lcha, pub selected_color: color::Lcha,
pub highlight_corners_radius: f32, pub highlight_corners_radius: f32,
@ -278,7 +282,7 @@ impl ensogl_grid_view::Entry for Entry {
margin <- input.set_params.map(|p| p.margin).on_change(); margin <- input.set_params.map(|p| p.margin).on_change();
hover_color <- input.set_params.map(|p| p.hover_color).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(); font <- input.set_params.map(|p| p.font_name.clone_ref()).on_change();
text_offset <- input.set_params.map(|p| p.text_padding_left).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_color <- input.set_params.map(|p| p.selected_color).on_change();
text_size <- input.set_params.map(|p| p.text_size).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(); greyed_out_color <- input.set_params.map(|p| p.greyed_out_color).on_change();
@ -306,7 +310,7 @@ impl ensogl_grid_view::Entry for Entry {
size: *size - Vector2(*margin, *margin) * 2.0, size: *size - Vector2(*margin, *margin) * 2.0,
corners_radius: 0.0, corners_radius: 0.0,
}); });
layout <- all(contour, text_size, text_offset); layout <- all(contour, text_size, text_padding);
eval layout ((&(c, ts, to)) data.update_layout(c, ts, to)); eval layout ((&(c, ts, to)) data.update_layout(c, ts, to));
eval color((c) data.set_default_color(*c)); eval color((c) data.set_default_color(*c));
eval font((f) data.set_font(f.to_string())); eval font((f) data.set_font(f.to_string()));
@ -324,16 +328,16 @@ impl ensogl_grid_view::Entry for Entry {
// === Override column width === // === Override column width ===
// We need to adjust the width of the grid view column depending on the width of // We need to adjust the width of the grid view column depending on the width of
// the entry. For entries displaying icons we use [`EntryData::fixed_width`] method. // the entry.
// For text entries, we listen for [`Text::width`] changes. out.override_column_width <+ input.set_model.map2(&text_padding,
out.override_column_width <+ input.set_model.map( f!([data](model, text_padding) {
f!((model) {
data.set_model(model); data.set_model(model);
data.fixed_width() data.width(*text_padding)
}) })
).filter_map(|w| *w); );
text_width <- data.text.width.filter(f_!(data.is_text_displayed())); text_width <- data.text.width.filter(f_!(data.is_text_displayed()));
entry_width <- text_width.map2(&text_offset, |w, text_offset| w + text_offset * 2.0); // For text entries, we also listen for [`Text::width`] changes.
entry_width <- text_width.map2(&text_padding, f!((w, o) data.text_width(*w, *o)));
out.override_column_width <+ entry_width; out.override_column_width <+ entry_width;
} }
init.emit(()); init.emit(());

View File

@ -75,6 +75,8 @@ mod entry;
/// the last one or if it is placed in the right region of the viewport. This way, we avoid /// the last one or if it is placed in the right region of the viewport. This way, we avoid
/// unnecessary scrolling when the user selects some breadcrumb close to the end of the list. /// unnecessary scrolling when the user selects some breadcrumb close to the end of the list.
const SCROLLING_THRESHOLD_FRACTION: f32 = 0.5; const SCROLLING_THRESHOLD_FRACTION: f32 = 0.5;
/// An index of the breadcrumb that displays the name of the active section.
pub const SECTION_NAME_CRUMB_INDEX: BreadcrumbId = 0;
@ -210,7 +212,7 @@ impl Model {
let (_, hover_color,selected_color,greyed_out_color) = colors; let (_, hover_color,selected_color,greyed_out_color) = colors;
entry::Params { entry::Params {
margin, margin,
text_padding_left: *text_padding, text_padding: *text_padding,
text_size: text::Size::from(*text_size), text_size: text::Size::from(*text_size),
hover_color: hover_color.into(), hover_color: hover_color.into(),
font_name: font.clone(), font_name: font.clone(),
@ -347,12 +349,20 @@ impl Model {
self.grid.set_entries_params(params); self.grid.set_entries_params(params);
} }
/// Set the breadcrumb at a specified index. Does nothing if index is out of bounds.
pub fn set_entry(&self, entry: &Breadcrumb, index: BreadcrumbId) {
if let Some(e) = self.entries.borrow_mut().get_mut(index) {
*e = entry.clone_ref();
}
self.grid.request_model_for_visible_entries();
}
/// Set the breadcrumbs starting from the [`starting_from`] index. Existing entries after /// Set the breadcrumbs starting from the [`starting_from`] index. Existing entries after
/// [`starting_from`] will be overwritten. [`self.entries`] will be extended if needed to fit /// [`starting_from`] will be overwritten. [`self.entries`] will be extended if needed to fit
/// all added entries. /// all added entries.
/// Immediately selects the last breadcrumb. All inactive (greyed out) breadcrumbs will be /// Immediately selects the last breadcrumb. All inactive (greyed out) breadcrumbs will be
/// removed. /// removed.
pub fn set_entries(&self, starting_from: usize, new_entries: &[Breadcrumb]) { pub fn set_entries(&self, new_entries: &[Breadcrumb], starting_from: BreadcrumbId) {
{ {
let mut borrowed = self.entries.borrow_mut(); let mut borrowed = self.entries.borrow_mut();
let end_of_overwritten_entries = starting_from + new_entries.len(); let end_of_overwritten_entries = starting_from + new_entries.len();
@ -375,7 +385,7 @@ impl Model {
/// A newly added breadcrumb will be placed after the currently selected one. All inactive /// A newly added breadcrumb will be placed after the currently selected one. All inactive
/// (greyed out) breadcrumbs will be removed. /// (greyed out) breadcrumbs will be removed.
pub fn push(&self, breadcrumb: &Breadcrumb) { pub fn push(&self, breadcrumb: &Breadcrumb) {
self.set_entries(self.entries.borrow().len(), &[breadcrumb.clone_ref()]); self.set_entries(&[breadcrumb.clone_ref()], self.entries.borrow().len());
} }
/// Move the selection to the previous breadcrumb. Stops at the first one. There is always at /// Move the selection to the previous breadcrumb. Stops at the first one. There is always at
@ -431,7 +441,9 @@ ensogl_core::define_endpoints_2! {
/// Add a new breadcrumb after the currently selected one. /// Add a new breadcrumb after the currently selected one.
push(Breadcrumb), push(Breadcrumb),
/// Set the displayed breadcrumbs starting from the specific index. /// Set the displayed breadcrumbs starting from the specific index.
set_entries_from((Vec<Breadcrumb>, usize)), set_entries_from((Vec<Breadcrumb>, BreadcrumbId)),
/// Set the breadcrumb at a specified index.
set_entry((BreadcrumbId, Breadcrumb)),
/// Enable or disable displaying of the ellipsis icon at the end of the list. /// Enable or disable displaying of the ellipsis icon at the end of the list.
show_ellipsis(bool), show_ellipsis(bool),
/// Remove all breadcrumbs. /// Remove all breadcrumbs.
@ -484,7 +496,8 @@ impl Breadcrumbs {
eval_ input.clear(model.clear()); eval_ input.clear(model.clear());
selected <- selected_grid_col.map(|(_, col)| col / 2); selected <- selected_grid_col.map(|(_, col)| col / 2);
eval input.push((b) model.push(b)); eval input.push((b) model.push(b));
eval input.set_entries_from(((entries, from)) model.set_entries(*from, entries)); eval input.set_entries_from(((entries, from)) model.set_entries(entries, *from));
eval input.set_entry(((index, entry)) model.set_entry(entry, *index));
out.selected <+ selected; out.selected <+ selected;
scroll_anim.target <+ all_with3(&model.grid.content_size, &input.set_size, &model.grid scroll_anim.target <+ all_with3(&model.grid.content_size, &input.set_size, &model.grid

View File

@ -26,6 +26,17 @@ pub enum SectionId {
SubModules, SubModules,
} }
impl SectionId {
/// Return a displayed name of the section.
pub const fn as_str(&self) -> &'static str {
match self {
Self::Popular => "Popular",
Self::LocalScope => "Local",
Self::SubModules => "Modules",
}
}
}
// === GroupId === // === GroupId ===

View File

@ -74,6 +74,7 @@ use ensogl_shadow as shadow;
mod navigator; mod navigator;
pub use breadcrumbs::BreadcrumbId; pub use breadcrumbs::BreadcrumbId;
pub use breadcrumbs::SECTION_NAME_CRUMB_INDEX;
pub use ensogl_core::prelude; pub use ensogl_core::prelude;
pub use ide_view_component_list_panel_breadcrumbs as breadcrumbs; pub use ide_view_component_list_panel_breadcrumbs as breadcrumbs;
pub use ide_view_component_list_panel_grid as grid; pub use ide_view_component_list_panel_grid as grid;
@ -87,6 +88,7 @@ pub use ide_view_component_list_panel_grid::entry::icon;
/// The selection animation is faster than the default one because of the increased spring force. /// The selection animation is faster than the default one because of the increased spring force.
const SELECTION_ANIMATION_SPRING_FORCE_MULTIPLIER: f32 = 1.5; const SELECTION_ANIMATION_SPRING_FORCE_MULTIPLIER: f32 = 1.5;
const INITIAL_SECTION_NAME: &str = grid::SectionId::Popular.as_str();
@ -269,7 +271,8 @@ impl Model {
} }
fn set_initial_breadcrumbs(&self) { fn set_initial_breadcrumbs(&self) {
self.breadcrumbs.set_entries_from((vec![breadcrumbs::Breadcrumb::new("All")], 0)); let breadcrumb = breadcrumbs::Breadcrumb::new(INITIAL_SECTION_NAME);
self.breadcrumbs.set_entries_from((vec![breadcrumb], 0));
self.breadcrumbs.show_ellipsis(true); self.breadcrumbs.show_ellipsis(true);
} }