mod app;
mod editor;
mod environments;
mod filepick;
mod icons;
mod menu;
mod remote;
mod report_editor;
mod report_run;
mod report_wizard;
mod reports;
mod requests;
mod response;
mod theme;
mod widgets;
pub use app::GuiApp;
use eframe::egui;
pub const DEFAULT_WINDOW: (f32, f32) = (1280.0, 820.0);
pub const MIN_WINDOW: (f32, f32) = (760.0, 500.0);
pub fn run() -> Result<(), String> {
install_desktop_integration();
let saved = crate::persistence::load_state()
.map(|s| s.gui)
.unwrap_or_default();
let (w, h) = saved
.window
.filter(|(w, h)| *w >= MIN_WINDOW.0 && *h >= MIN_WINDOW.1)
.unwrap_or(DEFAULT_WINDOW);
let mut viewport = egui::ViewportBuilder::default()
.with_inner_size([w, h])
.with_min_inner_size([MIN_WINDOW.0, MIN_WINDOW.1])
.with_title("PaperBoy")
.with_app_id("paperboy");
if let Some(icon) = app::load_app_icon() {
viewport = viewport.with_icon(std::sync::Arc::new(icon));
}
let native_options = eframe::NativeOptions {
viewport,
..Default::default()
};
eframe::run_native(
"PaperBoy",
native_options,
Box::new(|cc| Ok(Box::new(GuiApp::new(cc)))),
)
.map_err(|e| e.to_string())
}
#[cfg(target_os = "linux")]
fn install_desktop_integration() {
use std::path::PathBuf;
let home = std::env::var_os("HOME").map(PathBuf::from);
let data_home = std::env::var_os("XDG_DATA_HOME")
.map(PathBuf::from)
.filter(|p| p.is_absolute())
.or_else(|| home.as_ref().map(|h| h.join(".local/share")));
let Some(data_home) = data_home else {
return;
};
let icon_dir = data_home.join("paperboy");
let icon_path = icon_dir.join("paperboy_logo.png");
if !icon_path.exists() {
let _ = std::fs::create_dir_all(&icon_dir);
let _ = std::fs::write(&icon_path, app::LOGO_PNG);
}
let apps_dir = data_home.join("applications");
let desktop_path = apps_dir.join("paperboy.desktop");
if !desktop_path.exists() {
let exec = std::env::current_exe()
.ok()
.and_then(|p| p.to_str().map(str::to_string))
.unwrap_or_else(|| "paperboy".to_string());
let icon = icon_path.to_string_lossy();
let entry = format!(
"[Desktop Entry]\n\
Type=Application\n\
Name=PaperBoy\n\
Comment=Rust-native API client\n\
Exec={exec} -g\n\
Icon={icon}\n\
Terminal=false\n\
StartupWMClass=paperboy\n\
Categories=Development;Utility;\n"
);
let _ = std::fs::create_dir_all(&apps_dir);
let _ = std::fs::write(&desktop_path, entry);
}
}
#[cfg(not(target_os = "linux"))]
fn install_desktop_integration() {}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum Focus {
Tabs,
List,
Main,
GlobalEnv,
Response,
}
impl Focus {
pub const ORDER: [Focus; 5] = [
Focus::Tabs,
Focus::List,
Focus::Main,
Focus::GlobalEnv,
Focus::Response,
];
pub fn cycle(self, forward: bool) -> Focus {
let n = Self::ORDER.len();
let cur = Self::ORDER.iter().position(|f| *f == self).unwrap_or(0);
let next = if forward {
(cur + 1) % n
} else {
(cur + n - 1) % n
};
Self::ORDER[next]
}
}