use teksilo_canvas::{Point, Rect};
use crate::environment::LayoutDirection;
use crate::gesture::SwipeDirection;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HorizontalSide {
Left,
Right,
}
impl HorizontalSide {
pub const fn opposite(self) -> Self {
match self {
HorizontalSide::Left => HorizontalSide::Right,
HorizontalSide::Right => HorizontalSide::Left,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InlineDirection {
Forward,
Backward,
}
impl InlineDirection {
pub const fn opposite(self) -> Self {
match self {
InlineDirection::Forward => InlineDirection::Backward,
InlineDirection::Backward => InlineDirection::Forward,
}
}
pub const fn side(self, direction: LayoutDirection) -> HorizontalSide {
match (self, direction) {
(InlineDirection::Forward, LayoutDirection::LeftToRight)
| (InlineDirection::Backward, LayoutDirection::RightToLeft) => HorizontalSide::Right,
(InlineDirection::Backward, LayoutDirection::LeftToRight)
| (InlineDirection::Forward, LayoutDirection::RightToLeft) => HorizontalSide::Left,
}
}
pub const fn to_swipe(self, direction: LayoutDirection) -> SwipeDirection {
match self.side(direction) {
HorizontalSide::Left => SwipeDirection::Left,
HorizontalSide::Right => SwipeDirection::Right,
}
}
pub const fn from_swipe(swipe: SwipeDirection, direction: LayoutDirection) -> Option<Self> {
let side = match swipe {
SwipeDirection::Left => HorizontalSide::Left,
SwipeDirection::Right => HorizontalSide::Right,
SwipeDirection::Up | SwipeDirection::Down => return None,
};
Some(match (side, direction) {
(HorizontalSide::Right, LayoutDirection::LeftToRight)
| (HorizontalSide::Left, LayoutDirection::RightToLeft) => InlineDirection::Forward,
(HorizontalSide::Left, LayoutDirection::LeftToRight)
| (HorizontalSide::Right, LayoutDirection::RightToLeft) => InlineDirection::Backward,
})
}
}
pub fn inline_edge_band(
area: Rect,
band: f32,
edge: InlineDirection,
direction: LayoutDirection,
) -> Rect {
let band = band.clamp(0.0, area.width / 2.0);
match edge.side(direction) {
HorizontalSide::Left => Rect::new(area.x, area.y, band, area.height),
HorizontalSide::Right => Rect::new(area.right() - band, area.y, band, area.height),
}
}
pub fn inline_band_at(
area: Rect,
band: f32,
point: Point,
direction: LayoutDirection,
) -> Option<InlineDirection> {
[InlineDirection::Backward, InlineDirection::Forward]
.into_iter()
.find(|&edge| inline_edge_band(area, band, edge, direction).contains(point))
}
#[cfg(test)]
mod tests {
use super::*;
const LTR: LayoutDirection = LayoutDirection::LeftToRight;
const RTL: LayoutDirection = LayoutDirection::RightToLeft;
#[test]
fn inline_directions_mirror_between_scripts() {
assert_eq!(InlineDirection::Forward.side(LTR), HorizontalSide::Right);
assert_eq!(InlineDirection::Forward.side(RTL), HorizontalSide::Left);
assert_eq!(InlineDirection::Backward.side(LTR), HorizontalSide::Left);
assert_eq!(InlineDirection::Backward.side(RTL), HorizontalSide::Right);
assert_eq!(HorizontalSide::Left.opposite(), HorizontalSide::Right);
assert_eq!(
InlineDirection::Forward.opposite(),
InlineDirection::Backward
);
}
#[test]
fn a_swipe_resolves_through_the_layout_direction() {
assert_eq!(
InlineDirection::from_swipe(SwipeDirection::Right, LTR),
Some(InlineDirection::Forward)
);
assert_eq!(
InlineDirection::from_swipe(SwipeDirection::Right, RTL),
Some(InlineDirection::Backward)
);
assert_eq!(
InlineDirection::from_swipe(SwipeDirection::Left, LTR),
Some(InlineDirection::Backward)
);
assert_eq!(
InlineDirection::from_swipe(SwipeDirection::Left, RTL),
Some(InlineDirection::Forward)
);
for direction in [LTR, RTL] {
for inline in [InlineDirection::Forward, InlineDirection::Backward] {
assert_eq!(
InlineDirection::from_swipe(inline.to_swipe(direction), direction),
Some(inline),
"{inline:?} under {direction:?} must survive the round trip"
);
}
}
}
#[test]
fn the_block_axis_has_no_inline_meaning() {
for direction in [LTR, RTL] {
assert_eq!(
InlineDirection::from_swipe(SwipeDirection::Up, direction),
None
);
assert_eq!(
InlineDirection::from_swipe(SwipeDirection::Down, direction),
None
);
}
}
#[test]
fn auto_scroll_bands_mirror() {
let area = Rect::new(0.0, 0.0, 400.0, 100.0);
let back_ltr = inline_edge_band(area, 40.0, InlineDirection::Backward, LTR);
let back_rtl = inline_edge_band(area, 40.0, InlineDirection::Backward, RTL);
assert_eq!((back_ltr.x, back_ltr.width), (0.0, 40.0));
assert_eq!((back_rtl.x, back_rtl.width), (360.0, 40.0));
let left = Point::new(10.0, 50.0);
assert_eq!(
inline_band_at(area, 40.0, left, LTR),
Some(InlineDirection::Backward)
);
assert_eq!(
inline_band_at(area, 40.0, left, RTL),
Some(InlineDirection::Forward)
);
assert_eq!(
inline_band_at(area, 40.0, Point::new(200.0, 50.0), LTR),
None,
"the middle of a wide view is in neither band"
);
}
#[test]
fn an_oversized_band_is_clamped_to_half_the_view() {
let area = Rect::new(0.0, 0.0, 100.0, 20.0);
let back = inline_edge_band(area, 400.0, InlineDirection::Backward, LTR);
let fore = inline_edge_band(area, 400.0, InlineDirection::Forward, LTR);
assert_eq!(back.width, 50.0);
assert_eq!(fore.x, 50.0);
}
}