use super::{driver, Driver, MatrixData, SelectionMode};
use kas::event::{ChildMsg, Command, CursorIcon, GrabMode, PressSource};
use kas::layout::solve_size_rules;
use kas::prelude::*;
use kas::updatable::UpdatableAll;
#[allow(unused)]
use kas::widget::ScrollBars;
use kas::widget::{ScrollComponent, Scrollable};
use linear_map::set::LinearSet;
use log::{debug, trace};
use std::time::Instant;
#[derive(Clone, Debug, Default)]
struct WidgetData<K, W> {
key: Option<K>,
widget: W,
}
#[derive(Clone, Debug, Widget)]
#[handler(send=noauto, msg=ChildMsg<T::Key, <V::Widget as Handler>::Msg>)]
#[widget(children=noauto, config=noauto)]
pub struct MatrixView<
T: MatrixData + UpdatableAll<T::Key, V::Msg> + 'static,
V: Driver<T::Item> = driver::Default,
> {
first_id: WidgetId,
#[widget_core]
core: CoreData,
frame_offset: Offset,
frame_size: Size,
view: V,
data: T,
widgets: Vec<WidgetData<T::Key, V::Widget>>,
align_hints: AlignHints,
ideal_len: Size,
alloc_len: Size,
cur_len: Size,
child_size_min: Size,
child_size_ideal: Size,
child_inter_margin: Size,
child_size: Size,
scroll: ScrollComponent,
sel_mode: SelectionMode,
selection: LinearSet<T::Key>,
press_event: Option<PressSource>,
press_target: Option<T::Key>,
}
impl<T: MatrixData + UpdatableAll<T::Key, V::Msg>, V: Driver<T::Item> + Default> MatrixView<T, V> {
pub fn new(data: T) -> Self {
Self::new_with_driver(<V as Default>::default(), data)
}
}
impl<T: MatrixData + UpdatableAll<T::Key, V::Msg>, V: Driver<T::Item>> MatrixView<T, V> {
pub fn new_with_driver(view: V, data: T) -> Self {
MatrixView {
first_id: Default::default(),
core: Default::default(),
frame_offset: Default::default(),
frame_size: Default::default(),
view,
data,
widgets: Default::default(),
align_hints: Default::default(),
ideal_len: Size(5, 3),
alloc_len: Size::ZERO,
cur_len: Size::ZERO,
child_size_min: Size::ZERO,
child_size_ideal: Size::ZERO,
child_inter_margin: Size::ZERO,
child_size: Size::ZERO,
scroll: Default::default(),
sel_mode: SelectionMode::None,
selection: Default::default(),
press_event: None,
press_target: None,
}
}
pub fn data(&self) -> &T {
&self.data
}
pub fn data_mut(&mut self) -> &mut T {
&mut self.data
}
pub fn get_value(&self, key: &T::Key) -> Option<T::Item> {
self.data.get_cloned(key)
}
pub fn set_value(&self, mgr: &mut Manager, key: &T::Key, data: T::Item) {
if let Some(handle) = self.data.update(key, data) {
mgr.trigger_update(handle, 0);
}
}
pub fn update_value<F: Fn(T::Item) -> T::Item>(&self, mgr: &mut Manager, key: &T::Key, f: F) {
if let Some(item) = self.get_value(key) {
self.set_value(mgr, key, f(item));
}
}
pub fn selection_mode(&self) -> SelectionMode {
self.sel_mode
}
pub fn set_selection_mode(&mut self, mode: SelectionMode) -> TkAction {
self.sel_mode = mode;
match mode {
SelectionMode::None if !self.selection.is_empty() => {
self.selection.clear();
TkAction::REDRAW
}
SelectionMode::Single if self.selection.len() > 1 => {
if let Some(first) = self.selection.iter().next().cloned() {
self.selection.retain(|item| *item == first);
}
TkAction::REDRAW
}
_ => TkAction::empty(),
}
}
pub fn with_selection_mode(mut self, mode: SelectionMode) -> Self {
let _ = self.set_selection_mode(mode);
self
}
pub fn selected_iter<'a>(&'a self) -> impl Iterator<Item = &'a T::Key> + 'a {
self.selection.iter()
}
pub fn is_selected(&self, key: &T::Key) -> bool {
self.selection.contains(key)
}
pub fn clear_selected(&mut self) {
self.selection.clear();
}
pub fn select(&mut self, key: T::Key) -> Result<bool, ()> {
match self.sel_mode {
SelectionMode::None => return Err(()),
SelectionMode::Single => self.selection.clear(),
_ => (),
}
if !self.data.contains(&key) {
return Err(());
}
Ok(self.selection.insert(key))
}
pub fn deselect(&mut self, key: &T::Key) -> bool {
self.selection.remove(key)
}
pub fn update_view(&mut self, mgr: &mut Manager) {
let data = &self.data;
self.selection.retain(|key| data.contains(key));
for w in &mut self.widgets {
w.key = None;
}
self.update_widgets(mgr);
trace!("update_view triggers SET_SIZE");
*mgr |= TkAction::SET_SIZE;
}
pub fn with_num_visible(mut self, cols: i32, rows: i32) -> Self {
self.ideal_len = Size(cols, rows);
self
}
fn update_widgets(&mut self, mgr: &mut Manager) {
let time = Instant::now();
let data_len = Size(self.data.col_len().cast(), self.data.row_len().cast());
let view_size = self.rect().size;
let skip = self.child_size + self.child_inter_margin;
let content_size = (skip.cwise_mul(data_len) - self.child_inter_margin).max(Size::ZERO);
*mgr |= self.scroll.set_sizes(view_size, content_size);
let offset = self.scroll_offset();
let first_col = usize::conv(u64::conv(offset.0) / u64::conv(skip.0));
let first_row = usize::conv(u64::conv(offset.1) / u64::conv(skip.1));
let cols = self
.data
.col_iter_vec_from(first_col, self.alloc_len.0.cast());
let rows = self
.data
.row_iter_vec_from(first_row, self.alloc_len.1.cast());
self.cur_len = Size(cols.len().cast(), rows.len().cast());
let pos_start = self.core.rect.pos + self.frame_offset;
let mut rect = Rect::new(pos_start, self.child_size);
let mut action = TkAction::empty();
for (cn, col) in cols.iter().enumerate() {
let ci = first_col + cn;
for (rn, row) in rows.iter().enumerate() {
let ri = first_row + rn;
let i = (ci % cols.len()) + (ri % rows.len()) * cols.len();
let w = &mut self.widgets[i];
let key = T::make_key(&col, &row);
if w.key.as_ref() != Some(&key) {
if let Some(item) = self.data.get_cloned(&key) {
w.key = Some(key.clone());
action |= self.view.set(&mut w.widget, item);
} else {
w.key = None;
}
}
rect.pos = pos_start + skip.cwise_mul(Size(ci.cast(), ri.cast()));
if w.widget.rect() != rect {
w.widget.set_rect(mgr, rect, self.align_hints);
}
}
}
*mgr |= action;
let dur = (Instant::now() - time).as_micros();
trace!("MatrixView::update_widgets completed in {}μs", dur);
}
}
impl<T: MatrixData + UpdatableAll<T::Key, V::Msg>, V: Driver<T::Item>> Scrollable
for MatrixView<T, V>
{
fn scroll_axes(&self, size: Size) -> (bool, bool) {
let item_min = self.child_size_min + self.child_inter_margin;
let data_len = Size(self.data.col_len().cast(), self.data.row_len().cast());
let min_size = (item_min.cwise_mul(data_len) - self.child_inter_margin).max(Size::ZERO);
(min_size.0 > size.0, min_size.1 > size.1)
}
#[inline]
fn max_scroll_offset(&self) -> Offset {
self.scroll.max_offset()
}
#[inline]
fn scroll_offset(&self) -> Offset {
self.scroll.offset()
}
#[inline]
fn set_scroll_offset(&mut self, mgr: &mut Manager, offset: Offset) -> Offset {
*mgr |= self.scroll.set_offset(offset);
self.update_widgets(mgr);
self.scroll.offset()
}
}
impl<T: MatrixData + UpdatableAll<T::Key, V::Msg>, V: Driver<T::Item>> WidgetChildren
for MatrixView<T, V>
{
#[inline]
fn first_id(&self) -> WidgetId {
self.first_id
}
fn record_first_id(&mut self, id: WidgetId) {
self.first_id = id;
}
#[inline]
fn num_children(&self) -> usize {
self.widgets.len()
}
#[inline]
fn get_child(&self, index: usize) -> Option<&dyn WidgetConfig> {
self.widgets.get(index).map(|w| w.widget.as_widget())
}
#[inline]
fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig> {
self.widgets
.get_mut(index)
.map(|w| w.widget.as_widget_mut())
}
}
impl<T: MatrixData + UpdatableAll<T::Key, V::Msg>, V: Driver<T::Item>> WidgetConfig
for MatrixView<T, V>
{
fn configure(&mut self, mgr: &mut Manager) {
self.data.enable_recursive_updates(mgr);
if let Some(handle) = self.data.update_handle() {
mgr.update_on_handle(handle, self.id());
}
mgr.register_nav_fallback(self.id());
}
}
impl<T: MatrixData + UpdatableAll<T::Key, V::Msg>, V: Driver<T::Item>> Layout for MatrixView<T, V> {
fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules {
let inner_margin = size_handle.inner_margin().extract(axis);
let frame = FrameRules::new_sym(0, inner_margin, 0);
let mut rules = self.view.new().size_rules(size_handle, axis);
self.child_size_min.set_component(axis, rules.min_size());
self.child_size_ideal
.set_component(axis, rules.ideal_size());
let m = rules.margins_i32();
self.child_inter_margin
.set_component(axis, (m.0 + m.1).max(inner_margin));
rules.multiply_with_margin(2, self.ideal_len.extract(axis));
rules.set_stretch(rules.stretch().max(Stretch::High));
let (rules, offset, size) = frame.surround(rules);
self.frame_offset.set_component(axis, offset);
self.frame_size.set_component(axis, size);
rules
}
fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) {
self.core.rect = rect;
let mut child_size = rect.size - self.frame_size;
if child_size.0 >= self.ideal_len.0 * self.child_size_ideal.0 {
child_size.0 = self.child_size_ideal.0;
} else {
child_size.0 = self.child_size_min.0;
}
if child_size.1 >= self.ideal_len.1 * self.child_size_ideal.1 {
child_size.1 = self.child_size_ideal.1;
} else {
child_size.1 = self.child_size_min.1;
}
self.child_size = child_size;
self.align_hints = align;
let skip = child_size + self.child_inter_margin;
let vis_len = (rect.size + skip - Size::splat(1)).cwise_div(skip) + Size::splat(1);
self.alloc_len = vis_len;
let old_num = self.widgets.len();
let num = usize::conv(vis_len.0) * usize::conv(vis_len.1);
if old_num < num {
debug!("allocating widgets (old len = {}, new = {})", old_num, num);
*mgr |= TkAction::RECONFIGURE;
self.widgets.reserve(num - old_num);
mgr.size_handle(|size_handle| {
for _ in old_num..num {
let mut widget = self.view.new();
solve_size_rules(
&mut widget,
size_handle,
Some(child_size.0),
Some(child_size.1),
);
self.widgets.push(WidgetData { key: None, widget });
}
});
} else if num + 64 <= self.widgets.len() {
self.widgets.truncate(num);
}
self.update_widgets(mgr);
}
fn spatial_nav(&self, reverse: bool, from: Option<usize>) -> Option<usize> {
let cur_len = usize::conv(self.cur_len.0) * usize::conv(self.cur_len.1);
if cur_len == 0 {
return None;
}
let last = cur_len - 1;
if let Some(index) = from {
let p = self.widgets[index].widget.rect().pos;
let index = match reverse {
false if index < last => index + 1,
false => 0,
true if 0 < index => index - 1,
true => last,
};
let q = self.widgets[index].widget.rect().pos;
match reverse {
false if q.1 > p.1 || (q.1 == p.1 && q.0 > p.0) => Some(index),
true if q.1 < p.1 || (q.1 == p.1 && q.0 < p.0) => Some(index),
_ => None,
}
} else {
let skip = self.child_size + self.child_inter_margin;
let offset = self.scroll_offset();
let ci = usize::conv(u64::conv(offset.0) / u64::conv(skip.0));
let ri = usize::conv(u64::conv(offset.1) / u64::conv(skip.1));
let (cols, rows): (usize, usize) = (self.cur_len.0.cast(), self.cur_len.1.cast());
let mut data = (ci % cols) * rows + (ri % rows);
if reverse {
data += last;
}
Some(data % cur_len)
}
}
fn find_id(&self, coord: Coord) -> Option<WidgetId> {
if !self.rect().contains(coord) {
return None;
}
let coord = coord + self.scroll.offset();
let num = usize::conv(self.cur_len.0) * usize::conv(self.cur_len.1);
for child in &self.widgets[..num] {
if child.key.is_some() {
if let Some(id) = child.widget.find_id(coord) {
return Some(id);
}
}
}
Some(self.id())
}
fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) {
let disabled = disabled || self.is_disabled();
let offset = self.scroll_offset();
use kas::draw::ClipRegion::Scroll;
let num = usize::conv(self.cur_len.0) * usize::conv(self.cur_len.1);
draw_handle.clip_region(self.core.rect, offset, Scroll, &mut |draw_handle| {
for child in &self.widgets[..num] {
if let Some(ref key) = child.key {
child.widget.draw(draw_handle, mgr, disabled);
if self.is_selected(key) {
draw_handle.selection_box(child.widget.rect());
}
}
}
});
}
}
impl<T: MatrixData + UpdatableAll<T::Key, V::Msg>, V: Driver<T::Item>> SendEvent
for MatrixView<T, V>
{
fn send(&mut self, mgr: &mut Manager, id: WidgetId, event: Event) -> Response<Self::Msg> {
if self.is_disabled() {
return Response::Unhandled;
}
if id < self.id() {
let child_event = self.scroll.offset_event(event.clone());
let response = 'outer: loop {
for child in self.widgets.iter_mut() {
if id <= child.widget.id() {
let r = child.widget.send(mgr, id, child_event);
break 'outer (child.key.clone(), r);
}
}
debug_assert!(false, "SendEvent::send: bad WidgetId");
return Response::Unhandled;
};
match response {
(_, Response::None) => return Response::None,
(key, Response::Unhandled) => {
if let Event::PressStart { source, coord, .. } = event {
if source.is_primary() {
if mgr.request_grab(self.id(), source, coord, GrabMode::Grab, None) {
self.press_event = Some(source);
self.press_target = key;
}
return Response::None;
}
}
}
(_, Response::Focus(rect)) => {
let (rect, action) = self.scroll.focus_rect(rect, self.core.rect);
*mgr |= action;
self.update_widgets(mgr);
return Response::Focus(rect);
}
(Some(key), Response::Select) => {
return match self.sel_mode {
SelectionMode::None => Response::None,
SelectionMode::Single => {
self.selection.clear();
self.selection.insert(key.clone());
Response::Msg(ChildMsg::Select(key))
}
SelectionMode::Multiple => {
if self.selection.remove(&key) {
Response::Msg(ChildMsg::Deselect(key))
} else {
self.selection.insert(key.clone());
Response::Msg(ChildMsg::Select(key))
}
}
};
}
(None, Response::Select) => return Response::None,
(_, Response::Update) => return Response::None,
(key, Response::Msg(msg)) => {
if let Some(key) = key {
if let Some(handle) = self.data.handle(&key, &msg) {
mgr.trigger_update(handle, 0);
}
return Response::Msg(ChildMsg::Child(key, msg));
} else {
log::warn!("MatrixView: response from widget with no key");
return Response::None;
}
}
}
} else {
debug_assert!(id == self.id(), "SendEvent::send: bad WidgetId");
match event {
Event::HandleUpdate { .. } => {
self.update_view(mgr);
return Response::Update;
}
Event::PressMove { source, .. } if self.press_event == Some(source) => {
self.press_event = None;
mgr.update_grab_cursor(self.id(), CursorIcon::Grabbing);
}
Event::PressEnd { source, .. } if self.press_event == Some(source) => {
self.press_event = None;
return match self.sel_mode {
SelectionMode::None => Response::None,
SelectionMode::Single => {
self.selection.clear();
if let Some(ref key) = self.press_target {
self.selection.insert(key.clone());
ChildMsg::Select(key.clone()).into()
} else {
Response::None
}
}
SelectionMode::Multiple => {
if let Some(ref key) = self.press_target {
if self.selection.remove(key) {
ChildMsg::Deselect(key.clone()).into()
} else {
self.selection.insert(key.clone());
ChildMsg::Select(key.clone()).into()
}
} else {
Response::None
}
}
};
}
_ => (),
}
};
let id = self.id();
let (action, response) = if let Event::Command(cmd, _) = event {
let (cols, rows): (usize, usize) = (self.cur_len.0.cast(), self.cur_len.1.cast());
let skip = self.child_size + self.child_inter_margin;
let offset = self.scroll_offset();
let first_col = usize::conv(u64::conv(offset.0) / u64::conv(skip.0));
let first_row = usize::conv(u64::conv(offset.1) / u64::conv(skip.1));
let col_start = (first_col / cols) * cols;
let row_start = (first_row / rows) * rows;
let cur = mgr
.nav_focus()
.and_then(|id| self.find_child(id))
.map(|index| {
let mut col_index = col_start + index % cols;
let mut row_index = row_start + index / cols;
if col_index < first_col {
col_index += cols;
}
if row_index < first_row {
row_index += rows;
}
(col_index, row_index)
});
let last_col = self.data.col_len().wrapping_sub(1);
let last_row = self.data.row_len().wrapping_sub(1);
let data = match (cmd, cur) {
_ if last_col == usize::MAX || last_row == usize::MAX => None,
_ if !self.widgets[0].widget.key_nav() => None,
(Command::Home, _) => Some((0, 0)),
(Command::End, _) => Some((last_col, last_row)),
(Command::Left, Some((ci, ri))) if ci > 0 => Some((ci - 1, ri)),
(Command::Up, Some((ci, ri))) if ri > 0 => Some((ci, ri - 1)),
(Command::Right, Some((ci, ri))) if ci < last_col => Some((ci + 1, ri)),
(Command::Down, Some((ci, ri))) if ri < last_row => Some((ci, ri + 1)),
(Command::PageUp, Some((ci, ri))) if ri > 0 => {
Some((ci, ri.saturating_sub(rows / 2)))
}
(Command::PageDown, Some((ci, ri))) if ri < last_row => {
Some((ci, (ri + rows / 2).min(last_row)))
}
_ => None,
};
let action = if let Some((ci, ri)) = data {
let index = (ci % cols) + (ri % rows) * cols;
mgr.set_nav_focus(self.widgets[index].widget.id());
let pos = self.core.rect.pos
+ self.frame_offset
+ skip.cwise_mul(Size(ci.cast(), ri.cast()));
let item_rect = Rect::new(pos, self.child_size);
self.scroll.focus_rect(item_rect, self.core.rect).1
} else {
TkAction::empty()
};
(action, Response::None)
} else {
self.scroll
.scroll_by_event(event, self.core.rect.size, |source, _, coord| {
if source.is_primary() && mgr.config_enable_mouse_pan() {
let icon = Some(CursorIcon::Grabbing);
mgr.request_grab(id, source, coord, GrabMode::Grab, icon);
}
})
};
if !action.is_empty() {
*mgr |= action;
self.update_widgets(mgr);
Response::Focus(self.rect())
} else {
response.void_into()
}
}
}