diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 59bf15cdbdb24..ad479adbdfe2e 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -45,7 +45,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: diff --git a/.github/workflows/gui.yml b/.github/workflows/gui.yml index 3518af57105a6..6e4a70891eb0a 100644 --- a/.github/workflows/gui.yml +++ b/.github/workflows/gui.yml @@ -25,7 +25,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - id: changed_files run: "git fetch\nlist=`git diff --name-only origin/develop HEAD | tr '\\n' ' '`\necho $list\necho \"::set-output name=list::'$list'\"" - run: "if [[ ${{ contains(steps.changed_files.outputs.list,'CHANGELOG.md') || contains(github.event.head_commit.message,'[ci no changelog needed]') || contains(github.event.pull_request.body,'[ci no changelog needed]') || github.event.pull_request.user.login == 'dependabot' }} == false ]]; then exit 1; fi" @@ -63,7 +63,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -108,7 +108,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -155,7 +155,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -202,7 +202,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -247,7 +247,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -294,7 +294,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -352,7 +352,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -399,7 +399,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -446,7 +446,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -496,7 +496,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -544,7 +544,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -594,7 +594,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -643,7 +643,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a072f0d4e7de4..4f6e2bf268012 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -38,7 +38,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -83,7 +83,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -132,7 +132,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -182,7 +182,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -234,7 +234,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -289,7 +289,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -346,7 +346,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -398,7 +398,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -452,7 +452,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: diff --git a/.github/workflows/scala-new.yml b/.github/workflows/scala-new.yml index f0c04cd8ec52e..e824ddf5d2309 100644 --- a/.github/workflows/scala-new.yml +++ b/.github/workflows/scala-new.yml @@ -49,7 +49,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -102,7 +102,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: @@ -157,7 +157,7 @@ jobs: - name: Checking out the repository uses: actions/checkout@v2 with: - clean: true + clean: false - name: Build Script Setup run: "./run --help" env: diff --git a/Cargo.lock b/Cargo.lock index 06eec75f50ac7..5130f54c39abb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1751,7 +1751,7 @@ dependencies = [ [[package]] name = "enso-build" version = "0.1.0" -source = "git+https://github.com/enso-org/ci-build?branch=develop#2b998bd5021c387ad50e99f087b7605ddecfbcc0" +source = "git+https://github.com/enso-org/ci-build?branch=develop#621679899f67018ca50813975e59218abd75f81c" dependencies = [ "anyhow", "async-compression", @@ -1825,7 +1825,7 @@ dependencies = [ [[package]] name = "enso-build-cli" version = "0.1.0" -source = "git+https://github.com/enso-org/ci-build?branch=develop#2b998bd5021c387ad50e99f087b7605ddecfbcc0" +source = "git+https://github.com/enso-org/ci-build?branch=develop#621679899f67018ca50813975e59218abd75f81c" dependencies = [ "anyhow", "byte-unit", @@ -3724,10 +3724,10 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5617e92fc2f2501c3e2bc6ce547cad841adba2bae5b921c7e52510beca6d084c" dependencies = [ - "base64 0.10.1", + "base64 0.11.0", "bytes 1.1.0", "http", - "httpdate 0.3.2", + "httpdate 1.0.2", "language-tags 0.3.2", "mime 0.3.16", "percent-encoding 2.1.0", @@ -3737,7 +3737,7 @@ dependencies = [ [[package]] name = "ide-ci" version = "0.1.0" -source = "git+https://github.com/enso-org/ci-build?branch=develop#2b998bd5021c387ad50e99f087b7605ddecfbcc0" +source = "git+https://github.com/enso-org/ci-build?branch=develop#621679899f67018ca50813975e59218abd75f81c" dependencies = [ "anyhow", "async-compression", @@ -3885,6 +3885,7 @@ dependencies = [ "enso-frp", "ensogl-core", "ensogl-derive-theme", + "ensogl-grid-view", "ensogl-gui-component", "ensogl-hardcoded-theme", "ensogl-list-view", diff --git a/app/gui/view/component-browser/searcher-list-panel/Cargo.toml b/app/gui/view/component-browser/searcher-list-panel/Cargo.toml index d3d2465638ac3..504d2635edcdf 100644 --- a/app/gui/view/component-browser/searcher-list-panel/Cargo.toml +++ b/app/gui/view/component-browser/searcher-list-panel/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["cdylib", "rlib"] 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-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" } ensogl-derive-theme = { path = "../../../../../lib/rust/ensogl/app/theme/derive" } ensogl-list-view = { path = "../../../../../lib/rust/ensogl/component/list-view" } diff --git a/app/gui/view/component-browser/searcher-list-panel/src/column_grid.rs b/app/gui/view/component-browser/searcher-list-panel/src/column_grid.rs index 704fc5fda0821..38f874f78c7c8 100644 --- a/app/gui/view/component-browser/searcher-list-panel/src/column_grid.rs +++ b/app/gui/view/component-browser/searcher-list-panel/src/column_grid.rs @@ -5,6 +5,7 @@ use ensogl_core::display::shape::*; use ensogl_core::prelude::*; +use crate::layout; use crate::layouting; use crate::searcher_theme; use crate::GroupId; @@ -87,9 +88,10 @@ impl Model { let overall_width = style.content_width - 2.0 * style.content_padding; let column_width = (overall_width - 2.0 * style.column_gap) / NUMBER_OF_COLUMNS as f32; - let groups = content.iter().enumerate().map(|(index, provider)| { - let height = provider.original_entry_count; - layouting::Group { index, height } + let groups = content.iter().enumerate().map(|(index, provider)| layout::Group { + id: GroupId { section: *section, index }, + height: provider.content.entry_count(), + original_height: provider.original_entry_count, }); let arrangement = layouting::Layouter::new(groups).arrange(); @@ -127,7 +129,8 @@ impl Model { for entry in content.iter() { const DEFAULT_COLUMN_INDEX: usize = 1; let mut arranged = arrangement.iter().enumerate(); - let column_index = arranged.find_map(|(i, c)| c.contains(&entry.index).then_some(i)); + let column_index = + arranged.find_map(|(i, c)| c.iter().find(|g| g.id.index == entry.index).map(|_| i)); let column_index = column_index.unwrap_or(DEFAULT_COLUMN_INDEX); columns[column_index].push(&entry.content); heights[column_index] += entry.content.size.value().y + style.column_gap; diff --git a/app/gui/view/component-browser/searcher-list-panel/src/layout.rs b/app/gui/view/component-browser/searcher-list-panel/src/layout.rs new file mode 100644 index 0000000000000..1e462877522b6 --- /dev/null +++ b/app/gui/view/component-browser/searcher-list-panel/src/layout.rs @@ -0,0 +1,342 @@ +//! A module with description how the list of Component Groups is laid out in the Component Panel +//! List. + +use crate::prelude::*; + +use ensogl_grid_view::Col; +use ensogl_grid_view::Row; +use ide_view_component_group::set::GroupId; +use ide_view_component_group::set::SectionId; + + + +// ================= +// === Constants === +// ================= + +/// Height of the header of the component group. This value is added to the group's number of +/// entries to get the total height. +pub const HEADER_HEIGHT_IN_ROWS: usize = 1; + + + +// ================= +// === ElementId === +// ================= + +/// An identifier of element inside a concrete group: entry (by entry index) or header. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum GroupElement { + /// A group's header. + Header, + /// A group's normal entry with index. + Entry(usize), +} + +/// An identifier of some group's element in Component Browser. +#[allow(missing_docs)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct ElementId { + pub group: GroupId, + pub element: GroupElement, +} + + + +// ============= +// === Group === +// ============= + +/// A information about group needed to compute the Component Panel List layout. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Group { + /// The group identifier. + pub id: GroupId, + /// Height of group in rows, not counting the header nor filtered-out entries. + pub height: usize, + /// Height of group in rows if no entry is filtered out, not counting the header. + pub original_height: usize, +} + + +// === LaidGroup === + +/// The information of group in the layout. +#[derive(Copy, Clone, Debug)] +pub struct LaidGroup<'a> { + /// The row where header is placed. + pub header_row: Row, + /// The column where the group is placed. + pub column: Col, + /// The reference to the group information in [`Layout`] structure. + pub group: &'a Group, +} + +impl<'a> LaidGroup<'a> { + /// The range of rows where the group spans, _including_ the header. + pub fn rows(&self) -> Range { + self.header_row..(self.header_row + self.group.height + HEADER_HEIGHT_IN_ROWS) + } + + /// The id of element at given row, or `None` if row is outside the group. + pub fn element_at_row(&self, row: Row) -> Option { + let element = self.rows().contains(&row).as_some_from(|| { + if row < self.header_row + HEADER_HEIGHT_IN_ROWS { + GroupElement::Header + } else { + GroupElement::Entry(row - self.header_row - HEADER_HEIGHT_IN_ROWS) + } + }); + element.map(|element| ElementId { group: self.group.id, element }) + } +} + + + +// ============== +// === Layout === +// ============== + +/// The info about single column in [`Layout`]. +#[derive(Clone, Debug)] +struct Column { + /// A mapping between group position and the [`Group`] info. The keys in map are the rows with + /// given group's header. + groups: BTreeMap, + /// The top occupied row in this group. If there is no group in column, it contains the first + /// row of "Local scope" section. If the "Local scope" section is also empty, it's an index + /// of row after last row in grid. + top_row: Row, +} + +/// The Component List Panel Layout information. +/// +/// This structure allows for organizing the component groups according to received group +/// information about their heights. It provides information about where given group is laid out +/// in Grid View, and what group element is at given location (row and column). +#[derive(Clone, Debug)] +pub struct Layout { + columns: Vec, + positions: HashMap, + local_scope_section_start: Row, + local_scope_entry_count: usize, + row_count: Row, +} + +impl Layout { + /// Create layout without standard (not the "Local Scope") groups. + /// + /// The layout will be completely empty if `local_scope_entry_count` will be 0. + pub fn new(row_count: Row, column_count: Col, local_scope_entry_count: usize) -> Self { + let local_scope_rows = local_scope_entry_count.div_ceil(column_count); + let local_scope_section_start = row_count - local_scope_rows; + let columns = + iter::repeat_with(|| Column { groups: default(), top_row: local_scope_section_start }) + .take(column_count) + .collect_vec(); + let positions = default(); + Self { columns, positions, local_scope_section_start, local_scope_entry_count, row_count } + } + + /// Create the layout with given groups arranged in columns. + pub fn create_from_arranged_groups( + groups: [Vec; COLUMN_COUNT], + local_scope_entry_count: usize, + ) -> Self { + let local_scope_rows = local_scope_entry_count.div_ceil(COLUMN_COUNT); + let col_heights: [usize; COLUMN_COUNT] = groups + .each_ref() + .map(|v| v.iter().map(|g| g.original_height + HEADER_HEIGHT_IN_ROWS).sum()); + let groups_rows = col_heights.into_iter().max().unwrap_or_default(); + let all_rows = local_scope_rows + groups_rows; + let mut this = Self::new(all_rows, COLUMN_COUNT, local_scope_entry_count); + let with_col_index = groups.into_iter().enumerate().map(Self::column_to_col_group_pairs); + for (column, group) in with_col_index.flatten() { + this.push_group(column, group); + } + this + } + + fn column_to_col_group_pairs( + (index, column): (Col, Vec), + ) -> impl Iterator { + column.into_iter().map(move |group| (index, group)) + } + + /// Get the information about group other than "Local Scope" occupying given location. + /// + /// If there is no group there, or it's "Local Scope" section, `None` is returned. + pub fn group_at_location(&self, row: Row, column: Col) -> Option { + let groups_in_col = &self.columns.get(column)?.groups; + let (group_before_row, group_before) = groups_in_col.range(..=row).last()?; + let group_end = group_before_row + group_before.height + HEADER_HEIGHT_IN_ROWS; + (group_end > row).as_some_from(|| LaidGroup { + header_row: *group_before_row, + column, + group: group_before, + }) + } + + /// Get the information what element is at given location. + pub fn element_at_location(&self, row: Row, column: Col) -> Option { + if row >= self.local_scope_section_start { + let index = (row - self.local_scope_section_start) * self.columns.len() + column; + (index < self.local_scope_entry_count).as_some_from(|| ElementId { + group: GroupId::local_scope_group(), + element: GroupElement::Entry(index), + }) + } else { + let group = self.group_at_location(row, column)?; + group.element_at_row(row) + } + } + + /// Return the location of element in Grid View. + pub fn location_of_element(&self, element: ElementId) -> Option<(Row, Col)> { + if element.group.section == SectionId::LocalScope { + match element.element { + GroupElement::Header => None, + GroupElement::Entry(index) => { + let row = self.local_scope_section_start + index / self.columns.len(); + let col = index % self.columns.len(); + Some((row, col)) + } + } + } else { + let &(header_pos, col) = self.positions.get(&element.group)?; + match element.element { + GroupElement::Header => Some((header_pos, col)), + GroupElement::Entry(index) => + Some((header_pos + HEADER_HEIGHT_IN_ROWS + index, col)), + } + } + } + + /// Add group to the top of given column. + pub fn push_group(&mut self, column: Col, group: Group) -> Row { + let group_column = &mut self.columns[column]; + let next_header_row = group_column.top_row - group.height - HEADER_HEIGHT_IN_ROWS; + group_column.groups.insert(next_header_row, group); + group_column.top_row = next_header_row; + self.positions.insert(group.id, (next_header_row, column)); + next_header_row + } + + /// The topmost row containing any group. + pub fn top_row(&self) -> Row { + self.columns.iter().map(|column| column.top_row).min().unwrap_or(self.row_count) + } + + /// The topmost row containing any group in given column. + pub fn column_top_row(&self, column: Col) -> Row { + self.columns.get(column).map_or(self.row_count, |c| c.top_row) + } +} + + + +// ============= +// === Tests === +// ============= + +#[cfg(test)] +mod tests { + use super::*; + use ide_view_component_group::set::SectionId; + + const LEFT: usize = 0; + const CENTER: usize = 1; + const RIGHT: usize = 2; + + #[test] + fn group_layout() { + let group_ids = + (0..6).map(|index| GroupId { section: SectionId::Favorites, index }).collect_vec(); + let group_sizes = vec![2, 1, 3, 3, 2, 1]; + let group_data = group_ids.iter().zip(group_sizes.into_iter()); + let mk_group = |(id, size): (&GroupId, usize)| Group { + id: *id, + height: size, + original_height: size, + }; + let groups = group_data.map(mk_group).collect_vec(); + let groups_in_columns = + [vec![groups[1], groups[4]], vec![groups[0], groups[3]], vec![groups[2], groups[5]]]; + let layout = Layout::create_from_arranged_groups(groups_in_columns, 8); + + let header_of = + |group_idx| ElementId { group: group_ids[group_idx], element: GroupElement::Header }; + let entry_of = |group_idx, entry_idx| ElementId { + group: group_ids[group_idx], + element: GroupElement::Entry(entry_idx), + }; + let local_scope_entry = |entry_idx| ElementId { + group: GroupId::local_scope_group(), + element: GroupElement::Entry(entry_idx), + }; + + assert_eq!(layout.element_at_location(0, LEFT), None); + assert_eq!(layout.element_at_location(0, CENTER), Some(header_of(3))); + assert_eq!(layout.element_at_location(0, RIGHT), None); + assert_eq!(layout.element_at_location(1, LEFT), None); + assert_eq!(layout.element_at_location(1, CENTER), Some(entry_of(3, 0))); + assert_eq!(layout.element_at_location(1, RIGHT), Some(header_of(5))); + assert_eq!(layout.element_at_location(2, LEFT), Some(header_of(4))); + assert_eq!(layout.element_at_location(2, CENTER), Some(entry_of(3, 1))); + assert_eq!(layout.element_at_location(2, RIGHT), Some(entry_of(5, 0))); + assert_eq!(layout.element_at_location(5, LEFT), Some(header_of(1))); + assert_eq!(layout.element_at_location(5, CENTER), Some(entry_of(0, 0))); + assert_eq!(layout.element_at_location(5, RIGHT), Some(entry_of(2, 1))); + assert_eq!(layout.element_at_location(6, LEFT), Some(entry_of(1, 0))); + assert_eq!(layout.element_at_location(6, CENTER), Some(entry_of(0, 1))); + assert_eq!(layout.element_at_location(6, RIGHT), Some(entry_of(2, 2))); + assert_eq!(layout.element_at_location(7, LEFT), Some(local_scope_entry(0))); + assert_eq!(layout.element_at_location(7, CENTER), Some(local_scope_entry(1))); + assert_eq!(layout.element_at_location(7, RIGHT), Some(local_scope_entry(2))); + assert_eq!(layout.element_at_location(9, LEFT), Some(local_scope_entry(6))); + assert_eq!(layout.element_at_location(9, CENTER), Some(local_scope_entry(7))); + assert_eq!(layout.element_at_location(9, RIGHT), None); + + assert_eq!(layout.location_of_element(header_of(3)), Some((0, CENTER))); + assert_eq!(layout.location_of_element(entry_of(3, 0)), Some((1, CENTER))); + assert_eq!(layout.location_of_element(header_of(5)), Some((1, RIGHT))); + assert_eq!(layout.location_of_element(header_of(4)), Some((2, LEFT))); + assert_eq!(layout.location_of_element(entry_of(3, 1)), Some((2, CENTER))); + assert_eq!(layout.location_of_element(entry_of(5, 0)), Some((2, RIGHT))); + assert_eq!(layout.location_of_element(header_of(1)), Some((5, LEFT))); + assert_eq!(layout.location_of_element(entry_of(0, 0)), Some((5, CENTER))); + assert_eq!(layout.location_of_element(entry_of(2, 1)), Some((5, RIGHT))); + assert_eq!(layout.location_of_element(entry_of(1, 0)), Some((6, LEFT))); + assert_eq!(layout.location_of_element(entry_of(0, 1)), Some((6, CENTER))); + assert_eq!(layout.location_of_element(entry_of(2, 2)), Some((6, RIGHT))); + assert_eq!(layout.location_of_element(local_scope_entry(0)), Some((7, LEFT))); + assert_eq!(layout.location_of_element(local_scope_entry(1)), Some((7, CENTER))); + assert_eq!(layout.location_of_element(local_scope_entry(2)), Some((7, RIGHT))); + assert_eq!(layout.location_of_element(local_scope_entry(6)), Some((9, LEFT))); + assert_eq!(layout.location_of_element(local_scope_entry(7)), Some((9, CENTER))); + } + + #[test] + fn group_layouts_with_empty_column_and_local_scope() { + let mut layout = Layout::new(3, 3, 0); + let group = Group { + id: GroupId { section: SectionId::Favorites, index: 0 }, + height: 2, + original_height: 2, + }; + layout.push_group(CENTER, group); + + assert_eq!(layout.element_at_location(2, LEFT), None); + assert_eq!( + layout.element_at_location(2, CENTER), + Some(ElementId { + group: GroupId { section: SectionId::Favorites, index: 0 }, + element: GroupElement::Entry(1), + }) + ); + assert_eq!(layout.element_at_location(2, RIGHT), None); + assert_eq!(layout.element_at_location(3, LEFT), None); + assert_eq!(layout.element_at_location(3, CENTER), None); + assert_eq!(layout.element_at_location(3, RIGHT), None); + } +} diff --git a/app/gui/view/component-browser/searcher-list-panel/src/layouting.rs b/app/gui/view/component-browser/searcher-list-panel/src/layouting.rs index 81369406df726..038ee055d6515 100644 --- a/app/gui/view/component-browser/searcher-list-panel/src/layouting.rs +++ b/app/gui/view/component-browser/searcher-list-panel/src/layouting.rs @@ -5,6 +5,12 @@ use ensogl_core::prelude::*; +use crate::layout::Group; +use crate::layout::Layout; +use crate::layout::HEADER_HEIGHT_IN_ROWS; + +use ensogl_grid_view::Col; + // ================= @@ -17,26 +23,15 @@ const COLUMNS: usize = 3; const LEFT: usize = 0; const CENTER: usize = 1; const RIGHT: usize = 2; -/// Height of the header of the component group. This value is added to the group's number of -/// entries to get the total height. -const HEADER_HEIGHT: GroupHeight = 1; -type Column = usize; -// ============= -// === Group === -// ============= +// =============== +// === Aliases === +// =============== -type GroupIndex = usize; type GroupHeight = usize; -#[derive(Clone, Copy, Debug)] -pub struct Group { - pub index: GroupIndex, - pub height: GroupHeight, -} - // ================ @@ -58,8 +53,8 @@ pub struct Group { /// /// [design doc]: https://github.com/enso-org/design/blob/main/epics/component-browser/design.md#layouting-algorithm pub struct Layouter> { - columns: [Vec; COLUMNS], - groups_heights: [GroupHeight; COLUMNS], + columns: [Vec; COLUMNS], + column_heights: [GroupHeight; COLUMNS], iter: iter::Peekable, } @@ -68,13 +63,13 @@ impl> Layouter { pub fn new(iter: I) -> Self { Self { columns: default(), - groups_heights: default(), + column_heights: default(), iter: iter.peekable(), } } - /// Calculate the layouting of the groups. See struct documentation for more information. - pub fn arrange(mut self) -> [Vec; COLUMNS] { + /// Calculate the layout of the groups. See struct documentation for more information. + pub fn arrange(mut self) -> [Vec; COLUMNS] { let mut max_height = self.push_next_group_to(CENTER, None); while self.iter.peek().is_some() { self.push_next_group_to(LEFT, Some(max_height)); @@ -89,17 +84,21 @@ impl> Layouter { self.columns } + /// Calculate the layout of the groups, and return it as a [`Layout`] structure. + /// + /// See struct documentation for more information. + pub fn create_layout(self, local_scope_entry_count: usize) -> Layout { + let arranged_groups = self.arrange(); + Layout::create_from_arranged_groups(arranged_groups, local_scope_entry_count) + } + /// Push the next group to the given column. Returns the size of the added group, 0 if no group /// was added. If [`max_height`] is supplied, the group is pushed only if the column's height /// is less than [`max_height`] before adding new group. - fn push_next_group_to( - &mut self, - column: Column, - max_height: Option, - ) -> GroupHeight { + fn push_next_group_to(&mut self, column: Col, max_height: Option) -> GroupHeight { if let Some(group) = self.iter.next() { if let Some(max_height) = max_height { - if self.groups_heights[column] >= max_height { + if self.column_heights[column] >= max_height { return 0; } } @@ -110,8 +109,8 @@ impl> Layouter { } /// Fill the given column until it reaches the given height. - fn fill_till_height(&mut self, column: Column, max_height: GroupHeight) { - while self.groups_heights[column] < max_height { + fn fill_till_height(&mut self, column: Col, max_height: GroupHeight) { + while self.column_heights[column] < max_height { if let Some(group) = self.iter.next() { self.push(column, group); } else { @@ -122,10 +121,10 @@ impl> Layouter { /// Push a a group to the given column. Returns a height of the added group. (including the /// [`HEADER_HEIGHT`]) - fn push(&mut self, column: Column, group: Group) -> GroupHeight { - self.columns[column].push(group.index); - let group_height = group.height + HEADER_HEIGHT; - self.groups_heights[column] += group_height; + fn push(&mut self, column: Col, group: Group) -> GroupHeight { + self.columns[column].push(group); + let group_height = group.original_height + HEADER_HEIGHT_IN_ROWS; + self.column_heights[column] += group_height; group_height } } @@ -139,37 +138,61 @@ impl> Layouter { #[cfg(test)] mod tests { use super::*; + use ide_view_component_group::set::GroupId; + use ide_view_component_group::set::SectionId; /// Test that the algorithm doesn't panic even with a small count of component groups. #[test] fn test_small_count_of_groups() { for count in 0..4 { - let groups = (0..count).map(|index| Group { index, height: 1 }); + let group_ids = + (0..count).map(|index| GroupId { section: SectionId::Favorites, index }); + let groups = group_ids.map(|id| Group { id, height: 1, original_height: 1 }); let arranged = Layouter::new(groups).arrange(); let total_count = arranged[LEFT].len() + arranged[CENTER].len() + arranged[RIGHT].len(); assert_eq!(total_count, count); } } + fn make_groups(index_height_pairs: Vec<(usize, usize)>) -> impl Iterator { + index_height_pairs.into_iter().map(|(index, height)| Group { + id: GroupId { section: SectionId::Favorites, index }, + height, + original_height: height, + }) + } + + fn check_groups_indices(result: [Vec; COLUMNS], expected: [Vec; COLUMNS]) { + let result_ids = result.map(|groups| groups.into_iter().map(|g| g.id.index).collect_vec()); + assert_eq!(result_ids, expected); + } + /// See [design doc](https://github.com/enso-org/design/blob/main/epics/component-browser/design.md#layouting-algorithm). #[test] fn test_case_from_design_doc() { - let groups: Vec<(usize, usize)> = - vec![(1, 4), (2, 4), (3, 3), (4, 3), (5, 2), (6, 3), (7, 2)]; - let groups = groups.into_iter().map(|(index, size)| Group { index, height: size }); + let groups = make_groups(vec![(1, 4), (2, 4), (3, 3), (4, 3), (5, 2), (6, 3), (7, 2)]); let arranged = Layouter::new(groups).arrange(); - let expected: &[Vec] = &[vec![2, 6], vec![1, 4], vec![3, 5, 7]]; - assert_eq!(&arranged, expected); + let expected = [vec![2, 6], vec![1, 4], vec![3, 5, 7]]; + check_groups_indices(arranged, expected); } /// See [task #181431035](https://www.pivotaltracker.com/story/show/181431035). #[test] fn test_case_from_acceptance_criteria() { - let groups: Vec<(usize, usize)> = - vec![(1, 3), (2, 1), (3, 3), (4, 4), (5, 1), (6, 1), (7, 4), (8, 1), (9, 2), (10, 1)]; - let groups = groups.into_iter().map(|(index, size)| Group { index, height: size }); + let groups = make_groups(vec![ + (1, 3), + (2, 1), + (3, 3), + (4, 4), + (5, 1), + (6, 1), + (7, 4), + (8, 1), + (9, 2), + (10, 1), + ]); let arranged = Layouter::new(groups).arrange(); - let expected: &[Vec] = &[vec![2, 5, 6, 9, 10], vec![1, 4, 8], vec![3, 7]]; - assert_eq!(&arranged, expected); + let expected = [vec![2, 5, 6, 9, 10], vec![1, 4, 8], vec![3, 7]]; + check_groups_indices(arranged, expected); } } diff --git a/app/gui/view/component-browser/searcher-list-panel/src/lib.rs b/app/gui/view/component-browser/searcher-list-panel/src/lib.rs index 2c6604f92f23a..4b33afac32d0a 100644 --- a/app/gui/view/component-browser/searcher-list-panel/src/lib.rs +++ b/app/gui/view/component-browser/searcher-list-panel/src/lib.rs @@ -26,6 +26,8 @@ #![feature(derive_default_enum)] #![feature(slice_as_chunks)] #![feature(option_result_contains)] +#![feature(int_roundings)] +#![feature(array_methods)] // === Standard Linter Configuration === #![deny(non_ascii_idents)] #![warn(unsafe_code)] @@ -42,20 +44,13 @@ #![warn(unused_import_braces)] #![warn(unused_qualifications)] - - -// ============== -// === Export === -// ============== - -mod navigator; - +use crate::prelude::*; use ensogl_core::display::shape::*; -use ensogl_core::prelude::*; use crate::navigator::navigator_shadow; use crate::navigator::Navigator as SectionNavigator; use crate::navigator::Section; + use component_group::icon; use enso_frp as frp; use ensogl_core::animation::physics::inertia; @@ -92,11 +87,16 @@ use searcher_theme::list_panel as list_panel_theme; // ============== pub mod column_grid; +pub mod layout; + + mod layouting; +mod navigator; pub use column_grid::LabeledAnyModelProvider; pub use component_group::set::GroupId; +pub use ensogl_core::prelude; diff --git a/app/ide-desktop/lib/client/package.json b/app/ide-desktop/lib/client/package.json index 4bc6f0ef1a4bf..d61a23e63ccbb 100644 --- a/app/ide-desktop/lib/client/package.json +++ b/app/ide-desktop/lib/client/package.json @@ -26,7 +26,7 @@ }, "devDependencies": { "electron": "17.1.0", - "electron-builder": "^23.3.1", + "electron-builder": "^22.14.13", "esbuild": "^0.14.43", "crypto-js": "4.1.1", "electron-notarize": "1.2.1",