1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
//!
//! Some utility functions that pop up all the time.
//!
use crate::UsedEvent;
use ratatui::layout::Rect;
use std::cmp::{max, min};
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign};
/// Which row of the given contains the position.
/// This uses only the vertical components of the given areas.
///
/// You might want to limit calling this functions when the full
/// position is inside your target rect.
pub fn row_at_clicked(areas: &[Rect], y_pos: u16) -> Option<usize> {
for (i, r) in areas.iter().enumerate() {
if y_pos >= r.top() && y_pos < r.bottom() {
return Some(i);
}
}
None
}
/// Column at given position.
/// This uses only the horizontal components of the given areas.
///
/// You might want to limit calling this functions when the full
/// position is inside your target rect.
pub fn column_at_clicked(areas: &[Rect], x_pos: u16) -> Option<usize> {
for (i, r) in areas.iter().enumerate() {
if x_pos >= r.left() && x_pos < r.right() {
return Some(i);
}
}
None
}
/// Find a row position when dragging with the mouse. This uses positions
/// outside the given areas to estimate an invisible row that could be meant
/// by the mouse position. It uses the heuristic `1 row == 1 item` for simplicity’s
/// sake.
///
/// Rows outside the bounds are returned as Err(isize), rows inside as Ok(usize).
pub fn row_at_drag(encompassing: Rect, areas: &[Rect], y_pos: u16) -> Result<usize, isize> {
if let Some(row) = row_at_clicked(areas, y_pos) {
return Ok(row);
}
// assume row-height=1 for outside the box.
if y_pos < encompassing.top() {
Err(y_pos as isize - encompassing.top() as isize)
} else {
if let Some(last) = areas.last() {
Err(y_pos as isize - last.bottom() as isize + 1)
} else {
Err(y_pos as isize - encompassing.top() as isize)
}
}
}
/// Column when dragging. Can go outside the area.
pub fn column_at_drag(encompassing: Rect, areas: &[Rect], x_pos: u16) -> Result<usize, isize> {
if let Some(column) = column_at_clicked(areas, x_pos) {
return Ok(column);
}
// change by 1 column if outside the box
if x_pos < encompassing.left() {
Err(x_pos as isize - encompassing.left() as isize)
} else {
if let Some(last) = areas.last() {
Err(x_pos as isize - last.right() as isize + 1)
} else {
Err(x_pos as isize - encompassing.left() as isize)
}
}
}
/// A baseline Outcome for event-handling.
///
/// A widget can define its own, if it has more things to report.
/// It would be nice of the widget though, if its outcome would be
/// convertible to this baseline.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Outcome {
/// The given event has not been used at all.
NotUsed,
/// The event has been recognized, but the result was nil.
/// Further processing for this event may stop.
Unchanged,
/// The event has been recognized and there is some change
/// due to it.
/// Further processing for this event may stop.
/// Rendering the ui is advised.
Changed,
}
impl UsedEvent for Outcome {
fn used_event(&self) -> bool {
*self != Outcome::NotUsed
}
}
impl BitOr for Outcome {
type Output = Outcome;
fn bitor(self, rhs: Self) -> Self::Output {
max(self, rhs)
}
}
impl BitAnd for Outcome {
type Output = Outcome;
fn bitand(self, rhs: Self) -> Self::Output {
min(self, rhs)
}
}
impl BitOrAssign for Outcome {
fn bitor_assign(&mut self, rhs: Self) {
*self = self.bitor(rhs);
}
}
impl BitAndAssign for Outcome {
fn bitand_assign(&mut self, rhs: Self) {
*self = self.bitand(rhs);
}
}