mirror of
https://github.com/house-of-vanity/khm.git
synced 2025-08-21 14:27:14 +00:00
UI code reworked
This commit is contained in:
@@ -3,6 +3,12 @@ name = "khm"
|
|||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["AB <ab@hexor.cy>"]
|
authors = ["AB <ab@hexor.cy>"]
|
||||||
|
description = "KHM - Known Hosts Manager for SSH key management and synchronization"
|
||||||
|
homepage = "https://github.com/house-of-vanity/khm"
|
||||||
|
repository = "https://github.com/house-of-vanity/khm"
|
||||||
|
license = "WTFPL"
|
||||||
|
keywords = ["ssh", "known-hosts", "security", "system-admin", "automation"]
|
||||||
|
categories = ["command-line-utilities", "network-programming"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4"
|
actix-web = "4"
|
||||||
|
@@ -102,7 +102,7 @@ impl ConnectionTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Check for test/sync results
|
/// Check for test/sync results
|
||||||
pub fn check_results(&mut self, ctx: &egui::Context, settings: &KhmSettings) {
|
pub fn check_results(&mut self, ctx: &egui::Context, settings: &KhmSettings, operation_log: &mut Vec<String>) {
|
||||||
// Check for test connection result
|
// Check for test connection result
|
||||||
if let Some(receiver) = &self.test_result_receiver {
|
if let Some(receiver) = &self.test_result_receiver {
|
||||||
if let Ok(result) = receiver.try_recv() {
|
if let Ok(result) = receiver.try_recv() {
|
||||||
@@ -121,10 +121,16 @@ impl ConnectionTab {
|
|||||||
flow: settings.flow.clone()
|
flow: settings.flow.clone()
|
||||||
};
|
};
|
||||||
info!("Connection test successful: {}", message);
|
info!("Connection test successful: {}", message);
|
||||||
|
|
||||||
|
// Add to UI log
|
||||||
|
super::ui::add_log_entry(operation_log, format!("✅ Connection test successful: {}", message));
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
self.connection_status = ConnectionStatus::Error(error);
|
self.connection_status = ConnectionStatus::Error(error.clone());
|
||||||
error!("Connection test failed");
|
error!("Connection test failed");
|
||||||
|
|
||||||
|
// Add to UI log
|
||||||
|
super::ui::add_log_entry(operation_log, format!("❌ Connection test failed: {}", error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.test_result_receiver = None;
|
self.test_result_receiver = None;
|
||||||
@@ -142,10 +148,16 @@ impl ConnectionTab {
|
|||||||
let keys_count = parse_keys_count(&message);
|
let keys_count = parse_keys_count(&message);
|
||||||
self.sync_status = SyncStatus::Success { keys_count };
|
self.sync_status = SyncStatus::Success { keys_count };
|
||||||
info!("Sync successful: {}", message);
|
info!("Sync successful: {}", message);
|
||||||
|
|
||||||
|
// Add to UI log
|
||||||
|
super::ui::add_log_entry(operation_log, format!("✅ Sync completed: {}", message));
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
self.sync_status = SyncStatus::Error(error);
|
self.sync_status = SyncStatus::Error(error.clone());
|
||||||
error!("Sync failed");
|
error!("Sync failed");
|
||||||
|
|
||||||
|
// Add to UI log
|
||||||
|
super::ui::add_log_entry(operation_log, format!("❌ Sync failed: {}", error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.sync_result_receiver = None;
|
self.sync_result_receiver = None;
|
||||||
|
@@ -2,7 +2,7 @@ use eframe::egui;
|
|||||||
use crate::gui::common::{KhmSettings, get_config_path};
|
use crate::gui::common::{KhmSettings, get_config_path};
|
||||||
use super::connection::{ConnectionTab, ConnectionStatus, SyncStatus, save_settings_validated};
|
use super::connection::{ConnectionTab, ConnectionStatus, SyncStatus, save_settings_validated};
|
||||||
|
|
||||||
/// Render connection settings tab
|
/// Render connection settings tab with modern horizontal UI design
|
||||||
pub fn render_connection_tab(
|
pub fn render_connection_tab(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
@@ -12,259 +12,408 @@ pub fn render_connection_tab(
|
|||||||
operation_log: &mut Vec<String>
|
operation_log: &mut Vec<String>
|
||||||
) {
|
) {
|
||||||
// Check for connection test and sync results
|
// Check for connection test and sync results
|
||||||
connection_tab.check_results(ctx, settings);
|
connection_tab.check_results(ctx, settings, operation_log);
|
||||||
|
|
||||||
let available_height = ui.available_height();
|
// Use scrollable area for the entire content
|
||||||
let button_area_height = 120.0; // Reserve space for buttons and status
|
|
||||||
let content_height = available_height - button_area_height;
|
|
||||||
|
|
||||||
// Main content area (scrollable)
|
|
||||||
ui.allocate_ui_with_layout(
|
|
||||||
[ui.available_width(), content_height].into(),
|
|
||||||
egui::Layout::top_down(egui::Align::Min),
|
|
||||||
|ui| {
|
|
||||||
egui::ScrollArea::vertical()
|
egui::ScrollArea::vertical()
|
||||||
.auto_shrink([false; 2])
|
.auto_shrink([false; 2])
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
// Connection section
|
ui.spacing_mut().item_spacing = egui::vec2(6.0, 8.0);
|
||||||
render_connection_section(ui, settings, connection_tab);
|
ui.spacing_mut().button_padding = egui::vec2(12.0, 6.0);
|
||||||
ui.add_space(10.0);
|
ui.spacing_mut().indent = 16.0;
|
||||||
|
|
||||||
// Local settings section
|
// Connection Status Card at top (full width)
|
||||||
render_local_settings_section(ui, settings);
|
render_connection_status_card(ui, connection_tab);
|
||||||
ui.add_space(15.0);
|
|
||||||
|
|
||||||
// Auto-sync section
|
// Main configuration area - horizontal layout
|
||||||
render_auto_sync_section(ui, settings, auto_sync_interval_str);
|
ui.horizontal_top(|ui| {
|
||||||
ui.add_space(10.0);
|
let available_width = ui.available_width();
|
||||||
|
let left_panel_width = available_width * 0.6;
|
||||||
|
let right_panel_width = available_width * 0.38;
|
||||||
|
|
||||||
// Configuration file location
|
// Left panel - Connection and Local config
|
||||||
render_config_location_section(ui);
|
ui.allocate_ui_with_layout(
|
||||||
});
|
[left_panel_width, ui.available_height()].into(),
|
||||||
},
|
egui::Layout::top_down(egui::Align::Min),
|
||||||
|
|ui| {
|
||||||
|
// Connection Configuration Card
|
||||||
|
render_connection_config_card(ui, settings);
|
||||||
|
|
||||||
|
// Local Configuration Card
|
||||||
|
render_local_config_card(ui, settings);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Bottom area with buttons and log
|
ui.add_space(8.0);
|
||||||
render_bottom_area(ui, ctx, settings, connection_tab, operation_log);
|
|
||||||
|
// Right panel - Auto-sync and System info
|
||||||
|
ui.allocate_ui_with_layout(
|
||||||
|
[right_panel_width, ui.available_height()].into(),
|
||||||
|
egui::Layout::top_down(egui::Align::Min),
|
||||||
|
|ui| {
|
||||||
|
// Auto-sync Configuration Card
|
||||||
|
render_auto_sync_card(ui, settings, auto_sync_interval_str);
|
||||||
|
|
||||||
|
// System Information Card
|
||||||
|
render_system_info_card(ui);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(12.0);
|
||||||
|
|
||||||
|
// Action buttons at bottom
|
||||||
|
render_action_section(ui, ctx, settings, connection_tab, operation_log);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_connection_section(ui: &mut egui::Ui, settings: &mut KhmSettings, connection_tab: &ConnectionTab) {
|
/// Connection status card with modern visual design
|
||||||
ui.group(|ui| {
|
fn render_connection_status_card(ui: &mut egui::Ui, connection_tab: &ConnectionTab) {
|
||||||
ui.set_min_width(ui.available_width());
|
let frame = egui::Frame::group(ui.style())
|
||||||
ui.vertical(|ui| {
|
.fill(ui.visuals().faint_bg_color)
|
||||||
|
.stroke(egui::Stroke::new(1.0, ui.visuals().widgets.noninteractive.bg_stroke.color))
|
||||||
|
.rounding(6.0)
|
||||||
|
.inner_margin(egui::Margin::same(12.0));
|
||||||
|
|
||||||
|
frame.show(ui, |ui| {
|
||||||
|
// Header with status indicator
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(egui::RichText::new("🌐 Connection").size(16.0).strong());
|
let (status_icon, status_text, status_color) = match &connection_tab.connection_status {
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
|
||||||
// Display connection status with details
|
|
||||||
match &connection_tab.connection_status {
|
|
||||||
ConnectionStatus::Connected { keys_count, flow } => {
|
ConnectionStatus::Connected { keys_count, flow } => {
|
||||||
let status_text = if flow.is_empty() {
|
let text = if flow.is_empty() {
|
||||||
format!("Connected ({} keys)", keys_count)
|
format!("Connected • {} keys", keys_count)
|
||||||
} else {
|
} else {
|
||||||
format!("Connected to '{}' ({} keys)", flow, keys_count)
|
format!("Connected to '{}' • {} keys", flow, keys_count)
|
||||||
};
|
};
|
||||||
ui.add_enabled(false, egui::Checkbox::new(&mut true, status_text));
|
("🟢", text, egui::Color32::GREEN)
|
||||||
}
|
}
|
||||||
ConnectionStatus::Error(error_msg) => {
|
ConnectionStatus::Error(error_msg) => {
|
||||||
ui.label(egui::RichText::new("❌ Error").color(egui::Color32::RED))
|
("🔴", format!("Connection Error: {}", error_msg), egui::Color32::RED)
|
||||||
.on_hover_text(error_msg);
|
|
||||||
}
|
}
|
||||||
ConnectionStatus::Unknown => {
|
ConnectionStatus::Unknown => {
|
||||||
ui.add_enabled(false, egui::Checkbox::new(&mut false, "Not connected"));
|
("⚫", "Not Connected".to_string(), ui.visuals().text_color())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.label(egui::RichText::new(status_icon).size(14.0));
|
||||||
|
ui.label(egui::RichText::new("Connection Status").size(14.0).strong());
|
||||||
|
|
||||||
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
if connection_tab.is_testing_connection {
|
if connection_tab.is_testing_connection {
|
||||||
ui.spinner();
|
ui.spinner();
|
||||||
ui.label(egui::RichText::new("Testing...").italics());
|
ui.label(egui::RichText::new("Testing...").italics().color(ui.visuals().weak_text_color()));
|
||||||
|
} else {
|
||||||
|
ui.label(egui::RichText::new(&status_text).size(13.0).color(status_color));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Display sync status if available
|
// Sync status - always visible
|
||||||
|
ui.add_space(6.0);
|
||||||
|
ui.separator();
|
||||||
|
ui.add_space(6.0);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("🔄");
|
||||||
|
ui.label("Last Sync:");
|
||||||
|
|
||||||
match &connection_tab.sync_status {
|
match &connection_tab.sync_status {
|
||||||
SyncStatus::Success { keys_count } => {
|
SyncStatus::Success { keys_count } => {
|
||||||
ui.horizontal(|ui| {
|
ui.label(egui::RichText::new(format!("✅ {} keys synced", keys_count))
|
||||||
ui.label("🔄 Last sync:");
|
.size(13.0).color(egui::Color32::GREEN));
|
||||||
ui.label(egui::RichText::new(format!("{} keys synced", keys_count))
|
|
||||||
.color(egui::Color32::GREEN));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
SyncStatus::Error(error_msg) => {
|
SyncStatus::Error(error_msg) => {
|
||||||
ui.horizontal(|ui| {
|
ui.label(egui::RichText::new("❌ Failed")
|
||||||
ui.label("🔄 Last sync:");
|
.size(13.0).color(egui::Color32::RED))
|
||||||
ui.label(egui::RichText::new("Failed")
|
|
||||||
.color(egui::Color32::RED))
|
|
||||||
.on_hover_text(error_msg);
|
.on_hover_text(error_msg);
|
||||||
|
}
|
||||||
|
SyncStatus::Unknown => {
|
||||||
|
ui.label(egui::RichText::new("No sync performed yet")
|
||||||
|
.size(13.0).color(ui.visuals().weak_text_color()));
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
SyncStatus::Unknown => {}
|
|
||||||
|
ui.add_space(8.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_space(5.0);
|
/// Connection configuration card with input fields
|
||||||
|
fn render_connection_config_card(ui: &mut egui::Ui, settings: &mut KhmSettings) {
|
||||||
|
let frame = egui::Frame::group(ui.style())
|
||||||
|
.fill(ui.visuals().faint_bg_color)
|
||||||
|
.stroke(egui::Stroke::new(1.0, ui.visuals().widgets.noninteractive.bg_stroke.color))
|
||||||
|
.rounding(6.0)
|
||||||
|
.inner_margin(egui::Margin::same(12.0));
|
||||||
|
|
||||||
egui::Grid::new("connection_grid")
|
frame.show(ui, |ui| {
|
||||||
.num_columns(2)
|
// Header
|
||||||
.min_col_width(120.0)
|
ui.horizontal(|ui| {
|
||||||
.spacing([10.0, 8.0])
|
ui.label("🌐");
|
||||||
.show(ui, |ui| {
|
ui.label(egui::RichText::new("Server Configuration").size(14.0).strong());
|
||||||
ui.label("Host URL:");
|
});
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
// Input fields with better spacing
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.spacing_mut().item_spacing.y = 8.0;
|
||||||
|
|
||||||
|
// Host URL
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.label(egui::RichText::new("Host URL").size(13.0).strong());
|
||||||
|
ui.add_space(3.0);
|
||||||
ui.add_sized(
|
ui.add_sized(
|
||||||
[ui.available_width(), 20.0],
|
[ui.available_width(), 28.0], // Smaller height for better centering
|
||||||
egui::TextEdit::singleline(&mut settings.host)
|
egui::TextEdit::singleline(&mut settings.host)
|
||||||
.hint_text("https://your-khm-server.com")
|
.hint_text("https://your-khm-server.com")
|
||||||
|
.font(egui::FontId::new(14.0, egui::FontFamily::Monospace))
|
||||||
|
.margin(egui::Margin::symmetric(8.0, 6.0)) // Better vertical centering
|
||||||
);
|
);
|
||||||
ui.end_row();
|
});
|
||||||
|
|
||||||
ui.label("Flow Name:");
|
// Flow Name
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.label(egui::RichText::new("Flow Name").size(13.0).strong());
|
||||||
|
ui.add_space(3.0);
|
||||||
ui.add_sized(
|
ui.add_sized(
|
||||||
[ui.available_width(), 20.0],
|
[ui.available_width(), 28.0],
|
||||||
egui::TextEdit::singleline(&mut settings.flow)
|
egui::TextEdit::singleline(&mut settings.flow)
|
||||||
.hint_text("production, staging, etc.")
|
.hint_text("production, staging, development")
|
||||||
|
.font(egui::FontId::new(14.0, egui::FontFamily::Proportional))
|
||||||
|
.margin(egui::Margin::symmetric(8.0, 6.0))
|
||||||
);
|
);
|
||||||
ui.end_row();
|
});
|
||||||
|
|
||||||
ui.label("Basic Auth:");
|
// Basic Auth (optional)
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(egui::RichText::new("Basic Authentication").size(13.0).strong());
|
||||||
|
ui.label(egui::RichText::new("(optional)").size(12.0).weak().italics());
|
||||||
|
});
|
||||||
|
ui.add_space(3.0);
|
||||||
ui.add_sized(
|
ui.add_sized(
|
||||||
[ui.available_width(), 20.0],
|
[ui.available_width(), 28.0],
|
||||||
egui::TextEdit::singleline(&mut settings.basic_auth)
|
egui::TextEdit::singleline(&mut settings.basic_auth)
|
||||||
.hint_text("username:password (optional)")
|
.hint_text("username:password")
|
||||||
.password(true)
|
.password(true)
|
||||||
|
.font(egui::FontId::new(14.0, egui::FontFamily::Monospace))
|
||||||
|
.margin(egui::Margin::symmetric(8.0, 6.0))
|
||||||
);
|
);
|
||||||
ui.end_row();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_local_settings_section(ui: &mut egui::Ui, settings: &mut KhmSettings) {
|
/// Local configuration card
|
||||||
ui.group(|ui| {
|
fn render_local_config_card(ui: &mut egui::Ui, settings: &mut KhmSettings) {
|
||||||
ui.set_min_width(ui.available_width());
|
let frame = egui::Frame::group(ui.style())
|
||||||
ui.vertical(|ui| {
|
.fill(ui.visuals().faint_bg_color)
|
||||||
ui.label(egui::RichText::new("📁 Local Settings").size(16.0).strong());
|
.stroke(egui::Stroke::new(1.0, ui.visuals().widgets.noninteractive.bg_stroke.color))
|
||||||
|
.rounding(6.0)
|
||||||
|
.inner_margin(egui::Margin::same(12.0));
|
||||||
|
|
||||||
|
frame.show(ui, |ui| {
|
||||||
|
// Header
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("📁");
|
||||||
|
ui.label(egui::RichText::new("Local Configuration").size(14.0).strong());
|
||||||
|
});
|
||||||
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
egui::Grid::new("local_grid")
|
// Known hosts file
|
||||||
.num_columns(2)
|
ui.vertical(|ui| {
|
||||||
.min_col_width(120.0)
|
ui.label(egui::RichText::new("Known Hosts File Path").size(13.0).strong());
|
||||||
.spacing([10.0, 8.0])
|
ui.add_space(3.0);
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.label("Known Hosts File:");
|
|
||||||
ui.add_sized(
|
ui.add_sized(
|
||||||
[ui.available_width(), 20.0],
|
[ui.available_width(), 28.0],
|
||||||
egui::TextEdit::singleline(&mut settings.known_hosts)
|
egui::TextEdit::singleline(&mut settings.known_hosts)
|
||||||
.hint_text("~/.ssh/known_hosts")
|
.hint_text("~/.ssh/known_hosts")
|
||||||
|
.font(egui::FontId::new(14.0, egui::FontFamily::Monospace))
|
||||||
|
.margin(egui::Margin::symmetric(8.0, 6.0))
|
||||||
);
|
);
|
||||||
ui.end_row();
|
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
// In-place update option with better styling
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.checkbox(&mut settings.in_place, "");
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.label(egui::RichText::new("Update file in-place after sync").size(13.0).strong());
|
||||||
|
ui.label(egui::RichText::new("Automatically modify the known_hosts file when synchronizing").size(12.0).weak().italics());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.checkbox(&mut settings.in_place, "✏ Update known_hosts file in-place after sync");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_auto_sync_section(ui: &mut egui::Ui, settings: &mut KhmSettings, auto_sync_interval_str: &mut String) {
|
/// Auto-sync configuration card
|
||||||
ui.group(|ui| {
|
fn render_auto_sync_card(ui: &mut egui::Ui, settings: &mut KhmSettings, auto_sync_interval_str: &mut String) {
|
||||||
ui.set_min_width(ui.available_width());
|
let frame = egui::Frame::group(ui.style())
|
||||||
ui.vertical(|ui| {
|
.fill(ui.visuals().faint_bg_color)
|
||||||
ui.label(egui::RichText::new("🔄 Auto Sync").size(16.0).strong());
|
.stroke(egui::Stroke::new(1.0, ui.visuals().widgets.noninteractive.bg_stroke.color))
|
||||||
ui.add_space(8.0);
|
.rounding(6.0)
|
||||||
|
.inner_margin(egui::Margin::same(12.0));
|
||||||
|
|
||||||
|
frame.show(ui, |ui| {
|
||||||
let is_auto_sync_enabled = !settings.host.is_empty()
|
let is_auto_sync_enabled = !settings.host.is_empty()
|
||||||
&& !settings.flow.is_empty()
|
&& !settings.flow.is_empty()
|
||||||
&& settings.in_place;
|
&& settings.in_place;
|
||||||
|
|
||||||
|
// Header with status
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Interval (minutes):");
|
ui.label("🔄");
|
||||||
ui.add_sized(
|
ui.label(egui::RichText::new("Auto Sync").size(14.0).strong());
|
||||||
[80.0, 20.0],
|
|
||||||
egui::TextEdit::singleline(auto_sync_interval_str)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
|
let (status_text, status_color) = if is_auto_sync_enabled {
|
||||||
|
("● Active", egui::Color32::GREEN)
|
||||||
|
} else {
|
||||||
|
("○ Inactive", egui::Color32::from_gray(128))
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.label(egui::RichText::new(status_text).size(12.0).color(status_color));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
// Interval setting
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(egui::RichText::new("Interval").size(13.0).strong());
|
||||||
|
ui.add_space(6.0);
|
||||||
|
ui.add_sized(
|
||||||
|
[80.0, 26.0], // Smaller height
|
||||||
|
egui::TextEdit::singleline(auto_sync_interval_str)
|
||||||
|
.font(egui::FontId::new(14.0, egui::FontFamily::Monospace))
|
||||||
|
.margin(egui::Margin::symmetric(6.0, 5.0))
|
||||||
|
);
|
||||||
|
ui.label("min");
|
||||||
|
|
||||||
|
// Update the actual setting
|
||||||
if let Ok(value) = auto_sync_interval_str.parse::<u32>() {
|
if let Ok(value) = auto_sync_interval_str.parse::<u32>() {
|
||||||
if value > 0 {
|
if value > 0 {
|
||||||
settings.auto_sync_interval_minutes = value;
|
settings.auto_sync_interval_minutes = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
// Requirements - always visible
|
||||||
if is_auto_sync_enabled {
|
ui.add_space(8.0);
|
||||||
ui.label(egui::RichText::new("🔄 Enabled").color(egui::Color32::GREEN));
|
ui.separator();
|
||||||
} else {
|
ui.add_space(8.0);
|
||||||
ui.label(egui::RichText::new("❌ Disabled").color(egui::Color32::YELLOW));
|
|
||||||
ui.label("(Configure host, flow & enable in-place sync)");
|
ui.vertical(|ui| {
|
||||||
}
|
ui.label(egui::RichText::new("Requirements:").size(12.0).strong());
|
||||||
});
|
ui.add_space(3.0);
|
||||||
});
|
|
||||||
});
|
let host_ok = !settings.host.is_empty();
|
||||||
});
|
let flow_ok = !settings.flow.is_empty();
|
||||||
}
|
let in_place_ok = settings.in_place;
|
||||||
|
|
||||||
fn render_config_location_section(ui: &mut egui::Ui) {
|
|
||||||
ui.group(|ui| {
|
|
||||||
ui.set_min_width(ui.available_width());
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("🗁 Config file:");
|
let (icon, color) = if host_ok { ("✅", egui::Color32::GREEN) } else { ("❌", egui::Color32::RED) };
|
||||||
let config_path = get_config_path();
|
ui.label(egui::RichText::new(icon).color(color));
|
||||||
ui.add_sized(
|
ui.label(egui::RichText::new("Host URL").size(11.0));
|
||||||
[ui.available_width(), 20.0],
|
});
|
||||||
egui::TextEdit::singleline(&mut config_path.display().to_string())
|
|
||||||
.interactive(false)
|
ui.horizontal(|ui| {
|
||||||
);
|
let (icon, color) = if flow_ok { ("✅", egui::Color32::GREEN) } else { ("❌", egui::Color32::RED) };
|
||||||
|
ui.label(egui::RichText::new(icon).color(color));
|
||||||
|
ui.label(egui::RichText::new("Flow name").size(11.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let (icon, color) = if in_place_ok { ("✅", egui::Color32::GREEN) } else { ("❌", egui::Color32::RED) };
|
||||||
|
ui.label(egui::RichText::new(icon).color(color));
|
||||||
|
ui.label(egui::RichText::new("In-place update").size(11.0));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_bottom_area(
|
/// System information card
|
||||||
|
fn render_system_info_card(ui: &mut egui::Ui) {
|
||||||
|
let frame = egui::Frame::group(ui.style())
|
||||||
|
.fill(ui.visuals().extreme_bg_color)
|
||||||
|
.stroke(egui::Stroke::new(1.0, ui.visuals().widgets.noninteractive.bg_stroke.color))
|
||||||
|
.rounding(6.0)
|
||||||
|
.inner_margin(egui::Margin::same(12.0));
|
||||||
|
|
||||||
|
frame.show(ui, |ui| {
|
||||||
|
// Header
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("⚙️");
|
||||||
|
ui.label(egui::RichText::new("System Info").size(14.0).strong());
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
// Config file location
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.label(egui::RichText::new("Config File").size(13.0).strong());
|
||||||
|
ui.add_space(3.0);
|
||||||
|
|
||||||
|
let config_path = get_config_path();
|
||||||
|
let path_str = config_path.display().to_string();
|
||||||
|
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.add_sized(
|
||||||
|
[ui.available_width(), 26.0], // Smaller height
|
||||||
|
egui::TextEdit::singleline(&mut path_str.clone())
|
||||||
|
.interactive(false)
|
||||||
|
.font(egui::FontId::new(12.0, egui::FontFamily::Monospace))
|
||||||
|
.margin(egui::Margin::symmetric(8.0, 5.0))
|
||||||
|
);
|
||||||
|
|
||||||
|
ui.add_space(4.0);
|
||||||
|
|
||||||
|
if ui.small_button("📋 Copy Path").clicked() {
|
||||||
|
ui.output_mut(|o| o.copied_text = path_str);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Action section with buttons only (Activity Log moved to bottom panel)
|
||||||
|
fn render_action_section(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
settings: &KhmSettings,
|
settings: &KhmSettings,
|
||||||
connection_tab: &mut ConnectionTab,
|
connection_tab: &mut ConnectionTab,
|
||||||
operation_log: &mut Vec<String>
|
operation_log: &mut Vec<String>
|
||||||
) {
|
) {
|
||||||
let button_area_height = 120.0;
|
|
||||||
|
|
||||||
ui.allocate_ui_with_layout(
|
|
||||||
[ui.available_width(), button_area_height].into(),
|
|
||||||
egui::Layout::bottom_up(egui::Align::Min),
|
|
||||||
|ui| {
|
|
||||||
// Operation log area
|
|
||||||
ui.group(|ui| {
|
|
||||||
ui.set_min_width(ui.available_width());
|
|
||||||
ui.vertical(|ui| {
|
|
||||||
ui.label(egui::RichText::new("📄 Operation Log").size(14.0).strong());
|
|
||||||
ui.add_space(5.0);
|
|
||||||
|
|
||||||
let log_text = operation_log.join("\n");
|
|
||||||
ui.add_sized(
|
|
||||||
[ui.available_width(), 60.0],
|
|
||||||
egui::TextEdit::multiline(&mut log_text.clone())
|
|
||||||
.font(egui::FontId::monospace(10.0))
|
|
||||||
.interactive(false)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
// Show validation hints
|
// Validation message
|
||||||
let save_enabled = !settings.host.is_empty() && !settings.flow.is_empty();
|
let save_enabled = !settings.host.is_empty() && !settings.flow.is_empty();
|
||||||
if !save_enabled {
|
if !save_enabled {
|
||||||
ui.label(egui::RichText::new("❗ Please fill in Host URL and Flow Name to save settings")
|
ui.horizontal(|ui| {
|
||||||
.color(egui::Color32::YELLOW)
|
ui.label("⚠️");
|
||||||
|
ui.label(egui::RichText::new("Complete server configuration to enable saving")
|
||||||
|
.size(12.0)
|
||||||
|
.color(egui::Color32::LIGHT_YELLOW)
|
||||||
.italics());
|
.italics());
|
||||||
|
});
|
||||||
|
ui.add_space(8.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_space(5.0);
|
// Action buttons with modern styling
|
||||||
|
render_modern_action_buttons(ui, ctx, settings, connection_tab, save_enabled, operation_log);
|
||||||
// Action buttons
|
|
||||||
render_action_buttons(ui, ctx, settings, connection_tab, save_enabled, operation_log);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_action_buttons(
|
/// Modern action buttons with improved styling and layout
|
||||||
|
fn render_modern_action_buttons(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
settings: &KhmSettings,
|
settings: &KhmSettings,
|
||||||
@@ -273,10 +422,23 @@ fn render_action_buttons(
|
|||||||
operation_log: &mut Vec<String>
|
operation_log: &mut Vec<String>
|
||||||
) {
|
) {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
ui.spacing_mut().item_spacing.x = 8.0;
|
||||||
|
|
||||||
|
// Primary actions (left side)
|
||||||
if ui.add_enabled(
|
if ui.add_enabled(
|
||||||
save_enabled,
|
save_enabled,
|
||||||
egui::Button::new("💾 Save Settings")
|
egui::Button::new(
|
||||||
|
egui::RichText::new("💾 Save & Close")
|
||||||
|
.size(13.0)
|
||||||
|
.color(egui::Color32::WHITE)
|
||||||
|
)
|
||||||
|
.fill(if save_enabled {
|
||||||
|
egui::Color32::from_rgb(0, 120, 212)
|
||||||
|
} else {
|
||||||
|
ui.visuals().widgets.inactive.bg_fill
|
||||||
|
})
|
||||||
.min_size(egui::vec2(120.0, 32.0))
|
.min_size(egui::vec2(120.0, 32.0))
|
||||||
|
.rounding(6.0)
|
||||||
).clicked() {
|
).clicked() {
|
||||||
match save_settings_validated(settings) {
|
match save_settings_validated(settings) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
@@ -290,45 +452,76 @@ fn render_action_buttons(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ui.add(
|
if ui.add(
|
||||||
egui::Button::new("✖ Cancel")
|
egui::Button::new(
|
||||||
|
egui::RichText::new("✖ Cancel")
|
||||||
|
.size(13.0)
|
||||||
|
.color(ui.visuals().text_color())
|
||||||
|
)
|
||||||
|
.stroke(egui::Stroke::new(1.0, ui.visuals().text_color()))
|
||||||
|
.fill(egui::Color32::TRANSPARENT)
|
||||||
.min_size(egui::vec2(80.0, 32.0))
|
.min_size(egui::vec2(80.0, 32.0))
|
||||||
|
.rounding(6.0)
|
||||||
).clicked() {
|
).clicked() {
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
// Spacer
|
||||||
|
ui.add_space(ui.available_width() - 220.0);
|
||||||
|
|
||||||
|
// Secondary actions (right side)
|
||||||
let can_test = !settings.host.is_empty() && !settings.flow.is_empty() && !connection_tab.is_testing_connection;
|
let can_test = !settings.host.is_empty() && !settings.flow.is_empty() && !connection_tab.is_testing_connection;
|
||||||
let can_sync = !settings.host.is_empty() && !settings.flow.is_empty() && !connection_tab.is_syncing;
|
let can_sync = !settings.host.is_empty() && !settings.flow.is_empty() && !connection_tab.is_syncing;
|
||||||
|
|
||||||
if ui.add_enabled(
|
if ui.add_enabled(
|
||||||
can_test,
|
can_test,
|
||||||
egui::Button::new(
|
egui::Button::new(
|
||||||
|
egui::RichText::new(
|
||||||
if connection_tab.is_testing_connection {
|
if connection_tab.is_testing_connection {
|
||||||
"▶ Testing..."
|
"🔄 Testing..."
|
||||||
} else {
|
} else {
|
||||||
"🔍 Test Connection"
|
"🔍 Test"
|
||||||
}
|
}
|
||||||
).min_size(egui::vec2(120.0, 32.0))
|
)
|
||||||
).clicked() {
|
.size(13.0)
|
||||||
add_log_entry(operation_log, "🔍 Starting connection test...".to_string());
|
.color(egui::Color32::WHITE)
|
||||||
|
)
|
||||||
|
.fill(if can_test {
|
||||||
|
egui::Color32::from_rgb(16, 124, 16)
|
||||||
|
} else {
|
||||||
|
ui.visuals().widgets.inactive.bg_fill
|
||||||
|
})
|
||||||
|
.min_size(egui::vec2(80.0, 32.0))
|
||||||
|
.rounding(6.0)
|
||||||
|
).on_hover_text("Test server connection").clicked() {
|
||||||
|
add_log_entry(operation_log, "🔍 Testing connection...".to_string());
|
||||||
connection_tab.start_test(settings, ctx);
|
connection_tab.start_test(settings, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.add_enabled(
|
if ui.add_enabled(
|
||||||
can_sync,
|
can_sync,
|
||||||
egui::Button::new(
|
egui::Button::new(
|
||||||
|
egui::RichText::new(
|
||||||
if connection_tab.is_syncing {
|
if connection_tab.is_syncing {
|
||||||
"🔄 Syncing..."
|
"🔄 Syncing..."
|
||||||
} else {
|
} else {
|
||||||
"🔄 Sync Now"
|
"🔄 Sync"
|
||||||
}
|
}
|
||||||
).min_size(egui::vec2(100.0, 32.0))
|
)
|
||||||
).clicked() {
|
.size(13.0)
|
||||||
add_log_entry(operation_log, "🔄 Starting manual sync...".to_string());
|
.color(egui::Color32::WHITE)
|
||||||
|
)
|
||||||
|
.fill(if can_sync {
|
||||||
|
egui::Color32::from_rgb(255, 140, 0)
|
||||||
|
} else {
|
||||||
|
ui.visuals().widgets.inactive.bg_fill
|
||||||
|
})
|
||||||
|
.min_size(egui::vec2(80.0, 32.0))
|
||||||
|
.rounding(6.0)
|
||||||
|
).on_hover_text("Synchronize SSH keys now").clicked() {
|
||||||
|
add_log_entry(operation_log, "🔄 Starting sync...".to_string());
|
||||||
connection_tab.start_sync(settings, ctx);
|
connection_tab.start_sync(settings, ctx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add entry to operation log with timestamp
|
/// Add entry to operation log with timestamp
|
||||||
|
@@ -47,25 +47,123 @@ impl eframe::App for SettingsWindow {
|
|||||||
// Apply enhanced modern dark theme
|
// Apply enhanced modern dark theme
|
||||||
apply_modern_theme(ctx);
|
apply_modern_theme(ctx);
|
||||||
|
|
||||||
egui::CentralPanel::default()
|
// Bottom panel for Activity Log (fixed at bottom)
|
||||||
.frame(egui::Frame::none().inner_margin(egui::Margin::same(20.0)))
|
egui::TopBottomPanel::bottom("activity_log_panel")
|
||||||
|
.resizable(false)
|
||||||
|
.min_height(140.0)
|
||||||
|
.max_height(140.0)
|
||||||
|
.frame(egui::Frame::none()
|
||||||
|
.fill(egui::Color32::from_gray(12))
|
||||||
|
.stroke(egui::Stroke::new(1.0, egui::Color32::from_gray(60)))
|
||||||
|
)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
// Header with title
|
render_bottom_activity_log(ui, &mut self.operation_log);
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.heading(egui::RichText::new("🔑 KHM Settings").size(24.0));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(10.0);
|
egui::CentralPanel::default()
|
||||||
|
.frame(egui::Frame::none()
|
||||||
|
.fill(egui::Color32::from_gray(18))
|
||||||
|
.inner_margin(egui::Margin::same(20.0))
|
||||||
|
)
|
||||||
|
.show(ctx, |ui| {
|
||||||
|
// Modern header with gradient-like styling
|
||||||
|
let header_frame = egui::Frame::none()
|
||||||
|
.fill(ui.visuals().panel_fill)
|
||||||
|
.rounding(egui::Rounding::same(8.0))
|
||||||
|
.inner_margin(egui::Margin::same(12.0))
|
||||||
|
.stroke(egui::Stroke::new(1.0, ui.visuals().widgets.noninteractive.bg_stroke.color));
|
||||||
|
|
||||||
// Tab selector
|
header_frame.show(ui, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.selectable_value(&mut self.current_tab, SettingsTab::Connection, "📃 Settings");
|
ui.add_space(4.0);
|
||||||
ui.selectable_value(&mut self.current_tab, SettingsTab::Admin, "🔧 Admin");
|
ui.label("🔑");
|
||||||
|
ui.heading(egui::RichText::new("KHM Settings").size(20.0).strong());
|
||||||
|
ui.label(egui::RichText::new(
|
||||||
|
"(Known Hosts Manager for SSH key management and synchronization)"
|
||||||
|
).size(11.0).weak().italics());
|
||||||
|
|
||||||
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
|
// Version from Cargo.toml
|
||||||
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
|
if ui.small_button(format!("v{}", version))
|
||||||
|
.on_hover_text(format!(
|
||||||
|
"{}\n{}\nRepository: {}\nLicense: {}",
|
||||||
|
env!("CARGO_PKG_DESCRIPTION"),
|
||||||
|
env!("CARGO_PKG_AUTHORS"),
|
||||||
|
env!("CARGO_PKG_REPOSITORY"),
|
||||||
|
"WTFPL"
|
||||||
|
))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
// Open repository URL
|
||||||
|
if let Err(_) = std::process::Command::new("open")
|
||||||
|
.arg(env!("CARGO_PKG_REPOSITORY"))
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
// Fallback for non-macOS systems
|
||||||
|
let _ = std::process::Command::new("xdg-open")
|
||||||
|
.arg(env!("CARGO_PKG_REPOSITORY"))
|
||||||
|
.spawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.separator();
|
ui.add_space(12.0);
|
||||||
ui.add_space(15.0);
|
|
||||||
|
|
||||||
|
// Modern tab selector with card styling
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.spacing_mut().item_spacing.x = 6.0;
|
||||||
|
|
||||||
|
// Connection/Settings Tab
|
||||||
|
let connection_selected = matches!(self.current_tab, SettingsTab::Connection);
|
||||||
|
let connection_button = egui::Button::new(
|
||||||
|
egui::RichText::new("🌐 Connection").size(13.0)
|
||||||
|
)
|
||||||
|
.fill(if connection_selected {
|
||||||
|
egui::Color32::from_rgb(0, 120, 212)
|
||||||
|
} else {
|
||||||
|
ui.visuals().widgets.inactive.bg_fill
|
||||||
|
})
|
||||||
|
.stroke(if connection_selected {
|
||||||
|
egui::Stroke::new(1.0, egui::Color32::from_rgb(0, 120, 212))
|
||||||
|
} else {
|
||||||
|
egui::Stroke::new(1.0, ui.visuals().widgets.noninteractive.bg_stroke.color)
|
||||||
|
})
|
||||||
|
.rounding(6.0)
|
||||||
|
.min_size(egui::vec2(110.0, 32.0));
|
||||||
|
|
||||||
|
if ui.add(connection_button).clicked() {
|
||||||
|
self.current_tab = SettingsTab::Connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin Tab
|
||||||
|
let admin_selected = matches!(self.current_tab, SettingsTab::Admin);
|
||||||
|
let admin_button = egui::Button::new(
|
||||||
|
egui::RichText::new("🔧 Admin Panel").size(13.0)
|
||||||
|
)
|
||||||
|
.fill(if admin_selected {
|
||||||
|
egui::Color32::from_rgb(120, 80, 0)
|
||||||
|
} else {
|
||||||
|
ui.visuals().widgets.inactive.bg_fill
|
||||||
|
})
|
||||||
|
.stroke(if admin_selected {
|
||||||
|
egui::Stroke::new(1.0, egui::Color32::from_rgb(120, 80, 0))
|
||||||
|
} else {
|
||||||
|
egui::Stroke::new(1.0, ui.visuals().widgets.noninteractive.bg_stroke.color)
|
||||||
|
})
|
||||||
|
.rounding(6.0)
|
||||||
|
.min_size(egui::vec2(110.0, 32.0));
|
||||||
|
|
||||||
|
if ui.add(admin_button).clicked() {
|
||||||
|
self.current_tab = SettingsTab::Admin;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(16.0);
|
||||||
|
|
||||||
|
// Content area with proper spacing
|
||||||
match self.current_tab {
|
match self.current_tab {
|
||||||
SettingsTab::Connection => {
|
SettingsTab::Connection => {
|
||||||
render_connection_tab(
|
render_connection_tab(
|
||||||
@@ -352,25 +450,93 @@ impl SettingsWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply modern dark theme for the settings window
|
/// Apply modern dark theme for the settings window with enhanced styling
|
||||||
fn apply_modern_theme(ctx: &egui::Context) {
|
fn apply_modern_theme(ctx: &egui::Context) {
|
||||||
let mut visuals = egui::Visuals::dark();
|
let mut visuals = egui::Visuals::dark();
|
||||||
visuals.window_fill = egui::Color32::from_gray(25);
|
|
||||||
visuals.panel_fill = egui::Color32::from_gray(30);
|
// Modern color palette
|
||||||
visuals.faint_bg_color = egui::Color32::from_gray(35);
|
visuals.window_fill = egui::Color32::from_gray(18); // Darker background
|
||||||
visuals.extreme_bg_color = egui::Color32::from_gray(15);
|
visuals.panel_fill = egui::Color32::from_gray(24); // Panel background
|
||||||
|
visuals.faint_bg_color = egui::Color32::from_gray(32); // Card background
|
||||||
|
visuals.extreme_bg_color = egui::Color32::from_gray(12); // Darkest areas
|
||||||
|
|
||||||
|
// Enhanced widget styling
|
||||||
visuals.button_frame = true;
|
visuals.button_frame = true;
|
||||||
visuals.collapsing_header_frame = true;
|
visuals.collapsing_header_frame = true;
|
||||||
visuals.indent_has_left_vline = true;
|
visuals.indent_has_left_vline = true;
|
||||||
visuals.menu_rounding = egui::Rounding::same(8.0);
|
visuals.striped = true;
|
||||||
visuals.window_rounding = egui::Rounding::same(12.0);
|
|
||||||
visuals.widgets.noninteractive.rounding = egui::Rounding::same(6.0);
|
// Modern rounded corners
|
||||||
visuals.widgets.inactive.rounding = egui::Rounding::same(6.0);
|
let rounding = egui::Rounding::same(8.0);
|
||||||
visuals.widgets.hovered.rounding = egui::Rounding::same(6.0);
|
visuals.menu_rounding = rounding;
|
||||||
visuals.widgets.active.rounding = egui::Rounding::same(6.0);
|
visuals.window_rounding = egui::Rounding::same(16.0);
|
||||||
|
visuals.widgets.noninteractive.rounding = rounding;
|
||||||
|
visuals.widgets.inactive.rounding = rounding;
|
||||||
|
visuals.widgets.hovered.rounding = rounding;
|
||||||
|
visuals.widgets.active.rounding = rounding;
|
||||||
|
|
||||||
|
// Better widget colors
|
||||||
|
visuals.widgets.noninteractive.bg_fill = egui::Color32::from_gray(40);
|
||||||
|
visuals.widgets.inactive.bg_fill = egui::Color32::from_gray(45);
|
||||||
|
visuals.widgets.hovered.bg_fill = egui::Color32::from_gray(55);
|
||||||
|
visuals.widgets.active.bg_fill = egui::Color32::from_gray(60);
|
||||||
|
|
||||||
|
// Subtle borders
|
||||||
|
let border_color = egui::Color32::from_gray(60);
|
||||||
|
visuals.widgets.noninteractive.bg_stroke = egui::Stroke::new(1.0, border_color);
|
||||||
|
visuals.widgets.inactive.bg_stroke = egui::Stroke::new(1.0, border_color);
|
||||||
|
visuals.widgets.hovered.bg_stroke = egui::Stroke::new(1.5, egui::Color32::from_gray(80));
|
||||||
|
visuals.widgets.active.bg_stroke = egui::Stroke::new(1.5, egui::Color32::from_gray(100));
|
||||||
|
|
||||||
ctx.set_visuals(visuals);
|
ctx.set_visuals(visuals);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render bottom activity log panel
|
||||||
|
fn render_bottom_activity_log(ui: &mut egui::Ui, operation_log: &mut Vec<String>) {
|
||||||
|
ui.add_space(18.0); // Larger top padding
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_space(8.0);
|
||||||
|
ui.label("📋");
|
||||||
|
ui.label(egui::RichText::new("Activity Log").size(13.0).strong());
|
||||||
|
|
||||||
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| {
|
||||||
|
ui.add_space(8.0);
|
||||||
|
if ui.small_button("🗑 Clear").clicked() {
|
||||||
|
operation_log.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
// Add horizontal margin for the text area
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_space(8.0); // Left margin
|
||||||
|
|
||||||
|
// Show last 5 log entries in multiline text
|
||||||
|
let log_text = if operation_log.is_empty() {
|
||||||
|
"No recent activity".to_string()
|
||||||
|
} else {
|
||||||
|
let start_idx = if operation_log.len() > 5 {
|
||||||
|
operation_log.len() - 5
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
operation_log[start_idx..].join("\n")
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.add_sized(
|
||||||
|
[ui.available_width() - 8.0, 80.0], // Account for right margin
|
||||||
|
egui::TextEdit::multiline(&mut log_text.clone())
|
||||||
|
.font(egui::FontId::new(11.0, egui::FontFamily::Monospace))
|
||||||
|
.interactive(false)
|
||||||
|
);
|
||||||
|
|
||||||
|
ui.add_space(8.0); // Right margin
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Create window icon for settings window
|
/// Create window icon for settings window
|
||||||
pub fn create_window_icon() -> egui::IconData {
|
pub fn create_window_icon() -> egui::IconData {
|
||||||
// Create a simple programmatic icon (blue square with white border)
|
// Create a simple programmatic icon (blue square with white border)
|
||||||
@@ -394,15 +560,19 @@ pub fn create_window_icon() -> egui::IconData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the settings window application
|
/// Run the settings window application with modern horizontal styling
|
||||||
pub fn run_settings_window() {
|
pub fn run_settings_window() {
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
viewport: egui::ViewportBuilder::default()
|
viewport: egui::ViewportBuilder::default()
|
||||||
.with_title("KHM Settings")
|
.with_title("KHM Settings")
|
||||||
.with_inner_size([600.0, 800.0])
|
.with_inner_size([900.0, 905.0]) // Decreased height by another 15px
|
||||||
.with_min_inner_size([500.0, 650.0])
|
.with_min_inner_size([900.0, 905.0]) // Fixed size
|
||||||
.with_resizable(true)
|
.with_max_inner_size([900.0, 905.0]) // Same as min - fixed size
|
||||||
.with_icon(create_window_icon()),
|
.with_resizable(false) // Disable resizing since window is fixed size
|
||||||
|
.with_icon(create_window_icon())
|
||||||
|
.with_decorations(true)
|
||||||
|
.with_transparent(false),
|
||||||
|
centered: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user