mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 20:16:47 +03:00
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:
parent
5f7a4a5a39
commit
6c596f8760
@ -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))
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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) ()
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user