Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Component browser breadcrumbs #3686

Merged
merged 38 commits into from
Sep 19, 2022

Conversation

vitvakatu
Copy link
Contributor

@vitvakatu vitvakatu commented Sep 6, 2022

Pull Request Description

[ci no changelog needed]

Task link.

This PR implements a Breadcrumbs panel for the new component browser.
The Breadcrumbs is a horizontal list of text labels separated by a special icon and has an optional ellipsis icon at the end.
It is implemented using the new GridView component.

Video:

Demo of adding new breadcrumbs, scrolling behavior, and selecting breadcrumbs with the mouse.

2022-09-08.21-53-56.mp4

Demo of selecting breadcrumbs with keyboard shortcuts:

2022-09-08.21-54-47.mp4

Important Notes

  • This PR implements an old interaction of the design of the component browser. The new design of the breadcrumbs can not be easily integrated into the current look of the component browser, so we would need to update styles later. It should be a relatively simple task. The implementation uses color from the new design though. (but not fonts and sizes)
  • I found a bug in the grid view implementation that causes panics at runtime in some conditions. The reason is triggering FRP endpoints while constructing new entries. This issue is fixed in the PR.

Checklist

Please include the following checklist in your PR:

  • The documentation has been updated if necessary.
  • All code conforms to the
    Scala,
    Java,
    and
    Rust
    style guides.
  • All code has been tested:
    • Unit tests have been written where possible.
    • If GUI codebase was changed: Enso GUI was tested when built using BOTH
      ./run ide build and ./run ide watch.

Sorry, something went wrong.

Verified

This commit was signed with the committer’s verified signature. The key has expired.
chalasr Robin Chalas
…bs-182560265

# Conflicts:
#	app/gui/view/debug_scene/component-list-panel-view/src/lib.rs
@vitvakatu vitvakatu self-assigned this Sep 6, 2022
@vitvakatu vitvakatu marked this pull request as ready for review September 6, 2022 12:18
@vitvakatu vitvakatu marked this pull request as draft September 6, 2022 12:55
@vitvakatu vitvakatu marked this pull request as ready for review September 8, 2022 18:36
Copy link
Member

@wdanilo wdanilo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really love the quality of the code here, amazing job. The final effect looks amazing as well! I am not approving this PR ONLY because I don't know this space so well as Adam, so he should review it as well and test it.

Comment on lines 65 to 67
let left = Circle(radius.clone()).fill(circles_color.clone());
let center = Circle(radius.clone()).fill(circles_color.clone());
let right = Circle(radius).fill(circles_color);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of creating 3 circles, use the repeat function (our drawing API). It is WAY faster. Repeat adds 1 opcode to GPU code to repeat the same element infinite amount of times.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea and tried to implement it but faced an issue with the intersection method on shapes similar to https://www.pivotaltracker.com/story/show/182593513
I left a comment in the code to address it in the future.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not explain anything. You told that you faced issue "similar to", whcih doesnt describe what issue you were facing. The comment in the code only tells "its broken", not explaining what is happening there. We are using intersections in several places, it's worth understanding what happens here. Please describe it properly here + attach screenshots.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After your message, I checked again, with a slightly different code - and it worked. Now I can explain what happens. This code works:

let circles = Circle(...).repeat(...);
let mask = Rect(...);
let circles = circles.intersection(mask).fill(circles_color);

This one does not:

let circles = Circle(...).repeat(...).fill(circles_color);
let mask = Rect(...);
let circles = circles.intersection(mask);

The slight difference in placement of the fill breaks our renderer:

Screenshot 2022-09-13 at 22 46 06

Screenshot 2022-09-13 at 22 45 16

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very interesting - please create an issue with these screenshots on pivotal tracker in the graph editor epic and assing to me.

Comment on lines 162 to 171
fn set_separator(&self) {
if self.state.get() != State::Separator {
self.remove_current();
self.display_object.add_child(&self.separator);
self.state.set(State::Separator);
}
}

fn set_ellipsis(&self) {
if self.state.get() != State::Ellipsis {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these names are not explanatory. What does Entry::set_separator mean? It sounds like I will be setting a separator in an entry. I mean, I know wht it means, but I feel that a better name can be found here (I dont know yet what name, but I'd be thankful if you think about it).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to switch_to_....

eval text_size((s) data.set_default_text_size(*s));
is_disabled <- input.set_model.map(|m| matches!(m, Model::Separator | Model::Ellipsis));
width <- map2(&input.set_model, &text_offset,
f!([data](model, text_offset) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you dont need [data] here. If the code starts with something. then something is automatically put there in f! macro.

Comment on lines 68 to 70
/// If the selected entry is to the left of this fraction of the viewport, we scroll the breadcrumbs
/// to keep the entry visible and easily reachable.
const SCROLLING_THRESHOLD: f32 = 0.5;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont understand what it does - neither docs nor name really explains what is this number.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expanded the documentation around it. Not sure if it is clear enough now.

Comment on lines 88 to 89
/// A rectangular mask used to crop the breadcrumbs' content when it doesn't fit in the designated
/// space.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tell me more! What is the "designated space"? Where this mask is really used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expanded documentation. However, I don't quite follow the where this mask is used part. It is used as stated - to crop the breadcrumb's contents.

Comment on lines 194 to 197
// Additional padding is added to avoid rare glitches when the last entry is
// cropped because it is placed right on the border of the viewport. Even 1px seems
// enough, but we add a bit more to be sure.
let padding = 10.0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this padding for? The docs dont explain it - it is padding between what and what?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted comment. It is padding between the content and the right border of the viewport.

} else if let Some(entry) = entries.borrow().get(col / 2) {
entry::Model::Text(entry.0.clone_ref())
} else {
tracing::error!("Requested entry is missing in the breadcrumbs ({col})");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prelude exports error! now. Don't use tracing::.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prelude exports error! from our crate that accepts logger as the first argument. I believe I should use tracing instead - and I found multiple usages in other crates.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I forgot that my PR intorduces it and I havent merged it yet :P

Comment on lines 410 to 412
eval selected_grid_col([model]((_row, col)) {
model.grey_out(Some(col + 1));
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you dont need neither [model] here nor curly braces. Please clean similar places in other FRP places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an artifact of the older code changes. In some cases, our macro does require both [model] and curly braces.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not. Could you please explain why it is needed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not needed here, but it was required in my previous versions of the code. I changed these lines.

@@ -0,0 +1,12 @@
[package]
name = "ensogl-breadcrumbs"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name suggests that this is a crate with general-purpose breadcrumbs component, but the location suggests that it is dedicated for IDE application.

Comment on lines 4 to 27
use crate::language_server::CapabilityRegistration;
use crate::language_server::Client;
use crate::language_server::ContentRoot;
use crate::language_server::Event;
use crate::language_server::ExpressionUpdatePayload;
use crate::language_server::FileAttributes;
use crate::language_server::FileEdit;
use crate::language_server::FileEvent;
use crate::language_server::FileEventKind;
use crate::language_server::FileSystemObject;
use crate::language_server::LocalCall;
use crate::language_server::MethodPointer;
use crate::language_server::Notification;
use crate::language_server::Path;
use crate::language_server::Position;
use crate::language_server::RegisterOptions;
use crate::language_server::Result;
use crate::language_server::Sha3_224;
use crate::language_server::StackItem;
use crate::language_server::TextEdit;
use crate::language_server::TextRange;
use crate::language_server::Uuid;
use crate::language_server::VisualisationConfiguration;
use crate::language_server::API;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to import so many things? We prefer qualified names, so maybe it would be better to use language_server::FileEdit directly instead of importing it? @farmaazon what do u think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed briefly with @wdanilo: let's make a mass import use::crate::language_server::*

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vitvakatu we agreed on star import ONLY because these are tests. Do not use star import in other, non-test modules please. Just FYI.

Comment on lines 65 to 67
let left = Circle(radius.clone()).fill(circles_color.clone());
let center = Circle(radius.clone()).fill(circles_color.clone());
let right = Circle(radius).fill(circles_color);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not explain anything. You told that you faced issue "similar to", whcih doesnt describe what issue you were facing. The comment in the code only tells "its broken", not explaining what is happening there. We are using intersections in several places, it's worth understanding what happens here. Please describe it properly here + attach screenshots.


// === EntryData ===

/// An internal structure of [`Entry`], which may be passed to FRP network.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Does this doc tells that this structure may be passed to FRP, or that Entry can be passed to FRP? Please make sentences in docs more obvious (here in other places - please check where doc sentences can be shorter and make sure that the subject is not ambiguous).
  2. If this tells that this object can be passed to FRP, the clone here is pretty costly - it has 6 or 7 Rcs to be cloned. In such a case, this sohudl be wrapped in one Rc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Rephrased the documentation completely.
  2. It is wrapped in a single Rc (see Entry struct implementation)

// === Params ===

/// The parameters of Breadcrumbs' entries.
#[allow(missing_docs)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It states that docs are not needed, but in reality they are (or better field names):

  1. I dont understand what is "margin".
  2. What is greyed_out_start ? What is the usize there?
  3. What is text_offset - offset to what?
  4. What is font? Is it real font data or is it just font name? We are using "font" var name in many places to always indicate font, not font name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Added docs
  2. Added docs, replaced usize with Col (nice catch!)
  3. text_padding_left
  4. We use types, for this reason, don't we? For example, the DEFAULT_FONT constant is named as it is, but it should be named DEFAULT_FONT_NAME then. Anyway, I renamed it to font_name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, its ould be DEFAULT_FONT_NAME. I believe I changed it in my newest PR (not merged yet).


// === Params ===

/// The parameters of Breadcrumbs' entries.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont understand these docs -what are parameters of breadcrumbs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rephrased a little

Comment on lines 240 to 267
network: &frp::Network,
init: frp::Source<()>,
) -> frp::Sampler<Self> {
frp::extend! { network
let margin = style.get_number(theme::entry::margin);
let hover_color = style.get_color(theme::entry::hover_color);
let font = style.get_text(theme::entry::font);
let text_offset = style.get_number(theme::entry::text_offset);
let text_size = style.get_number(theme::entry::text_size);
let selected_color = style.get_color(theme::entry::selected_color);
let highlight_corners_radius = style.get_number(theme::entry::highlight_corners_radius);
let greyed_out_color = style.get_color(theme::entry::greyed_out_color);
greyed_out_start <- init.constant(None);
text_params <- all4(&init, &text_offset,&text_size,&font);
colors <- all4(&init, &hover_color,&selected_color,&greyed_out_color);
params <- all_with6(&init,&margin,&text_params,&colors,&highlight_corners_radius,
&greyed_out_start,
|_,&margin,text_params,colors,&highlight_corners_radius,&greyed_out_start| {
let (_, text_offset,text_size,font) = text_params;
let (_, hover_color,selected_color,greyed_out_color) = colors;
Params {
margin,
text_offset: *text_offset,
text_size: text::Size::from(*text_size),
hover_color:*hover_color,
font: ImString::new(font),
selected_color: *selected_color,
highlight_corners_radius,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code extends frp network not belonging to thsi struct. Unfortunately, due to lack of proper docs, I dont understand what this structure is for. Anyway, what network does it extend? Please remember that after you extend FRP network, you CAN'T remove this structure leving the FRP network - otherwise you will cause memory leak. Are you 100% sure that when this struct is dropped the network is dropped as well? If you are not sure, you can't do it and you'd need to use FRP Bridge Network (used for example in the nodes implementation), which will be automatically dropped OR create a local network in this struct. Anyway, this is not documented. If you are 100% sure that network will be droped with thsi struct, this needs to be extremely well documented and explained why it happens this way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be no mistake here. To avoid confusion, I moved the function closer to its usage (to the root of the crate) and added docs. Keep in mind that the return value is Sampler<Self>, not Self, and the Sampler is later used in the network we extended here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know that. Still, it sohuld always be well documented because it's dangereous. In new FRP implementation we will disallow extend function completely. Thanks for moving it closer to the usage point and the docs.

color <- all_with3(&transparent_color, &target_color, &appear_anim.value, mix);

contour <- all_with(&size, &margin, |size, margin| Contour {
size: *size - Vector2(*margin, *margin) * 2.0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magic number 2.0 - what it is?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It means twice. The margins are placed on all sides of the entry - left, right, top and bottom. So we need to multiply the margin by 2 to reduce the size correctly. I don't believe such numbers can be considered magic.

Comment on lines 306 to 324
size <- input.set_size.on_change();
margin <- input.set_params.map(|p| p.margin).on_change();
hover_color <- input.set_params.map(|p| p.hover_color).on_change();
font <- input.set_params.map(|p| p.font.clone_ref()).on_change();
text_offset <- input.set_params.map(|p| p.text_offset).on_change();
text_color <- input.set_params.map(|p| p.selected_color).on_change();
text_size <- input.set_params.map(|p| p.text_size).on_change();
greyed_out_color <- input.set_params.map(|p| p.greyed_out_color).on_change();
greyed_out_from <- input.set_params.map(|p| p.greyed_out_start).on_change();
highlight_corners_radius <- input.set_params.map(|p| p.highlight_corners_radius).on_change();
transparent_color <- init.constant(color::Rgba::transparent());

col <- input.set_location._1();
should_grey_out <- all_with(&col, &greyed_out_from,
|col, from| from.map_or(false, |from| *col >= from)
);
color_anim.target <+ should_grey_out.map(|should| if *should { 1.0 } else { 0.0 });
target_color <- all_with3(&text_color, &greyed_out_color, &color_anim.value, mix);
appear_anim.target <+ init.constant(1.0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice FRP names here - as I mentioned before, I like the FRP look in this PR!

use ensogl_core::Animation;
use ensogl_grid_view as grid_view;
use ensogl_grid_view::Viewport;
use ensogl_hardcoded_theme::application::component_browser::searcher::list_panel::breadcrumbs as theme;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100 chars - this code is not linted. Please check why linter did not catch it and write here if it was a bad configuration or someting else, please.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is the intended behavior of the rustfmt. There is no clear way to reformat this code to fit into the max_width limit. See rust-lang/rustfmt#3863 and rust-lang/rustfmt#4746

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you split it into two uses instead?

// =================

/// We virtually divide the breadcrumbs into two regions – left and right. This constant
/// determines the fraction of the left one.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word "fraction" is not explained. What does fraction in this context mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rephrased a little by using the word proportion instead.

if self.entries.borrow().is_empty() {
None
} else if self.show_ellipsis.get() {
Some(self.grid_columns().saturating_sub(3))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magic number

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by using a variable

@vitvakatu vitvakatu requested a review from wdanilo September 13, 2022 19:04
Copy link
Member

@wdanilo wdanilo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice implementation, well done!

Self { display_object, state, text, ellipsis, separator }
}

fn remove_current(&self) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is "current"? Please use better name - is it currently selected one?

@vitvakatu vitvakatu added the CI: Ready to merge This PR is eligible for automatic merge label Sep 15, 2022
@mergify mergify bot merged commit a771e40 into develop Sep 19, 2022
@mergify mergify bot deleted the wip/vitvakatu/component-browser-breadcrumbs-182560265 branch September 19, 2022 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: Ready to merge This PR is eligible for automatic merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants