UI redesign: onboarding card, browser tabs, accent colors, clean toolbar

This commit is contained in:
loki5512344 2026-06-15 21:32:53 +02:00
parent 500da04ce7
commit 46514298cd
7 changed files with 113 additions and 183 deletions

View file

@ -1,15 +1,12 @@
use crate::domain::transfer::TransferTask;
use crate::ui::panels::file_pane::format_size;
use crate::ui::state::AppState;
use egui::Color32;
pub fn render(ui: &mut egui::Ui, state: &mut AppState, tasks: &[TransferTask]) {
pub fn render(ui: &mut egui::Ui, _state: &mut AppState, tasks: &[TransferTask]) {
ui.horizontal(|ui| {
ui.heading("\u{1F4E6} Transfer Queue");
ui.strong("Transfer Queue");
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.small_button("\u{2715}").clicked() {
state.show_queue = false;
}
ui.label(format!("{} tasks", tasks.len()));
});
});
@ -27,13 +24,15 @@ pub fn render(ui: &mut egui::Ui, state: &mut AppState, tasks: &[TransferTask]) {
for task in tasks {
let bg = match &task.state {
crate::domain::transfer::TaskState::Completed => {
Color32::from_rgb(220, 255, 220)
egui::Color32::from_rgb(220, 255, 220)
}
crate::domain::transfer::TaskState::Failed(_) => {
Color32::from_rgb(255, 220, 220)
egui::Color32::from_rgb(255, 220, 220)
}
crate::domain::transfer::TaskState::Running => Color32::from_rgb(220, 220, 255),
_ => Color32::TRANSPARENT,
crate::domain::transfer::TaskState::Running => {
egui::Color32::from_rgb(220, 220, 255)
}
_ => egui::Color32::TRANSPARENT,
};
egui::Frame::none()
@ -59,6 +58,7 @@ pub fn render(ui: &mut egui::Ui, state: &mut AppState, tasks: &[TransferTask]) {
let pct = task.progress_pct();
let pb = egui::ProgressBar::new(pct as f32 / 100.0)
.text(format!("{:.1}%", pct))
.fill(egui::Color32::from_rgb(100, 80, 220))
.desired_width(ui.available_width() * 0.6);
ui.add(pb);

View file

@ -193,8 +193,9 @@ pub fn trigger_list(
let path = state.tabs[tab_idx].remote_path.clone();
let registry = registry.clone();
let result: Arc<std::sync::Mutex<Option<Result<Vec<FileEntry>, String>>>> =
Arc::new(std::sync::Mutex::new(None));
let result = Arc::new(std::sync::Mutex::new(
None::<Result<Vec<FileEntry>, String>>,
));
let result_clone = result.clone();
rt_handle.spawn(async move {

View file

@ -1,12 +1,19 @@
use crate::domain::connection::ConnectionStatus;
use crate::ui::state::AppState;
pub fn render(ui: &mut egui::Ui, state: &AppState) {
pub fn render(ui: &mut egui::Ui, state: &mut AppState) {
ui.horizontal(|ui| {
ui.label(&state.status_message);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let queued = state.queue_tasks.len();
ui.label(format!("Queue: {}", queued));
let queue_label = if state.show_queue {
format!("Queue ({}) \u{25BC}", queued)
} else {
format!("Queue ({}) \u{25B2}", queued)
};
if ui.button(queue_label).clicked() {
state.show_queue = !state.show_queue;
}
ui.separator();
let connected = state
.tabs
@ -14,7 +21,7 @@ pub fn render(ui: &mut egui::Ui, state: &AppState) {
.filter(|t| t.status == ConnectionStatus::Connected)
.count();
let total = state.tabs.len();
ui.label(format!("Connections: {}/{}", connected, total));
ui.label(format!("{}/{}", connected, total));
});
});
}

View file

@ -1,16 +1,10 @@
use crate::domain::connection::ConnectionStatus;
use crate::ui::state::AppState;
const ACCENT: egui::Color32 = egui::Color32::from_rgb(100, 80, 220);
pub fn render(ui: &mut egui::Ui, state: &mut AppState) {
ui.horizontal(|ui| {
if state.tabs.is_empty() {
ui.label("No connections");
if ui.button("+ New Connection").clicked() {
state.show_connect_dialog = true;
}
return;
}
let mut to_close: Option<usize> = None;
for (i, tab) in state.tabs.iter().enumerate() {
@ -23,18 +17,18 @@ pub fn render(ui: &mut egui::Ui, state: &mut AppState) {
ConnectionStatus::Error(_) => "\u{2716}",
};
let label = format!("{} {}", dot, tab.label);
let label = format!("{} {} ", dot, tab.label);
let mut tab_button = egui::Button::new(&label);
let mut tab_btn = egui::Button::new(&label)
.min_size(egui::vec2(60.0, 28.0));
if is_active {
tab_button = tab_button.fill(egui::Color32::from_rgb(60, 60, 80));
tab_btn = tab_btn.fill(ACCENT);
}
let response = ui.add(tab_button);
if response.clicked() {
if ui.add(tab_btn).clicked() {
state.active_tab = i;
}
if ui.small_button("\u{2715}").clicked() {
if ui.small_button("x").clicked() {
to_close = Some(i);
}
}
@ -46,8 +40,6 @@ pub fn render(ui: &mut egui::Ui, state: &mut AppState) {
}
}
ui.separator();
if ui.button("+").clicked() {
state.show_connect_dialog = true;
}

View file

@ -1,139 +1,11 @@
use crate::domain::connection::ConnectionStatus;
use crate::domain::file_entry::EntryKind;
use crate::domain::transfer::{TransferKind, TransferTask};
use crate::fs::remote::RemoteRegistry;
use crate::transfer::queue::TransferQueue;
use crate::ui::state::AppState;
use std::sync::Arc;
pub fn render(
ui: &mut egui::Ui,
state: &mut AppState,
queue: &TransferQueue,
registry: &Arc<RemoteRegistry>,
_rt_handle: &tokio::runtime::Handle,
) {
pub fn render(ui: &mut egui::Ui, state: &mut AppState) {
ui.horizontal(|ui| {
let add_btn = egui::Button::new("+ New Connection");
let add_btn = egui::Button::new("+ New Connection")
.fill(egui::Color32::from_rgb(80, 60, 180));
if ui.add(add_btn).clicked() {
state.show_connect_dialog = true;
}
ui.separator();
let has_local_selection = state.local_selected.is_some();
let has_remote_selection = state
.active_tab_ref()
.and_then(|t| t.remote_selected.as_ref())
.is_some();
let (is_connected, tab_exists) = {
let active = state.active_tab_ref();
(
active
.map(|t| t.status == ConnectionStatus::Connected)
.unwrap_or(false),
active.is_some(),
)
};
let tab_id = state.active_tab_ref().map(|t| t.id.clone());
let remote_path = state.active_tab_ref().map(|t| t.remote_path.clone());
let upload_btn = egui::Button::new("\u{2191} Upload")
.min_size(egui::vec2(100.0, 24.0));
ui.add_enabled_ui(is_connected && has_local_selection && tab_exists, |ui| {
if ui.add(upload_btn).clicked()
&& let Some(sel) = state.local_selected.clone()
&& let Some(entry) = state.local_entries.iter().find(|e| e.name == sel)
{
if entry.kind == EntryKind::File {
let file_name = entry.name.clone();
let remote_base = remote_path.clone().unwrap_or_else(|| "/".into());
let remote_path_str =
format!("{}/{}", remote_base.trim_end_matches('/'), file_name);
let cid = tab_id.clone().unwrap_or_default();
let task = TransferTask::new(
TransferKind::Upload,
cid,
entry.path.clone(),
remote_path_str,
file_name,
entry.size.unwrap_or(0),
);
if registry.get(&task.connection_id).is_some() {
queue.push(task);
state.status_message = "Upload queued".into();
} else {
state.status_message = "No connection".into();
}
} else {
state.status_message = "Cannot upload directory".into();
}
}
});
let download_btn = egui::Button::new("\u{2193} Download")
.min_size(egui::vec2(100.0, 24.0));
ui.add_enabled_ui(is_connected && has_remote_selection && tab_exists, |ui| {
if ui.add(download_btn).clicked()
&& let Some(sel) = tab_remote_selected(state)
{
let file_name = sel.clone();
let remote_base = remote_path.clone().unwrap_or_else(|| "/".into());
let remote_path_str =
format!("{}/{}", remote_base.trim_end_matches('/'), file_name);
let local_path_str =
format!("{}/{}", state.local_path.trim_end_matches('/'), file_name);
let size = state
.active_tab_ref()
.map(|t| {
t.remote_entries
.iter()
.find(|e| e.name == sel)
.and_then(|e| e.size)
.unwrap_or(0)
})
.unwrap_or(0);
let cid = tab_id.clone().unwrap_or_default();
let task = TransferTask::new(
TransferKind::Download,
cid,
local_path_str,
remote_path_str,
file_name,
size,
);
if registry.get(&task.connection_id).is_some() {
queue.push(task);
state.status_message = "Download queued".into();
} else {
state.status_message = "No connection".into();
}
}
});
ui.separator();
let queue_label = if state.show_queue {
"Queue \u{25BC}"
} else {
"Queue \u{25B2}"
};
if ui.button(queue_label).clicked() {
state.show_queue = !state.show_queue;
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.separator();
let task_count = state.queue_tasks.len();
if task_count > 0 {
ui.label(format!("{} tasks", task_count));
}
});
});
}
fn tab_remote_selected(state: &AppState) -> Option<String> {
let idx = state.active_tab.min(state.tabs.len().saturating_sub(1));
state.tabs.get(idx).and_then(|t| t.remote_selected.clone())
}