Хуйня не на tauri
This commit is contained in:
parent
e895fe3237
commit
403d7086d4
104 changed files with 8941 additions and 4772 deletions
219
src/ui/app/settings/mod.rs
Normal file
219
src/ui/app/settings/mod.rs
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
//! Диалог Settings — popup с меню разделов слева и содержимым справа
|
||||
//! (General/Appearance/Connections/History/Security/Transfers/About).
|
||||
use egui::{Align2, Context, RichText, Ui};
|
||||
|
||||
use super::FileManagerApp;
|
||||
use crate::ui::state::SettingsSection;
|
||||
use crate::ui::theme::*;
|
||||
use crate::ui::widgets::{button, overlay};
|
||||
|
||||
mod about;
|
||||
mod connections;
|
||||
mod general;
|
||||
mod nav;
|
||||
pub(super) mod prefs;
|
||||
mod transfers;
|
||||
|
||||
const NAV_HEIGHT: f32 = 420.0;
|
||||
|
||||
impl FileManagerApp {
|
||||
pub(super) fn render_settings_dialog(&mut self, ctx: &Context, t: f32) {
|
||||
if overlay::dim(ctx, "settings_overlay", t) {
|
||||
self.state.show_settings_dialog = false;
|
||||
}
|
||||
|
||||
egui::Window::new("settings_dialog")
|
||||
.collapsible(false)
|
||||
.title_bar(false)
|
||||
.resizable(false)
|
||||
.movable(false)
|
||||
.anchor(Align2::CENTER_CENTER, overlay::slide_offset(t))
|
||||
.interactable(t >= 1.0)
|
||||
.frame(overlay::panel_frame())
|
||||
.show(ctx, |ui| {
|
||||
ui.set_opacity(overlay::ease_out(t));
|
||||
ui.set_width(600.0);
|
||||
header(ui, &mut self.state.show_settings_dialog);
|
||||
ui.add_space(12.0);
|
||||
let (_, sep) = ui.allocate_space(egui::vec2(ui.available_width(), 1.0));
|
||||
ui.painter().rect_filled(sep, 0.0, BORDER);
|
||||
ui.add_space(14.0);
|
||||
|
||||
ui.horizontal_top(|ui| {
|
||||
// ScrollArea наследует layout родителя, а здесь он
|
||||
// горизонтальный — без ui.vertical() содержимое скроллов
|
||||
// растекается в одну строку и окно вылезает за экран.
|
||||
ui.vertical(|ui| {
|
||||
egui::ScrollArea::vertical()
|
||||
.id_salt("settings_nav_scroll")
|
||||
.max_height(NAV_HEIGHT)
|
||||
.show(ui, |ui| {
|
||||
nav::render(ui, &mut self.state.settings_section);
|
||||
});
|
||||
});
|
||||
|
||||
let (_, vsep) = ui.allocate_space(egui::vec2(1.0, NAV_HEIGHT));
|
||||
ui.painter().rect_filled(vsep, 0.0, BORDER);
|
||||
ui.add_space(18.0);
|
||||
|
||||
ui.vertical(|ui| {
|
||||
// Кросс-фейд содержимого при переключении раздела:
|
||||
// фиксируем момент смены и плавно поднимаем прозрачность.
|
||||
if self.state.settings_section != self.state.settings_prev_section {
|
||||
self.state.settings_prev_section = self.state.settings_section;
|
||||
self.state.settings_section_changed_at = ui.input(|i| i.time);
|
||||
}
|
||||
let fade = ((ui.input(|i| i.time) - self.state.settings_section_changed_at)
|
||||
/ 0.18)
|
||||
.clamp(0.0, 1.0) as f32;
|
||||
if fade < 1.0 {
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
ui.multiply_opacity(overlay::ease_out(fade));
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.id_salt("settings_content_scroll")
|
||||
.max_height(NAV_HEIGHT)
|
||||
.show(ui, |ui| {
|
||||
ui.set_width(370.0);
|
||||
match self.state.settings_section {
|
||||
SettingsSection::General => self.render_general(ui),
|
||||
SettingsSection::Appearance => self.render_appearance(ui),
|
||||
SettingsSection::Connections => self.render_connections(ui),
|
||||
SettingsSection::History => self.render_history_section(ui),
|
||||
SettingsSection::Security => self.render_security(ui),
|
||||
SettingsSection::Transfers => self.render_transfers(ui),
|
||||
SettingsSection::About => self.render_about(ui),
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn header(ui: &mut Ui, show: &mut bool) {
|
||||
ui.horizontal(|ui| {
|
||||
crate::ui::icons::icon(ui, crate::ui::icons::Icon::Settings, 16.0, ACCENT);
|
||||
ui.add_space(8.0);
|
||||
ui.label(
|
||||
RichText::new(crate::i18n::t("settings.title"))
|
||||
.color(TEXT_PRIMARY)
|
||||
.size(15.0)
|
||||
.strong(),
|
||||
);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
let close = egui::Button::new(RichText::new("✕").color(TEXT_DIM).size(12.0))
|
||||
.rounding(RADIUS_SM)
|
||||
.min_size(egui::vec2(24.0, 24.0));
|
||||
if button::ghost(ui, close).clicked() {
|
||||
*show = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Заголовок раздела ("General", "Transfers"...), одинаковый во всех секциях.
|
||||
pub(super) fn section_title(ui: &mut Ui, text: &str) {
|
||||
ui.label(RichText::new(text).color(TEXT_PRIMARY).size(15.0).strong());
|
||||
ui.add_space(12.0);
|
||||
}
|
||||
|
||||
/// Подсказка серым текстом под заголовком/контролом.
|
||||
pub(super) fn hint(ui: &mut Ui, text: &str) {
|
||||
ui.label(RichText::new(text).color(TEXT_HINT).size(11.0));
|
||||
}
|
||||
|
||||
/// Строка "метка — значение" только для чтения (версия, путь к БД и т.п.).
|
||||
/// Длинные значения (пути) усекаются с «…», полный текст — по наведению.
|
||||
pub(super) fn value_row(ui: &mut Ui, label: &str, value: &str) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(RichText::new(label).color(TEXT_DIM).size(11.5));
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
let text = RichText::new(value).color(TEXT_HINT).monospace().size(10.5);
|
||||
let resp = ui.add(egui::Label::new(text).truncate());
|
||||
if resp.hovered() && value.len() > 32 {
|
||||
resp.on_hover_text(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Пустое состояние списка ("No saved sites yet." и т.п.).
|
||||
pub(super) fn empty_state(ui: &mut Ui, text: &str) {
|
||||
ui.label(RichText::new(text).color(TEXT_HINT).size(11.5));
|
||||
}
|
||||
|
||||
/// Строка элемента списка с кнопкой-иконкой справа (удалить/забыть и т.п.).
|
||||
/// Возвращает `true`, если по кнопке кликнули.
|
||||
pub(super) fn list_row_with_action(
|
||||
ui: &mut Ui,
|
||||
label: &str,
|
||||
sublabel: &str,
|
||||
action_icon: crate::ui::icons::Icon,
|
||||
action_hover: &str,
|
||||
) -> bool {
|
||||
let mut clicked = false;
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.label(RichText::new(label).color(TEXT_PRIMARY).size(12.5));
|
||||
if !sublabel.is_empty() {
|
||||
ui.label(RichText::new(sublabel).color(TEXT_HINT).size(10.5));
|
||||
}
|
||||
});
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
let btn = egui::Button::image(crate::ui::icons::image(action_icon, 13.0, TEXT_DIM))
|
||||
.rounding(RADIUS_SM)
|
||||
.min_size(egui::vec2(26.0, 26.0));
|
||||
if button::ghost(ui, btn).on_hover_text(action_hover).clicked() {
|
||||
clicked = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
clicked
|
||||
}
|
||||
|
||||
/// Сегментированный ряд из равных по ширине кнопок (используется в Transfers
|
||||
/// для выбора числа/интервала) — тот же паттерн, что и выбор протокола в
|
||||
/// диалоге New Connection.
|
||||
pub(super) fn segmented_row<T: Copy + PartialEq>(
|
||||
ui: &mut Ui,
|
||||
options: &[(&str, T)],
|
||||
current: T,
|
||||
) -> Option<T> {
|
||||
let mut picked = None;
|
||||
let gap = 4.0;
|
||||
let n = options.len().max(1) as f32;
|
||||
// На первом кадре свежего egui::Window ширина ещё не устоялась и может
|
||||
// быть меньше суммарных отступов — clamp защищает от negative desired_size.
|
||||
let seg_w = ((ui.available_width() - gap * (n - 1.0) - 6.0) / n).max(0.0);
|
||||
|
||||
egui::Frame::none()
|
||||
.fill(BG_CONTENT)
|
||||
.stroke(egui::Stroke::new(1.0, BORDER))
|
||||
.rounding(RADIUS_MD)
|
||||
.inner_margin(egui::Margin::same(3.0))
|
||||
.show(ui, |ui| {
|
||||
ui.spacing_mut().item_spacing.x = gap;
|
||||
ui.horizontal(|ui| {
|
||||
for (label, value) in options {
|
||||
let active = *value == current;
|
||||
let tc = if active { ON_ACCENT } else { TEXT_DIM };
|
||||
let btn = egui::Button::new(RichText::new(*label).color(tc).size(11.5))
|
||||
.rounding(RADIUS_SM)
|
||||
.min_size(egui::vec2(seg_w, 26.0));
|
||||
let resp = if active {
|
||||
button::accent(ui, btn)
|
||||
} else {
|
||||
button::ghost(ui, btn)
|
||||
};
|
||||
if resp.clicked() {
|
||||
picked = Some(*value);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
picked
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue