triangle indicator for dropdowns (#5859)

Closes #5854

Switches dropdown activation indicator to a triangle shape, and moved it to the horizontal center of a port.
![image](https://user-images.githubusercontent.com/919491/223765985-ec2175b7-7b44-45fd-88ff-543e8c08538f.png)

# Important Notes
Modified triangle SDF to be exact. That way the grow operation behaves as expected, rounding the corners. Other than that, it produces the same bound shape at 0 distance.
This commit is contained in:
Paweł Grabarz 2023-03-11 08:12:18 +01:00 committed by GitHub
parent 5f7a4a5a39
commit 6c596f8760
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 75 additions and 41 deletions

View File

@ -424,8 +424,8 @@ impl Model {
let width = unit * size as f32;
let width_padded = width + 2.0 * PORT_PADDING_X;
let height = 18.0;
let padded_size = Vector2(width_padded, height);
let size = Vector2(width, height);
let padded_size = Vector2(width_padded, height);
let position_x = unit * index as f32;
let port_shape = port.payload.init_shape(size, node::HEIGHT);
@ -522,7 +522,7 @@ impl Model {
area_frp.source.pointer_style <+ pointer_style;
}
if let Some((widget_bind, widget)) = self.init_port_widget(port, call_info) {
if let Some((widget_bind, widget)) = self.init_port_widget(port, size, call_info) {
widgets_map.insert(widget_bind, crumbs.clone_ref());
widget.set_x(position_x);
builder.parent.add_child(&widget);
@ -572,6 +572,7 @@ impl Model {
fn init_port_widget(
&self,
port: &mut PortRefMut,
port_size: Vector2<f32>,
call_info: &CallInfoMap,
) -> Option<(WidgetBind, widget::View)> {
let call_id = port.kind.call_id().filter(|id| call_info.has_target(id))?;
@ -604,7 +605,7 @@ impl Model {
None => port.payload.init_widget(&self.app),
};
widget.set_node_data(widget::NodeData { argument_info, node_height: node::HEIGHT });
widget.set_node_data(widget::NodeData { argument_info, port_size });
Some((widget_bind, widget))
}

View File

@ -15,8 +15,11 @@ use ensogl_component::drop_down::Dropdown;
/// === Constants ===
/// =================
const DOT_COLOR: color::Lch = color::Lch::new(0.56708, 0.23249, 0.71372);
const ACTIVATION_SHAPE_COLOR: color::Lch = color::Lch::new(0.56708, 0.23249, 0.71372);
const ACTIVATION_SHAPE_Y_OFFSET: f32 = -5.0;
const ACTIVATION_SHAPE_SIZE: Vector2 = Vector2(15.0, 11.0);
/// Distance between the dropdown and the bottom of the port.
const DROPDOWN_Y_OFFSET: f32 = 5.0;
// ===========
@ -66,7 +69,7 @@ pub enum Display {
#[allow(missing_docs)]
pub struct NodeData {
pub argument_info: span_tree::ArgumentInfo,
pub node_height: f32,
pub port_size: Vector2,
}
@ -243,7 +246,7 @@ impl KindModel {
let tag_values = node_data.argument_info.tag_values.iter().map(Into::into);
let entries = dynamic_entries.unwrap_or_else(|| tag_values.collect());
inner.set_node_height(node_data.node_height);
inner.set_port_size(node_data.port_size);
inner.set_entries(entries);
}
}
@ -257,12 +260,12 @@ impl KindModel {
// =================
// === Dot Shape ===
// =================
// ======================
// === Triangle Shape ===
// ======================
/// Temporary dropdown activation shape definition.
pub mod dot {
pub mod triangle {
use super::*;
ensogl::shape! {
above = [
@ -271,8 +274,11 @@ pub mod dot {
];
(style:Style, color:Vector4) {
let size = Var::canvas_size();
let radius = Min::min(size.x(),size.y()) / 2.0;
let shape = Rect(size).corners_radius(radius);
let radius = 1.0.px();
let shrink = &radius * 2.0;
let shape = Triangle(size.x() - &shrink, size.y() - &shrink)
.flip_y()
.grow(radius);
shape.fill(color).into()
}
}
@ -289,10 +295,10 @@ pub mod dot {
#[derive(Debug)]
pub struct SingleChoiceModel {
#[allow(dead_code)]
network: frp::Network,
dropdown: Rc<RefCell<LazyDropdown>>,
network: frp::Network,
dropdown: Rc<RefCell<LazyDropdown>>,
/// temporary click handling
activation_dot: dot::View,
activation_shape: triangle::View,
}
impl SingleChoiceModel {
@ -301,9 +307,9 @@ impl SingleChoiceModel {
display_object: &display::object::Instance,
frp: &SampledFrp,
) -> Self {
let activation_dot = dot::View::new();
activation_dot.set_size((15.0, 15.0));
display_object.add_child(&activation_dot);
let activation_shape = triangle::View::new();
activation_shape.set_size(ACTIVATION_SHAPE_SIZE);
display_object.add_child(&activation_shape);
frp::new_network! { network
init <- source_();
@ -321,7 +327,7 @@ impl SingleChoiceModel {
let dropdown = Rc::new(RefCell::new(dropdown));
frp::extend! { network
let dot_clicked = activation_dot.events.mouse_down_primary.clone_ref();
let dot_clicked = activation_shape.events.mouse_down_primary.clone_ref();
toggle_focus <- dot_clicked.map(f!([display_object](()) !display_object.is_focused()));
set_focused <- any(toggle_focus, frp.set_focused);
eval set_focused([display_object](focus) match focus {
@ -330,10 +336,10 @@ impl SingleChoiceModel {
});
set_visible <- all(&frp.set_visible, &init)._0();
dot_alpha <- set_visible.map(|visible| if *visible { 1.0 } else { 0.0 });
dot_color <- dot_alpha.map(|alpha| DOT_COLOR.with_alpha(*alpha));
eval dot_color([activation_dot] (color) {
activation_dot.color.set(color::Rgba::from(color).into());
shape_alpha <- set_visible.map(|visible| if *visible { 1.0 } else { 0.0 });
shape_color <- shape_alpha.map(|a| ACTIVATION_SHAPE_COLOR.with_alpha(*a));
eval shape_color([activation_shape] (color) {
activation_shape.color.set(color::Rgba::from(color).into());
});
eval focus_in((_) dropdown.borrow_mut().initialize_on_open());
@ -341,12 +347,14 @@ impl SingleChoiceModel {
init.emit(());
Self { network, dropdown, activation_dot }
Self { network, dropdown, activation_shape }
}
fn set_node_height(&self, node_height: f32) {
self.activation_dot.set_y(-node_height / 2.0 - 1.0);
self.dropdown.borrow_mut().set_node_height(node_height);
fn set_port_size(&self, port_size: Vector2) {
self.activation_shape.set_x(port_size.x() / 2.0);
self.activation_shape
.set_y(-port_size.y() / 2.0 - ACTIVATION_SHAPE_SIZE.y() - ACTIVATION_SHAPE_Y_OFFSET);
self.dropdown.borrow_mut().set_port_size(port_size);
}
fn set_entries(&self, values: Vec<ImString>) {
@ -373,7 +381,7 @@ enum LazyDropdown {
NotInitialized {
app: Application,
display_object: display::object::Instance,
node_height: f32,
dropdown_y: f32,
entries: Vec<ImString>,
set_current_value: frp::Sampler<Option<ImString>>,
is_open: frp::Sampler<bool>,
@ -393,12 +401,12 @@ impl LazyDropdown {
) -> Self {
let app = app.clone_ref();
let display_object = display_object.clone_ref();
let node_height = default();
let dropdown_y = default();
let entries = default();
LazyDropdown::NotInitialized {
app,
display_object,
node_height,
dropdown_y,
entries,
set_current_value,
is_open,
@ -406,13 +414,14 @@ impl LazyDropdown {
}
}
fn set_node_height(&mut self, new_node_height: f32) {
fn set_port_size(&mut self, new_port_size: Vector2) {
let y = -new_port_size.y() - DROPDOWN_Y_OFFSET;
match self {
LazyDropdown::Initialized(dropdown, ..) => {
dropdown.set_y(-new_node_height);
dropdown.set_y(y);
}
LazyDropdown::NotInitialized { node_height, .. } => {
*node_height = new_node_height;
LazyDropdown::NotInitialized { dropdown_y, .. } => {
*dropdown_y = y;
}
}
}
@ -435,7 +444,7 @@ impl LazyDropdown {
LazyDropdown::NotInitialized {
app,
display_object,
node_height,
dropdown_y,
entries,
is_open,
set_current_value,
@ -444,7 +453,7 @@ impl LazyDropdown {
let dropdown = app.new_view::<Dropdown<ImString>>();
display_object.add_child(&dropdown);
app.display.default_scene.layers.above_nodes.add(&dropdown);
dropdown.set_y(-*node_height);
dropdown.set_y(*dropdown_y);
dropdown.set_max_open_size(Vector2(300.0, 500.0));
dropdown.set_all_entries(std::mem::take(entries));
dropdown.allow_deselect_all(true);

View File

@ -117,6 +117,11 @@ where for<'t> &'t Self: IntoOwned<Owned = Self> {
Rotation(self, angle)
}
/// Flip the shape upside-down, mirroring it over the X axis.
fn flip_y(&self) -> FlipY<Self> {
FlipY(self)
}
/// Scales the shape by a given value.
fn scale<S: Into<Var<f32>>>(&self, value: S) -> Scale<Self> {
Scale(self, value)

View File

@ -118,6 +118,7 @@ define_modifiers! {
Translate translate (child) (v:Vector2<Pixels>)
Rotation rotation (child) (angle:Radians)
Scale scale (child) (value:f32)
FlipY flip_y (child) ()
Union union (child1,child2) ()
Difference difference (child1,child2) ()
Intersection intersection (child1,child2) ()

View File

@ -332,11 +332,21 @@ define_sdf_shapes! {
// === Triangle ===
/// Isosceles Triangle pointing up.
/// This is an exact SDF. The calculated distance is exact both inside and outside of shape's
/// bounds. Growing it will result in a triangle with rounded corners.
/// Adapted from https://iquilezles.org/articles/distfunctions2d/
Triangle (width:f32, height:f32) {
vec2 norm = normalize(vec2(height,width/2.0));
float pos_y = -position.y - height/2.0;
float dist = max(abs(position).x*norm.x + position.y*norm.y - height/2.0*norm.y, pos_y);
return bound_sdf(dist,bounding_box(width,height/2.0));
vec2 q = vec2(width * 0.5, height);
vec2 p = vec2(abs(position.x), height * 0.5 - position.y);
vec2 a = p - q * clamp(dot(p,q) / dot(q,q), 0.0, 1.0);
vec2 b = p - q * vec2(clamp(p.x / q.x, 0.0, 1.0), 1.0);
float s = -sign(q.y);
vec2 d1 = vec2(dot(a,a), s * (p.x * q.y - p.y * q.x));
vec2 d2 = vec2(dot(b,b), s * (p.y - q.y));
vec2 d = min(d1, d2);
float dist = -sqrt(d.x) * sign(d.y);
return bound_sdf(dist,bounding_box(width * 0.5, height * 0.5));
}

View File

@ -230,6 +230,14 @@ impl Canvas {
})
}
/// Flip the shape upside-down, mirroring it over the X axis.
pub fn flip_y(&mut self, num: usize, s1: Shape) -> Shape {
self.if_not_defined(num, |this| {
this.add_current_function_code_line("position.y = -position.y;");
this.new_shape_from_expr(&format!("return {};", s1.getter()))
})
}
/// Fill the shape with the provided color.
pub fn fill<Color: Into<Var<color::Rgba>>>(
&mut self,