use std::path::PathBuf;
use std::sync::mpsc;
use eframe::egui;
use rust_i18n::t;
use crate::{
panels::settings,
preferences::{KeyBind, Keybinds, Language, Preferences},
state::AppState,
updater::{self, UpdateMsg, UpdateStatus},
};
#[derive(Clone, Copy, PartialEq, Default)]
enum Tab {
#[default]
General,
Defaults,
Updates,
Keybinds,
}
pub fn show(
ctx: &egui::Context,
prefs: &mut Preferences,
open: &mut bool,
update_status: &mut UpdateStatus,
update_rx: &mut Option<mpsc::Receiver<UpdateMsg>>,
) {
poll_updates(update_status, update_rx);
egui::Window::new(t!("prefs.title"))
.open(open)
.resizable(true)
.default_size([520.0, 500.0])
.collapsible(false)
.show(ctx, |ui| {
let tab_id = egui::Id::new("prefs_active_tab");
let mut tab: Tab = ctx.data(|d| d.get_temp(tab_id).unwrap_or_default());
ui.horizontal(|ui| {
ui.selectable_value(&mut tab, Tab::General, t!("prefs.tab_general"));
ui.selectable_value(&mut tab, Tab::Defaults, t!("prefs.tab_defaults"));
ui.selectable_value(&mut tab, Tab::Updates, t!("prefs.tab_updates"));
ui.selectable_value(&mut tab, Tab::Keybinds, "Keybinds");
});
ui.separator();
ctx.data_mut(|d| d.insert_temp(tab_id, tab));
match tab {
Tab::General => show_general(ui, prefs),
Tab::Defaults => show_defaults(ui, prefs),
Tab::Updates => show_updates(ui, prefs, update_status, update_rx),
Tab::Keybinds => show_keybinds(ui, prefs),
}
});
}
fn poll_updates(
update_status: &mut UpdateStatus,
update_rx: &mut Option<mpsc::Receiver<UpdateMsg>>,
) {
let Some(rx) = update_rx.as_ref() else { return };
match rx.try_recv() {
Ok(UpdateMsg::UpToDate { latest }) => {
*update_status = UpdateStatus::UpToDate { latest };
*update_rx = None;
}
Ok(UpdateMsg::Available(info)) => {
*update_status = UpdateStatus::Available(info);
*update_rx = None;
}
Ok(UpdateMsg::Downloaded(path)) => {
*update_status = UpdateStatus::Downloaded(path);
*update_rx = None;
}
Ok(UpdateMsg::Error(msg)) => {
*update_status = UpdateStatus::Error(msg);
*update_rx = None;
}
Err(mpsc::TryRecvError::Empty) => {}
Err(mpsc::TryRecvError::Disconnected) => {
*update_rx = None;
}
}
}
fn show_general(ui: &mut egui::Ui, prefs: &mut Preferences) {
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.label(t!("prefs.language"));
let mut selected = prefs.language;
egui::ComboBox::from_id_salt("language_selector")
.selected_text(selected.display())
.show_ui(ui, |ui| {
for &lang in Language::ALL {
ui.selectable_value(&mut selected, lang, lang.display());
}
});
if selected != prefs.language {
prefs.language = selected;
rust_i18n::set_locale(selected.code());
prefs.save();
}
});
}
fn show_defaults(ui: &mut egui::Ui, prefs: &mut Preferences) {
let mut tmp = AppState::default();
tmp.project.config = prefs.default_config.clone();
egui::ScrollArea::vertical()
.id_salt("prefs_defaults_scroll")
.show(ui, |ui| {
section(ui, "texture", t!("settings.texture"), |ui| {
settings::show_texture(ui, &mut tmp)
});
section(ui, "layout", t!("settings.layout"), |ui| {
settings::show_layout(ui, &mut tmp)
});
section(ui, "sprites", t!("settings.sprites"), |ui| {
settings::show_sprites(ui, &mut tmp)
});
section(ui, "variants", t!("settings.variants"), |ui| {
settings::show_variants(ui, &mut tmp)
});
ui.add_space(4.0);
ui.label(
egui::RichText::new(t!("prefs.defaults_hint"))
.small()
.weak(),
);
});
if tmp.dirty {
prefs.default_config = tmp.project.config;
prefs.save();
}
}
fn section(
ui: &mut egui::Ui,
id_key: &str,
label: impl Into<String>,
body: impl FnOnce(&mut egui::Ui),
) {
let id = egui::Id::new(("prefs_section", id_key));
egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, true)
.show_header(ui, |ui| {
ui.strong(label.into());
})
.body(|ui| {
ui.add_space(2.0);
body(ui);
ui.add_space(4.0);
});
ui.separator();
}
fn show_updates(
ui: &mut egui::Ui,
prefs: &mut Preferences,
update_status: &mut UpdateStatus,
update_rx: &mut Option<mpsc::Receiver<UpdateMsg>>,
) {
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.label(t!("prefs.version"));
ui.strong(format!("v{}", updater::CURRENT_VERSION));
});
ui.horizontal(|ui| {
ui.label(t!("prefs.latest"));
match update_status {
UpdateStatus::Idle => {
ui.weak(t!("prefs.not_checked"));
}
UpdateStatus::Checking => {
ui.weak(t!("prefs.checking"));
}
UpdateStatus::UpToDate { latest } => {
ui.label(format!("v{} {}", latest, t!("prefs.up_to_date")));
}
UpdateStatus::Available(info) => {
ui.strong(format!(
"v{} {}",
info.version,
t!("prefs.update_available")
));
}
UpdateStatus::Downloading => {
ui.weak(t!("prefs.downloading"));
}
UpdateStatus::Downloaded(_) => {
ui.label(t!("prefs.downloaded"));
}
UpdateStatus::Error(msg) => {
ui.colored_label(egui::Color32::from_rgb(220, 70, 70), msg.as_str());
}
}
});
ui.add_space(8.0);
let busy = matches!(
update_status,
UpdateStatus::Checking | UpdateStatus::Downloading
);
if ui
.add_enabled(!busy, egui::Button::new(t!("prefs.check_updates")))
.clicked()
{
let (tx, rx) = mpsc::channel();
updater::spawn_check(tx);
*update_rx = Some(rx);
*update_status = UpdateStatus::Checking;
}
if ui
.checkbox(&mut prefs.auto_check_updates, t!("prefs.auto_check"))
.changed()
{
prefs.save();
}
ui.add_space(8.0);
if let UpdateStatus::Available(info) = update_status {
egui::Frame::dark_canvas(ui.style()).show(ui, |ui| {
egui::ScrollArea::vertical()
.id_salt("prefs_notes_scroll")
.max_height(150.0)
.show(ui, |ui| {
ui.add(
egui::Label::new(egui::RichText::new(&info.notes).small())
.wrap_mode(egui::TextWrapMode::Wrap),
);
});
});
ui.add_space(4.0);
let info_clone = info.clone();
if ui
.add_enabled(!busy, egui::Button::new(t!("prefs.download_install")))
.clicked()
{
let (tx, rx) = mpsc::channel();
updater::spawn_download(info_clone, tx);
*update_rx = Some(rx);
*update_status = UpdateStatus::Downloading;
}
}
if let UpdateStatus::Downloaded(path) = update_status {
ui.label(t!("prefs.restart_hint"));
let path: PathBuf = path.clone();
if ui.button(t!("prefs.restart")).clicked() {
if let Err(e) = updater::apply_update(&path) {
*update_status = UpdateStatus::Error(e);
}
}
}
}
fn show_keybinds(ui: &mut egui::Ui, prefs: &mut Preferences) {
let cap_id = egui::Id::new("kb_capturing");
let mut cap: u8 = ui.ctx().data(|d| d.get_temp(cap_id).unwrap_or(0));
let mut save = false;
if cap != 0 {
let mut captured: Option<KeyBind> = None;
let mut canceled = false;
ui.ctx().input(|i| {
for event in &i.events {
if let egui::Event::Key {
key,
pressed: true,
modifiers,
..
} = event
{
if *key == egui::Key::Escape {
canceled = true;
} else if let Some(name) = crate::menu::egui_key_to_str(*key) {
captured = Some(KeyBind {
key: name.to_owned(),
ctrl: modifiers.ctrl,
shift: modifiers.shift,
alt: modifiers.alt,
});
}
}
}
});
if canceled {
cap = 0;
}
if let Some(bind) = captured {
match cap {
1 => prefs.keybinds.new_project = bind,
2 => prefs.keybinds.open_project = bind,
3 => prefs.keybinds.save_project = bind,
4 => prefs.keybinds.export = bind,
5 => prefs.keybinds.anim_preview = bind,
_ => {}
}
cap = 0;
save = true;
}
ui.ctx().request_repaint();
}
ui.ctx().data_mut(|d| d.insert_temp(cap_id, cap));
ui.add_space(4.0);
if keybind_row(ui, "New Project", &prefs.keybinds.new_project, cap == 1) {
cap = 1;
}
if keybind_row(ui, "Open Project", &prefs.keybinds.open_project, cap == 2) {
cap = 2;
}
if keybind_row(ui, "Save Project", &prefs.keybinds.save_project, cap == 3) {
cap = 3;
}
if keybind_row(ui, "Export", &prefs.keybinds.export, cap == 4) {
cap = 4;
}
if keybind_row(
ui,
"Animation Preview",
&prefs.keybinds.anim_preview,
cap == 5,
) {
cap = 5;
}
ui.ctx().data_mut(|d| d.insert_temp(cap_id, cap));
ui.add_space(8.0);
if ui.button("Reset to defaults").clicked() {
prefs.keybinds = Keybinds::default();
save = true;
}
if save {
prefs.save();
}
}
fn keybind_row(ui: &mut egui::Ui, label: &str, bind: &KeyBind, capturing: bool) -> bool {
let mut clicked = false;
ui.horizontal(|ui| {
ui.label(egui::RichText::new(label).strong());
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if capturing {
ui.label(egui::RichText::new("Press any key...").weak().italics());
} else {
if ui.small_button("Change").clicked() {
clicked = true;
}
ui.label(bind.display());
}
});
});
ui.separator();
clicked
}