Krolyxon
Commit b139f20

rmptuisrcscreensmod.rs

168 lines · 4.5 KB · Rust

1//! The screens. Each is a self-contained component that renders itself,
2//! interprets actions, and reacts to server events.
3
4mod browser;
5mod library;
6mod lyrics;
7mod playlists;
8mod queue;
9mod tag_editor;
10mod visualizer;
11
12use std::sync::Arc;
13
14use crossterm::event::MouseEvent;
15use ratatui::{Frame, layout::Rect, text::Line};
16use tokio::sync::watch;
17
18pub use browser::BrowserScreen;
19pub use library::LibraryScreen;
20pub use lyrics::{LyricsScreen, draw_lyrics};
21pub use playlists::PlaylistsScreen;
22pub use queue::QueueScreen;
23pub use tag_editor::TagEditorScreen;
24pub use visualizer::VisualizerScreen;
25
26use crate::{
27 action::{Action, ScreenId},
28 config::{Config, Theme},
29 lyrics::LyricsState,
30 mpd::{MpdEvent, MpdHandle},
31 popups::Popup,
32 state::MpdState,
33 tags::{TagField, TagWrite},
34 visualizer::AudioFrame,
35};
36
37/// What a screen or popup may read, plus a queue of effects for the app.
38pub struct Ctx<'a> {
39 pub config: &'a Config,
40 pub mpd: &'a MpdState,
41 pub client: &'a MpdHandle,
42 pub lyrics: &'a LyricsState,
43 pub effects: Vec<Effect>,
44}
45
46impl<'a> Ctx<'a> {
47 pub fn new(
48 config: &'a Config,
49 mpd: &'a MpdState,
50 client: &'a MpdHandle,
51 lyrics: &'a LyricsState,
52 ) -> Self {
53 Self {
54 config,
55 mpd,
56 client,
57 lyrics,
58 effects: Vec::new(),
59 }
60 }
61
62 pub fn theme(&self) -> &'a Theme {
63 &self.config.theme
64 }
65
66 pub fn notify(&mut self, text: impl Into<String>) {
67 self.effects.push(Effect::Notify {
68 text: text.into(),
69 error: false,
70 });
71 }
72
73 pub fn error(&mut self, text: impl Into<String>) {
74 self.effects.push(Effect::Notify {
75 text: text.into(),
76 error: true,
77 });
78 }
79
80 pub fn popup(&mut self, popup: impl Popup + 'static) {
81 self.effects.push(Effect::Popup(Box::new(popup)));
82 }
83}
84
85/// Requests from screens and popups that only the app can fulfil.
86pub enum Effect {
87 Notify { text: String, error: bool },
88 Popup(Box<dyn Popup>),
89 Message(ScreenId, ScreenMessage),
90 WriteTags(Vec<TagWrite>),
91 ReloadLyrics,
92}
93
94/// Data a popup hands back to a screen.
95#[derive(Clone, Debug)]
96pub enum ScreenMessage {
97 TagValue { field: TagField, value: String },
98 FileName(String),
99}
100
101pub trait Screen {
102 /// The text of the title row.
103 fn title(&self, ctx: &Ctx) -> Line<'static>;
104
105 fn draw(&mut self, frame: &mut Frame, area: Rect, ctx: &Ctx);
106
107 /// Returns whether the action was handled.
108 fn handle_action(&mut self, action: Action, ctx: &mut Ctx) -> bool;
109
110 /// Called for every server event, whether or not the screen is visible.
111 fn on_mpd(&mut self, _event: &MpdEvent, _ctx: &mut Ctx) {}
112
113 /// Called when the screen becomes visible.
114 fn on_enter(&mut self, _ctx: &mut Ctx) {}
115
116 fn on_message(&mut self, _message: ScreenMessage, _ctx: &mut Ctx) {}
117
118 fn on_mouse(&mut self, _event: MouseEvent, _ctx: &mut Ctx) {}
119
120 /// Searches the focused list. Returns `false` if there is nothing to
121 /// search on this screen.
122 fn search(&mut self, _query: &str, _ctx: &Ctx) -> bool {
123 false
124 }
125}
126
127/// All screens, owned by the app.
128pub struct Screens {
129 pub queue: QueueScreen,
130 pub browser: BrowserScreen,
131 pub playlists: PlaylistsScreen,
132 pub library: LibraryScreen,
133 pub tag_editor: TagEditorScreen,
134 pub visualizer: VisualizerScreen,
135 pub lyrics: LyricsScreen,
136}
137
138impl Screens {
139 pub fn new(config: &Config, spectrum: watch::Receiver<Arc<AudioFrame>>) -> Self {
140 Self {
141 queue: QueueScreen::new(config),
142 browser: BrowserScreen::default(),
143 playlists: PlaylistsScreen::default(),
144 library: LibraryScreen::default(),
145 tag_editor: TagEditorScreen::default(),
146 visualizer: VisualizerScreen::new(config, spectrum),
147 lyrics: LyricsScreen::default(),
148 }
149 }
150
151 pub fn get_mut(&mut self, id: ScreenId) -> &mut dyn Screen {
152 match id {
153 ScreenId::Queue => &mut self.queue,
154 ScreenId::Browser => &mut self.browser,
155 ScreenId::Playlists => &mut self.playlists,
156 ScreenId::Library => &mut self.library,
157 ScreenId::TagEditor => &mut self.tag_editor,
158 ScreenId::Visualizer => &mut self.visualizer,
159 ScreenId::Lyrics => &mut self.lyrics,
160 }
161 }
162
163 pub fn for_each(&mut self, mut f: impl FnMut(&mut dyn Screen)) {
164 for &id in ScreenId::ALL {
165 f(self.get_mut(id));
166 }
167 }
168}