use crate::focus::{FocusIntent, FocusQuery, FocusTarget};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum FocusWrap {
#[default]
Clamp,
Wrap,
}
impl FocusWrap {
pub fn step(self, index: usize, len: usize, forward: bool) -> usize {
match (self, forward) {
(FocusWrap::Wrap, true) => (index + 1) % len,
(FocusWrap::Wrap, false) => (index + len - 1) % len,
(FocusWrap::Clamp, true) => (index + 1).min(len - 1),
(FocusWrap::Clamp, false) => index.saturating_sub(1),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OverlayFocus<O = (), M = ()> {
Simple(O),
Modal { data: M, index: usize, count: usize },
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct EnteredSection {
section_id: usize,
item_index: usize,
item_count: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FocusManager<O = (), M = ()> {
targets: Vec<FocusTarget<O>>,
index: usize,
overlay: Option<OverlayFocus<O, M>>,
entered_section: Option<EnteredSection>,
section_items: Vec<(usize, usize)>,
wrap: FocusWrap,
}
impl<O, M> Default for FocusManager<O, M> {
fn default() -> Self {
Self::new()
}
}
pub trait FocusController<O = (), M = ()> {
fn apply_focus_intent(&mut self, intent: FocusIntent<O, M>);
}
impl<O, M> FocusManager<O, M> {
pub fn new() -> Self {
Self {
targets: Vec::new(),
index: 0,
overlay: None,
entered_section: None,
section_items: Vec::new(),
wrap: FocusWrap::Clamp,
}
}
pub fn focus_wrap(&self) -> FocusWrap {
self.wrap
}
pub fn set_focus_wrap(&mut self, wrap: FocusWrap) {
self.wrap = wrap;
}
pub fn targets(&self) -> &[FocusTarget<O>] {
&self.targets
}
pub fn overlay(&self) -> Option<&OverlayFocus<O, M>> {
self.overlay.as_ref()
}
pub fn overlay_mut(&mut self) -> Option<&mut OverlayFocus<O, M>> {
self.overlay.as_mut()
}
pub fn register_page(&mut self, targets: Vec<FocusTarget<O>>) {
self.targets = targets;
self.index = 0;
self.entered_section = None;
self.section_items.clear();
}
pub fn set_section_items(&mut self, section_items: Vec<(usize, usize)>) {
self.section_items = section_items;
if let Some(section) = &mut self.entered_section {
match self
.section_items
.iter()
.find(|(id, _)| *id == section.section_id)
.map(|(_, count)| *count)
{
Some(0) | None => self.entered_section = None,
Some(count) => {
section.item_count = count;
section.item_index = section.item_index.min(count - 1);
}
}
}
}
fn section_item_count(&self, section_id: usize) -> Option<usize> {
self.section_items
.iter()
.find(|(id, _)| *id == section_id)
.map(|(_, count)| *count)
}
pub fn has_overlay(&self) -> bool {
self.overlay.is_some()
}
pub fn next(&mut self) {
let wrap = self.wrap;
if let Some(OverlayFocus::Modal { index, count, .. }) = &mut self.overlay {
if *count > 0 {
*index = wrap.step(*index, *count, true);
}
return;
}
if self.overlay.is_some() {
return;
}
if let Some(section) = &self.entered_section {
if section.item_index + 1 < section.item_count {
if let Some(section) = &mut self.entered_section {
section.item_index += 1;
}
return;
}
self.entered_section = None;
}
if self
.targets
.get(self.index)
.map(FocusTarget::is_canvas)
.unwrap_or(false)
{
return;
}
for index in (self.index + 1)..self.targets.len() {
if self.targets[index].is_top_level_navigable() {
self.index = index;
return;
}
}
if matches!(wrap, FocusWrap::Wrap) {
for index in 0..self.index {
if self.targets[index].is_top_level_navigable() {
self.index = index;
return;
}
}
}
}
pub fn prev(&mut self) {
let wrap = self.wrap;
if let Some(OverlayFocus::Modal { index, count, .. }) = &mut self.overlay {
if *count > 0 {
*index = wrap.step(*index, *count, false);
}
return;
}
if self.overlay.is_some() {
return;
}
if let Some(section) = &self.entered_section {
if section.item_index > 0 {
if let Some(section) = &mut self.entered_section {
section.item_index -= 1;
}
return;
}
self.entered_section = None;
}
if self
.targets
.get(self.index)
.map(FocusTarget::is_canvas)
.unwrap_or(false)
{
return;
}
for index in (0..self.index).rev() {
if self.targets[index].is_top_level_navigable() {
self.index = index;
return;
}
}
if matches!(wrap, FocusWrap::Wrap) {
for index in ((self.index + 1)..self.targets.len()).rev() {
if self.targets[index].is_top_level_navigable() {
self.index = index;
return;
}
}
}
}
pub fn show_modal(&mut self, data: M, count: usize) {
self.overlay = Some(OverlayFocus::Modal {
data,
index: 0,
count,
});
}
pub fn clear_overlay(&mut self) {
self.overlay = None;
}
pub fn exit_canvas_forward(&mut self) {
for index in (self.index + 1)..self.targets.len() {
if self.targets[index].is_top_level_navigable() && !self.targets[index].is_canvas() {
self.index = index;
return;
}
}
}
pub fn exit_canvas_backward(&mut self) {
for index in (0..self.index).rev() {
if self.targets[index].is_top_level_navigable() && !self.targets[index].is_canvas() {
self.index = index;
return;
}
}
}
pub fn enter_section(&mut self, item_count: usize) {
if item_count == 0 {
return;
}
if let Some(FocusTarget::Section(section_id)) = self.targets.get(self.index) {
self.entered_section = Some(EnteredSection {
section_id: *section_id,
item_index: 0,
item_count,
});
}
}
pub fn activate(&mut self) {
if self.overlay.is_some() || self.entered_section.is_some() {
return;
}
if let Some(FocusTarget::Section(section_id)) = self.targets.get(self.index) {
if let Some(item_count) = self.section_item_count(*section_id) {
self.enter_section(item_count);
}
}
}
pub fn enter_section_at(&mut self, section_id: usize, item_count: usize, item_index: usize) {
if item_count == 0 {
return;
}
if let Some(position) = self
.targets
.iter()
.position(|target| matches!(target, FocusTarget::Section(id) if *id == section_id))
{
self.index = position;
self.overlay = None;
self.entered_section = Some(EnteredSection {
section_id,
item_index: item_index.min(item_count.saturating_sub(1)),
item_count,
});
}
}
pub fn leave_section(&mut self) {
self.entered_section = None;
}
}
impl<O: Clone, M> FocusManager<O, M> {
pub fn current(&self) -> Option<FocusTarget<O>> {
if let Some(overlay) = &self.overlay {
return Some(match overlay {
OverlayFocus::Simple(kind) => FocusTarget::Overlay(kind.clone()),
OverlayFocus::Modal { index, .. } => FocusTarget::ModalItem(*index),
});
}
if let Some(section) = &self.entered_section {
return Some(FocusTarget::SectionItem {
section: section.section_id,
item: section.item_index,
});
}
self.targets.get(self.index).cloned()
}
pub fn query(&self) -> FocusQuery<O> {
FocusQuery {
current: self.current(),
}
}
}
impl<O: Clone + PartialEq, M> FocusManager<O, M> {
pub fn is_focused(&self, target: &FocusTarget<O>) -> bool {
self.current().as_ref() == Some(target)
}
pub fn add_target(&mut self, target: FocusTarget<O>) {
if !self.targets.contains(&target) {
self.targets.push(target);
}
}
pub fn remove_target(&mut self, target: &FocusTarget<O>) {
if let Some(position) = self
.targets
.iter()
.position(|candidate| candidate == target)
{
self.targets.remove(position);
if self.index >= self.targets.len() && !self.targets.is_empty() {
self.index = self.targets.len() - 1;
}
}
}
pub fn set_focus(&mut self, target: FocusTarget<O>) {
if let Some(kind) = target.to_overlay() {
self.overlay = Some(OverlayFocus::Simple(kind));
return;
}
if let FocusTarget::ModalItem(next_index) = target {
if let Some(OverlayFocus::Modal { index, count, .. }) = &mut self.overlay {
if next_index < *count {
*index = next_index;
}
}
return;
}
if let FocusTarget::Section(section_id) = target {
if let Some(position) = self.targets.iter().position(
|candidate| matches!(candidate, FocusTarget::Section(id) if *id == section_id),
) {
self.index = position;
self.overlay = None;
self.entered_section = None;
}
return;
}
if let Some(position) = self
.targets
.iter()
.position(|candidate| candidate == &target)
{
self.index = position;
self.overlay = None;
self.entered_section = None;
}
}
pub fn open_overlay(&mut self, target: FocusTarget<O>) {
if let Some(kind) = target.to_overlay() {
self.overlay = Some(OverlayFocus::Simple(kind));
}
}
pub fn close_overlay(&mut self, target: FocusTarget<O>) {
let should_close = match (&self.overlay, target.to_overlay()) {
(Some(OverlayFocus::Simple(current)), Some(requested)) => current == &requested,
_ => false,
};
if should_close {
self.overlay = None;
}
}
pub fn toggle_overlay(&mut self, target: FocusTarget<O>) {
if self.is_overlay_open(&target) {
self.close_overlay(target);
} else {
self.open_overlay(target);
}
}
pub fn is_overlay_open(&self, target: &FocusTarget<O>) -> bool {
match (&self.overlay, target.to_overlay()) {
(Some(OverlayFocus::Simple(current)), Some(requested)) => current == &requested,
_ => false,
}
}
}
impl<O: Clone + PartialEq, M> FocusController<O, M> for FocusManager<O, M> {
fn apply_focus_intent(&mut self, intent: FocusIntent<O, M>) {
match intent {
FocusIntent::Next => self.next(),
FocusIntent::Prev => self.prev(),
FocusIntent::Set(target) => self.set_focus(target),
FocusIntent::Open(target) => self.open_overlay(target),
FocusIntent::Close(target) => self.close_overlay(target),
FocusIntent::Toggle(target) => self.toggle_overlay(target),
FocusIntent::RegisterPage(targets) => self.register_page(targets),
FocusIntent::RegisterPageAndEnterSection {
targets,
section,
item_count,
item,
} => {
self.register_page(targets);
self.enter_section_at(section, item_count, item);
}
FocusIntent::ShowModal { data, count } => self.show_modal(data, count),
FocusIntent::UpdateModal { data, count } => {
if let Some(OverlayFocus::Modal {
data: current_data,
count: current_count,
..
}) = &mut self.overlay
{
*current_data = data;
*current_count = count;
}
}
FocusIntent::ClearOverlay => self.clear_overlay(),
FocusIntent::ExitCanvasForward => self.exit_canvas_forward(),
FocusIntent::ExitCanvasBackward => self.exit_canvas_backward(),
FocusIntent::EnterSection { item_count } => self.enter_section(item_count),
FocusIntent::LeaveSection => self.leave_section(),
FocusIntent::Activate => self.activate(),
}
}
}