Compare commits
2 Commits
4c742ea210
...
3006fdbcc4
| Author | SHA1 | Date | |
|---|---|---|---|
| 3006fdbcc4 | |||
| 52ca495a6e |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
|
/.idea
|
||||||
|
src.zip
|
||||||
79
src/app.rs
79
src/app.rs
@@ -41,6 +41,8 @@ pub struct App {
|
|||||||
pub repeat: RepeatMode,
|
pub repeat: RepeatMode,
|
||||||
pub shuffle: bool,
|
pub shuffle: bool,
|
||||||
pub show_help: bool,
|
pub show_help: bool,
|
||||||
|
/// Показывать окно управления директориями
|
||||||
|
pub show_dirs: bool,
|
||||||
status_set_at: Instant,
|
status_set_at: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,9 +51,9 @@ const STATUS_RESET_AFTER: Duration = Duration::from_secs(2);
|
|||||||
impl App {
|
impl App {
|
||||||
pub fn new(music_dir: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
pub fn new(music_dir: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let saved = crate::state::load();
|
let saved = crate::state::load();
|
||||||
|
// Загружаем кэш
|
||||||
// Пробуем загрузить кэш. Если его нет или он пустой - сканируем папку.
|
let cached_tracks = crate::state::load_cache();
|
||||||
let tracks = match crate::state::load_cache() {
|
let tracks = match cached_tracks {
|
||||||
Some(cached) if !cached.is_empty() => cached,
|
Some(cached) if !cached.is_empty() => cached,
|
||||||
_ => {
|
_ => {
|
||||||
let scanned = scan_dir(music_dir);
|
let scanned = scan_dir(music_dir);
|
||||||
@@ -79,8 +81,20 @@ impl App {
|
|||||||
repeat: RepeatMode::Off,
|
repeat: RepeatMode::Off,
|
||||||
shuffle: false,
|
shuffle: false,
|
||||||
show_help: false,
|
show_help: false,
|
||||||
|
show_dirs: false,
|
||||||
status_set_at: Instant::now(),
|
status_set_at: Instant::now(),
|
||||||
};
|
};
|
||||||
|
// ВАЖНО: Если есть extra_dirs, но в кэше нет треков из них,
|
||||||
|
// пересобираем библиотеку с нуля (сканируем все директории).
|
||||||
|
if !app.extra_dirs.is_empty() {
|
||||||
|
// Проверяем, есть ли в кэше треки не из базовой директории
|
||||||
|
let base_dir = std::path::Path::new(music_dir);
|
||||||
|
let has_extra_tracks = app.raw_tracks.iter().any(|t| !t.path.starts_with(base_dir));
|
||||||
|
if !has_extra_tracks {
|
||||||
|
// Кэш не содержит extra_dirs — пересканируем всё
|
||||||
|
app.rebuild_from_dirs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(vol) = saved.volume {
|
if let Some(vol) = saved.volume {
|
||||||
app.player.set_volume(vol);
|
app.player.set_volume(vol);
|
||||||
@@ -198,6 +212,12 @@ impl App {
|
|||||||
"add" => self.add_dir(args),
|
"add" => self.add_dir(args),
|
||||||
"reload" => self.reload_library(),
|
"reload" => self.reload_library(),
|
||||||
"save" => self.save_now(),
|
"save" => self.save_now(),
|
||||||
|
"dirs" => {
|
||||||
|
self.show_dirs = true;
|
||||||
|
self.set_status("Список директорий".into());
|
||||||
|
}
|
||||||
|
"rmdir" => self.rmdir(args),
|
||||||
|
"clear" => self.clear_extra_dirs(),
|
||||||
"help" => {
|
"help" => {
|
||||||
self.show_help = true;
|
self.show_help = true;
|
||||||
self.set_status("Справка".into());
|
self.set_status("Справка".into());
|
||||||
@@ -256,7 +276,43 @@ impl App {
|
|||||||
self.set_status(format!("Добавлено {} треков из {}", added, expanded));
|
self.set_status(format!("Добавлено {} треков из {}", added, expanded));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reload_library(&mut self) {
|
fn rmdir(&mut self, arg: &str) {
|
||||||
|
let idx: usize = match arg.parse() {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
self.set_status("Использование: :rmdir <индекс>".into());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if idx == 0 {
|
||||||
|
self.set_status("Нельзя удалить базовую директорию".into());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let extra_idx = idx - 1;
|
||||||
|
if extra_idx >= self.extra_dirs.len() {
|
||||||
|
self.set_status(format!("Нет директории с индексом {}", idx));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let removed = self.extra_dirs.remove(extra_idx);
|
||||||
|
self.set_status(format!("Удалена директория: {}", removed));
|
||||||
|
self.rebuild_from_dirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_extra_dirs(&mut self) {
|
||||||
|
if self.extra_dirs.is_empty() {
|
||||||
|
self.set_status("extra_dirs уже пуст".into());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.extra_dirs.clear();
|
||||||
|
self.rebuild_from_dirs();
|
||||||
|
self.set_status("Все дополнительные директории удалены".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Пересканировать только оставшиеся директории и пересобрать библиотеку
|
||||||
|
fn rebuild_from_dirs(&mut self) {
|
||||||
let mut dirs = vec![self.music_dir.clone()];
|
let mut dirs = vec![self.music_dir.clone()];
|
||||||
dirs.extend(self.extra_dirs.clone());
|
dirs.extend(self.extra_dirs.clone());
|
||||||
|
|
||||||
@@ -271,9 +327,14 @@ impl App {
|
|||||||
|
|
||||||
self.raw_tracks = all;
|
self.raw_tracks = all;
|
||||||
self.deleted_paths.clear();
|
self.deleted_paths.clear();
|
||||||
|
|
||||||
crate::state::save_cache(&self.raw_tracks);
|
crate::state::save_cache(&self.raw_tracks);
|
||||||
|
|
||||||
self.rebuild_library();
|
self.rebuild_library();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reload_library(&mut self) {
|
||||||
|
self.rebuild_from_dirs();
|
||||||
self.set_status(format!("Пересканировано {} треков", self.raw_tracks.len()));
|
self.set_status(format!("Пересканировано {} треков", self.raw_tracks.len()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -795,6 +856,16 @@ impl App {
|
|||||||
};
|
};
|
||||||
self.set_status(msg);
|
self.set_status(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Геттеры для UI ─────────────────────────────────────────
|
||||||
|
|
||||||
|
pub fn base_dir(&self) -> &str {
|
||||||
|
&self.music_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extra_dirs(&self) -> &[String] {
|
||||||
|
&self.extra_dirs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Развернуть `~/...` в путь домашней директории.
|
/// Развернуть `~/...` в путь домашней директории.
|
||||||
|
|||||||
16
src/main.rs
16
src/main.rs
@@ -26,7 +26,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let mut app = App::new(&music_dir)?;
|
let mut app = App::new(&music_dir)?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Проверяем флаг выхода, выставленный командой :q
|
|
||||||
if app.request_quit {
|
if app.request_quit {
|
||||||
app.on_exit();
|
app.on_exit();
|
||||||
break;
|
break;
|
||||||
@@ -57,11 +56,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
fn handle_key(key: KeyEvent, app: &mut App) -> bool {
|
fn handle_key(key: KeyEvent, app: &mut App) -> bool {
|
||||||
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
||||||
|
|
||||||
|
// F1 переключает справку в любом режиме
|
||||||
if key.code == KeyCode::F(1) {
|
if key.code == KeyCode::F(1) {
|
||||||
app.toggle_help();
|
app.toggle_help();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Если открыта справка — только Esc её закрывает
|
||||||
if app.show_help {
|
if app.show_help {
|
||||||
if key.code == KeyCode::Esc {
|
if key.code == KeyCode::Esc {
|
||||||
app.toggle_help();
|
app.toggle_help();
|
||||||
@@ -69,6 +70,15 @@ fn handle_key(key: KeyEvent, app: &mut App) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Если открыто окно директорий — Esc его закрывает
|
||||||
|
if app.show_dirs {
|
||||||
|
if key.code == KeyCode::Esc {
|
||||||
|
app.show_dirs = false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ввод команды
|
||||||
if app.input_mode == InputMode::Command {
|
if app.input_mode == InputMode::Command {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Esc => app.cancel_command(),
|
KeyCode::Esc => app.cancel_command(),
|
||||||
@@ -80,6 +90,7 @@ fn handle_key(key: KeyEvent, app: &mut App) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ctrl-комбинации
|
||||||
if ctrl {
|
if ctrl {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Right => {
|
KeyCode::Right => {
|
||||||
@@ -105,6 +116,7 @@ fn handle_key(key: KeyEvent, app: &mut App) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ввод поиска
|
||||||
if app.input_mode == InputMode::Search {
|
if app.input_mode == InputMode::Search {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Esc => app.cancel_search(),
|
KeyCode::Esc => app.cancel_search(),
|
||||||
@@ -116,11 +128,13 @@ fn handle_key(key: KeyEvent, app: &mut App) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete
|
||||||
if key.code == KeyCode::Delete {
|
if key.code == KeyCode::Delete {
|
||||||
app.delete_selected();
|
app.delete_selected();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обычные клавиши
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Char(':') => {
|
KeyCode::Char(':') => {
|
||||||
app.start_command();
|
app.start_command();
|
||||||
|
|||||||
115
src/ui.rs
115
src/ui.rs
@@ -8,27 +8,24 @@ use crate::library::{RightRow, SortMode};
|
|||||||
use crate::state::RepeatMode;
|
use crate::state::RepeatMode;
|
||||||
|
|
||||||
pub fn draw(f: &mut Frame, app: &mut App) {
|
pub fn draw(f: &mut Frame, app: &mut App) {
|
||||||
// Вертикально: верхняя часть (панели + эквалайзер) и статус
|
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Min(1),
|
Constraint::Min(1),
|
||||||
Constraint::Length(3), // статус + время + mode + vol
|
Constraint::Length(3),
|
||||||
])
|
])
|
||||||
.split(f.area());
|
.split(f.area());
|
||||||
|
|
||||||
// Горизонтально: левая колонка (исполнители + эквалайзер) и правая (треки)
|
|
||||||
let top = Layout::default()
|
let top = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([Constraint::Percentage(30), Constraint::Percentage(70)])
|
.constraints([Constraint::Percentage(30), Constraint::Percentage(70)])
|
||||||
.split(chunks[0]);
|
.split(chunks[0]);
|
||||||
|
|
||||||
// Левая колонка делится вертикально: список сверху, эквалайзер снизу
|
|
||||||
let left_col = Layout::default()
|
let left_col = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Min(1),
|
Constraint::Min(1),
|
||||||
Constraint::Length(8), // эквалайзер
|
Constraint::Length(8),
|
||||||
])
|
])
|
||||||
.split(top[0]);
|
.split(top[0]);
|
||||||
|
|
||||||
@@ -40,6 +37,9 @@ pub fn draw(f: &mut Frame, app: &mut App) {
|
|||||||
if app.show_help {
|
if app.show_help {
|
||||||
draw_help(f, f.area());
|
draw_help(f, f.area());
|
||||||
}
|
}
|
||||||
|
if app.show_dirs {
|
||||||
|
draw_dirs(f, app, f.area());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_left_panel(f: &mut Frame, app: &mut App, area: Rect) {
|
fn draw_left_panel(f: &mut Frame, app: &mut App, area: Rect) {
|
||||||
@@ -65,11 +65,13 @@ fn draw_left_panel(f: &mut Frame, app: &mut App, area: Rect) {
|
|||||||
};
|
};
|
||||||
app.library.left_state.select(sel);
|
app.library.left_state.select(sel);
|
||||||
|
|
||||||
let title = match app.library.sort_mode {
|
let title_text = match app.library.sort_mode {
|
||||||
SortMode::ByArtist => " Исполнители ",
|
SortMode::ByArtist => " Исполнители ",
|
||||||
SortMode::ByAlbum => " Альбомы ",
|
SortMode::ByAlbum => " Альбомы ",
|
||||||
SortMode::ByFilename => " Источник ",
|
SortMode::ByFilename => " Источник ",
|
||||||
};
|
};
|
||||||
|
let album_color = Color::Rgb(100, 149, 237);
|
||||||
|
let title = Span::styled(title_text, Style::default().fg(album_color));
|
||||||
|
|
||||||
let list = List::new(items)
|
let list = List::new(items)
|
||||||
.block(Block::default().borders(Borders::ALL).title(title))
|
.block(Block::default().borders(Borders::ALL).title(title))
|
||||||
@@ -175,7 +177,7 @@ fn draw_right_panel(f: &mut Frame, app: &mut App, area: Rect) {
|
|||||||
.map(|a| a.name.as_str())
|
.map(|a| a.name.as_str())
|
||||||
.unwrap_or("—");
|
.unwrap_or("—");
|
||||||
|
|
||||||
let title = match app.library.sort_mode {
|
let title_text = match app.library.sort_mode {
|
||||||
SortMode::ByArtist => {
|
SortMode::ByArtist => {
|
||||||
if app.library.filter.is_empty() {
|
if app.library.filter.is_empty() {
|
||||||
format!(" Альбомы и треки - {} ", selected_name)
|
format!(" Альбомы и треки - {} ", selected_name)
|
||||||
@@ -204,6 +206,8 @@ fn draw_right_panel(f: &mut Frame, app: &mut App, area: Rect) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let album_color = Color::Rgb(100, 149, 237);
|
||||||
|
let title = Span::styled(title_text, Style::default().fg(album_color));
|
||||||
|
|
||||||
let highlight = if app.active_panel == Panel::Right {
|
let highlight = if app.active_panel == Panel::Right {
|
||||||
Style::default().add_modifier(Modifier::REVERSED)
|
Style::default().add_modifier(Modifier::REVERSED)
|
||||||
@@ -227,14 +231,11 @@ fn draw_right_panel(f: &mut Frame, app: &mut App, area: Rect) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw_equalizer(f: &mut Frame, app: &App, area: Rect) {
|
fn draw_equalizer(f: &mut Frame, app: &App, area: Rect) {
|
||||||
// Внутренняя ширина панели (минус 2 символа рамки)
|
let inner_width = area.width.saturating_sub(1) as usize;
|
||||||
let inner_width = area.width.saturating_sub(2) as usize;
|
|
||||||
if inner_width == 0 {
|
if inner_width == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Каждый столбик занимает 2 ячейки: 1 символ + 1 gap.
|
|
||||||
// Значит число столбиков = ширина / 2.
|
|
||||||
let num_bars = (inner_width / 2).max(4);
|
let num_bars = (inner_width / 2).max(4);
|
||||||
|
|
||||||
let bars_data = app.player.spectrum.bars();
|
let bars_data = app.player.spectrum.bars();
|
||||||
@@ -298,7 +299,12 @@ fn draw_status(f: &mut Frame, app: &App, area: Rect) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let status = Paragraph::new(status_text)
|
let status = Paragraph::new(status_text)
|
||||||
.block(Block::default().borders(Borders::ALL).title(status_title));
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title(status_title)
|
||||||
|
.title_style(Style::default().fg(Color::Rgb(100, 149, 237)))
|
||||||
|
);
|
||||||
f.render_widget(status, inner[0]);
|
f.render_widget(status, inner[0]);
|
||||||
|
|
||||||
let (pos, total) = app.progress();
|
let (pos, total) = app.progress();
|
||||||
@@ -311,7 +317,12 @@ fn draw_status(f: &mut Frame, app: &App, area: Rect) {
|
|||||||
);
|
);
|
||||||
let time = Paragraph::new(time_text)
|
let time = Paragraph::new(time_text)
|
||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.block(Block::default().borders(Borders::ALL).title(" Время "));
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title(" Время ")
|
||||||
|
.title_style(Style::default().fg(Color::Rgb(100, 149, 237)))
|
||||||
|
);
|
||||||
f.render_widget(time, inner[1]);
|
f.render_widget(time, inner[1]);
|
||||||
|
|
||||||
let mode_str = match (app.repeat, app.shuffle) {
|
let mode_str = match (app.repeat, app.shuffle) {
|
||||||
@@ -324,19 +335,90 @@ fn draw_status(f: &mut Frame, app: &App, area: Rect) {
|
|||||||
};
|
};
|
||||||
let mode = Paragraph::new(mode_str)
|
let mode = Paragraph::new(mode_str)
|
||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.block(Block::default().borders(Borders::ALL).title(" Mode "));
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title(" Pежим ")
|
||||||
|
.title_style(Style::default().fg(Color::Rgb(100, 149, 237)))
|
||||||
|
);
|
||||||
f.render_widget(mode, inner[2]);
|
f.render_widget(mode, inner[2]);
|
||||||
|
|
||||||
let vol_text = format!("Vol: {}%", (app.player.volume() * 100.0) as u32);
|
let vol_text = format!("Vol: {}%", (app.player.volume() * 100.0) as u32);
|
||||||
let vol = Paragraph::new(vol_text)
|
let vol = Paragraph::new(vol_text)
|
||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.block(Block::default().borders(Borders::ALL).title(" "));
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title(" Звук ")
|
||||||
|
.title_style(Style::default().fg(Color::Rgb(100, 149, 237)))
|
||||||
|
);
|
||||||
f.render_widget(vol, inner[3]);
|
f.render_widget(vol, inner[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_dirs(f: &mut Frame, app: &App, screen: Rect) {
|
||||||
|
let width = 70u16.min(screen.width.saturating_sub(4));
|
||||||
|
let total_lines = app.extra_dirs().len() + 8;
|
||||||
|
let desired_height = (total_lines + 2) as u16; // +2 на рамки
|
||||||
|
|
||||||
|
// Ограничиваем высотой экрана
|
||||||
|
let max_h = screen.height.saturating_sub(2);
|
||||||
|
let height = desired_height.min(max_h).max(5); // минимум 5 строк
|
||||||
|
|
||||||
|
let x = (screen.width.saturating_sub(width)) / 2;
|
||||||
|
let y = (screen.height.saturating_sub(height)) / 2;
|
||||||
|
let area = Rect::new(x, y, width, height);
|
||||||
|
|
||||||
|
f.render_widget(Clear, area);
|
||||||
|
|
||||||
|
let title_color = Style::default().fg(Color::Rgb(100, 149, 237));
|
||||||
|
let header = Style::default()
|
||||||
|
.fg(Color::Yellow)
|
||||||
|
.add_modifier(Modifier::BOLD);
|
||||||
|
let dim = Style::default().fg(Color::DarkGray);
|
||||||
|
|
||||||
|
let mut lines = vec![
|
||||||
|
Line::from(Span::styled(
|
||||||
|
" Директории библиотеки ",
|
||||||
|
title_color.add_modifier(Modifier::BOLD),
|
||||||
|
)),
|
||||||
|
Line::from(""),
|
||||||
|
Line::from(Span::styled(" Базовые", header)),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled(" [0] ", dim),
|
||||||
|
Span::raw(app.base_dir()),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
if !app.extra_dirs().is_empty() {
|
||||||
|
lines.push(Line::from(""));
|
||||||
|
lines.push(Line::from(Span::styled(" Дополнительные (:add)", header)));
|
||||||
|
for (i, dir) in app.extra_dirs().iter().enumerate() {
|
||||||
|
lines.push(Line::from(vec![
|
||||||
|
Span::styled(format!(" [{}] ", i + 1), dim),
|
||||||
|
Span::raw(dir.clone()),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(Line::from(""));
|
||||||
|
lines.push(Line::from(Span::styled(
|
||||||
|
" :rmdir <N> — удалить, :clear — очистить всё, Esc — закрыть",
|
||||||
|
dim,
|
||||||
|
)));
|
||||||
|
|
||||||
|
let para = Paragraph::new(lines).block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_style(title_color)
|
||||||
|
.title(" Библиотека "),
|
||||||
|
);
|
||||||
|
|
||||||
|
f.render_widget(para, area);
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_help(f: &mut Frame, screen: Rect) {
|
fn draw_help(f: &mut Frame, screen: Rect) {
|
||||||
let width = 62u16.min(screen.width.saturating_sub(4));
|
let width = 62u16.min(screen.width.saturating_sub(4));
|
||||||
let height = 40u16.min(screen.height.saturating_sub(4));
|
let height = 44u16.min(screen.height.saturating_sub(4));
|
||||||
|
|
||||||
let x = (screen.width.saturating_sub(width)) / 2;
|
let x = (screen.width.saturating_sub(width)) / 2;
|
||||||
let y = (screen.height.saturating_sub(height)) / 2;
|
let y = (screen.height.saturating_sub(height)) / 2;
|
||||||
@@ -380,6 +462,7 @@ fn draw_help(f: &mut Frame, screen: Rect) {
|
|||||||
Line::from(" :add <путь> добавить директорию"),
|
Line::from(" :add <путь> добавить директорию"),
|
||||||
Line::from(" :reload пересканировать всё"),
|
Line::from(" :reload пересканировать всё"),
|
||||||
Line::from(" :save сохранить библиотеку"),
|
Line::from(" :save сохранить библиотеку"),
|
||||||
|
Line::from(" :dirs управление директориями"),
|
||||||
Line::from(" :help эта справка"),
|
Line::from(" :help эта справка"),
|
||||||
Line::from(" :q выход"),
|
Line::from(" :q выход"),
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
|
|||||||
Reference in New Issue
Block a user