From 42c147a13efce1e39f7f39c10bf12aa3c5590f82 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Fri, 19 Jun 2026 17:03:20 +0200 Subject: [PATCH] chore: apply clippy fixes and cargo fmt - Fix 5 clippy warnings: collapsible ifs, useless format - Format all source files with cargo fmt --- src/ui/app.rs | 78 +++++++++++----------- src/ui/dialogs/connection.rs | 100 +++++++++++++++++++++-------- src/ui/panels/file_pane.rs | 121 ++++++++++++++++++++++++----------- src/ui/panels/local_pane.rs | 61 ++++++++++-------- src/ui/panels/queue.rs | 110 ++++++++++++++++++------------- src/ui/panels/remote_pane.rs | 46 ++++++++----- src/ui/panels/sidebar.rs | 45 +++++++------ src/ui/panels/status_bar.rs | 18 ++++-- src/ui/panels/tabs.rs | 49 +++++++++----- src/ui/panels/toolbar.rs | 62 +++++++++++------- src/ui/state.rs | 63 +++++++++++++----- src/ui/theme.rs | 54 ++++++++-------- 12 files changed, 509 insertions(+), 298 deletions(-) diff --git a/src/ui/app.rs b/src/ui/app.rs index a8bdb7d..ba8bb14 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use egui::{Color32, CentralPanel, RichText, SidePanel, TopBottomPanel, Visuals}; +use egui::{CentralPanel, Color32, RichText, SidePanel, TopBottomPanel, Visuals}; use crate::domain::connection::ConnectionStatus; use crate::domain::file_entry::{EntryKind, FileEntry}; @@ -8,9 +8,7 @@ use crate::domain::site::Site; use crate::fs::remote::RemoteRegistry; use crate::transfer::queue::TransferQueue; use crate::ui::dialogs::connection; -use crate::ui::panels::{ - local_pane, queue, remote_pane, sidebar, status_bar, tabs, toolbar, -}; +use crate::ui::panels::{local_pane, queue, remote_pane, sidebar, status_bar, tabs, toolbar}; use crate::ui::theme::*; pub struct FileManagerApp { @@ -83,7 +81,8 @@ impl FileManagerApp { self.state.status_message = format!("Connected to {}", params.host); self.state.connect_loading = false; self.state.show_connect_dialog = false; - self.state.add_history(¶ms.host, params.port, ¶ms.username); + self.state + .add_history(¶ms.host, params.port, ¶ms.username); } Err(e) => { self.state.status_message = format!("Connection failed: {}", e); @@ -152,7 +151,9 @@ impl FileManagerApp { } fn active_tab_idx(&self) -> Option { - if self.state.tabs.is_empty() { return None; } + if self.state.tabs.is_empty() { + return None; + } let idx = self.state.active_tab.min(self.state.tabs.len() - 1); if self.state.tabs[idx].status == ConnectionStatus::Connected { Some(idx) @@ -163,25 +164,25 @@ impl FileManagerApp { fn apply_visuals(&self, ctx: &egui::Context) { let mut vis = Visuals::dark(); - vis.window_fill = BG_PANEL; - vis.panel_fill = BG_CONTENT; + vis.window_fill = BG_PANEL; + vis.panel_fill = BG_CONTENT; vis.extreme_bg_color = BG_BASE; - vis.code_bg_color = BG_BASE; + vis.code_bg_color = BG_BASE; vis.override_text_color = Some(TEXT_PRIMARY); vis.widgets.noninteractive.bg_stroke = egui::Stroke::new(1.0, BORDER); - vis.widgets.inactive.bg_fill = Color32::from_rgb(36, 36, 40); - vis.widgets.hovered.bg_fill = BG_ROW_HOVER; - vis.widgets.active.bg_fill = ACCENT_DIM; - vis.selection.bg_fill = BG_ROW_SEL; - vis.selection.stroke = egui::Stroke::new(1.0, ACCENT); - vis.window_rounding = egui::Rounding::same(8.0); + vis.widgets.inactive.bg_fill = Color32::from_rgb(36, 36, 40); + vis.widgets.hovered.bg_fill = BG_ROW_HOVER; + vis.widgets.active.bg_fill = ACCENT_DIM; + vis.selection.bg_fill = BG_ROW_SEL; + vis.selection.stroke = egui::Stroke::new(1.0, ACCENT); + vis.window_rounding = egui::Rounding::same(8.0); ctx.set_visuals(vis); let fonts = egui::FontDefinitions::default(); ctx.set_fonts(fonts); let mut style = ctx.style().as_ref().clone(); - style.spacing.item_spacing = egui::vec2(4.0, 2.0); + style.spacing.item_spacing = egui::vec2(4.0, 2.0); style.spacing.button_padding = egui::vec2(8.0, 4.0); ctx.set_style(style); } @@ -234,7 +235,11 @@ impl eframe::App for FileManagerApp { QUEUE_COLLAPSED_H }; TopBottomPanel::bottom("queue_panel") - .frame(egui::Frame::none().fill(BG_QUEUE).stroke(egui::Stroke::new(1.0, BORDER))) + .frame( + egui::Frame::none() + .fill(BG_QUEUE) + .stroke(egui::Stroke::new(1.0, BORDER)), + ) .resizable(false) .exact_height(q_h) .show(ctx, |ui| { @@ -243,9 +248,11 @@ impl eframe::App for FileManagerApp { // ── Sidebar ─────────────────────────────────────────────────────────── SidePanel::left("sidebar") - .frame(egui::Frame::none() - .fill(BG_PANEL) - .stroke(egui::Stroke::new(1.0, BORDER))) + .frame( + egui::Frame::none() + .fill(BG_PANEL) + .stroke(egui::Stroke::new(1.0, BORDER)), + ) .resizable(true) .default_width(SIDEBAR_W) .width_range(120.0..=260.0) @@ -295,10 +302,7 @@ impl eframe::App for FileManagerApp { let idx = self.state.active_tab.min(self.state.tabs.len() - 1); // Хедер панели - let label = self.state.tabs[idx] - .params - .host - .clone(); + let label = self.state.tabs[idx].params.host.clone(); pane_header(ui, &format!("REMOTE · {}", label), half); remote_pane::render( @@ -313,9 +317,7 @@ impl eframe::App for FileManagerApp { // Empty state pane_header(ui, "REMOTE", half); ui.with_layout( - egui::Layout::centered_and_justified( - egui::Direction::TopDown, - ), + egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { ui.vertical_centered(|ui| { ui.add_space(ui.available_height() * 0.3); @@ -327,9 +329,11 @@ impl eframe::App for FileManagerApp { ); ui.add_space(4.0); ui.label( - RichText::new("Connect to a server to browse files") - .color(TEXT_HINT) - .size(12.0), + RichText::new( + "Connect to a server to browse files", + ) + .color(TEXT_HINT) + .size(12.0), ); ui.add_space(16.0); let btn = egui::Button::new( @@ -365,16 +369,16 @@ impl eframe::App for FileManagerApp { fn pane_header(ui: &mut egui::Ui, label: &str, width: f32) { egui::Frame::none() .fill(BG_BASE) - .inner_margin(egui::Margin { left: 10.0, right: 6.0, top: 4.0, bottom: 4.0 }) + .inner_margin(egui::Margin { + left: 10.0, + right: 6.0, + top: 4.0, + bottom: 4.0, + }) .show(ui, |ui| { ui.set_width(width); ui.horizontal(|ui| { - ui.label( - RichText::new(label) - .color(TEXT_DIM) - .size(10.0) - .strong(), - ); + ui.label(RichText::new(label).color(TEXT_DIM).size(10.0).strong()); }); }); diff --git a/src/ui/dialogs/connection.rs b/src/ui/dialogs/connection.rs index 9128fd6..75e973d 100644 --- a/src/ui/dialogs/connection.rs +++ b/src/ui/dialogs/connection.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use crate::domain::connection::{ConnectionParams, Protocol}; use crate::domain::file_entry::FileEntry; use crate::fs::remote::RemoteRegistry; -use crate::protocols::{ftp::FtpClient, sftp::SftpClient, RemoteFs}; +use crate::protocols::{RemoteFs, ftp::FtpClient, sftp::SftpClient}; use crate::storage::keychain; use crate::ui::state::{AppState, PendingConnect}; use crate::ui::theme::*; @@ -31,7 +31,8 @@ pub fn render( .order(egui::Order::Background) .show(ctx, |ui| { let r = ui.allocate_rect(screen, egui::Sense::click()); - ui.painter().rect_filled(screen, 0.0, Color32::from_black_alpha(160)); + ui.painter() + .rect_filled(screen, 0.0, Color32::from_black_alpha(160)); if r.clicked() && !state.connect_loading { state.show_connect_dialog = false; } @@ -57,10 +58,16 @@ pub fn render( // Заголовок ui.horizontal(|ui| { - ui.label(RichText::new("New Connection").color(TEXT_PRIMARY).size(16.0).strong()); + ui.label( + RichText::new("New Connection") + .color(TEXT_PRIMARY) + .size(16.0) + .strong(), + ); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let close = egui::Button::new(RichText::new("×").color(TEXT_HINT).size(14.0)) - .fill(Color32::TRANSPARENT); + let close = + egui::Button::new(RichText::new("×").color(TEXT_HINT).size(14.0)) + .fill(Color32::TRANSPARENT); if ui.add(close).clicked() { state.show_connect_dialog = false; } @@ -73,7 +80,11 @@ pub fn render( ui.horizontal(|ui| { for (i, p) in protocols.iter().enumerate() { let active = state.connect_protocol == i; - let bg = if active { ACCENT } else { Color32::from_rgb(40, 40, 44) }; + let bg = if active { + ACCENT + } else { + Color32::from_rgb(40, 40, 44) + }; let tc = if active { Color32::WHITE } else { TEXT_DIM }; let btn = egui::Button::new(RichText::new(*p).color(tc).size(12.0)) .fill(bg) @@ -90,7 +101,13 @@ pub fn render( // Поля ввода let field_w = ui.available_width(); - field(ui, "Label (optional)", &mut state.connect_label, field_w, false); + field( + ui, + "Label (optional)", + &mut state.connect_label, + field_w, + false, + ); ui.add_space(8.0); ui.horizontal(|ui| { @@ -108,13 +125,21 @@ pub fn render( if state.connect_protocol == 0 { ui.add_space(8.0); ui.horizontal(|ui| { - field(ui, "Key path", &mut state.connect_key_path, field_w - 80.0 - 8.0, false); + field( + ui, + "Key path", + &mut state.connect_key_path, + field_w - 80.0 - 8.0, + false, + ); ui.add_space(8.0); // Browse placeholder — open system picker if rfd added - let browse = egui::Button::new(RichText::new("Browse").color(TEXT_DIM).size(11.0)) - .fill(Color32::from_rgb(40, 40, 44)) - .min_size(egui::vec2(72.0, 32.0)) - .rounding(4.0); + let browse = egui::Button::new( + RichText::new("Browse").color(TEXT_DIM).size(11.0), + ) + .fill(Color32::from_rgb(40, 40, 44)) + .min_size(egui::vec2(72.0, 32.0)) + .rounding(4.0); ui.add_enabled(false, browse); }); } @@ -135,10 +160,11 @@ pub fn render( // Кнопки ui.horizontal(|ui| { - let cancel = egui::Button::new(RichText::new("Cancel").color(TEXT_DIM).size(12.0)) - .fill(Color32::from_rgb(40, 40, 44)) - .rounding(6.0) - .min_size(egui::vec2(90.0, 34.0)); + let cancel = + egui::Button::new(RichText::new("Cancel").color(TEXT_DIM).size(12.0)) + .fill(Color32::from_rgb(40, 40, 44)) + .rounding(6.0) + .min_size(egui::vec2(90.0, 34.0)); if ui.add(cancel).clicked() && !state.connect_loading { state.show_connect_dialog = false; } @@ -151,7 +177,10 @@ pub fn render( "Connect" }; let connect_btn = egui::Button::new( - RichText::new(connect_label).color(Color32::WHITE).size(12.0).strong() + RichText::new(connect_label) + .color(Color32::WHITE) + .size(12.0) + .strong(), ) .fill(ACCENT) .rounding(6.0) @@ -197,18 +226,22 @@ fn do_connect( let port: u16 = state.connect_port.parse().unwrap_or(match protocol { Protocol::Sftp => 22, - Protocol::Ftp => 21, + Protocol::Ftp => 21, Protocol::Ftps => 990, }); let params = ConnectionParams { id: uuid::Uuid::new_v4().to_string(), label: if state.connect_label.is_empty() { - format!("{} ({})", state.connect_host, match protocol { - Protocol::Sftp => "SFTP", - Protocol::Ftp => "FTP", - Protocol::Ftps => "FTPS", - }) + format!( + "{} ({})", + state.connect_host, + match protocol { + Protocol::Sftp => "SFTP", + Protocol::Ftp => "FTP", + Protocol::Ftps => "FTPS", + } + ) } else { state.connect_label.clone() }, @@ -216,8 +249,16 @@ fn do_connect( host: state.connect_host.clone(), port, username: state.connect_user.clone(), - password: if state.connect_pass.is_empty() { None } else { Some(state.connect_pass.clone()) }, - key_path: if state.connect_key_path.is_empty() { None } else { Some(state.connect_key_path.clone()) }, + password: if state.connect_pass.is_empty() { + None + } else { + Some(state.connect_pass.clone()) + }, + key_path: if state.connect_key_path.is_empty() { + None + } else { + Some(state.connect_key_path.clone()) + }, }; let registry = registry.clone(); @@ -253,8 +294,13 @@ async fn connect_async( ) } else { Arc::new( - SftpClient::connect_password(¶ms.host, params.port, ¶ms.username, &password) - .map_err(|e| e.to_string())?, + SftpClient::connect_password( + ¶ms.host, + params.port, + ¶ms.username, + &password, + ) + .map_err(|e| e.to_string())?, ) } } diff --git a/src/ui/panels/file_pane.rs b/src/ui/panels/file_pane.rs index 065ab38..3853f54 100644 --- a/src/ui/panels/file_pane.rs +++ b/src/ui/panels/file_pane.rs @@ -1,8 +1,8 @@ //! Переиспользуемая таблица файлов с нормальными колонками -use egui::{Color32, Id, RichText, Ui}; use crate::domain::file_entry::{EntryKind, FileEntry}; use crate::ui::drag::DragPayload; use crate::ui::theme::*; +use egui::{Color32, Id, RichText, Ui}; pub fn format_size(bytes: Option) -> String { match bytes { @@ -22,10 +22,18 @@ pub fn format_time(ts: Option) -> String { } #[derive(Clone, Copy, PartialEq)] -pub enum SortCol { Name, Size, Kind, Modified } +pub enum SortCol { + Name, + Size, + Kind, + Modified, +} #[derive(Clone, Copy, PartialEq)] -pub enum SortDir { Asc, Desc } +pub enum SortDir { + Asc, + Desc, +} pub struct FileTableResponse { pub double_clicked: Option, @@ -44,9 +52,9 @@ where { // Сортировка — читаем из egui memory let sort_id = Id::new((id, "sort")); - let (sort_col, sort_dir) = ui.ctx().data_mut(|d| { - *d.get_temp_mut_or(sort_id, (SortCol::Name, SortDir::Asc)) - }); + let (sort_col, sort_dir) = ui + .ctx() + .data_mut(|d| *d.get_temp_mut_or(sort_id, (SortCol::Name, SortDir::Asc))); let mut sorted = entries.to_vec(); let dotdot_entry = sorted.iter().position(|e| e.name == ".."); @@ -60,15 +68,21 @@ where (_, EntryKind::Dir) => std::cmp::Ordering::Less, _ => std::cmp::Ordering::Equal, }; - if dir_cmp != std::cmp::Ordering::Equal { return dir_cmp; } + if dir_cmp != std::cmp::Ordering::Equal { + return dir_cmp; + } let ord = match sort_col { - SortCol::Name => a.name.to_lowercase().cmp(&b.name.to_lowercase()), - SortCol::Size => a.size.unwrap_or(0).cmp(&b.size.unwrap_or(0)), - SortCol::Kind => format!("{:?}", a.kind).cmp(&format!("{:?}", b.kind)), + SortCol::Name => a.name.to_lowercase().cmp(&b.name.to_lowercase()), + SortCol::Size => a.size.unwrap_or(0).cmp(&b.size.unwrap_or(0)), + SortCol::Kind => format!("{:?}", a.kind).cmp(&format!("{:?}", b.kind)), SortCol::Modified => a.modified.unwrap_or(0).cmp(&b.modified.unwrap_or(0)), }; - if sort_dir == SortDir::Desc { ord.reverse() } else { ord } + if sort_dir == SortDir::Desc { + ord.reverse() + } else { + ord + } }); if let Some(dd) = dotdot { @@ -82,7 +96,12 @@ where // Заголовки колонок let header_frame = egui::Frame::none() .fill(BG_HEADER) - .inner_margin(egui::Margin { left: 6.0, right: 6.0, top: 0.0, bottom: 0.0 }); + .inner_margin(egui::Margin { + left: 6.0, + right: 6.0, + top: 0.0, + bottom: 0.0, + }); header_frame.show(ui, |ui| { ui.set_min_height(22.0); @@ -93,20 +112,34 @@ where } ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let mod_r = sort_header(ui, "Modified", 140.0, sort_col == SortCol::Modified, sort_dir); - if mod_r { new_sort = Some(toggle_sort(SortCol::Modified, sort_col, sort_dir)); } + let mod_r = sort_header( + ui, + "Modified", + 140.0, + sort_col == SortCol::Modified, + sort_dir, + ); + if mod_r { + new_sort = Some(toggle_sort(SortCol::Modified, sort_col, sort_dir)); + } let type_r = sort_header(ui, "Type", 40.0, sort_col == SortCol::Kind, sort_dir); - if type_r { new_sort = Some(toggle_sort(SortCol::Kind, sort_col, sort_dir)); } + if type_r { + new_sort = Some(toggle_sort(SortCol::Kind, sort_col, sort_dir)); + } let size_r = sort_header(ui, "Size", 70.0, sort_col == SortCol::Size, sort_dir); - if size_r { new_sort = Some(toggle_sort(SortCol::Size, sort_col, sort_dir)); } + if size_r { + new_sort = Some(toggle_sort(SortCol::Size, sort_col, sort_dir)); + } }); }); }); if let Some(s) = new_sort { - ui.ctx().data_mut(|d| { d.insert_temp(sort_id, s); }); + ui.ctx().data_mut(|d| { + d.insert_temp(sort_id, s); + }); } // разделитель @@ -137,18 +170,23 @@ where let response = egui::Frame::none() .fill(bg) - .inner_margin(egui::Margin { left: 6.0, right: 6.0, top: 0.0, bottom: 0.0 }) + .inner_margin(egui::Margin { + left: 6.0, + right: 6.0, + top: 0.0, + bottom: 0.0, + }) .show(ui, |ui| { ui.set_min_height(ROW_H); ui.horizontal_centered(|ui| { // иконка + имя let name_text = if entry.name == ".." { - format!("⬆ ..") + "⬆ ..".to_string() } else { match entry.kind { - EntryKind::Dir => format!("📁 {}", entry.name), + EntryKind::Dir => format!("📁 {}", entry.name), EntryKind::Symlink => format!("🔗 {}", entry.name), - EntryKind::File => { + EntryKind::File => { let icon = file_icon(&entry.name); format!("{} {}", icon, entry.name) } @@ -165,9 +203,7 @@ where if let Some(payload) = make_drag_payload(entry) { ui.dnd_drag_source(row_id, payload, |ui| { - ui.label( - RichText::new(&name_text).color(name_col).size(12.0) - ); + ui.label(RichText::new(&name_text).color(name_col).size(12.0)); }); } else { ui.label(RichText::new(&name_text).color(name_col).size(12.0)); @@ -185,12 +221,12 @@ where ui.add_space(6.0); let (type_str, type_col) = match entry.kind { - EntryKind::Dir => ("Dir", TEXT_DIM), - EntryKind::File => ("File", TEXT_HINT), + EntryKind::Dir => ("Dir", TEXT_DIM), + EntryKind::File => ("File", TEXT_HINT), EntryKind::Symlink => ("Link", YELLOW), }; let type_label = egui::Label::new( - RichText::new(type_str).color(type_col).size(11.0) + RichText::new(type_str).color(type_col).size(11.0), ) .truncate(); ui.add_sized([38.0, ROW_H], type_label); @@ -217,7 +253,10 @@ where } }); - FileTableResponse { double_clicked, clicked } + FileTableResponse { + double_clicked, + clicked, + } } fn sort_header(ui: &mut Ui, label: &str, min_w: f32, active: bool, dir: SortDir) -> bool { @@ -236,7 +275,11 @@ fn sort_header(ui: &mut Ui, label: &str, min_w: f32, active: bool, dir: SortDir) fn toggle_sort(col: SortCol, cur_col: SortCol, cur_dir: SortDir) -> (SortCol, SortDir) { if col == cur_col { - let new_dir = if cur_dir == SortDir::Asc { SortDir::Desc } else { SortDir::Asc }; + let new_dir = if cur_dir == SortDir::Asc { + SortDir::Desc + } else { + SortDir::Asc + }; (col, new_dir) } else { (col, SortDir::Asc) @@ -246,18 +289,18 @@ fn toggle_sort(col: SortCol, cur_col: SortCol, cur_dir: SortDir) -> (SortCol, So fn file_icon(name: &str) -> &'static str { let ext = name.rsplit('.').next().unwrap_or("").to_lowercase(); match ext.as_str() { - "rs" => "🦀", + "rs" => "🦀", "toml" | "yaml" | "yml" | "json" => "⚙", - "md" | "txt" | "log" => "📝", + "md" | "txt" | "log" => "📝", "png" | "jpg" | "jpeg" | "gif" | "webp" | "svg" => "🖼", - "mp4" | "mkv" | "avi" | "mov" => "🎬", - "mp3" | "ogg" | "flac" | "wav" => "🎵", + "mp4" | "mkv" | "avi" | "mov" => "🎬", + "mp3" | "ogg" | "flac" | "wav" => "🎵", "zip" | "tar" | "gz" | "bz2" | "xz" | "7z" => "📦", - "pdf" => "📕", - "sh" | "bash" | "fish" | "zsh" => "⚡", - "html" | "css" | "js" | "ts" => "🌐", - "py" => "🐍", - "java" | "class" | "jar" => "☕", - _ => "📄", + "pdf" => "📕", + "sh" | "bash" | "fish" | "zsh" => "⚡", + "html" | "css" | "js" | "ts" => "🌐", + "py" => "🐍", + "java" | "class" | "jar" => "☕", + _ => "📄", } } diff --git a/src/ui/panels/local_pane.rs b/src/ui/panels/local_pane.rs index 042b416..a24e613 100644 --- a/src/ui/panels/local_pane.rs +++ b/src/ui/panels/local_pane.rs @@ -1,14 +1,14 @@ -use egui::{RichText, Ui}; use crate::domain::connection::ConnectionStatus; use crate::domain::file_entry::{EntryKind, FileEntry}; use crate::domain::transfer::{TransferKind, TransferTask}; use crate::fs::local; use crate::fs::remote::RemoteRegistry; use crate::transfer::queue::TransferQueue; -use crate::ui::drag::{make_local_payload, DragPayload}; -use crate::ui::panels::file_pane::{file_table, FileTableResponse, format_size}; +use crate::ui::drag::{DragPayload, make_local_payload}; +use crate::ui::panels::file_pane::{FileTableResponse, file_table, format_size}; use crate::ui::state::AppState; use crate::ui::theme::*; +use egui::{RichText, Ui}; use std::sync::Arc; fn parent_path(path: &str) -> String { @@ -88,29 +88,24 @@ fn render_inner(ui: &mut Ui, state: &mut AppState) { .map(|t| t.status == ConnectionStatus::Connected) .unwrap_or(false); - let FileTableResponse { double_clicked, .. } = file_table( - ui, - "local_table", - &entries, - &mut selected, - |entry| { + let FileTableResponse { double_clicked, .. } = + file_table(ui, "local_table", &entries, &mut selected, |entry| { if has_connection { make_local_payload(entry) } else { None } - }, - ); + }); state.local_selected = selected; if let Some(name) = double_clicked { if name == ".." { state.local_path = parent_path(&state.local_path); - } else if let Some(entry) = state.local_entries.iter().find(|e| e.name == name) { - if entry.kind == EntryKind::Dir { - state.local_path = entry.path.clone(); - } + } else if let Some(entry) = state.local_entries.iter().find(|e| e.name == name) + && entry.kind == EntryKind::Dir + { + state.local_path = entry.path.clone(); } refresh_local(state); } @@ -122,7 +117,12 @@ fn render_inner(ui: &mut Ui, state: &mut AppState) { fn render_path_bar(ui: &mut Ui, state: &mut AppState) { let frame = egui::Frame::none() .fill(BG_PANEL) - .inner_margin(egui::Margin { left: 8.0, right: 8.0, top: 4.0, bottom: 4.0 }); + .inner_margin(egui::Margin { + left: 8.0, + right: 8.0, + top: 4.0, + bottom: 4.0, + }); frame.show(ui, |ui| { ui.horizontal(|ui| { @@ -132,12 +132,14 @@ fn render_path_bar(ui: &mut Ui, state: &mut AppState) { .to_string_lossy() .to_string(); let is_home = state.local_path == home; - let home_btn = egui::Button::new( - RichText::new("🏠").size(13.0) - ) - .fill(if is_home { BG_ROW_SEL } else { egui::Color32::TRANSPARENT }) - .rounding(4.0) - .min_size(egui::vec2(22.0, 22.0)); + let home_btn = egui::Button::new(RichText::new("🏠").size(13.0)) + .fill(if is_home { + BG_ROW_SEL + } else { + egui::Color32::TRANSPARENT + }) + .rounding(4.0) + .min_size(egui::vec2(22.0, 22.0)); if ui.add(home_btn).clicked() { state.local_path = home; refresh_local(state); @@ -159,9 +161,8 @@ fn render_path_bar(ui: &mut Ui, state: &mut AppState) { ui.label(RichText::new(*part).color(TEXT_PRIMARY).size(12.0).strong()); } else { let path_snap = acc.clone(); - let link = egui::Label::new( - RichText::new(*part).color(TEXT_DIM).size(12.0) - ).sense(egui::Sense::click()); + let link = egui::Label::new(RichText::new(*part).color(TEXT_DIM).size(12.0)) + .sense(egui::Sense::click()); if ui.add(link).clicked() { state.local_path = path_snap; refresh_local(state); @@ -183,20 +184,24 @@ fn render_footer(ui: &mut Ui, state: &mut AppState) { frame.show(ui, |ui| { ui.horizontal(|ui| { - let count = state.local_entries.iter().filter(|e| e.name != "..").count(); + let count = state + .local_entries + .iter() + .filter(|e| e.name != "..") + .count(); let total_size: u64 = state.local_entries.iter().filter_map(|e| e.size).sum(); ui.label( RichText::new(format!("{} items", count)) .color(TEXT_DIM) - .size(11.0) + .size(11.0), ); if total_size > 0 { ui.label( RichText::new(format!("({})", format_size(Some(total_size)))) .color(TEXT_HINT) - .size(11.0) + .size(11.0), ); } diff --git a/src/ui/panels/queue.rs b/src/ui/panels/queue.rs index 53d9faa..1247a4b 100644 --- a/src/ui/panels/queue.rs +++ b/src/ui/panels/queue.rs @@ -1,9 +1,9 @@ -use egui::{Color32, RichText, Ui}; -use crate::domain::transfer::{TaskState, TransferKind}; use crate::domain::transfer::TransferTask; +use crate::domain::transfer::{TaskState, TransferKind}; use crate::ui::panels::file_pane::format_size; use crate::ui::state::AppState; use crate::ui::theme::*; +use egui::{Color32, RichText, Ui}; pub fn render(ui: &mut Ui, state: &mut AppState, tasks: &[TransferTask]) { // Заголовок очереди (всегда виден) @@ -15,7 +15,10 @@ pub fn render(ui: &mut Ui, state: &mut AppState, tasks: &[TransferTask]) { ui.set_min_height(QUEUE_COLLAPSED_H); ui.horizontal_centered(|ui| { let arrow = if state.show_queue { "▼" } else { "▲" }; - let n_active = tasks.iter().filter(|t| matches!(t.state, TaskState::Running | TaskState::Queued)).count(); + let n_active = tasks + .iter() + .filter(|t| matches!(t.state, TaskState::Running | TaskState::Queued)) + .count(); let title = if n_active > 0 { format!("{} Transfer Queue ({})", arrow, n_active) @@ -24,7 +27,10 @@ pub fn render(ui: &mut Ui, state: &mut AppState, tasks: &[TransferTask]) { }; let btn = egui::Button::new( - RichText::new(&title).color(TEXT_PRIMARY).size(12.0).strong() + RichText::new(&title) + .color(TEXT_PRIMARY) + .size(12.0) + .strong(), ) .fill(Color32::TRANSPARENT) .min_size(egui::vec2(0.0, QUEUE_COLLAPSED_H)); @@ -35,7 +41,8 @@ pub fn render(ui: &mut Ui, state: &mut AppState, tasks: &[TransferTask]) { // Аггрегированная скорость справа ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let agg_speed: u64 = tasks.iter() + let agg_speed: u64 = tasks + .iter() .filter(|t| t.state == TaskState::Running) .filter_map(|t| t.speed) .sum(); @@ -44,19 +51,29 @@ pub fn render(ui: &mut Ui, state: &mut AppState, tasks: &[TransferTask]) { ui.label( RichText::new(format!("{}/s", format_size(Some(agg_speed)))) .color(GREEN) - .size(11.0) + .size(11.0), ); } else if !tasks.is_empty() { - let done = tasks.iter().filter(|t| t.state == TaskState::Completed).count(); - let failed = tasks.iter().filter(|t| matches!(t.state, TaskState::Failed(_))).count(); + let done = tasks + .iter() + .filter(|t| t.state == TaskState::Completed) + .count(); + let failed = tasks + .iter() + .filter(|t| matches!(t.state, TaskState::Failed(_))) + .count(); ui.label( RichText::new(format!("{}/{} done", done, tasks.len())) .color(TEXT_DIM) - .size(11.0) + .size(11.0), ); if failed > 0 { ui.add_space(8.0); - ui.label(RichText::new(format!("{} failed", failed)).color(RED).size(11.0)); + ui.label( + RichText::new(format!("{} failed", failed)) + .color(RED) + .size(11.0), + ); } } }); @@ -82,21 +99,26 @@ pub fn render(ui: &mut Ui, state: &mut AppState, tasks: &[TransferTask]) { fn render_task(ui: &mut Ui, task: &TransferTask) { let bg = match &task.state { - TaskState::Completed => Color32::from_rgb(22, 38, 24), - TaskState::Failed(_) => Color32::from_rgb(38, 22, 22), - TaskState::Running => Color32::from_rgb(22, 28, 42), - TaskState::Queued => BG_QUEUE, - _ => BG_QUEUE, + TaskState::Completed => Color32::from_rgb(22, 38, 24), + TaskState::Failed(_) => Color32::from_rgb(38, 22, 22), + TaskState::Running => Color32::from_rgb(22, 28, 42), + TaskState::Queued => BG_QUEUE, + _ => BG_QUEUE, }; egui::Frame::none() .fill(bg) - .inner_margin(egui::Margin { left: 10.0, right: 10.0, top: 4.0, bottom: 4.0 }) + .inner_margin(egui::Margin { + left: 10.0, + right: 10.0, + top: 4.0, + bottom: 4.0, + }) .show(ui, |ui| { ui.horizontal(|ui| { // стрелка вида let (arrow, arrow_col) = match task.kind { - TransferKind::Upload => ("↑", ACCENT), + TransferKind::Upload => ("↑", ACCENT), TransferKind::Download => ("↓", GREEN), }; ui.label(RichText::new(arrow).color(arrow_col).size(13.0).strong()); @@ -104,19 +126,21 @@ fn render_task(ui: &mut Ui, task: &TransferTask) { // имя файла ui.label( - RichText::new(&task.file_name).color(TEXT_PRIMARY).size(12.0) + RichText::new(&task.file_name) + .color(TEXT_PRIMARY) + .size(12.0), ); // статус справа ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { let (state_str, state_col) = match &task.state { - TaskState::Queued => ("Queued", TEXT_HINT), - TaskState::Running => ("Uploading", ACCENT), - TaskState::Completed => ("✓ Done", GREEN), - TaskState::Failed(_) => ("× Failed", RED), - TaskState::Paused => ("Paused", YELLOW), - TaskState::Cancelled => ("Cancelled", TEXT_HINT), - TaskState::Retrying(_) => ("Retrying", YELLOW), + TaskState::Queued => ("Queued", TEXT_HINT), + TaskState::Running => ("Uploading", ACCENT), + TaskState::Completed => ("✓ Done", GREEN), + TaskState::Failed(_) => ("× Failed", RED), + TaskState::Paused => ("Paused", YELLOW), + TaskState::Cancelled => ("Cancelled", TEXT_HINT), + TaskState::Retrying(_) => ("Retrying", YELLOW), }; ui.label(RichText::new(state_str).color(state_col).size(11.0)); }); @@ -128,7 +152,7 @@ fn render_task(ui: &mut Ui, task: &TransferTask) { let bar_col = match &task.state { TaskState::Completed => GREEN, TaskState::Failed(_) => RED, - _ => ACCENT, + _ => ACCENT, }; let bar = egui::ProgressBar::new(pct) @@ -144,26 +168,26 @@ fn render_task(ui: &mut Ui, task: &TransferTask) { format_size(Some(task.total_bytes)) )) .color(TEXT_DIM) - .size(11.0) + .size(11.0), ); - if let Some(speed) = task.speed { - if speed > 0 { - ui.label( - RichText::new(format!(" {}/s", format_size(Some(speed)))) - .color(GREEN) - .size(11.0) - ); - } + if let Some(speed) = task.speed + && speed > 0 + { + ui.label( + RichText::new(format!(" {}/s", format_size(Some(speed)))) + .color(GREEN) + .size(11.0), + ); } - if let Some(eta) = task.eta_secs { - if eta > 0 { - ui.label( - RichText::new(format!(" ETA {}s", eta)) - .color(TEXT_HINT) - .size(10.0) - ); - } + if let Some(eta) = task.eta_secs + && eta > 0 + { + ui.label( + RichText::new(format!(" ETA {}s", eta)) + .color(TEXT_HINT) + .size(10.0), + ); } }); diff --git a/src/ui/panels/remote_pane.rs b/src/ui/panels/remote_pane.rs index 68f2c7e..f946e4f 100644 --- a/src/ui/panels/remote_pane.rs +++ b/src/ui/panels/remote_pane.rs @@ -6,17 +6,23 @@ use crate::domain::file_entry::{EntryKind, FileEntry}; use crate::domain::transfer::{TransferKind, TransferTask}; use crate::fs::remote::RemoteRegistry; use crate::transfer::queue::TransferQueue; -use crate::ui::drag::{make_remote_payload, DragPayload}; -use crate::ui::panels::file_pane::{file_table, FileTableResponse, format_size}; +use crate::ui::drag::{DragPayload, make_remote_payload}; +use crate::ui::panels::file_pane::{FileTableResponse, file_table, format_size}; use crate::ui::state::{AppState, PendingRemoteList}; use crate::ui::theme::*; pub fn remote_parent(path: &str) -> String { let trimmed = path.trim_end_matches('/'); - if trimmed.is_empty() || trimmed == "/" { return "/".into(); } + if trimmed.is_empty() || trimmed == "/" { + return "/".into(); + } if let Some(idx) = trimmed.rfind('/') { let p = &trimmed[..idx]; - if p.is_empty() { "/".into() } else { p.to_string() } + if p.is_empty() { + "/".into() + } else { + p.to_string() + } } else { "/".into() } @@ -150,16 +156,21 @@ fn render_path_bar( ) { let frame = egui::Frame::none() .fill(BG_PANEL) - .inner_margin(egui::Margin { left: 8.0, right: 8.0, top: 4.0, bottom: 4.0 }); + .inner_margin(egui::Margin { + left: 8.0, + right: 8.0, + top: 4.0, + bottom: 4.0, + }); frame.show(ui, |ui| { ui.horizontal(|ui| { // статус точка let (dot, dot_col) = match state.tabs[tab_idx].status { - ConnectionStatus::Connected => ("●", GREEN), + ConnectionStatus::Connected => ("●", GREEN), ConnectionStatus::Disconnected => ("○", TEXT_HINT), - ConnectionStatus::Connecting => ("◐", YELLOW), - ConnectionStatus::Error(_) => ("×", RED), + ConnectionStatus::Connecting => ("◐", YELLOW), + ConnectionStatus::Error(_) => ("×", RED), }; ui.label(RichText::new(dot).color(dot_col).size(10.0)); ui.add_space(4.0); @@ -189,9 +200,8 @@ fn render_path_bar( if is_last { ui.label(RichText::new(*part).color(TEXT_PRIMARY).size(12.0).strong()); } else { - let link = egui::Label::new( - RichText::new(*part).color(TEXT_DIM).size(12.0) - ).sense(egui::Sense::click()); + let link = egui::Label::new(RichText::new(*part).color(TEXT_DIM).size(12.0)) + .sense(egui::Sense::click()); if ui.add(link).clicked() { state.tabs[tab_idx].remote_path = path_snap; if !state.tabs[tab_idx].loading { @@ -237,7 +247,7 @@ fn render_footer( ui.label( RichText::new(format!("({})", format_size(Some(total_size)))) .color(TEXT_HINT) - .size(11.0) + .size(11.0), ); } @@ -246,10 +256,10 @@ fn render_footer( let refresh = egui::Button::new(RichText::new("⟳").color(ACCENT).size(14.0)) .fill(egui::Color32::TRANSPARENT) .min_size(egui::vec2(20.0, 20.0)); - if ui.add(refresh).on_hover_text("Refresh").clicked() { - if !state.tabs[tab_idx].loading { - trigger_list(state, tab_idx, registry, rt_handle); - } + if ui.add(refresh).on_hover_text("Refresh").clicked() + && !state.tabs[tab_idx].loading + { + trigger_list(state, tab_idx, registry, rt_handle); } }); }); @@ -289,5 +299,7 @@ pub fn trigger_list( *result_clone.lock().unwrap() = Some(r); }); - state.pending_remote_list.push(PendingRemoteList { tab_idx, result }); + state + .pending_remote_list + .push(PendingRemoteList { tab_idx, result }); } diff --git a/src/ui/panels/sidebar.rs b/src/ui/panels/sidebar.rs index 9f06916..9ef49db 100644 --- a/src/ui/panels/sidebar.rs +++ b/src/ui/panels/sidebar.rs @@ -1,8 +1,8 @@ //! Левая боковая панель — bookmarks + места -use egui::{RichText, Ui}; +use crate::ui::panels::local_pane::refresh_local; use crate::ui::state::AppState; use crate::ui::theme::*; -use crate::ui::panels::local_pane::refresh_local; +use egui::{RichText, Ui}; pub fn render(ui: &mut Ui, state: &mut AppState) { let frame = egui::Frame::none() @@ -69,12 +69,12 @@ pub fn render(ui: &mut Ui, state: &mut AppState) { ui.label( RichText::new(" No bookmarks yet") .color(TEXT_HINT) - .size(11.0) + .size(11.0), ); ui.label( RichText::new(" Use ★ in file list") .color(TEXT_HINT) - .size(10.0) + .size(10.0), ); } else { for bm in &custom { @@ -104,23 +104,32 @@ fn section_divider(ui: &mut Ui) { } fn sidebar_item(ui: &mut Ui, icon: &str, label: &str, active: bool, mut on_click: impl FnMut()) { - let bg = if active { BG_ROW_SEL } else { egui::Color32::TRANSPARENT }; - let text_col = if active { egui::Color32::WHITE } else { TEXT_PRIMARY }; + let bg = if active { + BG_ROW_SEL + } else { + egui::Color32::TRANSPARENT + }; + let text_col = if active { + egui::Color32::WHITE + } else { + TEXT_PRIMARY + }; let resp = egui::Frame::none() .fill(bg) .rounding(4.0) - .inner_margin(egui::Margin { left: 10.0, right: 6.0, top: 2.0, bottom: 2.0 }) + .inner_margin(egui::Margin { + left: 10.0, + right: 6.0, + top: 2.0, + bottom: 2.0, + }) .show(ui, |ui| { ui.set_min_width(SIDEBAR_W - 8.0); ui.horizontal(|ui| { ui.label(RichText::new(icon).size(12.0)); ui.add_space(6.0); - ui.label( - RichText::new(label) - .color(text_col) - .size(12.0) - ); + ui.label(RichText::new(label).color(text_col).size(12.0)); }); }); @@ -131,14 +140,14 @@ fn sidebar_item(ui: &mut Ui, icon: &str, label: &str, active: bool, mut on_click fn bookmark_icon(name: &str) -> &'static str { match name { - "Home" => "🏠", - "Desktop" => "🖥", + "Home" => "🏠", + "Desktop" => "🖥", "Documents" => "📄", "Downloads" => "⬇", - "Pictures" => "🖼", - "Music" => "🎵", - "Videos" => "🎬", - _ => "📁", + "Pictures" => "🖼", + "Music" => "🎵", + "Videos" => "🎬", + _ => "📁", } } diff --git a/src/ui/panels/status_bar.rs b/src/ui/panels/status_bar.rs index dc0cda8..2f1f6a6 100644 --- a/src/ui/panels/status_bar.rs +++ b/src/ui/panels/status_bar.rs @@ -1,8 +1,8 @@ -use egui::RichText; use crate::domain::connection::ConnectionStatus; use crate::ui::panels::file_pane::format_size; use crate::ui::state::AppState; use crate::ui::theme::*; +use egui::RichText; pub fn render(ui: &mut egui::Ui, state: &AppState) { let frame = egui::Frame::none() @@ -16,12 +16,14 @@ pub fn render(ui: &mut egui::Ui, state: &AppState) { ui.label( RichText::new(&state.status_message) .color(TEXT_DIM) - .size(11.0) + .size(11.0), ); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { // аггрегированная скорость - let agg: u64 = state.queue_tasks.iter() + let agg: u64 = state + .queue_tasks + .iter() .filter(|t| t.state == crate::domain::transfer::TaskState::Running) .filter_map(|t| t.speed) .sum(); @@ -30,13 +32,15 @@ pub fn render(ui: &mut egui::Ui, state: &AppState) { ui.label( RichText::new(format!("⬆⬇ {}/s", format_size(Some(agg)))) .color(GREEN) - .size(11.0) + .size(11.0), ); ui.add_space(10.0); } // соединения - let connected = state.tabs.iter() + let connected = state + .tabs + .iter() .filter(|t| t.status == ConnectionStatus::Connected) .count(); let total = state.tabs.len(); @@ -46,13 +50,13 @@ pub fn render(ui: &mut egui::Ui, state: &AppState) { ui.label( RichText::new(format!("● {}/{}", connected, total)) .color(dot_col) - .size(11.0) + .size(11.0), ); } else { ui.label( RichText::new("○ Not connected") .color(TEXT_HINT) - .size(11.0) + .size(11.0), ); } }); diff --git a/src/ui/panels/tabs.rs b/src/ui/panels/tabs.rs index cb0f0d1..ac849d7 100644 --- a/src/ui/panels/tabs.rs +++ b/src/ui/panels/tabs.rs @@ -1,12 +1,17 @@ -use egui::{Color32, RichText, Ui}; use crate::domain::connection::ConnectionStatus; use crate::ui::state::AppState; use crate::ui::theme::*; +use egui::{Color32, RichText, Ui}; pub fn render(ui: &mut Ui, state: &mut AppState) { let frame = egui::Frame::none() .fill(BG_BASE) - .inner_margin(egui::Margin { left: 6.0, right: 6.0, top: 0.0, bottom: 0.0 }); + .inner_margin(egui::Margin { + left: 6.0, + right: 6.0, + top: 0.0, + bottom: 0.0, + }); frame.show(ui, |ui| { ui.set_min_height(TABBAR_H); @@ -21,19 +26,33 @@ pub fn render(ui: &mut Ui, state: &mut AppState) { let is_active = i == state.active_tab; let (dot_col, dot) = match &tab.status { - ConnectionStatus::Connected => (GREEN, "●"), + ConnectionStatus::Connected => (GREEN, "●"), ConnectionStatus::Disconnected => (TEXT_HINT, "○"), - ConnectionStatus::Connecting => (YELLOW, "◐"), - ConnectionStatus::Error(_) => (RED, "×"), + ConnectionStatus::Connecting => (YELLOW, "◐"), + ConnectionStatus::Error(_) => (RED, "×"), }; - let bg = if is_active { BG_TAB_ACTIVE } else { BG_TAB_IDLE }; + let bg = if is_active { + BG_TAB_ACTIVE + } else { + BG_TAB_IDLE + }; let tcol = if is_active { TEXT_PRIMARY } else { TEXT_DIM }; let tab_response = egui::Frame::none() .fill(bg) - .rounding(egui::Rounding { nw: 4.0, ne: 4.0, sw: 0.0, se: 0.0 }) - .inner_margin(egui::Margin { left: 10.0, right: 4.0, top: 0.0, bottom: 0.0 }) + .rounding(egui::Rounding { + nw: 4.0, + ne: 4.0, + sw: 0.0, + se: 0.0, + }) + .inner_margin(egui::Margin { + left: 10.0, + right: 4.0, + top: 0.0, + bottom: 0.0, + }) .show(ui, |ui| { ui.set_min_height(TABBAR_H); ui.horizontal_centered(|ui| { @@ -41,19 +60,17 @@ pub fn render(ui: &mut Ui, state: &mut AppState) { ui.label(RichText::new(dot).color(dot_col).size(9.0)); ui.add_space(4.0); // название вкладки - let lbl = egui::Label::new( - RichText::new(&tab.label).color(tcol).size(12.0) - ) - .sense(egui::Sense::click()); + let lbl = + egui::Label::new(RichText::new(&tab.label).color(tcol).size(12.0)) + .sense(egui::Sense::click()); if ui.add(lbl).clicked() { // обрабатывается ниже } ui.add_space(6.0); // кнопка закрытия - let close = egui::Label::new( - RichText::new("×").color(TEXT_HINT).size(14.0) - ) - .sense(egui::Sense::click()); + let close = + egui::Label::new(RichText::new("×").color(TEXT_HINT).size(14.0)) + .sense(egui::Sense::click()); if ui.add(close).clicked() { to_close = Some(i); } diff --git a/src/ui/panels/toolbar.rs b/src/ui/panels/toolbar.rs index 5204989..eddf6c8 100644 --- a/src/ui/panels/toolbar.rs +++ b/src/ui/panels/toolbar.rs @@ -1,6 +1,6 @@ -use egui::{Align, Layout, RichText, Ui}; use crate::ui::state::AppState; use crate::ui::theme::*; +use egui::{Align, Layout, RichText, Ui}; pub fn render(ui: &mut Ui, state: &mut AppState) { let frame = egui::Frame::none() @@ -112,34 +112,44 @@ fn render_history_popup(ui: &mut Ui, state: &mut AppState) { .inner_margin(egui::Margin::same(8.0)) .show(ui, |ui| { ui.set_width(260.0); - ui.label(RichText::new("Recent Connections").color(TEXT_DIM).size(11.0)); + ui.label( + RichText::new("Recent Connections") + .color(TEXT_DIM) + .size(11.0), + ); ui.add_space(4.0); if state.history.is_empty() { ui.label(RichText::new("No history yet").color(TEXT_HINT)); } else { - egui::ScrollArea::vertical().max_height(160.0).show(ui, |ui| { - for entry in &state.history.clone() { - let label = format!("{}@{}:{}", entry.user, entry.host, entry.port); - let time = RichText::new(&entry.time).color(TEXT_HINT).size(10.0); - if ui - .add( - egui::Button::new( - RichText::new(&label).color(TEXT_PRIMARY).size(12.0), + egui::ScrollArea::vertical() + .max_height(160.0) + .show(ui, |ui| { + for entry in &state.history.clone() { + let label = + format!("{}@{}:{}", entry.user, entry.host, entry.port); + let time = + RichText::new(&entry.time).color(TEXT_HINT).size(10.0); + if ui + .add( + egui::Button::new( + RichText::new(&label) + .color(TEXT_PRIMARY) + .size(12.0), + ) + .fill(egui::Color32::TRANSPARENT) + .min_size(egui::vec2(240.0, 24.0)), ) - .fill(egui::Color32::TRANSPARENT) - .min_size(egui::vec2(240.0, 24.0)), - ) - .clicked() - { - state.connect_host = entry.host.clone(); - state.connect_user = entry.user.clone(); - state.connect_port = entry.port.to_string(); - state.show_connect_dialog = true; - state.show_history = false; + .clicked() + { + state.connect_host = entry.host.clone(); + state.connect_user = entry.user.clone(); + state.connect_port = entry.port.to_string(); + state.show_connect_dialog = true; + state.show_history = false; + } + ui.label(time); } - ui.label(time); - } - }); + }); } ui.add_space(4.0); if ui @@ -156,7 +166,11 @@ fn render_history_popup(ui: &mut Ui, state: &mut AppState) { } fn toolbar_btn(ui: &mut Ui, label: &str, active: bool, mut on_click: impl FnMut()) { - let fill = if active { BG_TAB_ACTIVE } else { egui::Color32::TRANSPARENT }; + let fill = if active { + BG_TAB_ACTIVE + } else { + egui::Color32::TRANSPARENT + }; let text_col = if active { ACCENT } else { TEXT_PRIMARY }; let btn = egui::Button::new(RichText::new(label).color(text_col).size(12.0)) .fill(fill) diff --git a/src/ui/state.rs b/src/ui/state.rs index 9b4e330..e177f14 100644 --- a/src/ui/state.rs +++ b/src/ui/state.rs @@ -98,15 +98,36 @@ impl Default for AppState { show_bookmarks: false, show_history: false, bookmarks: vec![ - Bookmark { name: "Home".into(), path: home.clone() }, - Bookmark { name: "Desktop".into(), path: format!("{}/Desktop", home) }, - Bookmark { name: "Documents".into(), path: format!("{}/Documents", home) }, - Bookmark { name: "Downloads".into(), path: dirs::download_dir() - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_else(|| format!("{}/Downloads", home)) }, - Bookmark { name: "Pictures".into(), path: format!("{}/Pictures", home) }, - Bookmark { name: "Music".into(), path: format!("{}/Music", home) }, - Bookmark { name: "Videos".into(), path: format!("{}/Videos", home) }, + Bookmark { + name: "Home".into(), + path: home.clone(), + }, + Bookmark { + name: "Desktop".into(), + path: format!("{}/Desktop", home), + }, + Bookmark { + name: "Documents".into(), + path: format!("{}/Documents", home), + }, + Bookmark { + name: "Downloads".into(), + path: dirs::download_dir() + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_else(|| format!("{}/Downloads", home)), + }, + Bookmark { + name: "Pictures".into(), + path: format!("{}/Pictures", home), + }, + Bookmark { + name: "Music".into(), + path: format!("{}/Music", home), + }, + Bookmark { + name: "Videos".into(), + path: format!("{}/Videos", home), + }, ], history: Vec::new(), connect_label: String::new(), @@ -134,13 +155,17 @@ impl Default for AppState { impl AppState { pub fn active_tab_mut(&mut self) -> Option<&mut ConnectionTab> { - if self.tabs.is_empty() { return None; } + if self.tabs.is_empty() { + return None; + } let idx = self.active_tab.min(self.tabs.len() - 1); Some(&mut self.tabs[idx]) } pub fn active_tab_ref(&self) -> Option<&ConnectionTab> { - if self.tabs.is_empty() { return None; } + if self.tabs.is_empty() { + return None; + } let idx = self.active_tab.min(self.tabs.len() - 1); Some(&self.tabs[idx]) } @@ -148,9 +173,17 @@ impl AppState { pub fn add_history(&mut self, host: &str, port: u16, user: &str) { use chrono::Local; let now = Local::now().format("%H:%M %d.%m").to_string(); - self.history.insert(0, HistoryEntry { - host: host.into(), port, user: user.into(), time: now, - }); - if self.history.len() > 20 { self.history.truncate(20); } + self.history.insert( + 0, + HistoryEntry { + host: host.into(), + port, + user: user.into(), + time: now, + }, + ); + if self.history.len() > 20 { + self.history.truncate(20); + } } } diff --git a/src/ui/theme.rs b/src/ui/theme.rs index 610ef26..965c6f2 100644 --- a/src/ui/theme.rs +++ b/src/ui/theme.rs @@ -2,39 +2,39 @@ use egui::Color32; // Backgrounds -pub const BG_BASE: Color32 = Color32::from_rgb(18, 18, 18); // самый тёмный фон -pub const BG_PANEL: Color32 = Color32::from_rgb(24, 24, 24); // боковая панель -pub const BG_CONTENT: Color32 = Color32::from_rgb(28, 28, 28); // основная область -pub const BG_TOOLBAR: Color32 = Color32::from_rgb(22, 22, 22); // тулбар -pub const BG_TAB_ACTIVE: Color32 = Color32::from_rgb(38, 38, 42); // активная вкладка -pub const BG_TAB_IDLE: Color32 = Color32::from_rgb(28, 28, 30); // неактивная -pub const BG_ROW_HOVER: Color32 = Color32::from_rgb(35, 35, 38); -pub const BG_ROW_SEL: Color32 = Color32::from_rgb(42, 64, 95); // выделение — синеватое -pub const BG_QUEUE: Color32 = Color32::from_rgb(20, 20, 22); -pub const BG_HEADER: Color32 = Color32::from_rgb(22, 22, 24); +pub const BG_BASE: Color32 = Color32::from_rgb(18, 18, 18); // самый тёмный фон +pub const BG_PANEL: Color32 = Color32::from_rgb(24, 24, 24); // боковая панель +pub const BG_CONTENT: Color32 = Color32::from_rgb(28, 28, 28); // основная область +pub const BG_TOOLBAR: Color32 = Color32::from_rgb(22, 22, 22); // тулбар +pub const BG_TAB_ACTIVE: Color32 = Color32::from_rgb(38, 38, 42); // активная вкладка +pub const BG_TAB_IDLE: Color32 = Color32::from_rgb(28, 28, 30); // неактивная +pub const BG_ROW_HOVER: Color32 = Color32::from_rgb(35, 35, 38); +pub const BG_ROW_SEL: Color32 = Color32::from_rgb(42, 64, 95); // выделение — синеватое +pub const BG_QUEUE: Color32 = Color32::from_rgb(20, 20, 22); +pub const BG_HEADER: Color32 = Color32::from_rgb(22, 22, 24); // Accents -pub const ACCENT: Color32 = Color32::from_rgb(64, 130, 220); // синий акцент -pub const ACCENT_DIM: Color32 = Color32::from_rgb(45, 95, 165); -pub const GREEN: Color32 = Color32::from_rgb(60, 180, 100); -pub const RED: Color32 = Color32::from_rgb(210, 70, 70); -pub const YELLOW: Color32 = Color32::from_rgb(210, 160, 50); -pub const ORANGE: Color32 = Color32::from_rgb(210, 120, 50); +pub const ACCENT: Color32 = Color32::from_rgb(64, 130, 220); // синий акцент +pub const ACCENT_DIM: Color32 = Color32::from_rgb(45, 95, 165); +pub const GREEN: Color32 = Color32::from_rgb(60, 180, 100); +pub const RED: Color32 = Color32::from_rgb(210, 70, 70); +pub const YELLOW: Color32 = Color32::from_rgb(210, 160, 50); +pub const ORANGE: Color32 = Color32::from_rgb(210, 120, 50); // Text -pub const TEXT_PRIMARY: Color32 = Color32::from_rgb(220, 220, 220); -pub const TEXT_DIM: Color32 = Color32::from_rgb(130, 130, 135); -pub const TEXT_HINT: Color32 = Color32::from_rgb(80, 80, 85); +pub const TEXT_PRIMARY: Color32 = Color32::from_rgb(220, 220, 220); +pub const TEXT_DIM: Color32 = Color32::from_rgb(130, 130, 135); +pub const TEXT_HINT: Color32 = Color32::from_rgb(80, 80, 85); // Borders / separators -pub const BORDER: Color32 = Color32::from_rgb(40, 40, 44); -pub const SEPARATOR: Color32 = Color32::from_rgb(35, 35, 38); +pub const BORDER: Color32 = Color32::from_rgb(40, 40, 44); +pub const SEPARATOR: Color32 = Color32::from_rgb(35, 35, 38); // Size constants -pub const SIDEBAR_W: f32 = 180.0; -pub const TOOLBAR_H: f32 = 40.0; -pub const TABBAR_H: f32 = 32.0; +pub const SIDEBAR_W: f32 = 180.0; +pub const TOOLBAR_H: f32 = 40.0; +pub const TABBAR_H: f32 = 32.0; pub const QUEUE_COLLAPSED_H: f32 = 32.0; -pub const QUEUE_EXPANDED_H: f32 = 160.0; -pub const STATUS_H: f32 = 24.0; -pub const ROW_H: f32 = 22.0; +pub const QUEUE_EXPANDED_H: f32 = 160.0; +pub const STATUS_H: f32 = 24.0; +pub const ROW_H: f32 = 22.0;