lingora-tui 0.4.11

Lingora is a free and open-source localization management program that analyses fluent translation files highlighting discrepancies between reference and target languages. This application provides a terminal user interface; run as `lingora-tui`.
Documentation
use crossterm::event::Event;
use rat_event::{ConsumedEvent, HandleEvent, Outcome, Regular};
use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
use rat_text::HasScreenCursor;
use ratatui::{prelude::*, widgets::StatefulWidget};

use crate::{
    components::{Cursor, LocaleFilter, LocaleFilterState, LocaleTree, LocaleTreeState},
    projections::{FilteredLocalesHierarchy, HasSelectionPair, LocaleNodeId, LocalesHierarchy},
    theme::LingoraTheme,
};

#[derive(Debug)]
pub struct LocalesState {
    filter_state: LocaleFilterState,
    tree_state: LocaleTreeState,
}

impl LocalesState {
    pub fn new(
        reference_node_id: Option<LocaleNodeId>,
        nodes: impl IntoIterator<Item = LocaleNodeId>,
    ) -> Self {
        let filter_state = LocaleFilterState::default();
        let tree_state = LocaleTreeState::new(reference_node_id, nodes);
        Self {
            filter_state,
            tree_state,
        }
    }

    pub fn filter(&self) -> &str {
        self.filter_state.text()
    }
}

impl HasSelectionPair for LocalesState {
    type Item = LocaleNodeId;

    fn reference(&self) -> Option<&Self::Item> {
        self.tree_state.reference()
    }

    fn target(&self) -> Option<&Self::Item> {
        self.tree_state.target()
    }
}

impl HasFocus for LocalesState {
    fn build(&self, builder: &mut FocusBuilder) {
        builder.widget(&self.filter_state);
        builder.widget(&self.tree_state);
    }

    fn focus(&self) -> FocusFlag {
        unreachable!()
    }

    fn area(&self) -> Rect {
        unreachable!()
    }
}

impl HasScreenCursor for LocalesState {
    fn screen_cursor(&self) -> Cursor {
        self.filter_state.screen_cursor()
    }
}

impl HandleEvent<Event, Regular, Outcome> for LocalesState {
    fn handle(&mut self, event: &Event, qualifier: Regular) -> Outcome {
        self.filter_state
            .handle(event, qualifier)
            .or_else(|| self.tree_state.handle(event, qualifier))
    }
}

pub struct Locales<'a> {
    theme: &'a LingoraTheme,
    locales_hierarchy: &'a LocalesHierarchy,
}

impl<'a> Locales<'a> {
    pub fn new(theme: &'a LingoraTheme, locales_hierarchy: &'a LocalesHierarchy) -> Self {
        Self {
            theme,
            locales_hierarchy,
        }
    }
}

impl StatefulWidget for &Locales<'_> {
    type State = LocalesState;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
    where
        Self: Sized,
    {
        let filtered_hierarchy = FilteredLocalesHierarchy::filter_from(
            self.locales_hierarchy,
            state.filter_state.text(),
        );

        let filter = LocaleFilter::new(self.theme);
        let tree = LocaleTree::new(self.theme, &filtered_hierarchy);

        let chunks = Layout::vertical(vec![Constraint::Length(3), Constraint::Fill(1)]).split(area);
        filter.render(chunks[0], buf, &mut state.filter_state);
        tree.render(chunks[1], buf, &mut state.tree_state);
    }
}