Skip to content

Commit

Permalink
New selection box implementation for component groups (#3520)
Browse files Browse the repository at this point in the history
[Task link](https://www.pivotaltracker.com/story/show/182194574).
[ci no changelog needed]

This PR implements a new selection box that will replace an old (not really working) one in the component browser. The old selection box wasn't working well with the headers of the component groups, so we were forced to make a much harder implementation.

The new implementation duplicates some visual components and places them in a separate layer. Then, a rectangular mask cuts off everything that is not "selected". This way:
- We have more control over what the selected entries should look like.
- We can easily support the multi-layer structure of the component groups with headers.
- We avoid problems with nested masks that our renderer doesn't support at the moment.

To be more precise, we duplicate the following:
- Background of the component group becomes the "fill" of the selection.
- Entries text and icons - we can alter them easily.
- Header background and header text. By placing them in separate scene layers we ensure correct rendering order.

https://user-images.githubusercontent.com/6566674/173657899-1067f538-4329-44f9-9dc2-78c8a4708b5a.mp4

# Important Notes
- This PR implements the base of our future selection mechanism, selecting entries with a mouse and keyboard still has several issues that will be fixed in the future tasks.
- The scrolling behavior will also be improved in future tasks. Right we only restrict the selection box position so that it never leaves the borders of the component group.
- I added a new function to `add` shapes to new layers in a non-exclusive way (we had only `add_exclusive`) before. I have no idea how we didn't use this feature before even though we mention it a lot in the docs.
- The demo scene restricts the position of the selection box for one-column component groups but does not for the wide component group.
  • Loading branch information
vitvakatu committed Jun 17, 2022
1 parent 37925fc commit e1d4369
Show file tree
Hide file tree
Showing 12 changed files with 879 additions and 503 deletions.
175 changes: 130 additions & 45 deletions app/gui/view/component-browser/component-group/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ use crate::prelude::*;

use crate::icon;
use crate::Colors;
use crate::SelectedColors;

use enso_frp as frp;
use ensogl::application::Application;
use ensogl::data::color;
use ensogl::display;
use ensogl::display::scene::Layer;
use ensogl::display::scene::layer::Layer;
use ensogl::display::scene::layer::WeakLayer;
use ensogl::display::style;
use ensogl::display::Scene;
use ensogl_hardcoded_theme::application::component_browser::component_group::entry_list as theme;
Expand Down Expand Up @@ -77,22 +79,32 @@ impl From<&str> for Model {
#[allow(missing_docs)]
#[derive(Clone, CloneRef, Debug)]
pub struct Params {
pub colors: Colors,
pub colors: Colors,
pub selection_layer: Rc<Option<WeakLayer>>,
}

impl Default for Params {
fn default() -> Self {
let network = frp::Network::new("component_browser::entry::Params::default");
frp::extend! { network
icon_strong <- source::<color::Rgba>().sampler();
icon_weak <- source::<color::Rgba>().sampler();
header_text <- source::<color::Rgba>().sampler();
entry_text <- source::<color::Rgba>().sampler();
background <- source::<color::Rgba>().sampler();
default_color <- source::<color::Rgba>().sampler();
}

let colors = Colors { icon_strong, icon_weak, header_text, entry_text, background };
Self { colors }
let selected = SelectedColors {
background: default_color.clone_ref(),
header_text: default_color.clone_ref(),
entry_text: default_color.clone_ref(),
icon_strong: default_color.clone_ref(),
icon_weak: default_color.clone_ref(),
};
let colors = Colors {
icon_strong: default_color.clone_ref(),
icon_weak: default_color.clone_ref(),
header_text: default_color.clone_ref(),
entry_text: default_color.clone_ref(),
background: default_color.clone_ref(),
selected,
};
Self { colors, selection_layer: default() }
}
}

Expand All @@ -105,41 +117,100 @@ impl Default for Params {

// === CurrentIcon ===

/// The structure keeping a currently displayed incon in Component Group Entry [`View`]. Remembering
/// The structure keeping a currently displayed icon in Component Group Entry [`View`]. Remembering
/// id allows us to skip icon generation when not changed.
#[derive(Debug, Default)]
struct CurrentIcon {
shape: Option<icon::Any>,
id: Option<icon::Id>,
}

impl CurrentIcon {
fn set_strong_color(&self, color: color::Rgba) {
if let Some(shape) = &self.shape {
shape.strong_color.set(color.into());
}
}

fn set_weak_color(&self, color: color::Rgba) {
if let Some(shape) = &self.shape {
shape.weak_color.set(color.into());
}
}
}



// === View ===

/// A visual representation of a [`Model`].
#[derive(Clone, CloneRef, Debug)]
pub struct View {
network: frp::Network,
logger: Logger,
display_object: display::object::Instance,
icon: Rc<RefCell<CurrentIcon>>,
max_width_px: frp::Source<f32>,
icon_strong_color: frp::Sampler<color::Rgba>,
icon_weak_color: frp::Sampler<color::Rgba>,
label: GlyphHighlightedLabel,
network: frp::Network,
logger: Logger,
display_object: display::object::Instance,
icon: Rc<RefCell<CurrentIcon>>,
selected_icon: Rc<RefCell<CurrentIcon>>,
max_width_px: frp::Source<f32>,
label: GlyphHighlightedLabel,
selected_label: GlyphHighlightedLabel,
selection_layer: Rc<Option<WeakLayer>>,
colors: Colors,
}

impl View {
/// Update an icon shape (create it if necessary), update its color, and add it to the
/// [`layer`] if supplied.
fn update_icon(
&self,
model: &Model,
icon: &RefCell<CurrentIcon>,
layer: Option<Layer>,
strong_color: color::Rgba,
weak_color: color::Rgba,
) {
let mut icon = icon.borrow_mut();
if !icon.id.contains(&model.icon) {
icon.id = Some(model.icon);
let shape = model.icon.create_shape(&self.logger, Vector2(icon::SIZE, icon::SIZE));
shape.strong_color.set(strong_color.into());
shape.weak_color.set(weak_color.into());
shape.set_position_x(icon::SIZE / 2.0);
self.display_object.add_child(&shape);
if let Some(layer) = layer {
layer.add_exclusive(&shape);
}
icon.shape = Some(shape);
}
}
}

impl list_view::Entry for View {
type Model = Model;
type Params = Params;

fn new(app: &Application, style_prefix: &style::Path, Params { colors }: &Params) -> Self {
fn new(
app: &Application,
style_prefix: &style::Path,
Params { colors, selection_layer }: &Params,
) -> Self {
let logger = Logger::new("component_group::Entry");
let display_object = display::object::Instance::new(&logger);
let icon: Rc<RefCell<CurrentIcon>> = default();
let selected_icon: Rc<RefCell<CurrentIcon>> = default();
let label = GlyphHighlightedLabel::new(app, style_prefix, &());
let selected_label = GlyphHighlightedLabel::new(app, style_prefix, &());
display_object.add_child(&label);

if let Some(selection_layer) = &**selection_layer {
if let Some(layer) = selection_layer.upgrade() {
selected_label.set_label_layer(&layer);
display_object.add_child(&selected_label);
} else {
error!(logger, "Selection layer is dropped.");
}
}

let network = frp::Network::new("component_group::Entry");
let style = &label.inner.style_watch;
let icon_text_gap = style.get_number(theme::icon_text_gap);
Expand All @@ -150,46 +221,60 @@ impl list_view::Entry for View {
label_x_position <- icon_text_gap.map(|gap| icon::SIZE + gap);
label_max_width <-
all_with(&max_width_px, &icon_text_gap, |width,gap| width - icon::SIZE- gap);
eval label_x_position ((x) label.set_position_x(*x));
eval label_max_width ((width) label.set_max_width(*width));
eval label_x_position ([label, selected_label](x) {
label.set_position_x(*x);
selected_label.set_position_x(*x);
});
eval label_max_width ([label, selected_label](width) {
label.set_max_width(*width);
selected_label.set_max_width(*width);
});
label.inner.label.set_default_color <+ all(&colors.entry_text, &init)._0();
eval colors.icon_strong ([icon](color)
if let Some(shape) = &icon.borrow().shape {
shape.strong_color.set(color.into());
}
);
eval colors.icon_weak ([icon](color)
if let Some(shape) = &icon.borrow().shape {
shape.weak_color.set(color.into());
}
);
icon_strong_color <- colors.icon_strong.sampler();
icon_weak_color <- colors.icon_weak.sampler();
selected_label.inner.label.set_default_color <+ all(&colors.selected.entry_text,&init)._0();
eval colors.icon_strong ((&c) icon.borrow().set_strong_color(c));
eval colors.selected.icon_strong((&c) selected_icon.borrow().set_strong_color(c));
eval colors.icon_weak ((&c) icon.borrow().set_weak_color(c));
eval colors.selected.icon_weak((&c) selected_icon.borrow().set_weak_color(c));
}
init.emit(());
let selection_layer = selection_layer.clone_ref();
let colors = colors.clone_ref();
Self {
logger,
network,
display_object,
icon,
selected_icon,
max_width_px,
icon_strong_color,
icon_weak_color,
colors,
label,
selected_label,
selection_layer,
}
}

fn update(&self, model: &Self::Model) {
self.label.update(&model.highlighted_text);
let mut icon = self.icon.borrow_mut();
if !icon.id.contains(&model.icon) {
icon.id = Some(model.icon);
let shape = model.icon.create_shape(&self.logger, Vector2(icon::SIZE, icon::SIZE));
shape.strong_color.set(self.icon_strong_color.value().into());
shape.weak_color.set(self.icon_weak_color.value().into());
shape.set_position_x(icon::SIZE / 2.0);
self.display_object.add_child(&shape);
icon.shape = Some(shape);
self.selected_label.update(&model.highlighted_text);
self.update_icon(
model,
&self.icon,
None,
self.colors.icon_strong.value(),
self.colors.icon_weak.value(),
);
if let Some(weak_layer) = &*self.selection_layer {
if let Some(layer) = weak_layer.upgrade() {
self.update_icon(
model,
&self.selected_icon,
Some(layer),
self.colors.selected.icon_strong.value(),
self.colors.selected.icon_weak.value(),
);
} else {
error!(self.logger, "Cannot add icon shape to a dropped scene layer.");
}
}
}

Expand Down
Loading

0 comments on commit e1d4369

Please sign in to comment.