use embedded_graphics::{prelude::Point, primitives::Rectangle};
use crate::{TouchEvent, TouchPhase, ViewRedraw};
use super::{ListActivity, ListDataSource, ListDelegate, ListEvent, ListSelection, ListView};
impl<DataSource, Delegate> ListView<DataSource, Delegate>
where
DataSource: ListDataSource,
{
pub fn handle_touch<'text>(
&mut self,
touch: TouchEvent,
viewport: Rectangle,
) -> ListEvent<Delegate::Message>
where
Delegate: ListDelegate<'text, DataSource::ItemId>,
{
if !touch.within(viewport) && !self.scroll_view.is_dragging() {
return ListEvent::none();
}
let local_touch = offset_touch(touch, viewport.top_left);
let touched_index = self.item_index_at_point(touch.point, viewport);
let was_dragging = self.scroll_view.is_dragging();
let previous_highlight = self.highlighted_index;
let previous_selection = self.selected_index;
let mut activity = ListActivity::None;
let mut message = None;
let scrolled = match touch.phase {
TouchPhase::Start => {
self.touch_started_inside = touch.within(viewport);
self.tap_candidate = self.touch_started_inside;
self.scroll_view.begin_drag(local_touch);
message = message.or(
self.set_highlighted_index(touched_index.filter(|_| self.touch_started_inside))
);
false
}
TouchPhase::Move => {
let changed =
self.scroll_view
.drag(local_touch, self.content_height(), viewport.size.height);
if changed || self.scroll_view.is_scrolling() {
self.tap_candidate = false;
message = message.or(self.set_highlighted_index(None));
} else {
let next_highlight = if self.touch_started_inside && touch.within(viewport) {
touched_index
} else {
None
};
message = message.or(self.set_highlighted_index(next_highlight));
}
changed
}
TouchPhase::End | TouchPhase::Cancel => {
let changed = self.scroll_view.end_drag(
local_touch,
self.content_height(),
viewport.size.height,
);
if matches!(touch.phase, TouchPhase::End)
&& self.touch_started_inside
&& self.tap_candidate
&& touch.within(viewport)
{
if let Some(index) = touched_index {
if self.allows_selection {
self.selected_index = Some(index);
}
if let Some(selection) = self.selection_for_index(index) {
message = message.or(self.delegate.did_select_item(selection));
}
}
}
message = message.or(self.set_highlighted_index(None));
self.touch_started_inside = false;
self.tap_candidate = false;
changed
}
};
let visual_state_changed = previous_highlight != self.highlighted_index
|| previous_selection != self.selected_index;
let motion_active = scrolled || self.scroll_view.is_scrolling();
if motion_active {
activity = ListActivity::Motion;
} else if visual_state_changed {
activity = ListActivity::Interactive;
}
ListEvent {
redraw: match activity {
ListActivity::None => ViewRedraw::None,
ListActivity::Interactive | ListActivity::Motion => ViewRedraw::Dirty(viewport),
},
captured: was_dragging || touch.within(viewport) || self.touch_started_inside,
message,
activity,
}
}
pub fn item_at_point(
&self,
point: Point,
viewport: Rectangle,
) -> Option<ListSelection<DataSource::ItemId>> {
self.selection_for_index(self.item_index_at_point(point, viewport)?)
}
fn item_index_at_point(&self, point: Point, viewport: Rectangle) -> Option<usize> {
if !point_in_rect(point, viewport) {
return None;
}
let mut cursor = self.scroll_view.content_offset_y();
for index in 0..self.data_source.item_count() {
let height = self.data_source.item_height(index);
let frame = Rectangle::new(
Point::new(viewport.top_left.x, viewport.top_left.y + cursor),
embedded_graphics::prelude::Size::new(viewport.size.width, height),
);
if point_in_rect(point, frame) {
return Some(index);
}
cursor += height as i32;
}
None
}
pub(super) fn selection_for_index(
&self,
index: usize,
) -> Option<ListSelection<DataSource::ItemId>> {
(index < self.data_source.item_count()).then_some(ListSelection {
id: self.data_source.item_id(index),
index,
})
}
fn set_highlighted_index<'text>(&mut self, next: Option<usize>) -> Option<Delegate::Message>
where
Delegate: ListDelegate<'text, DataSource::ItemId>,
{
if self.highlighted_index == next {
return None;
}
let previous = self.highlighted_index.take();
self.highlighted_index = next.filter(|index| *index < self.data_source.item_count());
let mut message = None;
if let Some(index) = previous
&& let Some(selection) = self.selection_for_index(index)
{
message = message.or(self.delegate.did_highlight_item(selection, false));
}
if let Some(index) = self.highlighted_index
&& let Some(selection) = self.selection_for_index(index)
{
message = message.or(self.delegate.did_highlight_item(selection, true));
}
message
}
}
fn offset_touch(touch: TouchEvent, top_left: Point) -> TouchEvent {
TouchEvent::new(
Point::new(touch.point.x - top_left.x, touch.point.y - top_left.y),
touch.phase,
touch.timestamp_ms,
)
}
fn point_in_rect(point: Point, rect: Rectangle) -> bool {
let right = rect.top_left.x + rect.size.width as i32;
let bottom = rect.top_left.y + rect.size.height as i32;
point.x >= rect.top_left.x && point.x < right && point.y >= rect.top_left.y && point.y < bottom
}