Permanent fix to repeat MouseRegion Tag failure in Workspace

Polish tab bar buttons

Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
K Simmons
2022-09-11 15:34:10 -07:00
co-authored by Mikayla Maki
parent 69ecbb644d
commit 6b26965074
33 changed files with 542 additions and 545 deletions
+5 -8
View File
@@ -4076,10 +4076,7 @@ impl<'a, V: View> RenderContext<'a, V> {
}
pub fn mouse_state<Tag: 'static>(&self, region_id: usize) -> MouseState {
let region_id = MouseRegionId {
view_id: self.view_id,
discriminant: (TypeId::of::<Tag>(), region_id),
};
let region_id = MouseRegionId::new::<Tag>(self.view_id, region_id);
MouseState {
hovered: self.hovered_region_ids.contains(&region_id),
clicked: self.clicked_region_ids.as_ref().and_then(|(ids, button)| {
@@ -6032,12 +6029,12 @@ mod tests {
}
impl super::View for View {
fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
enum Handler {}
let mouse_down_count = self.mouse_down_count.clone();
EventHandler::new(Empty::new().boxed())
.on_mouse_down(move |_| {
MouseEventHandler::<Handler>::new(0, cx, |_, _| Empty::new().boxed())
.on_down(MouseButton::Left, move |_, _| {
mouse_down_count.fetch_add(1, SeqCst);
true
})
.boxed()
}
+3 -4
View File
@@ -3,7 +3,6 @@ mod canvas;
mod constrained_box;
mod container;
mod empty;
mod event_handler;
mod expanded;
mod flex;
mod hook;
@@ -21,9 +20,9 @@ mod uniform_list;
use self::expanded::Expanded;
pub use self::{
align::*, canvas::*, constrained_box::*, container::*, empty::*, event_handler::*, flex::*,
hook::*, image::*, keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*,
stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
align::*, canvas::*, constrained_box::*, container::*, empty::*, flex::*, hook::*, image::*,
keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*, stack::*, svg::*,
text::*, tooltip::*, uniform_list::*,
};
pub use crate::presenter::ChildView;
use crate::{
-179
View File
@@ -1,179 +0,0 @@
use crate::{
geometry::vector::Vector2F, presenter::MeasurementContext, scene::HandlerSet, CursorRegion,
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseButton,
MouseButtonEvent, MouseRegion, NavigationDirection, PaintContext, SizeConstraint,
};
use pathfinder_geometry::rect::RectF;
use serde_json::json;
use std::{any::TypeId, ops::Range};
pub struct EventHandler {
child: ElementBox,
capture_all: Option<(TypeId, usize)>,
mouse_down: Option<Box<dyn FnMut(&mut EventContext) -> bool>>,
right_mouse_down: Option<Box<dyn FnMut(&mut EventContext) -> bool>>,
navigate_mouse_down: Option<Box<dyn FnMut(NavigationDirection, &mut EventContext) -> bool>>,
}
impl EventHandler {
pub fn new(child: ElementBox) -> Self {
Self {
child,
capture_all: None,
mouse_down: None,
right_mouse_down: None,
navigate_mouse_down: None,
}
}
pub fn on_mouse_down<F>(mut self, callback: F) -> Self
where
F: 'static + FnMut(&mut EventContext) -> bool,
{
self.mouse_down = Some(Box::new(callback));
self
}
pub fn on_right_mouse_down<F>(mut self, callback: F) -> Self
where
F: 'static + FnMut(&mut EventContext) -> bool,
{
self.right_mouse_down = Some(Box::new(callback));
self
}
pub fn on_navigate_mouse_down<F>(mut self, callback: F) -> Self
where
F: 'static + FnMut(NavigationDirection, &mut EventContext) -> bool,
{
self.navigate_mouse_down = Some(Box::new(callback));
self
}
pub fn capture_all<T: 'static>(mut self, id: usize) -> Self {
self.capture_all = Some((TypeId::of::<T>(), id));
self
}
}
impl Element for EventHandler {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, cx);
(size, ())
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
if let Some(discriminant) = self.capture_all {
cx.scene.push_stacking_context(None);
cx.scene.push_cursor_region(CursorRegion {
bounds: visible_bounds,
style: Default::default(),
});
cx.scene.push_mouse_region(MouseRegion {
view_id: cx.current_view_id(),
discriminant,
bounds: visible_bounds,
handlers: HandlerSet::capture_all(),
hoverable: true,
});
cx.scene.pop_stacking_context();
}
self.child.paint(bounds.origin(), visible_bounds, cx);
}
fn dispatch_event(
&mut self,
event: &Event,
_: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
cx: &mut EventContext,
) -> bool {
if self.capture_all.is_some() {
return true;
}
if self.child.dispatch_event(event, cx) {
true
} else {
match event {
Event::MouseDown(MouseButtonEvent {
button: MouseButton::Left,
position,
..
}) => {
if let Some(callback) = self.mouse_down.as_mut() {
if visible_bounds.contains_point(*position) {
return callback(cx);
}
}
false
}
Event::MouseDown(MouseButtonEvent {
button: MouseButton::Right,
position,
..
}) => {
if let Some(callback) = self.right_mouse_down.as_mut() {
if visible_bounds.contains_point(*position) {
return callback(cx);
}
}
false
}
Event::MouseDown(MouseButtonEvent {
button: MouseButton::Navigate(direction),
position,
..
}) => {
if let Some(callback) = self.navigate_mouse_down.as_mut() {
if visible_bounds.contains_point(*position) {
return callback(*direction, cx);
}
}
false
}
_ => false,
}
}
}
fn rect_for_text_range(
&self,
range_utf16: Range<usize>,
_: RectF,
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
cx: &MeasurementContext,
) -> Option<RectF> {
self.child.rect_for_text_range(range_utf16, cx)
}
fn debug(
&self,
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
cx: &DebugContext,
) -> serde_json::Value {
json!({
"type": "EventHandler",
"child": self.child.debug(cx),
})
}
}
+19 -16
View File
@@ -13,31 +13,32 @@ use crate::{
MouseButton, MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View,
};
use serde_json::json;
use std::{any::TypeId, ops::Range};
use std::{marker::PhantomData, ops::Range};
pub struct MouseEventHandler {
pub struct MouseEventHandler<Tag: 'static> {
child: ElementBox,
discriminant: (TypeId, usize),
region_id: usize,
cursor_style: Option<CursorStyle>,
handlers: HandlerSet,
hoverable: bool,
padding: Padding,
_tag: PhantomData<Tag>,
}
impl MouseEventHandler {
pub fn new<Tag, V, F>(id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
impl<Tag> MouseEventHandler<Tag> {
pub fn new<V, F>(region_id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
where
Tag: 'static,
V: View,
F: FnOnce(MouseState, &mut RenderContext<V>) -> ElementBox,
{
Self {
child: render_child(cx.mouse_state::<Tag>(id), cx),
child: render_child(cx.mouse_state::<Tag>(region_id), cx),
region_id,
cursor_style: None,
discriminant: (TypeId::of::<Tag>(), id),
handlers: Default::default(),
hoverable: true,
padding: Default::default(),
_tag: PhantomData,
}
}
@@ -140,7 +141,7 @@ impl MouseEventHandler {
}
}
impl Element for MouseEventHandler {
impl<Tag> Element for MouseEventHandler<Tag> {
type LayoutState = ();
type PaintState = ();
@@ -167,13 +168,15 @@ impl Element for MouseEventHandler {
});
}
cx.scene.push_mouse_region(MouseRegion {
view_id: cx.current_view_id(),
discriminant: self.discriminant,
bounds: hit_bounds,
handlers: self.handlers.clone(),
hoverable: self.hoverable,
});
cx.scene.push_mouse_region(
MouseRegion::from_handlers::<Tag>(
cx.current_view_id(),
self.region_id,
hit_bounds,
self.handlers.clone(),
)
.with_hoverable(self.hoverable),
);
self.child.paint(bounds.origin(), visible_bounds, cx);
}
+124 -47
View File
@@ -1,49 +1,98 @@
use std::{any::TypeId, ops::Range};
use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
presenter::MeasurementContext,
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion,
Axis, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion,
PaintContext, SizeConstraint,
};
use serde_json::json;
pub struct Overlay {
child: ElementBox,
abs_position: Option<Vector2F>,
anchor_position: Option<Vector2F>,
fit_mode: OverlayFitMode,
anchor_corner: AnchorCorner,
hoverable: bool,
}
#[derive(Copy, Clone)]
pub enum OverlayFitMode {
SnapToWindow,
FlipAlignment,
SwitchAnchor,
None,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum AnchorCorner {
TopLeft,
TopRight,
BottomLeft,
BottomRight,
}
impl AnchorCorner {
fn get_bounds(&self, anchor_position: Vector2F, size: Vector2F) -> RectF {
match self {
Self::TopLeft => RectF::from_points(anchor_position, anchor_position + size),
Self::TopRight => RectF::from_points(
anchor_position - Vector2F::new(size.x(), 0.),
anchor_position + Vector2F::new(0., size.y()),
),
Self::BottomLeft => RectF::from_points(
anchor_position - Vector2F::new(0., size.y()),
anchor_position + Vector2F::new(size.x(), 0.),
),
Self::BottomRight => RectF::from_points(anchor_position - size, anchor_position),
}
}
fn switch_axis(self, axis: Axis) -> Self {
match axis {
Axis::Vertical => match self {
AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
AnchorCorner::TopRight => AnchorCorner::BottomRight,
AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
AnchorCorner::BottomRight => AnchorCorner::TopRight,
},
Axis::Horizontal => match self {
AnchorCorner::TopLeft => AnchorCorner::TopRight,
AnchorCorner::TopRight => AnchorCorner::TopLeft,
AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
},
}
}
}
impl Overlay {
pub fn new(child: ElementBox) -> Self {
Self {
child,
abs_position: None,
anchor_position: None,
fit_mode: OverlayFitMode::None,
anchor_corner: AnchorCorner::TopLeft,
hoverable: false,
}
}
pub fn with_abs_position(mut self, position: Vector2F) -> Self {
self.abs_position = Some(position);
pub fn with_anchor_position(mut self, position: Vector2F) -> Self {
self.anchor_position = Some(position);
self
}
pub fn fit_mode(mut self, fit_mode: OverlayFitMode) -> Self {
pub fn with_anchor_corner(mut self, anchor_corner: AnchorCorner) -> Self {
self.anchor_corner = anchor_corner;
self
}
pub fn with_fit_mode(mut self, fit_mode: OverlayFitMode) -> Self {
self.fit_mode = fit_mode;
self
}
pub fn hoverable(mut self, hoverable: bool) -> Self {
pub fn with_hoverable(mut self, hoverable: bool) -> Self {
self.hoverable = hoverable;
self
}
@@ -58,7 +107,7 @@ impl Element for Overlay {
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let constraint = if self.abs_position.is_some() {
let constraint = if self.anchor_position.is_some() {
SizeConstraint::new(Vector2F::zero(), cx.window_size)
} else {
constraint
@@ -74,46 +123,74 @@ impl Element for Overlay {
size: &mut Self::LayoutState,
cx: &mut PaintContext,
) {
let mut bounds = RectF::new(self.abs_position.unwrap_or_else(|| bounds.origin()), *size);
let anchor_position = self.anchor_position.unwrap_or_else(|| bounds.origin());
let mut bounds = self.anchor_corner.get_bounds(anchor_position, *size);
match self.fit_mode {
OverlayFitMode::SnapToWindow => {
// Snap the horizontal edges of the overlay to the horizontal edges of the window if
// its horizontal bounds overflow
if bounds.max_x() > cx.window_size.x() {
let mut lower_right = bounds.lower_right();
lower_right.set_x(cx.window_size.x());
bounds = RectF::from_points(lower_right - *size, lower_right);
} else if bounds.min_x() < 0. {
let mut upper_left = bounds.origin();
upper_left.set_x(0.);
bounds = RectF::from_points(upper_left, upper_left + *size);
}
// Snap the vertical edges of the overlay to the vertical edges of the window if
// its vertical bounds overflow.
if bounds.max_y() > cx.window_size.y() {
let mut lower_right = bounds.lower_right();
lower_right.set_y(cx.window_size.y());
bounds = RectF::from_points(lower_right - *size, lower_right);
} else if bounds.min_y() < 0. {
let mut upper_left = bounds.origin();
upper_left.set_y(0.);
bounds = RectF::from_points(upper_left, upper_left + *size);
}
}
OverlayFitMode::SwitchAnchor => {
let mut anchor_corner = self.anchor_corner;
if bounds.max_x() > cx.window_size.x() {
anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
}
if bounds.max_y() > cx.window_size.y() {
anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
}
if bounds.min_x() < 0. {
anchor_corner = anchor_corner.switch_axis(Axis::Horizontal)
}
if bounds.min_y() < 0. {
anchor_corner = anchor_corner.switch_axis(Axis::Vertical)
}
// Update bounds if needed
if anchor_corner != self.anchor_corner {
bounds = anchor_corner.get_bounds(anchor_position, *size)
}
}
OverlayFitMode::None => {}
}
cx.scene.push_stacking_context(None);
if self.hoverable {
enum OverlayHoverCapture {}
cx.scene.push_mouse_region(MouseRegion {
view_id: cx.current_view_id(),
bounds,
discriminant: (TypeId::of::<OverlayHoverCapture>(), cx.current_view_id()),
handlers: Default::default(),
hoverable: true,
});
}
match self.fit_mode {
OverlayFitMode::SnapToWindow => {
// Snap the right edge of the overlay to the right edge of the window if
// its horizontal bounds overflow.
if bounds.lower_right().x() > cx.window_size.x() {
bounds.set_origin_x((cx.window_size.x() - bounds.width()).max(0.));
}
// Snap the bottom edge of the overlay to the bottom edge of the window if
// its vertical bounds overflow.
if bounds.lower_right().y() > cx.window_size.y() {
bounds.set_origin_y((cx.window_size.y() - bounds.height()).max(0.));
}
}
OverlayFitMode::FlipAlignment => {
// Right-align overlay if its horizontal bounds overflow.
if bounds.lower_right().x() > cx.window_size.x() {
bounds.set_origin_x(bounds.origin_x() - bounds.width());
}
// Bottom-align overlay if its vertical bounds overflow.
if bounds.lower_right().y() > cx.window_size.y() {
bounds.set_origin_y(bounds.origin_y() - bounds.height());
}
}
OverlayFitMode::None => {}
cx.scene.push_mouse_region(
MouseRegion::new::<OverlayHoverCapture>(
cx.current_view_id(),
cx.current_view_id(),
bounds,
)
.with_hoverable(true),
);
}
self.child.paint(bounds.origin(), bounds, cx);
@@ -153,7 +230,7 @@ impl Element for Overlay {
) -> serde_json::Value {
json!({
"type": "Overlay",
"abs_position": self.abs_position.to_json(),
"abs_position": self.anchor_position.to_json(),
"child": self.child.debug(cx),
})
}
+26 -27
View File
@@ -84,42 +84,41 @@ impl Tooltip {
})
.boxed(),
)
.fit_mode(OverlayFitMode::FlipAlignment)
.with_abs_position(state.position.get())
.with_fit_mode(OverlayFitMode::SwitchAnchor)
.with_anchor_position(state.position.get())
.boxed(),
)
} else {
None
};
let child =
MouseEventHandler::new::<MouseEventHandlerState<Tag>, _, _>(id, cx, |_, _| child)
.on_hover(move |e, cx| {
let position = e.position;
let window_id = cx.window_id();
if let Some(view_id) = cx.view_id() {
if e.started {
if !state.visible.get() {
state.position.set(position);
let child = MouseEventHandler::<MouseEventHandlerState<Tag>>::new(id, cx, |_, _| child)
.on_hover(move |e, cx| {
let position = e.position;
let window_id = cx.window_id();
if let Some(view_id) = cx.view_id() {
if e.started {
if !state.visible.get() {
state.position.set(position);
let mut debounce = state.debounce.borrow_mut();
if debounce.is_none() {
*debounce = Some(cx.spawn({
let state = state.clone();
|mut cx| async move {
cx.background().timer(DEBOUNCE_TIMEOUT).await;
state.visible.set(true);
cx.update(|cx| cx.notify_view(window_id, view_id));
}
}));
}
let mut debounce = state.debounce.borrow_mut();
if debounce.is_none() {
*debounce = Some(cx.spawn({
let state = state.clone();
|mut cx| async move {
cx.background().timer(DEBOUNCE_TIMEOUT).await;
state.visible.set(true);
cx.update(|cx| cx.notify_view(window_id, view_id));
}
}));
}
} else {
state.visible.set(false);
state.debounce.take();
}
} else {
state.visible.set(false);
state.debounce.take();
}
})
.boxed();
}
})
.boxed();
Self {
child,
tooltip,
+6 -4
View File
@@ -372,13 +372,13 @@ impl Presenter {
//Ensure that hover entrance events aren't sent twice
if self.hovered_region_ids.insert(region.id()) {
valid_regions.push(region.clone());
invalidated_views.insert(region.view_id);
invalidated_views.insert(region.id().view_id());
}
} else {
// Ensure that hover exit events aren't sent twice
if self.hovered_region_ids.remove(&region.id()) {
valid_regions.push(region.clone());
invalidated_views.insert(region.view_id);
invalidated_views.insert(region.id().view_id());
}
}
}
@@ -452,8 +452,10 @@ impl Presenter {
if let Some(callback) = valid_region.handlers.get(&region_event.handler_key()) {
event_cx.handled = true;
event_cx.invalidated_views.insert(valid_region.view_id);
event_cx.with_current_view(valid_region.view_id, {
event_cx
.invalidated_views
.insert(valid_region.id().view_id());
event_cx.with_current_view(valid_region.id().view_id(), {
let region_event = region_event.clone();
|cx| {
callback(region_event, cx);
+26 -6
View File
@@ -1,6 +1,7 @@
mod mouse_region;
mod mouse_region_event;
use collections::HashSet;
use serde::Deserialize;
use serde_json::json;
use std::{borrow::Cow, sync::Arc};
@@ -20,6 +21,8 @@ pub struct Scene {
scale_factor: f32,
stacking_contexts: Vec<StackingContext>,
active_stacking_context_stack: Vec<usize>,
#[cfg(debug_assertions)]
mouse_region_ids: HashSet<MouseRegionId>,
}
struct StackingContext {
@@ -177,6 +180,8 @@ impl Scene {
scale_factor,
stacking_contexts: vec![stacking_context],
active_stacking_context_stack: vec![0],
#[cfg(debug_assertions)]
mouse_region_ids: Default::default(),
}
}
@@ -241,7 +246,23 @@ impl Scene {
pub fn push_mouse_region(&mut self, region: MouseRegion) {
if can_draw(region.bounds) {
self.active_layer().push_mouse_region(region);
// Ensure that Regions cannot be added to a scene with the same region id.
let region_id;
#[cfg(debug_assertions)]
{
region_id = region.id();
}
if self.active_layer().push_mouse_region(region) {
#[cfg(debug_assertions)]
{
if !self.mouse_region_ids.insert(region_id) {
let tag_name = region_id.tag_type_name();
panic!("Same MouseRegionId: {region_id:?} inserted multiple times to the same scene. \
Will cause problems! Look for MouseRegion that uses Tag: {tag_name}");
}
}
}
}
}
@@ -364,15 +385,17 @@ impl Layer {
}
}
fn push_mouse_region(&mut self, region: MouseRegion) {
fn push_mouse_region(&mut self, region: MouseRegion) -> bool {
if let Some(bounds) = region
.bounds
.intersection(self.clip_bounds.unwrap_or(region.bounds))
{
if can_draw(bounds) {
self.mouse_regions.push(region);
return true;
}
}
false
}
fn push_underline(&mut self, underline: Underline) {
@@ -537,10 +560,7 @@ impl ToJson for Border {
impl MouseRegion {
pub fn id(&self) -> MouseRegionId {
MouseRegionId {
view_id: self.view_id,
discriminant: self.discriminant,
}
self.id
}
}
+35 -7
View File
@@ -1,4 +1,4 @@
use std::{any::TypeId, mem::Discriminant, rc::Rc};
use std::{any::TypeId, fmt::Debug, mem::Discriminant, rc::Rc};
use collections::HashMap;
@@ -16,8 +16,7 @@ use super::{
#[derive(Clone)]
pub struct MouseRegion {
pub view_id: usize,
pub discriminant: (TypeId, usize),
pub id: MouseRegionId,
pub bounds: RectF,
pub handlers: HandlerSet,
pub hoverable: bool,
@@ -43,8 +42,13 @@ impl MouseRegion {
handlers: HandlerSet,
) -> Self {
Self {
view_id,
discriminant: (TypeId::of::<Tag>(), region_id),
id: MouseRegionId {
view_id,
tag: TypeId::of::<Tag>(),
region_id,
#[cfg(debug_assertions)]
tag_type_name: std::any::type_name::<Tag>(),
},
bounds,
handlers,
hoverable: true,
@@ -137,8 +141,32 @@ impl MouseRegion {
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct MouseRegionId {
pub view_id: usize,
pub discriminant: (TypeId, usize),
view_id: usize,
tag: TypeId,
region_id: usize,
#[cfg(debug_assertions)]
tag_type_name: &'static str,
}
impl MouseRegionId {
pub(crate) fn new<Tag: 'static>(view_id: usize, region_id: usize) -> Self {
MouseRegionId {
view_id,
region_id,
tag: TypeId::of::<Tag>(),
#[cfg(debug_assertions)]
tag_type_name: std::any::type_name::<Tag>(),
}
}
pub fn view_id(&self) -> usize {
self.view_id
}
#[cfg(debug_assertions)]
pub fn tag_type_name(&self) -> &'static str {
self.tag_type_name
}
}
#[derive(Clone, Default)]
+13 -17
View File
@@ -109,7 +109,7 @@ impl View for Select {
Default::default()
};
let mut result = Flex::column().with_child(
MouseEventHandler::new::<Header, _, _>(self.handle.id(), cx, |mouse_state, cx| {
MouseEventHandler::<Header>::new(self.handle.id(), cx, |mouse_state, cx| {
Container::new((self.render_item)(
self.selected_item_ix,
ItemType::Header,
@@ -137,22 +137,18 @@ impl View for Select {
let selected_item_ix = this.selected_item_ix;
range.end = range.end.min(this.item_count);
items.extend(range.map(|ix| {
MouseEventHandler::new::<Item, _, _>(
ix,
cx,
|mouse_state, cx| {
(this.render_item)(
ix,
if ix == selected_item_ix {
ItemType::Selected
} else {
ItemType::Unselected
},
mouse_state.hovered,
cx,
)
},
)
MouseEventHandler::<Item>::new(ix, cx, |mouse_state, cx| {
(this.render_item)(
ix,
if ix == selected_item_ix {
ItemType::Selected
} else {
ItemType::Unselected
},
mouse_state.hovered,
cx,
)
})
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(SelectItem(ix))
})