From 3bd8f1c75d7887c0f2df8034304e33398828415b Mon Sep 17 00:00:00 2001 From: "alterwain@protonmail.com" Date: Mon, 17 Feb 2025 19:23:30 +0300 Subject: [PATCH] Scrolling through list added, splash screen modified. --- Cargo.lock | 78 ++++++++- Cargo.toml | 6 +- src/component.rs | 19 ++- src/file_system.rs | 2 +- src/main.rs | 6 +- src/main_screen.rs | 392 +++++++++++++++++++++------------------------ src/wait_screen.rs | 49 ++++-- 7 files changed, 316 insertions(+), 236 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33caf00..1ef2c8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,6 +301,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn", +] + [[package]] name = "dirs" version = "6.0.0" @@ -405,6 +436,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +[[package]] +name = "font8x8" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875488b8711a968268c7cf5d139578713097ca4635a76044e8fe8eedf831d07e" + [[package]] name = "foreign-types" version = "0.3.2" @@ -928,6 +965,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" @@ -936,8 +982,8 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "itunesdb" -version = "0.1.50" -source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#2c38d91cd89908b20a2b08da1b3202dacf5d1f9b" +version = "0.1.51" +source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#ed639f96d59b6777114551e8c3054f2a0ac9c7da" dependencies = [ "bincode", "env_logger", @@ -1047,9 +1093,11 @@ dependencies = [ "serde", "serde_json", "soundcloud", + "throbber-widgets-tui", "tokio", "tokio-util", "toml", + "tui-big-text", ] [[package]] @@ -1344,7 +1392,7 @@ dependencies = [ "crossterm", "indoc", "instability", - "itertools", + "itertools 0.13.0", "lru", "paste", "strum", @@ -1893,6 +1941,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "throbber-widgets-tui" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d36b5738d666a2b4c91b7c24998a8588db724b3107258343ebf8824bf55b06d" +dependencies = [ + "rand", + "ratatui", +] + [[package]] name = "time" version = "0.3.37" @@ -2094,6 +2152,18 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tui-big-text" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97cefa9f1425ab6146db2961241cec86845d11105b5dd6bb504294b0cdd21af" +dependencies = [ + "derive_builder", + "font8x8", + "itertools 0.14.0", + "ratatui", +] + [[package]] name = "unicode-ident" version = "1.0.16" @@ -2112,7 +2182,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "itertools", + "itertools 0.13.0", "unicode-segmentation", "unicode-width 0.1.14", ] diff --git a/Cargo.toml b/Cargo.toml index 9ae576d..061f1a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,9 @@ futures = "0.3" tokio = { version = "1", features = ["full"] } tokio-util = { version = "0.7.12", features = ["codec"] } soundcloud = { version = "0.1.8", git = "https://gitea.awain.net/alterwain/soundcloud_api.git" } -itunesdb = { version = "0.1.50", git = "https://gitea.awain.net/alterwain/ITunesDB.git" } +itunesdb = { version = "0.1.51", git = "https://gitea.awain.net/alterwain/ITunesDB.git" } puremp3 = "0.1.0" mp3-duration = "0.1.10" -rand = "0.8.5" \ No newline at end of file +rand = "0.8.5" +tui-big-text = "0.7.1" +throbber-widgets-tui = "0.8.0" diff --git a/src/component.rs b/src/component.rs index 7f7333a..809fdb0 100644 --- a/src/component.rs +++ b/src/component.rs @@ -4,7 +4,9 @@ pub mod table { use ratatui::widgets::{Block, Borders, Row, Table}; use ratatui::Frame; + #[derive(Default)] pub struct SmartTable { + is_checked: bool, header: Vec, data: Vec>, constraints: Vec, @@ -15,6 +17,7 @@ pub mod table { impl SmartTable { pub fn new(header: Vec, constraints: Vec) -> Self { Self { + is_checked: true, header, data: Vec::new(), constraints, @@ -23,6 +26,10 @@ pub mod table { } } + pub fn set_checked(&mut self, checked: bool) { + self.is_checked = checked; + } + pub fn set_data(&mut self, data: Vec>) { self.data = data; } @@ -49,14 +56,22 @@ pub mod table { for (i, entry) in self.data.iter().enumerate() { v.push( Row::new(entry.clone()).style(if self.selected_row as usize == i { - Style::default().bg(Color::LightBlue).fg(Color::White) + Style::default() + .bg(if self.is_checked { + Color::LightBlue + } else { + Color::Gray + }) + .fg(Color::White) } else { Style::default() }), ); } - if self.selected_row as usize > area.rows().count() - 4 { + if self.selected_row as usize > area.rows().count() - 4 + && self.selected_row - ((area.rows().count() - 4) as i32) >= 0 + { v = v[(self.selected_row as usize - (area.rows().count() - 4))..].to_vec(); } diff --git a/src/file_system.rs b/src/file_system.rs index 898ec69..c6db3ce 100644 --- a/src/file_system.rs +++ b/src/file_system.rs @@ -45,7 +45,7 @@ impl AppScreen for FileSystem { } } - fn render(&self, frame: &mut ratatui::Frame, theme: &Theme) { + fn render(&self, frame: &mut Frame, _theme: &Theme) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ diff --git a/src/main.rs b/src/main.rs index c686579..ff933ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use futures::StreamExt; +use futures::{StreamExt, TryStreamExt}; use loading_screen::LoadingScreen; use main_screen::MainScreen; use ratatui::{ @@ -18,6 +18,7 @@ use ratatui::{ Frame, Terminal, }; use screen::AppScreen; +use std::time::Duration; use std::{collections::HashMap, error::Error, io}; use sync::AppEvent; use tokio::sync::mpsc::{self, Receiver, UnboundedSender}; @@ -134,6 +135,9 @@ impl App { _ => {} } } + _ = tokio::time::sleep(Duration::from_millis(200)) => { + + } } } diff --git a/src/main_screen.rs b/src/main_screen.rs index c8ea046..699568d 100644 --- a/src/main_screen.rs +++ b/src/main_screen.rs @@ -4,22 +4,21 @@ use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style, Stylize}, text::{Line, Span}, - widgets::{Block, Borders, Paragraph, Row, Table, Tabs}, + widgets::{Block, Borders, Paragraph, Tabs}, Frame, }; use soundcloud::sobjects::{CloudPlaylist, CloudPlaylists}; use tokio::sync::mpsc::UnboundedSender; +use crate::component::table::SmartTable; use crate::sync::DBPlaylist; use crate::{screen::AppScreen, sync::AppEvent, theme::Theme, AppState}; pub struct MainScreen { mode: bool, selected_tab: i8, - selected_playlist: i32, - selected_song: i32, - max_pls: i32, - max_songs: i32, + pl_table: SmartTable, + song_table: SmartTable, tab_titles: Vec, soundcloud: Option>, playlists: Option>, @@ -33,17 +32,18 @@ impl AppScreen for MainScreen { KeyCode::Left => self.previous_tab(), KeyCode::Up => self.previous_row(), KeyCode::Down => self.next_row(), - KeyCode::F(6) => self.download_row(), + KeyCode::F(5) => self.download_row(), KeyCode::Tab => self.switch_mode(), - KeyCode::F(2) => { - self.sender + KeyCode::F(4) => { + let _ = self + .sender .send(AppEvent::SwitchScreen(AppState::FileSystem)); } _ => {} } } - fn render(&self, frame: &mut Frame, theme: &Theme) { + fn render(&self, frame: &mut Frame, _theme: &Theme) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ @@ -76,9 +76,11 @@ impl AppScreen for MainScreen { let status_bar = Paragraph::new(Line::from(vec![ "◄ ► to change tab".bold(), " | ".dark_gray(), - " SAVE FS".bold(), + " SWITCH PANEL".bold(), " | ".dark_gray(), - " DL".bold(), + " FS MODE".bold(), + " | ".dark_gray(), + " DOWNLOAD".bold(), " | ".dark_gray(), " DEL".bold(), " | ".dark_gray(), @@ -97,10 +99,8 @@ impl MainScreen { pub fn new(sender: UnboundedSender) -> Self { MainScreen { mode: false, - selected_playlist: 0, - selected_song: 0, - max_pls: 0, - max_songs: 0, + pl_table: SmartTable::default(), + song_table: SmartTable::default(), soundcloud: None, playlists: None, selected_tab: 0, @@ -114,42 +114,14 @@ impl MainScreen { } } - fn update_max_rows(&mut self) { - self.selected_song = 0; - self.selected_playlist = 0; - self.max_songs = 0; - self.max_pls = match self.selected_tab { - 1 => self.soundcloud.as_deref().unwrap_or(&[]).len(), - 2 => self.playlists.as_deref().unwrap_or(&[]).len(), - _ => 0, - } - .try_into() - .unwrap(); - self.update_max_songs(); - } - - fn update_max_songs(&mut self) { - if self.max_pls > 0 { - self.max_songs = match self.selected_tab { - 1 => self - .soundcloud - .as_deref() - .unwrap() - .get(self.selected_playlist as usize) - .unwrap() - .tracks - .len(), - _ => 0, - } - .try_into() - .unwrap(); - - self.selected_song = 0; - } - } - fn switch_mode(&mut self) { - self.mode = !self.mode; + self.set_mode(!self.mode); + } + + fn set_mode(&mut self, mode: bool) { + self.mode = mode; + self.pl_table.set_checked(!self.mode); + self.song_table.set_checked(self.mode); } fn next_tab(&mut self) { @@ -157,31 +129,30 @@ impl MainScreen { self.selected_tab + 1, (self.tab_titles.len() - 1).try_into().unwrap(), ); - self.update_max_rows(); + self.update_tables(); } fn previous_tab(&mut self) { self.selected_tab = std::cmp::max(0, self.selected_tab - 1); - self.update_max_rows(); + self.update_tables(); } fn previous_row(&mut self) { match self.mode { - true => self.selected_song = std::cmp::max(0, self.selected_song - 1), + true => self.song_table.previous_row(), false => { - self.selected_playlist = std::cmp::max(0, self.selected_playlist - 1); - self.update_max_songs(); + self.pl_table.previous_row(); + self.update_songs(); } } } fn next_row(&mut self) { match self.mode { - true => self.selected_song = std::cmp::min(self.selected_song + 1, self.max_songs - 1), + true => self.song_table.next_row(), false => { - self.selected_playlist = - std::cmp::min(self.selected_playlist + 1, self.max_pls - 1); - self.update_max_songs(); + self.pl_table.next_row(); + self.update_songs(); } } } @@ -193,7 +164,7 @@ impl MainScreen { .soundcloud .as_ref() .unwrap() - .get(self.selected_playlist as usize) + .get(self.pl_table.selected_row()) .unwrap() .clone(); let _ = self.sender.send(AppEvent::DownloadPlaylist(playlist)); @@ -203,72 +174,165 @@ impl MainScreen { pub fn set_soundcloud_playlists(&mut self, pl: CloudPlaylists) { self.soundcloud = Some(pl.collection); if self.selected_tab == 1 { - self.update_max_rows(); + self.update_tables(); } } pub fn set_itunes(&mut self, pl: Vec) { self.playlists = Some(pl); if self.selected_tab == 2 { - self.update_max_rows(); + self.update_tables(); + } + } + + fn update_tables(&mut self) { + self.set_mode(false); + + self.pl_table = SmartTable::new( + ["Id", "Title", "Songs Count", "Date", "IS"] + .iter_mut() + .map(|s| s.to_string()) + .collect(), + [ + Constraint::Length(3), // ID column + Constraint::Percentage(50), // Playlist name column + Constraint::Percentage(20), // Song count column + Constraint::Percentage(30), + Constraint::Length(2), + ] + .to_vec(), + ); + + let data = match self.selected_tab { + 1 => { + if let Some(sc) = &self.soundcloud { + sc.iter() + .map(|playlist| { + let date: DateTime = playlist.created_at.parse().unwrap(); + vec![ + playlist.id.to_string(), + playlist.title.clone(), + [playlist.track_count.to_string(), " songs".to_string()].concat(), + format!("{}", date.format("%Y-%m-%d %H:%M")), + "NO".to_string(), + ] + }) + .collect::>>() + } else { + Vec::new() + } + } + 2 => { + if let Some(it) = &self.playlists { + it.iter() + .map(|playlist| { + let date = Utc.timestamp_millis_opt(playlist.timestamp as i64).unwrap(); + vec![ + playlist.id.to_string(), + "".to_string(), + playlist.tracks.len().to_string(), + format!("{}", date.format("%Y-%m-%d %H:%M")), + "YES".to_string(), + ] + }) + .collect::>>() + } else { + Vec::new() + } + } + _ => { + self.pl_table = SmartTable::default(); + Vec::new() + } + }; + self.pl_table.set_data(data); + self.pl_table.set_title("Playlists".to_string()); + self.update_songs(); + } + + fn update_songs(&mut self) { + let constraints = [ + Constraint::Length(3), // ID column + Constraint::Percentage(50), // Playlist name column + Constraint::Percentage(20), // Song count column + Constraint::Length(5), + Constraint::Min(0), + ] + .to_vec(); + + match self.selected_tab { + 1 => { + self.song_table = SmartTable::new( + ["Id", "Title", "Artist", "Duration", "Genre"] + .iter_mut() + .map(|s| s.to_string()) + .collect(), + constraints, + ); + self.set_mode(self.mode); + + if let Some(pls) = &self.soundcloud { + let s = &pls.get(self.pl_table.selected_row()).unwrap().tracks; + let data = s + .iter() + .map(|track| { + vec![ + track.id.to_string(), + track.title.as_deref().unwrap().to_string(), + track + .user + .clone() + .unwrap() + .username + .unwrap_or(track.user.as_ref().unwrap().permalink.clone()), + track.duration.unwrap_or(0).to_string(), + track.genre.as_ref().unwrap_or(&String::new()).to_string(), + ] + }) + .collect::>>(); + + self.song_table.set_data(data); + } + self.song_table.set_title(" Songs ".to_string()); + } + 2 => { + self.song_table = SmartTable::new( + ["Id", "Title", "Artist", "Bitrate", "Genre"] + .iter_mut() + .map(|s| s.to_string()) + .collect(), + constraints, + ); + self.set_mode(self.mode); + + if let Some(pls) = &self.playlists { + let s = &pls.get(self.pl_table.selected_row()).unwrap().tracks; + let data = s + .iter() + .map(|track| { + vec![ + track.data.unique_id.to_string(), + track.get_title(), + track.get_location(), + track.data.bitrate.to_string(), + track.get_genre(), + ] + }) + .collect::>>(); + + self.song_table.set_data(data); + } + + self.song_table.set_title(" Songs ".to_string()); + } + _ => { + self.song_table = SmartTable::default(); + self.set_mode(self.mode); + } } } fn render_tab(&self, frame: &mut Frame, area: Rect) { - let rows = match self.selected_tab { - 1 => { - // SC - let mut v = Vec::new(); - v.push( - Row::new(vec!["Id", "Title", "Songs Count", "Date", "IS"]) - .style(Style::default().fg(Color::Gray)), - ); - if let Some(s) = &self.soundcloud { - for (i, playlist) in s.iter().enumerate() { - let date: DateTime = playlist.created_at.parse().unwrap(); - let mut row = Row::new(vec![ - playlist.id.to_string(), - playlist.title.clone(), - [playlist.track_count.to_string(), " songs".to_string()].concat(), - format!("{}", date.format("%Y-%m-%d %H:%M")), - "NO".to_string(), - ]); - if self.selected_playlist == i as i32 { - row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White)); - } - v.push(row); - } - } - v - } - 2 => { - // local - let mut v = Vec::new(); - v.push( - Row::new(vec!["Id", "Title", "Songs Count", "Date", "IS"]) - .style(Style::default().fg(Color::Gray)), - ); - if let Some(s) = &self.playlists { - for (i, playlist) in s.iter().enumerate() { - let date = Utc.timestamp_millis_opt(playlist.timestamp as i64).unwrap(); - let mut row = Row::new(vec![ - playlist.id.to_string(), - "".to_string(), - playlist.tracks.len().to_string(), - format!("{}", date.format("%Y-%m-%d %H:%M")), - "YES".to_string(), - ]); - if self.selected_playlist == i as i32 { - row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White)); - } - v.push(row); - } - } - v - } - _ => Vec::new(), - }; - let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ @@ -277,99 +341,7 @@ impl MainScreen { ]) .split(area); - // Create the table - let table = Table::new( - rows, - &[ - Constraint::Length(3), // ID column - Constraint::Percentage(50), // Playlist name column - Constraint::Percentage(20), // Song count column - Constraint::Percentage(30), - Constraint::Length(2), - ], - ) - .block(Block::default().borders(Borders::ALL).title(" Playlists ")) - .style(Style::default().fg(Color::Black)); - - frame.render_widget(table, chunks[0]); - - let mut rows = match self.selected_tab { - 1 => { - // sc - let mut v = Vec::new(); - v.push( - Row::new(vec!["Id", "Title", "Artist", "Duration", "Genre"]) - .style(Style::default().fg(Color::Gray)), - ); - if let Some(pls) = &self.soundcloud { - let s = &pls.get(self.selected_playlist as usize).unwrap().tracks; - for (i, track) in s.iter().enumerate() { - let mut row = Row::new(vec![ - track.id.to_string(), - track.title.as_deref().unwrap().to_string(), - track - .user - .clone() - .unwrap() - .username - .unwrap_or(track.user.as_ref().unwrap().permalink.clone()), - track.duration.unwrap_or(0).to_string(), - track.genre.as_ref().unwrap_or(&String::new()).to_string(), - ]); - if self.selected_song == i as i32 { - row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White)); - } - v.push(row); - } - } - v - } - 2 => { - // local - let mut v = Vec::new(); - v.push( - Row::new(vec!["Id", "Title", "Artist", "Bitrate", "Genre"]) - .style(Style::default().fg(Color::Gray)), - ); - if let Some(pls) = &self.playlists { - let s = &pls.get(self.selected_playlist as usize).unwrap().tracks; - for (i, track) in s.iter().enumerate() { - let mut row = Row::new(vec![ - track.data.unique_id.to_string(), - track.get_title(), - track.get_location(), - track.data.bitrate.to_string(), - track.get_genre(), - ]); - if self.selected_song == i as i32 { - row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White)); - } - v.push(row); - } - } - v - } - _ => Vec::new(), - }; - - if chunks[1].rows().count() <= self.selected_song as usize { - rows = rows[self.selected_song as usize..].to_vec(); - } - - // Create the table - let table = Table::new( - rows, - &[ - Constraint::Length(3), // ID column - Constraint::Percentage(50), // Playlist name column - Constraint::Percentage(20), // Song count column - Constraint::Length(5), - Constraint::Min(0), - ], - ) - .block(Block::default().borders(Borders::ALL).title(" Songs ")) - .style(Style::default().fg(Color::Black)); - - frame.render_widget(table, chunks[1]); + self.pl_table.render(frame, chunks[0]); + self.song_table.render(frame, chunks[1]); } } diff --git a/src/wait_screen.rs b/src/wait_screen.rs index 9b2446f..cf8d9d7 100644 --- a/src/wait_screen.rs +++ b/src/wait_screen.rs @@ -1,13 +1,14 @@ +use crate::{screen::AppScreen, theme::Theme}; use color_eyre::owo_colors::OwoColorize; +use ratatui::layout::{Constraint, Direction, Flex, Layout}; +use ratatui::widgets::Paragraph; use ratatui::{ style::{Style, Stylize}, - symbols::border, - text::{Line, Text}, - widgets::{Block, Paragraph}, + text::Line, Frame, }; - -use crate::{screen::AppScreen, theme::Theme}; +use throbber_widgets_tui::{ThrobberState, BOX_DRAWING}; +use tui_big_text::{BigText, PixelSize}; #[derive(Debug, Clone, Default)] pub struct WaitScreen {} @@ -16,21 +17,37 @@ impl AppScreen for WaitScreen { fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) {} fn render(&self, frame: &mut Frame, theme: &Theme) { - let title = Line::from(" Lyrica ".bold()); - let instructions = Line::from(vec![" Quit ".into(), " ".red().bold()]); - let block = Block::bordered() - .title(title.centered()) - .title_bottom(instructions.centered()) - .border_set(border::ROUNDED); + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Percentage(33); 3]) + .split(frame.area()); - let counter_text = Text::from(vec![Line::from(vec!["Searching for iPod...".into()])]); + let simple = throbber_widgets_tui::Throbber::default() + .label("Searching for your iPod") + .throbber_set(BOX_DRAWING); - let par = Paragraph::new(counter_text) - .style(Style::new().bg(theme.background())) + let bottom = + Paragraph::new(vec![Line::from(vec![" Quit ".into(), " ".red().bold()])]).centered(); + let bottom_l = + Layout::vertical([Constraint::Min(0), Constraint::Length(1)]).split(chunks[2]); + + let [throbber_l] = Layout::horizontal([Constraint::Length( + simple.to_line(&ThrobberState::default()).width() as u16, + )]) + .flex(Flex::Center) + .areas(bottom_l[0]); + + frame.render_widget(simple, throbber_l); + frame.render_widget(bottom, bottom_l[1]); + + let title = BigText::builder() + .pixel_size(PixelSize::Full) + .style(Style::new().blue()) + .lines(vec!["Lyrica".light_blue().into()]) .centered() - .block(block); + .build(); - frame.render_widget(par, frame.area()); + frame.render_widget(title, chunks[1]); } fn as_any(&mut self) -> &mut dyn std::any::Any {