use crate::{FocusFlag, Navigation};
use fxhash::FxBuildHasher;
use ratatui_core::layout::Rect;
use std::cell::Cell;
use std::collections::HashSet;
use std::ops::Range;
macro_rules! focus_debug {
($core:expr, $($arg:tt)+) => {
if $core.log.get() {
log::log!(log::Level::Debug, $($arg)+);
}
}
}
macro_rules! focus_fail {
($core:expr, $($arg:tt)+) => {
if $core.log.get() {
log::log!(log::Level::Debug, $($arg)+);
}
if $core.insta_panic.get() {
panic!($($arg)+)
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Container {
pub(crate) container_flag: FocusFlag,
pub(crate) area: (Rect, u16),
pub(crate) delta_z: u16,
pub(crate) complete: bool,
}
#[derive(Debug, Default, Clone)]
pub(crate) struct FocusCore {
pub(crate) log: Cell<bool>,
pub(crate) insta_panic: Cell<bool>,
pub(crate) focus_ids: HashSet<usize, FxBuildHasher>,
pub(crate) focus_flags: Vec<FocusFlag>,
pub(crate) duplicate: Vec<bool>,
pub(crate) areas: Vec<(Rect, u16)>,
pub(crate) navigable: Vec<Navigation>,
pub(crate) container_ids: HashSet<usize, FxBuildHasher>,
pub(crate) containers: Vec<(Container, Range<usize>)>,
}
impl FocusCore {
pub(crate) fn clear(&mut self) {
self.focus_ids.clear();
self.focus_flags.clear();
self.duplicate.clear();
self.areas.clear();
self.navigable.clear();
self.container_ids.clear();
self.containers.clear();
}
pub(crate) fn find_widget_id(&self, widget_id: usize) -> Option<FocusFlag> {
self.focus_flags
.iter()
.find(|v| widget_id == v.widget_id())
.cloned()
}
pub(crate) fn is_widget(&self, focus_flag: &FocusFlag) -> bool {
self.focus_ids.contains(&focus_flag.widget_id())
}
pub(crate) fn index_of(&self, focus_flag: &FocusFlag) -> Option<usize> {
self.focus_flags
.iter()
.enumerate()
.find(|(_, f)| *f == focus_flag)
.map(|(idx, _)| idx)
}
pub(crate) fn is_container(&self, focus_flag: &FocusFlag) -> bool {
self.container_ids.contains(&focus_flag.widget_id())
}
pub(crate) fn container_index_of(
&self,
container_flag: &FocusFlag,
) -> Option<(usize, Range<usize>)> {
self.containers
.iter()
.enumerate()
.find(|(_, (c, _))| &c.container_flag == container_flag)
.map(|(idx, (_, range))| (idx, range.clone()))
}
pub(crate) fn insert_container(&mut self, idx: usize, cidx: usize, mut container: FocusCore) {
for c in &self.focus_flags {
for d in &container.focus_flags {
assert_ne!(c, d);
}
}
let start = idx;
let end = idx + container.focus_flags.len();
self.focus_ids.extend(container.focus_ids.iter());
self.focus_flags
.splice(idx..idx, container.focus_flags.drain(..));
self.duplicate
.splice(idx..idx, container.duplicate.drain(..));
self.areas.splice(idx..idx, container.areas.drain(..));
self.navigable
.splice(idx..idx, container.navigable.drain(..));
for (_, r) in &mut self.containers {
*r = Self::expand(start..end, r.clone());
}
self.containers.splice(
cidx..cidx,
container
.containers
.drain(..)
.map(|(c, r)| (c, Self::shift(start, r))),
);
self.container_ids.extend(container.container_ids.iter());
}
pub(crate) fn remove_container(&mut self, cidx: usize) -> FocusCore {
let crange = self.containers[cidx].1.clone();
let focus_flags = self.focus_flags.drain(crange.clone()).collect::<Vec<_>>();
let mut focus_ids = HashSet::<_, FxBuildHasher>::default();
for f in focus_flags.iter() {
self.focus_ids.remove(&f.widget_id());
focus_ids.insert(f.widget_id());
}
let duplicate = self.duplicate.drain(crange.clone()).collect::<Vec<_>>();
let areas = self.areas.drain(crange.clone()).collect::<Vec<_>>();
let navigable = self.navigable.drain(crange.clone()).collect::<Vec<_>>();
let sub_containers = self
.containers
.iter()
.filter(|(_, r)| r.start >= crange.start && r.end <= crange.end)
.cloned()
.collect::<Vec<_>>();
self.containers
.retain(|(_, r)| !(r.start >= crange.start && r.end <= crange.end));
let mut sub_container_ids: HashSet<usize, FxBuildHasher> = HashSet::default();
for (sc, _) in sub_containers.iter() {
self.container_ids.remove(&sc.container_flag.widget_id());
sub_container_ids.insert(sc.container_flag.widget_id());
}
for (_, r) in &mut self.containers {
*r = Self::shrink(crange.start..crange.end, r.clone());
}
FocusCore {
log: Cell::new(false),
insta_panic: Cell::new(false),
focus_ids,
focus_flags,
duplicate,
areas,
navigable,
container_ids: sub_container_ids,
containers: sub_containers,
}
}
fn shift(n: usize, range: Range<usize>) -> Range<usize> {
range.start + n..range.end + n
}
fn expand(insert: Range<usize>, mut range: Range<usize>) -> Range<usize> {
let len = insert.end - insert.start;
if range.start >= insert.start {
range.start += len;
}
if range.end > insert.start {
range.end += len;
}
range
}
fn shrink(remove: Range<usize>, mut range: Range<usize>) -> Range<usize> {
let len = remove.end - remove.start;
if range.start < remove.start {
} else if range.start >= remove.start && range.start <= remove.end {
range.start = remove.start;
} else {
range.start -= len;
}
if range.end < remove.start {
} else if range.end >= remove.start && range.end <= remove.end {
range.end = remove.start;
} else {
range.end -= len;
}
range
}
fn __start_change(&self, set_lost: bool) {
for (f, duplicate) in self.focus_flags.iter().zip(self.duplicate.iter()) {
if *duplicate {
continue;
}
if set_lost {
f.set_lost(f.get());
} else {
f.set_lost(false);
}
f.set_gained(false);
f.set(false);
}
}
fn __focus(&self, n: usize, set_lost: bool) -> bool {
if let Some(f) = self.focus_flags.get(n) {
focus_debug!(self, " -> focus {}:{:?}", n, f.name());
f.set(true);
if set_lost {
if f.lost() {
f.set_lost(false);
f.set_gained(false);
false
} else {
f.set_gained(true);
true
}
} else {
false
}
} else {
false
}
}
#[allow(clippy::collapsible_if)]
fn __accumulate(&self) {
for (n, f) in self.focus_flags.iter().enumerate() {
if f.gained() && !self.duplicate[n] {
if f.call_on_gained() {
focus_debug!(self, " -> notify_on_gained {}:{:?}", n, f.name());
}
}
if f.lost() && !self.duplicate[n] {
if f.call_on_lost() {
focus_debug!(self, " -> notify_on_lost {}:{:?}", n, f.name());
}
}
}
for (f, r) in &self.containers {
let mut any_gained = false;
let mut any_lost = false;
let mut any_focused = false;
for idx in r.clone() {
any_gained |= self.focus_flags[idx].gained();
any_lost |= self.focus_flags[idx].lost();
any_focused |= self.focus_flags[idx].get();
}
f.container_flag.set(any_focused);
f.container_flag.set_lost(any_lost && !any_gained);
if any_lost && !any_gained {
if f.container_flag.call_on_lost() {
focus_debug!(
self,
"-> notify_on_lost container {:?}",
f.container_flag.name()
);
}
}
f.container_flag.set_gained(any_gained && !any_lost);
if any_gained && !any_lost {
if f.container_flag.call_on_gained() {
focus_debug!(
self,
"-> notify_on_gained container {:?}",
f.container_flag.name()
);
}
}
}
}
pub(crate) fn reset(&self) {
for f in self.focus_flags.iter() {
f.set(false);
f.set_lost(false);
f.set_gained(false);
f.set_mouse_focus(true);
}
for (f, _) in self.containers.iter() {
f.container_flag.set(false);
f.container_flag.set_gained(false);
f.container_flag.set_lost(false);
f.container_flag.set_mouse_focus(true);
}
}
pub(crate) fn reset_lost_gained(&self) {
for f in self.focus_flags.iter() {
f.set_lost(false);
f.set_gained(false);
}
for (f, _) in self.containers.iter() {
f.container_flag.set_gained(false);
f.container_flag.set_lost(false);
}
}
pub(crate) fn reset_mouse_focus(&self) -> bool {
let mut r = false;
for (sub, _) in self.containers.iter() {
r |= sub.container_flag.mouse_focus();
sub.container_flag.set_mouse_focus(true);
}
for w in self.focus_flags.iter() {
r |= w.mouse_focus();
w.set_mouse_focus(true);
}
r
}
pub(crate) fn first(&self) {
if let Some(n) = self.first_navigable(0) {
self.__start_change(true);
self.__focus(n, true);
self.__accumulate();
} else {
focus_debug!(self, " -> no navigable widget");
}
}
pub(crate) fn none(&self) {
self.__start_change(true);
self.__accumulate();
}
pub(crate) fn first_container(&self, container: &FocusFlag) {
if let Some((_idx, range)) = self.container_index_of(container) {
if let Some(n) = self.first_navigable(range.start) {
if n < range.end {
self.__start_change(true);
self.__focus(n, true);
self.__accumulate();
} else {
focus_debug!(self, " -> no navigable widget for container");
}
} else {
focus_debug!(self, " -> no navigable widget");
}
} else {
focus_fail!(self, " => container not found");
}
}
pub(crate) fn focus_idx(&self, n: usize, set_lost: bool) {
self.__start_change(set_lost);
self.__focus(n, set_lost);
self.__accumulate();
}
pub(crate) fn mouse_focus(&self, col: u16, row: u16) -> bool {
let pos = (col, row).into();
let mut z_container: Vec<(usize, u16)> = Vec::new();
let mut z_widget: Option<(usize, u16)> = None;
for (idx, (sub, _)) in self.containers.iter().enumerate() {
if sub.area.0.contains(pos) {
focus_debug!(
self,
" container area-match {:?} {:?} z={}",
sub.container_flag.name(),
sub.area.0,
sub.area.1
);
if let Some(zz) = z_container.last() {
if zz.1 < sub.area.1 {
z_container.clear();
z_container.push((idx, sub.area.1));
} else if zz.1 == sub.area.1 {
z_container.push((idx, sub.area.1));
}
} else {
z_container.push((idx, sub.area.1));
}
}
}
let min_z = z_container.last().map(|v| v.1).unwrap_or(0);
for (idx, area) in self.areas.iter().enumerate() {
if area.1 < min_z {
continue;
}
if area.0.contains(pos) {
focus_debug!(
self,
" area-match {:?} {:?} z={}",
self.focus_flags[idx].name(),
area.0,
area.1
);
if let Some(zz) = z_widget {
if zz.1 <= area.1 {
z_widget = Some((idx, area.1));
}
} else {
z_widget = Some((idx, area.1));
}
}
}
for (c, _) in self.containers.iter() {
c.container_flag.set_mouse_focus(false);
}
for w in self.focus_flags.iter() {
w.set_mouse_focus(false);
}
let mut r = false;
for (idx, _) in z_container {
focus_debug!(
self,
" => mouse-focus {:?} {:?} z={}",
self.containers[idx].0.container_flag.name(),
self.containers[idx].0.area.0,
self.containers[idx].0.area.1
);
self.containers[idx].0.container_flag.set_mouse_focus(true);
r = true;
}
if let Some((idx, _)) = z_widget {
focus_debug!(
self,
" => mouse-focus {:?} {:?} z={}",
self.focus_flags[idx].name(),
self.areas[idx].0,
self.areas[idx].1,
);
self.focus_flags[idx].set_mouse_focus(true);
r = true;
}
r
}
pub(crate) fn focus_at(&self, col: u16, row: u16) -> bool {
let pos = (col, row).into();
enum ZOrder {
Widget(usize),
Container(usize),
}
let mut z_order: Option<(ZOrder, u16)> = None;
for (idx, (sub, _)) in self.containers.iter().enumerate() {
if sub.area.0.contains(pos) {
focus_debug!(
self,
" container area-match {:?} {:?}",
sub.container_flag.name(),
sub.area.0
);
z_order = if let Some(zz) = z_order {
if zz.1 <= sub.area.1 {
Some((ZOrder::Container(idx), sub.area.1))
} else {
Some(zz)
}
} else {
Some((ZOrder::Container(idx), sub.area.1))
};
}
}
for (idx, area) in self.areas.iter().enumerate() {
if area.0.contains(pos) {
focus_debug!(
self,
" area-match {:?} {:?}",
self.focus_flags[idx].name(),
area.0
);
z_order = if let Some(zz) = z_order {
if zz.1 <= area.1 {
Some((ZOrder::Widget(idx), area.1))
} else {
Some(zz)
}
} else {
Some((ZOrder::Widget(idx), area.1))
};
}
}
if let Some((idx, _)) = z_order {
match idx {
ZOrder::Widget(idx) => {
if self.navigable[idx] != Navigation::None {
self.__start_change(true);
let r = self.__focus(idx, true);
self.__accumulate();
return r;
} else {
focus_debug!(
self,
" -> not mouse reachable {:?}",
self.focus_flags[idx].name()
);
return false;
}
}
ZOrder::Container(idx) => {
let range = &self.containers[idx].1;
if let Some(n) = self.first_navigable(range.start) {
self.__start_change(true);
let r = self.__focus(n, true);
self.__accumulate();
return r;
}
}
}
}
focus_debug!(self, " -> no widget at pos");
false
}
pub(crate) fn expel_container(&self, flag: FocusFlag) -> bool {
if let Some((_idx, range)) = self.container_index_of(&flag) {
self.__start_change(true);
let n = self.next_navigable(range.end);
self.__focus(n, true);
self.__accumulate();
if flag.get() {
focus_debug!(self, " -> focus not usable. cleared");
self.none();
} else {
focus_debug!(self, " -> expelled.");
}
true
} else {
focus_fail!(self, " => container not found");
false
}
}
pub(crate) fn next(&self) -> bool {
self.__start_change(true);
for (n, p) in self.focus_flags.iter().enumerate() {
if p.lost() {
let n = self.next_navigable(n);
self.__focus(n, true);
self.__accumulate();
return true;
}
}
if let Some(n) = self.first_navigable(0) {
focus_debug!(
self,
" use first_navigable {}:{:?}",
n,
self.focus_flags[n].name()
);
self.__focus(n, true);
self.__accumulate();
return true;
}
focus_debug!(self, " -> no next");
false
}
pub(crate) fn prev(&self) -> bool {
self.__start_change(true);
for (i, p) in self.focus_flags.iter().enumerate() {
if p.lost() {
let n = self.prev_navigable(i);
self.__focus(n, true);
self.__accumulate();
return true;
}
}
if let Some(n) = self.first_navigable(0) {
focus_debug!(
self,
" use first_navigable {}:{:?}",
n,
self.focus_flags[n].name()
);
self.__focus(n, true);
self.__accumulate();
return true;
}
focus_debug!(self, " -> no prev");
false
}
pub(crate) fn navigation(&self) -> Option<Navigation> {
self.focus_flags
.iter()
.enumerate()
.find(|(_, v)| v.get())
.map(|(i, _)| self.navigable[i])
}
pub(crate) fn focused(&self) -> Option<FocusFlag> {
self.focus_flags.iter().find(|v| v.get()).cloned()
}
pub(crate) fn lost_focus(&self) -> Option<FocusFlag> {
self.focus_flags.iter().find(|v| v.lost()).cloned()
}
pub(crate) fn gained_focus(&self) -> Option<FocusFlag> {
self.focus_flags.iter().find(|v| v.gained()).cloned()
}
fn first_navigable(&self, start: usize) -> Option<usize> {
focus_debug!(
self,
"first navigable, start at {}:{:?} ",
start,
if start < self.focus_flags.len() {
self.focus_flags[start].name()
} else {
"beginning".into()
}
);
for n in start..self.focus_flags.len() {
if matches!(
self.navigable[n],
Navigation::Reach
| Navigation::ReachLeaveBack
| Navigation::ReachLeaveFront
| Navigation::Regular
) {
focus_debug!(self, " -> {}:{:?}", n, self.focus_flags[n].name());
return Some(n);
}
}
focus_debug!(self, " -> no first");
None
}
fn next_navigable(&self, start: usize) -> usize {
focus_debug!(
self,
"next navigable after {}:{:?}",
start,
if start < self.focus_flags.len() {
self.focus_flags[start].name()
} else {
"last".into()
}
);
let mut n = start;
loop {
n = if n + 1 < self.focus_flags.len() {
n + 1
} else {
0
};
if matches!(
self.navigable[n],
Navigation::Reach
| Navigation::ReachLeaveBack
| Navigation::ReachLeaveFront
| Navigation::Regular
) {
focus_debug!(self, " -> {}:{:?}", n, self.focus_flags[n].name());
return n;
}
if n == start {
focus_debug!(self, " -> {}:end at start", n);
return n;
}
}
}
fn prev_navigable(&self, start: usize) -> usize {
focus_debug!(
self,
"prev navigable before {}:{:?}",
start,
self.focus_flags[start].name()
);
let mut n = start;
loop {
n = if n > 0 {
n - 1
} else {
self.focus_flags.len() - 1
};
if matches!(
self.navigable[n],
Navigation::Reach
| Navigation::ReachLeaveBack
| Navigation::ReachLeaveFront
| Navigation::Regular
) {
focus_debug!(self, " -> {}:{:?}", n, self.focus_flags[n].name());
return n;
}
if n == start {
focus_debug!(self, " -> {}:end at start", n);
return n;
}
}
}
#[allow(clippy::type_complexity)]
pub(crate) fn clone_destruct(
&self,
) -> (
Vec<FocusFlag>,
Vec<bool>,
Vec<(Rect, u16)>,
Vec<Navigation>,
Vec<(FocusFlag, (Rect, u16), Range<usize>)>,
) {
(
self.focus_flags.clone(),
self.duplicate.clone(),
self.areas.clone(),
self.navigable.clone(),
self.containers
.iter()
.map(|(v, w)| (v.container_flag.clone(), v.area, w.clone()))
.collect::<Vec<_>>(),
)
}
}
#[cfg(test)]
mod test {
use crate::core::FocusCore;
use crate::{FocusBuilder, FocusFlag, HasFocus};
use ratatui_core::layout::Rect;
#[test]
fn test_change() {
assert_eq!(FocusCore::shift(0, 1..1), 1..1);
assert_eq!(FocusCore::shift(1, 1..1), 2..2);
assert_eq!(FocusCore::expand(3..4, 0..1), 0..1);
assert_eq!(FocusCore::expand(3..4, 1..2), 1..2);
assert_eq!(FocusCore::expand(3..4, 2..3), 2..3);
assert_eq!(FocusCore::expand(3..4, 3..4), 4..5);
assert_eq!(FocusCore::expand(3..4, 4..5), 5..6);
assert_eq!(FocusCore::expand(3..3, 0..1), 0..1);
assert_eq!(FocusCore::expand(3..3, 1..2), 1..2);
assert_eq!(FocusCore::expand(3..3, 2..3), 2..3);
assert_eq!(FocusCore::expand(3..3, 3..4), 3..4);
assert_eq!(FocusCore::expand(3..3, 4..5), 4..5);
assert_eq!(FocusCore::shrink(3..4, 0..1), 0..1);
assert_eq!(FocusCore::shrink(3..4, 2..3), 2..3);
assert_eq!(FocusCore::shrink(3..4, 3..4), 3..3);
assert_eq!(FocusCore::shrink(3..4, 4..5), 3..4);
assert_eq!(FocusCore::shrink(3..4, 5..6), 4..5);
assert_eq!(FocusCore::shrink(3..3, 0..1), 0..1);
assert_eq!(FocusCore::shrink(3..3, 1..2), 1..2);
assert_eq!(FocusCore::shrink(3..3, 2..3), 2..3);
assert_eq!(FocusCore::shrink(3..3, 3..4), 3..4);
assert_eq!(FocusCore::shrink(3..3, 4..5), 4..5);
}
#[test]
#[should_panic]
fn test_double_insert() {
let a = FocusFlag::new().with_name("a");
let b = FocusFlag::new().with_name("b");
let mut fb = FocusBuilder::new(None);
fb.widget(&a);
fb.widget(&b);
fb.widget(&a);
fb.build();
}
#[test]
fn test_insert_remove() {
let a = FocusFlag::new().with_name("a");
let b = FocusFlag::new().with_name("b");
let c = FocusFlag::new().with_name("c");
let d = FocusFlag::new().with_name("d");
let e = FocusFlag::new().with_name("e");
let f = FocusFlag::new().with_name("f");
let g = FocusFlag::new().with_name("g");
let h = FocusFlag::new().with_name("h");
let i = FocusFlag::new().with_name("i");
let mut fb = FocusBuilder::new(None);
fb.widget(&a);
fb.widget(&b);
fb.widget(&c);
let ff = fb.build();
assert_eq!(ff.core.focus_flags[0], a);
assert_eq!(ff.core.focus_flags[1], b);
assert_eq!(ff.core.focus_flags[2], c);
let cc = FocusFlag::new().with_name("cc");
let mut fb = FocusBuilder::new(None);
fb.widget(&a);
let cc_end = fb.start_with_flags(cc.clone(), Rect::default(), 0);
fb.widget(&d);
fb.widget(&e);
fb.widget(&f);
fb.end(cc_end);
fb.widget(&b);
fb.widget(&c);
let mut ff = fb.build();
assert_eq!(ff.core.focus_flags[0], a);
assert_eq!(ff.core.focus_flags[1], d);
assert_eq!(ff.core.focus_flags[2], e);
assert_eq!(ff.core.focus_flags[3], f);
assert_eq!(ff.core.focus_flags[4], b);
assert_eq!(ff.core.focus_flags[5], c);
assert_eq!(ff.core.containers[0].1, 1..4);
struct DD {
dd: FocusFlag,
g: FocusFlag,
h: FocusFlag,
i: FocusFlag,
}
impl HasFocus for DD {
fn build(&self, fb: &mut FocusBuilder) {
let tag = fb.start_with_flags(self.dd.clone(), self.area(), self.area_z());
fb.widget(&self.g);
fb.widget(&self.h);
fb.widget(&self.i);
fb.end(tag);
}
fn focus(&self) -> FocusFlag {
self.dd.clone()
}
fn area(&self) -> Rect {
Rect::default()
}
}
let dd = DD {
dd: FocusFlag::new().with_name("dd"),
g: g.clone(),
h: h.clone(),
i: i.clone(),
};
ff.replace_container(&cc, &dd);
assert_eq!(ff.core.focus_flags[0], a);
assert_eq!(ff.core.focus_flags[1], g);
assert_eq!(ff.core.focus_flags[2], h);
assert_eq!(ff.core.focus_flags[3], i);
assert_eq!(ff.core.focus_flags[4], b);
assert_eq!(ff.core.focus_flags[5], c);
assert_eq!(ff.core.containers[0].1, 1..4);
}
}