Skip to main content

rsynko_manager/
menu.rs

1use crate::*;
2use alux_ext::ext;
3
4/// Derives action availability from renderer-neutral manager observations.
5#[ext(name = ManagerMenuExt)]
6pub impl<This> This
7where
8    This: NavigationStateAlg
9        + QueueCatalogAlg
10        + DraftStateAlg
11        + InputDraftAlg
12        + OutputDraftAlg
13        + TextEditorStateAlg
14        + DetailSelectionAlg,
15    This::Entry: QueueEntryAlg + RequestOptionsAlg,
16    This::Id: Copy + Eq,
17{
18    /// Derives whether one reusable action currently has meaning.
19    fn action_availability(&self, action: ManagerAction) -> ActionAvailability {
20        let enabled = match action {
21            ManagerAction::Exit => true,
22            ManagerAction::Back => self.page() != ManagerPage::Collection,
23            ManagerAction::AddSources => self.page() == ManagerPage::Collection,
24            ManagerAction::Space => {
25                matches!(self.page(), ManagerPage::Collection | ManagerPage::Details(_))
26                    && self
27                        .selected_queue_id()
28                        .and_then(|id| self.queue_entry(id))
29                        .and_then(QueueEntryAlg::space_action)
30                        .is_some()
31            }
32            ManagerAction::Remove => self.page() == ManagerPage::Collection && self.selected_queue_id().is_some(),
33            ManagerAction::Previous => self.cursor_can_move(false),
34            ManagerAction::Next => self.cursor_can_move(true),
35            ManagerAction::Activate => self.selection_can_activate(),
36        };
37        if enabled { ActionAvailability::Enabled } else { ActionAvailability::Disabled }
38    }
39
40    /// Derives whether the current page cursor can move in one direction.
41    fn cursor_can_move(&self, forward: bool) -> bool {
42        match self.page() {
43            ManagerPage::Collection => self.queue_ids().take(2).count() > 1,
44            ManagerPage::AddSources | ManagerPage::Output(_) | ManagerPage::Input(_) => self
45                .active_text_editor()
46                .is_some_and(|(text, cursor)| if forward { cursor < text.len() } else { cursor > 0 }),
47            ManagerPage::Details(id) => self.queue_entry(id).is_some_and(|entry| !entry.detail_controls().is_empty()),
48            ManagerPage::Formats(id) => {
49                self.queue_entry(id).is_some_and(|entry| entry.selectable_choices().next().is_some())
50            }
51            // A record, a report, and a command are read, not traversed: they state no cursor.
52            ManagerPage::Log(_) | ManagerPage::Report(_) | ManagerPage::Command(_) => false,
53        }
54    }
55
56    /// Derives whether the current cursor position denotes an activatable value.
57    fn selection_can_activate(&self) -> bool {
58        match self.page() {
59            ManagerPage::Collection => self.selected_queue_id().is_some(),
60            ManagerPage::AddSources => self.draft().lines().any(|line| !line.trim().is_empty()),
61            ManagerPage::Output(id) => {
62                self.queue_entry(id).is_some_and(QueueEntryAlg::is_editable) && !self.output_draft().trim().is_empty()
63            }
64            ManagerPage::Input(id) => {
65                self.queue_entry(id).is_some_and(QueueEntryAlg::is_editable) && !self.input_draft().trim().is_empty()
66            }
67            ManagerPage::Details(id) => self.queue_entry(id).is_some_and(|entry| {
68                self.selected_detail_control().is_none_or(|control| entry.detail_controls().contains(&control))
69            }),
70            ManagerPage::Formats(id) => {
71                self.queue_entry(id).is_some_and(|entry| entry.selectable_choices().next().is_some())
72            }
73            ManagerPage::Log(_) | ManagerPage::Report(_) | ManagerPage::Command(_) => false,
74        }
75    }
76}
77
78/// Identifies one reusable manager-menu action independently of its input binding.
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub enum ManagerAction {
81    /// Moves the current page's cursor to the preceding value.
82    Previous,
83    /// Moves the current page's cursor to the following value.
84    Next,
85    /// Activates the current page's selected value.
86    Activate,
87    /// Opens the add-sources editor.
88    AddSources,
89    /// Applies the selected queue entry's state-dependent Space action.
90    Space,
91    /// Removes the selected queue entry.
92    Remove,
93    /// Returns to the parent page.
94    Back,
95    /// Requests safe application exit.
96    Exit,
97}
98
99/// Denotes whether a menu action currently has meaning.
100#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101pub enum ActionAvailability {
102    /// Denotes an action that may be selected and activated.
103    Enabled,
104    /// Denotes a visible action that must not be selected or activated.
105    Disabled,
106}
107
108impl ActionAvailability {
109    /// Observes whether the action may be activated.
110    #[must_use]
111    pub const fn is_enabled(self) -> bool {
112        matches!(self, Self::Enabled)
113    }
114}