mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 23:31:42 +03:00
Sync breadcrumbs and documentation panel. (#7508)
Implements #7310. ![Peek 2023-08-09 16-07](https://github.com/enso-org/enso/assets/1428930/1a244e38-5c34-4c8b-8885-1cf84ac7b6a7)
This commit is contained in:
parent
7a272ec152
commit
7f19b09d13
31
Cargo.lock
generated
31
Cargo.lock
generated
@ -2203,6 +2203,7 @@ dependencies = [
|
|||||||
"enso-text",
|
"enso-text",
|
||||||
"enso-web",
|
"enso-web",
|
||||||
"ensogl",
|
"ensogl",
|
||||||
|
"ensogl-breadcrumbs",
|
||||||
"ensogl-component",
|
"ensogl-component",
|
||||||
"ensogl-drop-manager",
|
"ensogl-drop-manager",
|
||||||
"ensogl-dynamic-assets",
|
"ensogl-dynamic-assets",
|
||||||
@ -2629,6 +2630,19 @@ dependencies = [
|
|||||||
"ensogl-text",
|
"ensogl-text",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ensogl-breadcrumbs"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"enso-frp",
|
||||||
|
"ensogl-core",
|
||||||
|
"ensogl-derive-theme",
|
||||||
|
"ensogl-grid-view",
|
||||||
|
"ensogl-hardcoded-theme",
|
||||||
|
"ensogl-icons",
|
||||||
|
"ensogl-text",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ensogl-button"
|
name = "ensogl-button"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -4328,11 +4342,11 @@ dependencies = [
|
|||||||
"enso-frp",
|
"enso-frp",
|
||||||
"enso-prelude",
|
"enso-prelude",
|
||||||
"ensogl",
|
"ensogl",
|
||||||
|
"ensogl-breadcrumbs",
|
||||||
"ensogl-gui-component",
|
"ensogl-gui-component",
|
||||||
"ensogl-hardcoded-theme",
|
"ensogl-hardcoded-theme",
|
||||||
"ensogl-text",
|
"ensogl-text",
|
||||||
"ide-view-component-list-panel",
|
"ide-view-component-list-panel",
|
||||||
"ide-view-component-list-panel-breadcrumbs",
|
|
||||||
"ide-view-documentation",
|
"ide-view-documentation",
|
||||||
"ide-view-graph-editor",
|
"ide-view-graph-editor",
|
||||||
]
|
]
|
||||||
@ -4357,19 +4371,6 @@ dependencies = [
|
|||||||
"ordered-float",
|
"ordered-float",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ide-view-component-list-panel-breadcrumbs"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"enso-frp",
|
|
||||||
"ensogl-core",
|
|
||||||
"ensogl-derive-theme",
|
|
||||||
"ensogl-grid-view",
|
|
||||||
"ensogl-hardcoded-theme",
|
|
||||||
"ensogl-icons",
|
|
||||||
"ensogl-text",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ide-view-component-list-panel-grid"
|
name = "ide-view-component-list-panel-grid"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -4398,11 +4399,11 @@ dependencies = [
|
|||||||
"enso-profiler",
|
"enso-profiler",
|
||||||
"enso-suggestion-database",
|
"enso-suggestion-database",
|
||||||
"ensogl",
|
"ensogl",
|
||||||
|
"ensogl-breadcrumbs",
|
||||||
"ensogl-component",
|
"ensogl-component",
|
||||||
"ensogl-hardcoded-theme",
|
"ensogl-hardcoded-theme",
|
||||||
"horrorshow",
|
"horrorshow",
|
||||||
"ide-ci",
|
"ide-ci",
|
||||||
"ide-view-component-list-panel-breadcrumbs",
|
|
||||||
"ide-view-graph-editor",
|
"ide-view-graph-editor",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -33,6 +33,7 @@ ensogl-dynamic-assets = { path = "../../lib/rust/ensogl/component/dynamic-assets
|
|||||||
ensogl-text-msdf = { path = "../../lib/rust/ensogl/component/text/src/font/msdf" }
|
ensogl-text-msdf = { path = "../../lib/rust/ensogl/component/text/src/font/msdf" }
|
||||||
ensogl-hardcoded-theme = { path = "../../lib/rust/ensogl/app/theme/hardcoded" }
|
ensogl-hardcoded-theme = { path = "../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||||
ensogl-drop-manager = { path = "../../lib/rust/ensogl/component/drop-manager" }
|
ensogl-drop-manager = { path = "../../lib/rust/ensogl/component/drop-manager" }
|
||||||
|
ensogl-breadcrumbs = { path = "../../lib/rust/ensogl/component/breadcrumbs" }
|
||||||
fuzzly = { path = "../../lib/rust/fuzzly" }
|
fuzzly = { path = "../../lib/rust/fuzzly" }
|
||||||
ast = { path = "language/ast/impl" }
|
ast = { path = "language/ast/impl" }
|
||||||
parser = { path = "language/parser" }
|
parser = { path = "language/parser" }
|
||||||
|
@ -5,6 +5,7 @@ use crate::prelude::*;
|
|||||||
|
|
||||||
use crate::controller::graph::ImportType;
|
use crate::controller::graph::ImportType;
|
||||||
use crate::controller::graph::RequiredImport;
|
use crate::controller::graph::RequiredImport;
|
||||||
|
use crate::controller::searcher::breadcrumbs::BreadcrumbEntry;
|
||||||
use crate::model::execution_context::GroupQualifiedName;
|
use crate::model::execution_context::GroupQualifiedName;
|
||||||
use crate::model::module::NodeEditStatus;
|
use crate::model::module::NodeEditStatus;
|
||||||
use crate::model::suggestion_database;
|
use crate::model::suggestion_database;
|
||||||
@ -354,16 +355,7 @@ impl Searcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enter the specified module. The displayed content of the browser will be updated.
|
/// Enter the specified module. The displayed content of the browser will be updated.
|
||||||
pub fn enter_entry(&self, entry: usize) -> FallibleResult {
|
pub fn enter_entry(&self, _entry: usize) -> FallibleResult {
|
||||||
let id = {
|
|
||||||
let data = self.data.borrow();
|
|
||||||
let error = || NoSuchComponent { index: entry };
|
|
||||||
let component = data.components.displayed().get(entry).ok_or_else(error)?;
|
|
||||||
component.id().ok_or(NotEnterable { index: entry })?
|
|
||||||
};
|
|
||||||
let bc_builder = breadcrumbs::Builder::new(&self.database);
|
|
||||||
let breadcrumbs = bc_builder.build(id);
|
|
||||||
self.breadcrumbs.set_content(breadcrumbs);
|
|
||||||
self.reload_list();
|
self.reload_list();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -374,11 +366,43 @@ impl Searcher {
|
|||||||
self.breadcrumbs.names()
|
self.breadcrumbs.names()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select the breadcrumb with the index [`id`]. The displayed content of the browser will be
|
/// Set the selected breadcrumb. The `id` is the index of the breadcrumb from left to right.
|
||||||
/// updated.
|
|
||||||
pub fn select_breadcrumb(&self, id: usize) {
|
pub fn select_breadcrumb(&self, id: usize) {
|
||||||
self.breadcrumbs.select(id);
|
self.breadcrumbs.select(id);
|
||||||
self.reload_list();
|
}
|
||||||
|
|
||||||
|
/// Set the breadcrumbs to match the component at the given `index`. The index refers to the
|
||||||
|
/// displayed list of components. Returns the full breadcrumb for the entry, if there is one.
|
||||||
|
pub fn update_breadcrumbs(&self, index: usize) -> Option<Vec<BreadcrumbEntry>> {
|
||||||
|
let data = self.data.borrow();
|
||||||
|
if let Some(component) = data.components.displayed().get(index) {
|
||||||
|
if let Some(id) = component.id() {
|
||||||
|
let bc_builder = breadcrumbs::Builder::new(&self.database);
|
||||||
|
let breadcrumbs = bc_builder.build(id).collect_vec();
|
||||||
|
assert!(breadcrumbs.iter().all(|e| self.database.lookup(e.id()).is_ok()));
|
||||||
|
self.breadcrumbs.set_content(breadcrumbs.clone().into_iter());
|
||||||
|
Some(breadcrumbs)
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"Cannot update breadcrumbs with component that has no suggestion database \
|
||||||
|
entry. Invalid component: {:?}",
|
||||||
|
component
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Update breadcrumbs called with invalid index: {}", index);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the documentation for the breadcrumb.
|
||||||
|
pub fn documentation_for_selected_breadcrumb(&self) -> Option<EntryDocumentation> {
|
||||||
|
let selected = self.breadcrumbs.selected();
|
||||||
|
let component = selected?;
|
||||||
|
assert!(self.database.lookup(component).is_ok());
|
||||||
|
let docs = self.database.documentation_for_entry(component);
|
||||||
|
Some(docs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the Searcher Input.
|
/// Set the Searcher Input.
|
||||||
@ -1217,30 +1241,6 @@ pub mod test {
|
|||||||
assert_eq!(notification, Some(Notification::NewComponentList));
|
assert_eq!(notification, Some(Notification::NewComponentList));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn entering_module() {
|
|
||||||
let mut fixture =
|
|
||||||
Fixture::new_custom(suggestion_database_with_mock_entries, |data, client| {
|
|
||||||
data.expect_completion(client, None, &(0..11).collect_vec());
|
|
||||||
data.expect_completion(client, None, &(0..11).collect_vec());
|
|
||||||
});
|
|
||||||
|
|
||||||
let searcher = &fixture.searcher;
|
|
||||||
searcher.reload_list();
|
|
||||||
fixture.test.run_until_stalled();
|
|
||||||
// There are two virtual entries and two top-modules.
|
|
||||||
assert_eq!(searcher.components().displayed().len(), 4);
|
|
||||||
|
|
||||||
let mut subscriber = searcher.subscribe();
|
|
||||||
searcher.enter_entry(3).expect("Entering entry failed");
|
|
||||||
fixture.test.run_until_stalled();
|
|
||||||
let list = searcher.components();
|
|
||||||
assert_eq!(list.displayed().len(), 1);
|
|
||||||
assert_eq!(list.displayed()[0].suggestion, fixture.test_method_3_suggestion());
|
|
||||||
let notification = subscriber.next().boxed_local().expect_ready();
|
|
||||||
assert_eq!(notification, Some(Notification::NewComponentList));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn picked_completions_list_maintaining() {
|
fn picked_completions_list_maintaining() {
|
||||||
let fixture = Fixture::new_custom(suggestion_database_with_mock_entries, |data, client| {
|
let fixture = Fixture::new_custom(suggestion_database_with_mock_entries, |data, client| {
|
||||||
|
@ -6,6 +6,7 @@ use crate::model::suggestion_database;
|
|||||||
|
|
||||||
use double_representation::name::QualifiedName;
|
use double_representation::name::QualifiedName;
|
||||||
use double_representation::name::QualifiedNameRef;
|
use double_representation::name::QualifiedNameRef;
|
||||||
|
use ensogl_icons::icon;
|
||||||
use model::suggestion_database::Entry;
|
use model::suggestion_database::Entry;
|
||||||
|
|
||||||
|
|
||||||
@ -32,9 +33,11 @@ impl Breadcrumbs {
|
|||||||
|
|
||||||
/// Set the list of breadcrumbs to be displayed in the breadcrumbs panel.
|
/// Set the list of breadcrumbs to be displayed in the breadcrumbs panel.
|
||||||
pub fn set_content(&self, breadcrumbs: impl Iterator<Item = BreadcrumbEntry>) {
|
pub fn set_content(&self, breadcrumbs: impl Iterator<Item = BreadcrumbEntry>) {
|
||||||
|
let breadcrumbs: Vec<_> = breadcrumbs.collect();
|
||||||
let mut borrowed = self.list.borrow_mut();
|
let mut borrowed = self.list.borrow_mut();
|
||||||
*borrowed = breadcrumbs.collect();
|
*borrowed = breadcrumbs;
|
||||||
self.select(borrowed.len());
|
let len = borrowed.len();
|
||||||
|
self.select(len.saturating_sub(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A list of breadcrumbs' text labels to be displayed in the panel.
|
/// A list of breadcrumbs' text labels to be displayed in the panel.
|
||||||
@ -60,12 +63,8 @@ impl Breadcrumbs {
|
|||||||
/// Returns a currently selected breadcrumb id. Returns [`None`] if the top level breadcrumb
|
/// Returns a currently selected breadcrumb id. Returns [`None`] if the top level breadcrumb
|
||||||
/// is selected.
|
/// is selected.
|
||||||
pub fn selected(&self) -> Option<suggestion_database::entry::Id> {
|
pub fn selected(&self) -> Option<suggestion_database::entry::Id> {
|
||||||
if self.is_top_module() {
|
let index = self.selected.get();
|
||||||
None
|
self.list.borrow().get(index).map(BreadcrumbEntry::id)
|
||||||
} else {
|
|
||||||
let index = self.selected.get();
|
|
||||||
self.list.borrow().get(index - 1).map(BreadcrumbEntry::id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +80,7 @@ pub struct BreadcrumbEntry {
|
|||||||
displayed_name: ImString,
|
displayed_name: ImString,
|
||||||
component_id: suggestion_database::entry::Id,
|
component_id: suggestion_database::entry::Id,
|
||||||
qualified_name: QualifiedName,
|
qualified_name: QualifiedName,
|
||||||
|
icon: Option<icon::Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BreadcrumbEntry {
|
impl BreadcrumbEntry {
|
||||||
@ -98,18 +98,33 @@ impl BreadcrumbEntry {
|
|||||||
pub fn qualified_name(&self) -> &QualifiedName {
|
pub fn qualified_name(&self) -> &QualifiedName {
|
||||||
&self.qualified_name
|
&self.qualified_name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An icon of the entry.
|
||||||
|
pub fn icon(&self) -> Option<icon::Id> {
|
||||||
|
self.icon
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a [`ensogl_breadcrumbs::Breadcrumb`] with the entries name and icon.
|
||||||
|
pub fn view_without_icon(&self) -> ensogl_breadcrumbs::Breadcrumb {
|
||||||
|
ensogl_breadcrumbs::Breadcrumb::new(self.name().as_str(), None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a [`ensogl_breadcrumbs::Breadcrumb`] with the entries name but no icon.
|
||||||
|
pub fn view_with_icon(&self) -> ensogl_breadcrumbs::Breadcrumb {
|
||||||
|
ensogl_breadcrumbs::Breadcrumb::new(self.name().as_str(), self.icon())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(suggestion_database::entry::Id, Rc<Entry>)> for BreadcrumbEntry {
|
impl From<(suggestion_database::entry::Id, Rc<Entry>)> for BreadcrumbEntry {
|
||||||
fn from((component_id, entry): (suggestion_database::entry::Id, Rc<Entry>)) -> Self {
|
fn from((component_id, entry): (suggestion_database::entry::Id, Rc<Entry>)) -> Self {
|
||||||
let qualified_name = entry.qualified_name();
|
let qualified_name = entry.qualified_name();
|
||||||
let displayed_name = entry.name.clone();
|
let displayed_name = entry.name.clone();
|
||||||
BreadcrumbEntry { displayed_name, component_id, qualified_name }
|
let icon = Some(entry.as_ref().icon());
|
||||||
|
BreadcrumbEntry { displayed_name, component_id, qualified_name, icon }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ===============
|
// ===============
|
||||||
// === Builder ===
|
// === Builder ===
|
||||||
// ===============
|
// ===============
|
||||||
|
@ -86,10 +86,7 @@ impl Model {
|
|||||||
) -> Option<(ViewNodeId, text::Range<text::Byte>, ImString)> {
|
) -> Option<(ViewNodeId, text::Range<text::Byte>, ImString)> {
|
||||||
let new_code = self.controller.use_suggestion_by_index(id);
|
let new_code = self.controller.use_suggestion_by_index(id);
|
||||||
match new_code {
|
match new_code {
|
||||||
Ok(text::Change { range, text }) => {
|
Ok(text::Change { range, text }) => Some((self.input_view, range, text.into())),
|
||||||
self.update_breadcrumbs();
|
|
||||||
Some((self.input_view, range, text.into()))
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Error while applying suggestion: {err}.");
|
error!("Error while applying suggestion: {err}.");
|
||||||
None
|
None
|
||||||
@ -101,20 +98,23 @@ impl Model {
|
|||||||
self.controller.select_breadcrumb(id);
|
self.controller.select_breadcrumb(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_breadcrumbs(&self) {
|
fn module_entered(&self, entry: component_grid::EntryId) {
|
||||||
let names = self.controller.breadcrumbs().into_iter();
|
if let Err(error) = self.controller.enter_entry(entry) {
|
||||||
let browser = &self.view;
|
error!("Failed to enter entry in Component Browser: {error}")
|
||||||
// We only update the breadcrumbs starting from the second element because the first
|
}
|
||||||
// one is reserved as a section name.
|
|
||||||
let from = 1;
|
|
||||||
let breadcrumbs_from = (names.map(Into::into).collect(), from);
|
|
||||||
browser.model().documentation.breadcrumbs.set_entries_from(breadcrumbs_from);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn module_entered(&self, entry: component_grid::EntryId) {
|
fn update_breadcrumbs(&self, target_entry: component_grid::EntryId) {
|
||||||
match self.controller.enter_entry(entry) {
|
let breadcrumbs = self.controller.update_breadcrumbs(target_entry);
|
||||||
Ok(()) => self.update_breadcrumbs(),
|
if let Some(breadcrumbs) = breadcrumbs {
|
||||||
Err(error) => error!("Failed to enter entry in Component Browser: {error}"),
|
let browser = &self.view;
|
||||||
|
let breadcrumbs_count = breadcrumbs.len();
|
||||||
|
let without_icon =
|
||||||
|
breadcrumbs[0..breadcrumbs_count - 1].iter().map(|crumb| crumb.view_without_icon());
|
||||||
|
let with_icon =
|
||||||
|
breadcrumbs[breadcrumbs_count - 1..].iter().map(|crumb| crumb.view_with_icon());
|
||||||
|
let all = without_icon.chain(with_icon).collect_vec();
|
||||||
|
browser.model().documentation.breadcrumbs.set_entries(all);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,9 +138,19 @@ impl Model {
|
|||||||
self.controller.documentation_for_entry(id)
|
self.controller.documentation_for_entry(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn docs_for_breadcrumb(&self) -> Option<EntryDocumentation> {
|
||||||
|
self.controller.documentation_for_selected_breadcrumb()
|
||||||
|
}
|
||||||
|
|
||||||
fn should_select_first_entry(&self) -> bool {
|
fn should_select_first_entry(&self) -> bool {
|
||||||
self.controller.is_filtering() || self.controller.is_input_empty()
|
self.controller.is_filtering() || self.controller.is_input_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_entry_for_docs_selected(&self, id: Option<component_grid::EntryId>) {
|
||||||
|
if let Some(id) = id {
|
||||||
|
self.update_breadcrumbs(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Searcher presenter, synchronizing state between searcher view and searcher controller.
|
/// The Searcher presenter, synchronizing state between searcher view and searcher controller.
|
||||||
@ -267,12 +277,17 @@ impl ComponentBrowserSearcher {
|
|||||||
docs <- docs_params.filter_map(f!([model]((_, entry)) {
|
docs <- docs_params.filter_map(f!([model]((_, entry)) {
|
||||||
entry.map(|entry_id| model.documentation_of_component(entry_id))
|
entry.map(|entry_id| model.documentation_of_component(entry_id))
|
||||||
}));
|
}));
|
||||||
|
docs_from_breadcrumbs <- breadcrumbs.selected.map(f!((selected){
|
||||||
|
model.breadcrumb_selected(*selected);
|
||||||
|
model.docs_for_breadcrumb()
|
||||||
|
})).unwrap();
|
||||||
|
docs <- any(docs,docs_from_breadcrumbs);
|
||||||
documentation.frp.display_documentation <+ docs;
|
documentation.frp.display_documentation <+ docs;
|
||||||
|
eval grid.active ((entry) model.on_entry_for_docs_selected(*entry));
|
||||||
|
|
||||||
eval_ grid.suggestion_accepted([]analytics::remote_log_event("component_browser::suggestion_accepted"));
|
eval_ grid.suggestion_accepted([]analytics::remote_log_event("component_browser::suggestion_accepted"));
|
||||||
eval grid.active((entry) model.suggestion_selected(*entry));
|
eval grid.active((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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let weak_model = Rc::downgrade(&model);
|
let weak_model = Rc::downgrade(&model);
|
||||||
|
@ -71,7 +71,7 @@ impl Default for EntryDocumentation {
|
|||||||
pub struct LinkedDocPage {
|
pub struct LinkedDocPage {
|
||||||
/// The name of the liked entry. It is used to produce a unique ID for the link.
|
/// The name of the liked entry. It is used to produce a unique ID for the link.
|
||||||
pub name: Rc<QualifiedName>,
|
pub name: Rc<QualifiedName>,
|
||||||
/// The intermediate reprentation of the linked entry's documentation.
|
/// The intermediate representation of the linked entry's documentation.
|
||||||
pub page: EntryDocumentation,
|
pub page: EntryDocumentation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ use enso_doc_parser::doc_sections::HtmlString;
|
|||||||
use enso_doc_parser::DocSection;
|
use enso_doc_parser::DocSection;
|
||||||
use enso_doc_parser::Tag;
|
use enso_doc_parser::Tag;
|
||||||
use enso_text::Location;
|
use enso_text::Location;
|
||||||
|
use ensogl_icons::icon;
|
||||||
use language_server::types::FieldAction;
|
use language_server::types::FieldAction;
|
||||||
|
|
||||||
|
|
||||||
@ -528,6 +529,17 @@ impl Entry {
|
|||||||
|
|
||||||
// === Other Properties ===
|
// === Other Properties ===
|
||||||
|
|
||||||
|
macro_rules! kind_to_icon {
|
||||||
|
([ $( $variant:ident ),* ] $kind:ident) => {
|
||||||
|
{
|
||||||
|
use ensogl_icons::icon::Id;
|
||||||
|
match $kind {
|
||||||
|
$( Kind::$variant => Id::$variant, )*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
/// Return the Method Id of suggested method.
|
/// Return the Method Id of suggested method.
|
||||||
///
|
///
|
||||||
@ -621,6 +633,14 @@ impl Entry {
|
|||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the icon of the entry.
|
||||||
|
pub fn icon(&self) -> icon::Id {
|
||||||
|
let kind = self.kind;
|
||||||
|
let icon_name = self.icon_name.as_ref();
|
||||||
|
let icon = icon_name.and_then(|n| n.to_pascal_case().parse().ok());
|
||||||
|
icon.unwrap_or_else(|| for_each_kind_variant!(kind_to_icon(kind)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ ensogl = { path = "../../../../lib/rust/ensogl" }
|
|||||||
ensogl-gui-component = { path = "../../../../lib/rust/ensogl/component/gui" }
|
ensogl-gui-component = { path = "../../../../lib/rust/ensogl/component/gui" }
|
||||||
ensogl-hardcoded-theme = { path = "../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
ensogl-hardcoded-theme = { path = "../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||||
enso-prelude = { path = "../../../../lib/rust/prelude" }
|
enso-prelude = { path = "../../../../lib/rust/prelude" }
|
||||||
ide-view-component-list-panel-breadcrumbs = { path = "../../../../lib/rust/ensogl/component/breadcrumbs" }
|
ensogl-breadcrumbs = { path = "../../../../lib/rust/ensogl/component/breadcrumbs" }
|
||||||
ide-view-component-list-panel = { path = "component-list-panel" }
|
ide-view-component-list-panel = { path = "component-list-panel" }
|
||||||
ide-view-documentation = { path = "../documentation" }
|
ide-view-documentation = { path = "../documentation" }
|
||||||
ide-view-graph-editor = { path = "../graph-editor" }
|
ide-view-graph-editor = { path = "../graph-editor" }
|
||||||
|
@ -35,8 +35,8 @@ use ide_view_graph_editor::component::node::HEIGHT as NODE_HEIGHT;
|
|||||||
// === Export ===
|
// === Export ===
|
||||||
// ==============
|
// ==============
|
||||||
|
|
||||||
|
pub use ensogl_breadcrumbs as breadcrumbs;
|
||||||
pub use ide_view_component_list_panel as component_list_panel;
|
pub use ide_view_component_list_panel as component_list_panel;
|
||||||
pub use ide_view_component_list_panel_breadcrumbs as breadcrumbs;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ ensogl = { path = "../../../../lib/rust/ensogl" }
|
|||||||
ensogl-component = { path = "../../../../lib/rust/ensogl/component" }
|
ensogl-component = { path = "../../../../lib/rust/ensogl/component" }
|
||||||
ensogl-hardcoded-theme = { path = "../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
ensogl-hardcoded-theme = { path = "../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||||
ide-view-graph-editor = { path = "../graph-editor" }
|
ide-view-graph-editor = { path = "../graph-editor" }
|
||||||
ide-view-component-list-panel-breadcrumbs = { path = "../../../../lib/rust/ensogl/component/breadcrumbs" }
|
ensogl-breadcrumbs = { path = "../../../../lib/rust/ensogl/component/breadcrumbs" }
|
||||||
wasm-bindgen = { workspace = true }
|
wasm-bindgen = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
horrorshow = "0.8.4"
|
horrorshow = "0.8.4"
|
||||||
|
@ -44,7 +44,7 @@ use ide_view_graph_editor as graph_editor;
|
|||||||
|
|
||||||
pub mod html;
|
pub mod html;
|
||||||
|
|
||||||
pub use ide_view_component_list_panel_breadcrumbs as breadcrumbs;
|
pub use ensogl_breadcrumbs as breadcrumbs;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -151,8 +151,8 @@ impl Model {
|
|||||||
|
|
||||||
fn set_initial_breadcrumbs(&self) {
|
fn set_initial_breadcrumbs(&self) {
|
||||||
let breadcrumb = breadcrumbs::Breadcrumb::new(INITIAL_SECTION_NAME, None);
|
let breadcrumb = breadcrumbs::Breadcrumb::new(INITIAL_SECTION_NAME, None);
|
||||||
self.breadcrumbs.set_entries_from((vec![breadcrumb], 0));
|
self.breadcrumbs.set_entries(vec![breadcrumb]);
|
||||||
self.breadcrumbs.show_ellipsis(true);
|
self.breadcrumbs.show_ellipsis(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set size of the documentation view.
|
/// Set size of the documentation view.
|
||||||
|
@ -145,7 +145,7 @@ pub fn main() {
|
|||||||
panel.show();
|
panel.show();
|
||||||
|
|
||||||
let breadcrumbs = app.new_view::<Breadcrumbs>();
|
let breadcrumbs = app.new_view::<Breadcrumbs>();
|
||||||
breadcrumbs.set_y(400.0);
|
breadcrumbs.set_y(500.0);
|
||||||
breadcrumbs.frp().set_size(Vector2(500.0, 100.0));
|
breadcrumbs.frp().set_size(Vector2(500.0, 100.0));
|
||||||
breadcrumbs.set_entries_from((
|
breadcrumbs.set_entries_from((
|
||||||
vec![
|
vec![
|
||||||
|
@ -383,6 +383,8 @@ define_themes! { [light:0, dark:1]
|
|||||||
text_y_offset = 6.0, 6.0;
|
text_y_offset = 6.0, 6.0;
|
||||||
text_padding_left = 0.0, 0.0;
|
text_padding_left = 0.0, 0.0;
|
||||||
text_size = 11.5, 11.5;
|
text_size = 11.5, 11.5;
|
||||||
|
icon_x_offset = 2.0, 2.0;
|
||||||
|
icon_y_offset = 6.0, 6.0;
|
||||||
selected_color = Rgba(1.0, 1.0, 1.0, 1.0), Rgba(1.0, 1.0, 1.0, 1.0);
|
selected_color = Rgba(1.0, 1.0, 1.0, 1.0), Rgba(1.0, 1.0, 1.0, 1.0);
|
||||||
highlight_corners_radius = 15.0, 15.0;
|
highlight_corners_radius = 15.0, 15.0;
|
||||||
greyed_out_color = Rgba(1.0, 1.0, 1.0, 0.15), Rgba(1.0, 1.0, 1.0, 0.15);
|
greyed_out_color = Rgba(1.0, 1.0, 1.0, 0.15), Rgba(1.0, 1.0, 1.0, 0.15);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ide-view-component-list-panel-breadcrumbs"
|
name = "ensogl-breadcrumbs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Enso Team <contact@enso.org>"]
|
authors = ["Enso Team <contact@enso.org>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
@ -113,6 +113,8 @@ pub struct Style {
|
|||||||
pub text_y_offset: f32,
|
pub text_y_offset: f32,
|
||||||
pub text_padding_left: f32,
|
pub text_padding_left: f32,
|
||||||
pub text_size: f32,
|
pub text_size: f32,
|
||||||
|
pub icon_x_offset: f32,
|
||||||
|
pub icon_y_offset: f32,
|
||||||
pub selected_color: color::Rgba,
|
pub selected_color: color::Rgba,
|
||||||
pub highlight_corners_radius: f32,
|
pub highlight_corners_radius: f32,
|
||||||
pub greyed_out_color: color::Rgba,
|
pub greyed_out_color: color::Rgba,
|
||||||
@ -125,7 +127,7 @@ pub struct Style {
|
|||||||
|
|
||||||
/// A model for the entry in the breadcrumbs list.
|
/// A model for the entry in the breadcrumbs list.
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Clone, CloneRef, Debug, Default)]
|
#[derive(Clone, CloneRef, Debug, Default, PartialEq)]
|
||||||
pub enum Model {
|
pub enum Model {
|
||||||
#[default]
|
#[default]
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
@ -186,8 +188,8 @@ impl EntryData {
|
|||||||
let separator = separator::View::new();
|
let separator = separator::View::new();
|
||||||
let state = default();
|
let state = default();
|
||||||
let icon: any_icon::View = default();
|
let icon: any_icon::View = default();
|
||||||
icon.set_size((ICON_WIDTH, ICON_WIDTH));
|
|
||||||
ellipsis.set_size((ellipsis::ICON_WIDTH, ellipsis::ICON_WIDTH));
|
ellipsis.set_size((ellipsis::ICON_WIDTH, ellipsis::ICON_WIDTH));
|
||||||
|
icon.set_size((ICON_WIDTH, ICON_WIDTH));
|
||||||
display_object.add_child(&icon);
|
display_object.add_child(&icon);
|
||||||
display_object.add_child(&ellipsis);
|
display_object.add_child(&ellipsis);
|
||||||
Self { display_object, state, text, ellipsis, separator, icon }
|
Self { display_object, state, text, ellipsis, separator, icon }
|
||||||
@ -223,7 +225,6 @@ impl EntryData {
|
|||||||
if let Some(icon) = icon {
|
if let Some(icon) = icon {
|
||||||
self.display_object.add_child(&self.icon);
|
self.display_object.add_child(&self.icon);
|
||||||
self.icon.icon.set(icon.any_cached_shape_location());
|
self.icon.icon.set(icon.any_cached_shape_location());
|
||||||
self.text.set_x(ICON_WIDTH);
|
|
||||||
} else {
|
} else {
|
||||||
self.icon.unset_parent();
|
self.icon.unset_parent();
|
||||||
self.text.set_x(0.0);
|
self.text.set_x(0.0);
|
||||||
@ -256,15 +257,22 @@ impl EntryData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_layout(&self, contour: Contour, text_padding: f32, text_y_offset: f32) {
|
fn update_layout(
|
||||||
|
&self,
|
||||||
|
contour: Contour,
|
||||||
|
text_padding: f32,
|
||||||
|
text_y_offset: f32,
|
||||||
|
icon_x_offset: f32,
|
||||||
|
icon_y_offset: f32,
|
||||||
|
) {
|
||||||
let size = contour.size;
|
let size = contour.size;
|
||||||
let icon_offset = if self.has_icon() { ICON_WIDTH } else { 0.0 };
|
let icon_offset = if self.has_icon() { ICON_WIDTH } else { 0.0 };
|
||||||
self.text.set_xy(Vector2(icon_offset + text_padding - size.x / 2.0, text_y_offset));
|
self.text.set_xy(Vector2(icon_offset + text_padding - size.x / 2.0, text_y_offset));
|
||||||
self.separator.set_size(Vector2(separator::ICON_WIDTH, size.y));
|
self.separator.set_size(Vector2(separator::ICON_WIDTH, size.y));
|
||||||
self.ellipsis.set_size(Vector2(ellipsis::ICON_WIDTH, size.y));
|
self.ellipsis.set_size(Vector2(ellipsis::ICON_WIDTH, size.y));
|
||||||
self.icon.set_size(Vector2(ICON_WIDTH, size.y));
|
self.icon.set_size(Vector2(ICON_WIDTH, size.y));
|
||||||
self.icon.set_x(-ICON_WIDTH);
|
self.icon.set_x(-size.x / 2.0 - icon_x_offset);
|
||||||
self.icon.set_y(-2.0);
|
self.icon.set_y(-ICON_WIDTH - icon_y_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_default_color(&self, color: color::Lcha) {
|
fn set_default_color(&self, color: color::Lcha) {
|
||||||
@ -372,10 +380,16 @@ impl ensogl_grid_view::Entry for Entry {
|
|||||||
text_color <- input.set_params.map(|p| p.style.selected_color).cloned_into().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_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();
|
text_size <- input.set_params.map(|p| p.style.text_size).on_change();
|
||||||
|
icon_x_offset <- input.set_params.map(|p| p.style.icon_x_offset).on_change();
|
||||||
|
icon_y_offset <- input.set_params.map(|p| p.style.icon_y_offset).on_change();
|
||||||
greyed_out_color <- input.set_params.map(|p| p.style.greyed_out_color).cloned_into().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();
|
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();
|
greyed_out_from <- input.set_params.map(|p| p.greyed_out_start).on_change();
|
||||||
transparent_color <- init.constant(color::Lcha::transparent());
|
transparent_color <- init.constant(color::Lcha::transparent());
|
||||||
|
new_model <- input.set_model.on_change();
|
||||||
|
|
||||||
|
|
||||||
|
// === Appearance ===
|
||||||
|
|
||||||
col <- input.set_location._1();
|
col <- input.set_location._1();
|
||||||
should_grey_out <- all_with(&col, &greyed_out_from,
|
should_grey_out <- all_with(&col, &greyed_out_from,
|
||||||
@ -397,7 +411,6 @@ 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,
|
||||||
});
|
});
|
||||||
|
|
||||||
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()));
|
||||||
eval text_size((s) data.set_default_text_size(*s));
|
eval text_size((s) data.set_default_text_size(*s));
|
||||||
@ -410,24 +423,35 @@ impl ensogl_grid_view::Entry for Entry {
|
|||||||
out.hover_highlight_color <+ hover_color;
|
out.hover_highlight_color <+ hover_color;
|
||||||
out.selection_highlight_color <+ init.constant(color::Lcha::transparent());
|
out.selection_highlight_color <+ init.constant(color::Lcha::transparent());
|
||||||
|
|
||||||
|
|
||||||
// === Override column width ===
|
|
||||||
|
|
||||||
// We need to adjust the width of the grid view column depending on the width of
|
|
||||||
// the entry.
|
|
||||||
out.override_column_width <+ input.set_model.map2(&text_padding,
|
|
||||||
f!([data](model, text_padding) {
|
|
||||||
data.set_model(model);
|
|
||||||
data.width(*text_padding)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
// For text entries, we also listen for [`Text::width`] changes.
|
// For text entries, we also listen for [`Text::width`] changes.
|
||||||
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_padding, f!((w, o) data.text_width(*w, *o)));
|
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;
|
||||||
|
|
||||||
layout <- all(contour, text_padding, text_y_offset, input.set_model);
|
|
||||||
eval layout ((&(c, to, tyo, _)) data.update_layout(c, to, tyo));
|
// === Layout ===
|
||||||
|
|
||||||
|
override_column_width <- new_model.map2(&text_padding,
|
||||||
|
f!([data](model, text_padding) {
|
||||||
|
data.set_model(model);
|
||||||
|
data.width(*text_padding)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
layout <- all6(
|
||||||
|
&contour,
|
||||||
|
&text_padding,
|
||||||
|
&text_y_offset,
|
||||||
|
&new_model,
|
||||||
|
&icon_x_offset,
|
||||||
|
&icon_y_offset);
|
||||||
|
eval layout ((&(c, to, tyo, _, ix, iy)) data.update_layout(c, to, tyo, ix, iy));
|
||||||
|
|
||||||
|
// === Override column width ===
|
||||||
|
|
||||||
|
// We need to adjust the width of the grid view column depending on the width of
|
||||||
|
// the entry.
|
||||||
|
out.override_column_width <+ override_column_width;
|
||||||
}
|
}
|
||||||
init.emit(());
|
init.emit(());
|
||||||
Self { frp, data }
|
Self { frp, data }
|
||||||
|
@ -477,6 +477,8 @@ ensogl_core::define_endpoints_2! {
|
|||||||
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>, BreadcrumbId)),
|
set_entries_from((Vec<Breadcrumb>, BreadcrumbId)),
|
||||||
|
/// Set the displayed breadcrumbs.
|
||||||
|
set_entries(Vec<Breadcrumb>),
|
||||||
/// Set the breadcrumb at a specified index.
|
/// Set the breadcrumb at a specified index.
|
||||||
set_entry((BreadcrumbId, Breadcrumb)),
|
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.
|
||||||
@ -534,7 +536,10 @@ 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(entries, *from));
|
|
||||||
|
set_entries_from_zero <- input.set_entries.map(|entries| (entries.clone(), 0));
|
||||||
|
set_entries_from <- any(set_entries_from_zero, input.set_entries_from);
|
||||||
|
eval set_entries_from(((entries, from)) model.set_entries(entries, *from));
|
||||||
eval input.set_entry(((index, entry)) model.set_entry(entry, *index));
|
eval input.set_entry(((index, entry)) model.set_entry(entry, *index));
|
||||||
out.selected <+ selected;
|
out.selected <+ selected;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user