#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TabScope {
Global,
Project,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum Tab {
#[default]
Projects,
Sessions,
Review,
Settings,
}
impl Tab {
pub const ALL: [Self; 4] = [Self::Projects, Self::Sessions, Self::Review, Self::Settings];
pub const PROJECT_SCOPED: [Self; 3] = [Self::Sessions, Self::Review, Self::Settings];
pub fn available_tabs() -> &'static [Self] {
&Self::ALL
}
pub fn project_scoped_tabs() -> &'static [Self] {
&Self::PROJECT_SCOPED
}
pub fn title(self) -> &'static str {
match self {
Tab::Projects => "Projects",
Tab::Sessions => "Sessions",
Tab::Review => "Review",
Tab::Settings => "Settings",
}
}
#[must_use]
pub fn scope(self) -> TabScope {
match self {
Tab::Projects => TabScope::Global,
Tab::Sessions | Tab::Review | Tab::Settings => TabScope::Project,
}
}
#[must_use]
fn next(self) -> Self {
let tabs = Self::available_tabs();
let tab_index = self.index();
let next_index = (tab_index + 1) % tabs.len();
tabs[next_index]
}
#[must_use]
fn previous(self) -> Self {
let tabs = Self::available_tabs();
let tab_index = self.index();
let previous_index = (tab_index + tabs.len() - 1) % tabs.len();
tabs[previous_index]
}
fn index(self) -> usize {
match Self::available_tabs().iter().position(|tab| *tab == self) {
Some(tab_index) => tab_index,
None => unreachable!("tab must exist in the display order"),
}
}
}
#[derive(Default)]
pub struct TabManager {
current: Tab,
}
impl TabManager {
#[must_use]
pub fn current(&self) -> Tab {
self.current
}
pub fn next(&mut self) {
self.current = self.current.next();
}
pub fn previous(&mut self) {
self.current = self.current.previous();
}
pub fn set(&mut self, tab: Tab) {
self.current = tab;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tab_title() {
let titles = Tab::ALL.map(Tab::title);
assert_eq!(titles, ["Projects", "Sessions", "Review", "Settings"]);
}
#[test]
fn test_tab_scope_marks_only_projects_as_global() {
let scopes = Tab::ALL.map(Tab::scope);
assert_eq!(
scopes,
[
TabScope::Global,
TabScope::Project,
TabScope::Project,
TabScope::Project
]
);
}
#[test]
fn test_tab_next_cycles_in_display_order() {
let next_tabs = Tab::ALL.map(Tab::next);
assert_eq!(
next_tabs,
[Tab::Sessions, Tab::Review, Tab::Settings, Tab::Projects]
);
}
#[test]
fn test_tab_previous_cycles_in_display_order() {
let previous_tabs = Tab::ALL.map(Tab::previous);
assert_eq!(
previous_tabs,
[Tab::Settings, Tab::Projects, Tab::Sessions, Tab::Review]
);
}
#[test]
fn test_tab_project_scoped_order_keeps_project_pages_grouped() {
let project_scoped_tabs = Tab::project_scoped_tabs();
assert_eq!(
project_scoped_tabs,
&[Tab::Sessions, Tab::Review, Tab::Settings]
);
}
#[test]
fn test_tab_manager_new_defaults_to_projects() {
let manager = TabManager::default();
assert_eq!(manager.current(), Tab::Projects);
}
#[test]
fn test_tab_manager_next_cycles_tabs_with_tasks() {
let mut manager = TabManager::default();
let mut observed_tabs = Vec::new();
observed_tabs.push(manager.current());
manager.next();
observed_tabs.push(manager.current());
manager.next();
observed_tabs.push(manager.current());
manager.next();
observed_tabs.push(manager.current());
manager.next();
observed_tabs.push(manager.current());
assert_eq!(
observed_tabs,
vec![
Tab::Projects,
Tab::Sessions,
Tab::Review,
Tab::Settings,
Tab::Projects
]
);
}
#[test]
fn test_tab_manager_previous_cycles_tabs_without_tasks() {
let mut manager = TabManager::default();
let mut observed_tabs = Vec::new();
observed_tabs.push(manager.current());
manager.previous();
observed_tabs.push(manager.current());
manager.previous();
observed_tabs.push(manager.current());
manager.previous();
observed_tabs.push(manager.current());
manager.previous();
observed_tabs.push(manager.current());
assert_eq!(
observed_tabs,
vec![
Tab::Projects,
Tab::Settings,
Tab::Review,
Tab::Sessions,
Tab::Projects
]
);
}
#[test]
fn test_tab_manager_set_updates_current_tab() {
let mut manager = TabManager::default();
manager.set(Tab::Settings);
assert_eq!(manager.current(), Tab::Settings);
}
}