use std::{cmp::{self},
fmt::Debug};
use crate::{caret_scr_adj, row, width, CaretScrAdj, ChUnitPrimitiveType, ColIndex,
ColWidth, GCString, ScrOfs};
#[derive(Default, Clone, PartialEq, Copy)]
pub struct SelectionRange {
start: CaretScrAdj,
end: CaretScrAdj,
}
#[derive(Clone, PartialEq, Copy, Debug)]
pub enum ScrollOffsetColLocationInRange {
Overflow,
Underflow,
}
mod convert {
use super::{caret_scr_adj, row, CaretScrAdj, ChUnitPrimitiveType, SelectionRange};
impl From<(CaretScrAdj, CaretScrAdj)> for SelectionRange {
fn from((start, end): (CaretScrAdj, CaretScrAdj)) -> Self {
let start = caret_scr_adj(start.col_index + row(ChUnitPrimitiveType::MAX));
let end = caret_scr_adj(end.col_index + row(ChUnitPrimitiveType::MAX));
Self { start, end }
}
}
}
impl SelectionRange {
#[must_use]
pub fn start(&self) -> ColIndex { self.start.col_index }
#[must_use]
pub fn end(&self) -> ColIndex { self.end.col_index }
#[must_use]
pub fn get_start_display_col_index_as_width(&self) -> ColWidth {
width(*self.start.col_index)
}
#[must_use]
pub fn as_tuple(&self) -> (ColIndex, ColIndex) {
(self.start.col_index, self.end.col_index)
}
#[must_use]
pub fn clip_to_range<'a>(&self, us: &'a GCString) -> &'a str {
let (start_display_col_index, end_display_col_index) = self.as_tuple();
let max_display_width_col_count =
width(*(end_display_col_index - start_display_col_index));
us.clip(start_display_col_index, max_display_width_col_count)
}
}
impl SelectionRange {
#[must_use]
pub fn locate_scroll_offset_col(
&self,
scroll_offset: ScrOfs,
) -> ScrollOffsetColLocationInRange {
if self.start.col_index >= scroll_offset.col_index {
ScrollOffsetColLocationInRange::Underflow
} else {
ScrollOffsetColLocationInRange::Overflow
}
}
}
#[derive(Clone, PartialEq, Copy, Debug)]
pub enum CaretLocationInRange {
Overflow,
Underflow,
Contained,
}
#[derive(Clone, PartialEq, Eq, Copy, Debug)]
pub enum CaretMovementDirection {
Up,
Down,
Left,
Right,
Overlap,
}
impl SelectionRange {
#[must_use]
pub fn caret_movement_direction(
prev: CaretScrAdj,
curr: CaretScrAdj,
) -> CaretMovementDirection {
if prev.row_index == curr.row_index {
Self::caret_movement_direction_left_right(prev, curr)
} else {
Self::caret_movement_direction_up_down(prev, curr)
}
}
#[must_use]
pub fn caret_movement_direction_up_down(
prev: CaretScrAdj,
curr: CaretScrAdj,
) -> CaretMovementDirection {
match curr.row_index.cmp(&prev.row_index) {
cmp::Ordering::Greater => CaretMovementDirection::Down,
cmp::Ordering::Less => CaretMovementDirection::Up,
cmp::Ordering::Equal => CaretMovementDirection::Overlap,
}
}
#[must_use]
pub fn caret_movement_direction_left_right(
prev: CaretScrAdj,
curr: CaretScrAdj,
) -> CaretMovementDirection {
match curr.col_index.cmp(&prev.col_index) {
cmp::Ordering::Greater => CaretMovementDirection::Right,
cmp::Ordering::Less => CaretMovementDirection::Left,
cmp::Ordering::Equal => CaretMovementDirection::Overlap,
}
}
#[must_use]
pub fn locate_column(&self, caret: CaretScrAdj) -> CaretLocationInRange {
if caret.col_index < self.start.col_index {
CaretLocationInRange::Underflow
} else if caret.col_index >= self.end.col_index {
CaretLocationInRange::Overflow
} else {
CaretLocationInRange::Contained
}
}
#[must_use]
pub fn new(start: CaretScrAdj, end: CaretScrAdj) -> Self { Self { start, end } }
#[must_use]
pub fn grow_end_by(&self, col_amt: ColWidth) -> Self {
let mut copy = *self;
copy.end.col_index += col_amt;
copy
}
#[must_use]
pub fn shrink_end_by(&self, col_amt: ColWidth) -> Self {
let mut copy = *self;
copy.end.col_index -= col_amt;
copy
}
#[must_use]
pub fn grow_start_by(&self, col_amt: ColWidth) -> Self {
let mut copy = *self;
copy.start.col_index -= col_amt;
copy
}
#[must_use]
pub fn shrink_start_by(&self, col_amt: ColWidth) -> Self {
let mut copy = *self;
copy.start.col_index += col_amt;
copy
}
}
mod range_impl_debug_format {
use super::{Debug, SelectionRange};
impl Debug for SelectionRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[start.col_index: {start:?}, end.col_index: {end:?}]",
start = self.start.col_index,
end = self.end.col_index
)
}
}
}
#[cfg(test)]
mod tests_range {
use super::*;
use crate::{assert_eq2, col};
#[test]
fn test_locate() {
let range = {
let start = caret_scr_adj(col(1) + row(0));
let end = caret_scr_adj(col(4) + row(0));
SelectionRange::new(start, end)
};
assert_eq2!(
range.locate_column(caret_scr_adj(col(0) + row(0))),
CaretLocationInRange::Underflow
);
assert_eq2!(
range.locate_column(caret_scr_adj(col(1) + row(0))),
CaretLocationInRange::Contained
);
assert_eq2!(
range.locate_column(caret_scr_adj(col(2) + row(0))),
CaretLocationInRange::Contained
);
assert_eq2!(
range.locate_column(caret_scr_adj(col(3) + row(0))),
CaretLocationInRange::Contained
);
assert_eq2!(
range.locate_column(caret_scr_adj(col(4) + row(0))),
CaretLocationInRange::Overflow
);
assert_eq2!(
range.locate_column(caret_scr_adj(col(5) + row(0))),
CaretLocationInRange::Overflow
);
}
#[test]
fn test_tuple() {
let range = {
let start = caret_scr_adj(col(1) + row(0));
let end = caret_scr_adj(col(4) + row(0));
SelectionRange::new(start, end)
};
assert_eq2!(range.as_tuple(), (col(1), col(4)));
}
}