feat(UI): Added comments for Ui
This commit is contained in:
1 parent
39e19f29c1
commit
91b56f9319
1 file changed
+85
@@ -1,3 +1,8 @@
|
|||||||
|
//! Графический интерфейс для операций rclone.
|
||||||
|
//!
|
||||||
|
//! Этот модуль содержит главную структуру `RcloneUI` и её реализацию,
|
||||||
|
//! включая панели, диалоги, просмотр файлов и управление передачей данных.
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use egui::{CentralPanel, ScrollArea, Window, Align2, Color32, ProgressBar};
|
use egui::{CentralPanel, ScrollArea, Window, Align2, Color32, ProgressBar};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -5,56 +10,102 @@ use tokio::runtime::Runtime;
|
|||||||
use crate::rclone_install::RcloneApp;
|
use crate::rclone_install::RcloneApp;
|
||||||
use crate::operations::{self, FileInfo, Remote, CopyOptions, FindOptions};
|
use crate::operations::{self, FileInfo, Remote, CopyOptions, FindOptions};
|
||||||
|
|
||||||
|
/// Состояние приложения.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum AppState {
|
pub enum AppState {
|
||||||
|
/// Приложение инициализируется (проверка наличия rclone).
|
||||||
Initializing,
|
Initializing,
|
||||||
|
/// Приложение готово и простаивает.
|
||||||
Ready,
|
Ready,
|
||||||
|
/// Произошла ошибка, хранится текст ошибки.
|
||||||
Error(String),
|
Error(String),
|
||||||
|
/// Выполняется копирование.
|
||||||
Copying,
|
Copying,
|
||||||
|
/// Выполняется синхронизация.
|
||||||
Syncing,
|
Syncing,
|
||||||
|
/// Выполняется перемещение.
|
||||||
Moving,
|
Moving,
|
||||||
|
/// Выполняется удаление.
|
||||||
Deleting,
|
Deleting,
|
||||||
|
/// Загрузка файлов или информации о хранилищах.
|
||||||
Loading,
|
Loading,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Информация о ходе передачи.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TransferProgress {
|
pub struct TransferProgress {
|
||||||
|
/// Передано байт.
|
||||||
pub current: u64,
|
pub current: u64,
|
||||||
|
/// Всего байт.
|
||||||
pub total: u64,
|
pub total: u64,
|
||||||
|
/// Текущая скорость (байт/с).
|
||||||
pub speed: f64,
|
pub speed: f64,
|
||||||
|
/// Имя текущего передаваемого файла.
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Главная структура UI, содержащая всё состояние и логику.
|
||||||
pub struct RcloneUI {
|
pub struct RcloneUI {
|
||||||
|
/// Экземпляр rclone (инициализируется при первом использовании).
|
||||||
rclone: Option<Arc<RcloneApp>>,
|
rclone: Option<Arc<RcloneApp>>,
|
||||||
|
/// Текущее состояние приложения.
|
||||||
state: AppState,
|
state: AppState,
|
||||||
|
/// Глобальное сообщение об ошибке.
|
||||||
error_message: Option<String>,
|
error_message: Option<String>,
|
||||||
|
|
||||||
|
// Навигация
|
||||||
|
/// Текущий отображаемый путь.
|
||||||
current_path: String,
|
current_path: String,
|
||||||
|
/// Список доступных удалённых хранилищ.
|
||||||
remote_list: Vec<Remote>,
|
remote_list: Vec<Remote>,
|
||||||
|
/// Файлы и папки в текущем пути.
|
||||||
current_files: Vec<FileInfo>,
|
current_files: Vec<FileInfo>,
|
||||||
|
|
||||||
|
// Выделение
|
||||||
|
/// Выбранные пользователем пути.
|
||||||
selected_paths: Vec<String>,
|
selected_paths: Vec<String>,
|
||||||
|
|
||||||
|
// Передача
|
||||||
|
/// Исходный путь для операции передачи.
|
||||||
transfer_source: String,
|
transfer_source: String,
|
||||||
|
/// Целевой путь для операции передачи.
|
||||||
transfer_dest: String,
|
transfer_dest: String,
|
||||||
|
/// Прогресс текущей передачи.
|
||||||
transfer_progress: Option<TransferProgress>,
|
transfer_progress: Option<TransferProgress>,
|
||||||
|
|
||||||
|
// Поиск
|
||||||
|
/// Текущий шаблон поиска.
|
||||||
search_pattern: String,
|
search_pattern: String,
|
||||||
|
/// Результаты последнего поиска.
|
||||||
search_results: Vec<FileInfo>,
|
search_results: Vec<FileInfo>,
|
||||||
|
|
||||||
|
// Диалоги UI
|
||||||
|
/// Открыт ли диалог передачи.
|
||||||
show_transfer_dialog: bool,
|
show_transfer_dialog: bool,
|
||||||
|
/// Открыт ли диалог создания нового хранилища.
|
||||||
show_new_remote_dialog: bool,
|
show_new_remote_dialog: bool,
|
||||||
|
/// Имя нового хранилища.
|
||||||
new_remote_name: String,
|
new_remote_name: String,
|
||||||
|
/// Тип нового хранилища (например, "s3", "dropbox").
|
||||||
new_remote_type: String,
|
new_remote_type: String,
|
||||||
|
/// Конфигурационные параметры нового хранилища.
|
||||||
new_remote_config: std::collections::HashMap<String, String>,
|
new_remote_config: std::collections::HashMap<String, String>,
|
||||||
|
|
||||||
|
// Настройки
|
||||||
|
/// Настройки пользователя.
|
||||||
settings: AppSettings,
|
settings: AppSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Настройки приложения, задаваемые пользователем.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppSettings {
|
pub struct AppSettings {
|
||||||
|
/// Показывать скрытые файлы.
|
||||||
pub show_hidden: bool,
|
pub show_hidden: bool,
|
||||||
|
/// Запрашивать подтверждение перед началом передачи.
|
||||||
pub confirm_before_transfer: bool,
|
pub confirm_before_transfer: bool,
|
||||||
|
/// Максимальное количество одновременных передач.
|
||||||
pub max_concurrent_transfers: u32,
|
pub max_concurrent_transfers: u32,
|
||||||
|
/// Ограничение пропускной способности (например, "1M").
|
||||||
pub bandwidth_limit: Option<String>,
|
pub bandwidth_limit: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +121,9 @@ impl Default for AppSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RcloneUI {
|
impl RcloneUI {
|
||||||
|
/// Создаёт новый экземпляр UI.
|
||||||
|
///
|
||||||
|
/// Бэкенд rclone инициализируется позже, при первом вызове `update`.
|
||||||
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rclone: None,
|
rclone: None,
|
||||||
@@ -93,6 +147,9 @@ impl RcloneUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Инициализирует бэкенд rclone.
|
||||||
|
///
|
||||||
|
/// Пытается найти системную установку или скачивает последнюю версию.
|
||||||
fn init_rclone(&mut self) {
|
fn init_rclone(&mut self) {
|
||||||
let rt = Runtime::new().unwrap();
|
let rt = Runtime::new().unwrap();
|
||||||
let rclone_result = rt.block_on(async {
|
let rclone_result = rt.block_on(async {
|
||||||
@@ -112,6 +169,7 @@ impl RcloneUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Загружает список удалённых хранилищ и обновляет левую панель.
|
||||||
fn load_remotes(&mut self) {
|
fn load_remotes(&mut self) {
|
||||||
if let Some(rclone) = &self.rclone {
|
if let Some(rclone) = &self.rclone {
|
||||||
match operations::remotes::list(rclone) {
|
match operations::remotes::list(rclone) {
|
||||||
@@ -128,6 +186,10 @@ impl RcloneUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Загружает содержимое каталога.
|
||||||
|
///
|
||||||
|
/// # Аргументы
|
||||||
|
/// * `path` – путь к удалённому хранилищу (например, "remote:" или "remote:папка/подпапка").
|
||||||
fn load_files(&mut self, path: &str) {
|
fn load_files(&mut self, path: &str) {
|
||||||
if let Some(rclone) = &self.rclone {
|
if let Some(rclone) = &self.rclone {
|
||||||
match operations::files::list(rclone, path) {
|
match operations::files::list(rclone, path) {
|
||||||
@@ -144,6 +206,9 @@ impl RcloneUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Выполняет операцию копирования из источника в приёмник.
|
||||||
|
///
|
||||||
|
/// Запускается в отдельном потоке, чтобы не блокировать UI.
|
||||||
fn perform_copy(&mut self, source: &str, dest: &str) {
|
fn perform_copy(&mut self, source: &str, dest: &str) {
|
||||||
if let Some(rclone) = &self.rclone {
|
if let Some(rclone) = &self.rclone {
|
||||||
self.state = AppState::Copying;
|
self.state = AppState::Copying;
|
||||||
@@ -155,6 +220,7 @@ impl RcloneUI {
|
|||||||
no_traverse: false,
|
no_traverse: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Копирование в отдельном потоке
|
||||||
let rclone_clone = rclone.clone();
|
let rclone_clone = rclone.clone();
|
||||||
let source_clone = source.to_string();
|
let source_clone = source.to_string();
|
||||||
let dest_clone = dest.to_string();
|
let dest_clone = dest.to_string();
|
||||||
@@ -167,6 +233,7 @@ impl RcloneUI {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Для демонстрации имитируем завершение
|
||||||
self.transfer_progress = Some(TransferProgress {
|
self.transfer_progress = Some(TransferProgress {
|
||||||
current: 100,
|
current: 100,
|
||||||
total: 100,
|
total: 100,
|
||||||
@@ -177,6 +244,7 @@ impl RcloneUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ищет файлы по шаблону.
|
||||||
fn search_files(&mut self, pattern: &str) {
|
fn search_files(&mut self, pattern: &str) {
|
||||||
if let Some(rclone) = &self.rclone {
|
if let Some(rclone) = &self.rclone {
|
||||||
let options = FindOptions {
|
let options = FindOptions {
|
||||||
@@ -195,6 +263,7 @@ impl RcloneUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Форматирует размер в байтах в удобочитаемый вид.
|
||||||
fn format_size(bytes: u64) -> String {
|
fn format_size(bytes: u64) -> String {
|
||||||
const KB: u64 = 1024;
|
const KB: u64 = 1024;
|
||||||
const MB: u64 = KB * 1024;
|
const MB: u64 = KB * 1024;
|
||||||
@@ -211,6 +280,7 @@ impl RcloneUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Форматирует скорость передачи (байт/с) в удобочитаемый вид.
|
||||||
fn format_speed(speed: f64) -> String {
|
fn format_speed(speed: f64) -> String {
|
||||||
if speed < 1024.0 {
|
if speed < 1024.0 {
|
||||||
format!("{:.0} Б/с", speed)
|
format!("{:.0} Б/с", speed)
|
||||||
@@ -225,11 +295,14 @@ impl RcloneUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for RcloneUI {
|
impl eframe::App for RcloneUI {
|
||||||
|
/// Вызывается каждый кадр для обновления интерфейса.
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
// Инициализация rclone, если ещё не выполнена
|
||||||
if matches!(self.state, AppState::Initializing) && self.rclone.is_none() {
|
if matches!(self.state, AppState::Initializing) && self.rclone.is_none() {
|
||||||
self.init_rclone();
|
self.init_rclone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Верхняя панель меню
|
||||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||||
egui::menu::bar(ui, |ui| {
|
egui::menu::bar(ui, |ui| {
|
||||||
ui.menu_button("Файл", |ui| {
|
ui.menu_button("Файл", |ui| {
|
||||||
@@ -292,6 +365,7 @@ impl eframe::App for RcloneUI {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Левая панель — список хранилищ
|
||||||
egui::SidePanel::left("remotes_panel")
|
egui::SidePanel::left("remotes_panel")
|
||||||
.default_width(200.0)
|
.default_width(200.0)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
@@ -319,6 +393,7 @@ impl eframe::App for RcloneUI {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Центральная панель — основное содержимое
|
||||||
CentralPanel::default().show(ctx, |ui| {
|
CentralPanel::default().show(ctx, |ui| {
|
||||||
// Верхняя панель с путём и поиском
|
// Верхняя панель с путём и поиском
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
@@ -353,6 +428,7 @@ impl eframe::App for RcloneUI {
|
|||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
|
// Состояние загрузки
|
||||||
if matches!(self.state, AppState::Loading) || matches!(self.state, AppState::Initializing) {
|
if matches!(self.state, AppState::Loading) || matches!(self.state, AppState::Initializing) {
|
||||||
ui.centered_and_justified(|ui| {
|
ui.centered_and_justified(|ui| {
|
||||||
ui.label("Загрузка...");
|
ui.label("Загрузка...");
|
||||||
@@ -360,6 +436,7 @@ impl eframe::App for RcloneUI {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Состояние ошибки
|
||||||
if let AppState::Error(msg) = &self.state {
|
if let AppState::Error(msg) = &self.state {
|
||||||
ui.centered_and_justified(|ui| {
|
ui.centered_and_justified(|ui| {
|
||||||
ui.colored_label(Color32::RED, format!("Ошибка: {}", msg));
|
ui.colored_label(Color32::RED, format!("Ошибка: {}", msg));
|
||||||
@@ -367,6 +444,7 @@ impl eframe::App for RcloneUI {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Результаты поиска или список файлов
|
||||||
if !self.search_results.is_empty() {
|
if !self.search_results.is_empty() {
|
||||||
let search_results = self.search_results.clone();
|
let search_results = self.search_results.clone();
|
||||||
let current_path = self.current_path.clone();
|
let current_path = self.current_path.clone();
|
||||||
@@ -396,6 +474,7 @@ impl eframe::App for RcloneUI {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if !self.current_path.is_empty() {
|
} else if !self.current_path.is_empty() {
|
||||||
|
// Файловый менеджер
|
||||||
let current_files = self.current_files.clone();
|
let current_files = self.current_files.clone();
|
||||||
let current_path = self.current_path.clone();
|
let current_path = self.current_path.clone();
|
||||||
ScrollArea::vertical().show(ui, |ui| {
|
ScrollArea::vertical().show(ui, |ui| {
|
||||||
@@ -448,6 +527,7 @@ impl eframe::App for RcloneUI {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Приветственный экран
|
||||||
ui.centered_and_justified(|ui| {
|
ui.centered_and_justified(|ui| {
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
ui.add_space(50.0);
|
ui.add_space(50.0);
|
||||||
@@ -466,6 +546,7 @@ impl eframe::App for RcloneUI {
|
|||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
|
// Кнопки действий
|
||||||
if !self.selected_paths.is_empty() {
|
if !self.selected_paths.is_empty() {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(format!("Выбрано: {} элемент(ов)", self.selected_paths.len()));
|
ui.label(format!("Выбрано: {} элемент(ов)", self.selected_paths.len()));
|
||||||
@@ -489,6 +570,7 @@ impl eframe::App for RcloneUI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Прогресс передачи
|
||||||
if let Some(progress) = &self.transfer_progress {
|
if let Some(progress) = &self.transfer_progress {
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
@@ -504,6 +586,7 @@ impl eframe::App for RcloneUI {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Диалоги
|
||||||
if self.show_transfer_dialog {
|
if self.show_transfer_dialog {
|
||||||
let transfer_source = self.transfer_source.clone();
|
let transfer_source = self.transfer_source.clone();
|
||||||
let transfer_dest = self.transfer_dest.clone();
|
let transfer_dest = self.transfer_dest.clone();
|
||||||
@@ -600,6 +683,7 @@ impl eframe::App for RcloneUI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Диалог ошибок
|
||||||
if let Some(error) = &self.error_message {
|
if let Some(error) = &self.error_message {
|
||||||
let error_clone = error.clone();
|
let error_clone = error.clone();
|
||||||
Window::new("Ошибка")
|
Window::new("Ошибка")
|
||||||
@@ -620,6 +704,7 @@ impl eframe::App for RcloneUI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Запрос перерисовки для анимаций
|
||||||
ctx.request_repaint();
|
ctx.request_repaint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user