feat: init; basic app render loop
This commit is contained in:
commit
17b102aac1
10 changed files with 5167 additions and 0 deletions
31
.gitignore
vendored
Executable file
31
.gitignore
vendored
Executable file
|
@ -0,0 +1,31 @@
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Binaries
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.lib
|
||||||
|
*.a
|
||||||
|
*.obj
|
||||||
|
*.o
|
||||||
|
*.bin
|
||||||
|
|
||||||
|
# RustRover
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
4538
Cargo.lock
generated
Executable file
4538
Cargo.lock
generated
Executable file
File diff suppressed because it is too large
Load diff
41
Cargo.toml
Executable file
41
Cargo.toml
Executable file
|
@ -0,0 +1,41 @@
|
||||||
|
[package]
|
||||||
|
name = "pbt-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "PortableBuildTools"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
better-panic = "0.3.0"
|
||||||
|
color-eyre = "0.6.3"
|
||||||
|
human-panic = "2.0.2"
|
||||||
|
strip-ansi-escapes = "0.2.1"
|
||||||
|
tracing = "0.1.41"
|
||||||
|
tracing-error = "0.2.1"
|
||||||
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "serde"] }
|
||||||
|
|
||||||
|
### GUI
|
||||||
|
eframe = "0.31.1"
|
||||||
|
egui = "0.31.1"
|
||||||
|
epaint = "0.31.1"
|
||||||
|
|
||||||
|
### WINAPI
|
||||||
|
windows = { version = "0.61.1", features = [
|
||||||
|
"Win32_System_Console",
|
||||||
|
"Win32_System_Threading",
|
||||||
|
"Win32_UI_WindowsAndMessaging",
|
||||||
|
"Win32_UI_Shell",
|
||||||
|
"Win32_System_Registry",
|
||||||
|
"Win32_Security",
|
||||||
|
] }
|
||||||
|
windows-result = "0.3.2"
|
||||||
|
|
||||||
|
### MISC
|
||||||
|
libc = "0.2.172"
|
||||||
|
strum = "0.27.1"
|
||||||
|
strum_macros = "0.27.1"
|
||||||
|
lazy_static = "1.5.0"
|
226
src/app.rs
Executable file
226
src/app.rs
Executable file
|
@ -0,0 +1,226 @@
|
||||||
|
use std::{fmt::Display, string::ToString, sync::Arc};
|
||||||
|
|
||||||
|
use arch::Arch;
|
||||||
|
use choice::ChoiceOption;
|
||||||
|
use egui::{Galley, Rect, Ui};
|
||||||
|
|
||||||
|
pub(crate) mod arch;
|
||||||
|
#[macro_use]
|
||||||
|
pub(crate) mod choice;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct App {
|
||||||
|
env_setup_level: EnvSetupLevel,
|
||||||
|
preview_channel: bool,
|
||||||
|
msvc_version: String,
|
||||||
|
sdk_version: String,
|
||||||
|
host_arch: String,
|
||||||
|
target_arch: String,
|
||||||
|
install_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for App {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
env_setup_level: EnvSetupLevel::default(),
|
||||||
|
preview_channel: bool::default(),
|
||||||
|
msvc_version: String::default(),
|
||||||
|
sdk_version: String::default(),
|
||||||
|
host_arch: Arch::default().to_string(),
|
||||||
|
target_arch: Arch::default().to_string(),
|
||||||
|
install_path: String::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
||||||
|
pub enum EnvSetupLevel {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
User,
|
||||||
|
Global,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnvSetupLevel {
|
||||||
|
pub const ALL: [Self; 3] = [Self::None, Self::User, Self::Global];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for EnvSetupLevel {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let option_strs = match self {
|
||||||
|
&Self::None => "Create environment setup scripts",
|
||||||
|
&Self::User => "Add to user environment",
|
||||||
|
&Self::Global => "Add to global environment",
|
||||||
|
};
|
||||||
|
|
||||||
|
f.write_str(option_strs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn env_setup_selection(&mut self, ui: &mut Ui) {
|
||||||
|
egui::ComboBox::from_label(String::new())
|
||||||
|
.width(ui.available_width() / 1.5)
|
||||||
|
.selected_text(self.env_setup_level.to_string())
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
EnvSetupLevel::ALL.iter().for_each(|opt| {
|
||||||
|
ui.selectable_value(&mut self.env_setup_level, *opt, format!("{}", opt));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selection_menu(
|
||||||
|
selected: &mut String,
|
||||||
|
ui: &mut Ui,
|
||||||
|
width: f32,
|
||||||
|
component: ChoiceOption<Vec<String>>,
|
||||||
|
) {
|
||||||
|
let name = component.to_string();
|
||||||
|
|
||||||
|
ui.label(name);
|
||||||
|
egui::ComboBox::from_id_salt(&component)
|
||||||
|
.width(width)
|
||||||
|
.selected_text(selected.clone()) // FIXME
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
component.options().iter().for_each(|opt| {
|
||||||
|
ui.selectable_value(selected, opt.to_string(), opt.to_string());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boxed_inner(
|
||||||
|
ui: &mut Ui,
|
||||||
|
text: String,
|
||||||
|
box_rect: Rect,
|
||||||
|
text_rect: Rect,
|
||||||
|
galley: Arc<Galley>,
|
||||||
|
) {
|
||||||
|
let left = box_rect.left();
|
||||||
|
let right = box_rect.right();
|
||||||
|
let top = box_rect.top();
|
||||||
|
let bottom = box_rect.bottom();
|
||||||
|
let stroke = ui.style().visuals.window_stroke();
|
||||||
|
|
||||||
|
// Left side
|
||||||
|
ui.painter()
|
||||||
|
.line_segment([egui::pos2(left, top), egui::pos2(left, bottom)], stroke);
|
||||||
|
|
||||||
|
// Bottom side
|
||||||
|
ui.painter().line_segment(
|
||||||
|
[egui::pos2(left, bottom), egui::pos2(right, bottom)],
|
||||||
|
stroke,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Right side
|
||||||
|
ui.painter()
|
||||||
|
.line_segment([egui::pos2(right, top), egui::pos2(right, bottom)], stroke);
|
||||||
|
|
||||||
|
// Top side
|
||||||
|
ui.painter().line_segment(
|
||||||
|
[egui::pos2(left, top), egui::pos2(text_rect.left(), top)],
|
||||||
|
stroke,
|
||||||
|
);
|
||||||
|
ui.painter().line_segment(
|
||||||
|
[
|
||||||
|
egui::pos2(text_rect.left() + galley.size().x, top),
|
||||||
|
egui::pos2(right, top),
|
||||||
|
],
|
||||||
|
stroke,
|
||||||
|
);
|
||||||
|
|
||||||
|
ui.painter().text(
|
||||||
|
text_rect.left_center(),
|
||||||
|
egui::Align2::LEFT_CENTER,
|
||||||
|
text,
|
||||||
|
ui.style().text_styles[&egui::TextStyle::Body].clone(),
|
||||||
|
ui.style().visuals.text_color(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn boxed<R>(ui: &mut Ui, heading: impl ToString, draw: impl FnOnce(&mut Ui) -> R) {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
let text = heading.to_string();
|
||||||
|
let margin = ui.spacing().window_margin;
|
||||||
|
let available_width = ui.available_width() - margin.left as f32 - margin.right as f32;
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_space(margin.left as f32);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.set_width(available_width);
|
||||||
|
|
||||||
|
egui::Frame::new()
|
||||||
|
.inner_margin(epaint::Margin::symmetric(10, 15))
|
||||||
|
.show(ui, draw);
|
||||||
|
|
||||||
|
let galley = ui.painter().layout_no_wrap(
|
||||||
|
text.clone(),
|
||||||
|
ui.style().text_styles[&egui::TextStyle::Body].clone(),
|
||||||
|
egui::Color32::LIGHT_GRAY, // TOOD: use something within defaults
|
||||||
|
);
|
||||||
|
|
||||||
|
let border_rect = ui.min_rect();
|
||||||
|
let text_rect = egui::Rect::from_min_size(
|
||||||
|
egui::pos2(
|
||||||
|
border_rect.left() + 8.0,
|
||||||
|
border_rect.top() - galley.size().y / 2.0,
|
||||||
|
),
|
||||||
|
egui::vec2(galley.size().x, galley.size().y),
|
||||||
|
);
|
||||||
|
|
||||||
|
Self::boxed_inner(ui, text, border_rect, text_rect, galley);
|
||||||
|
ui.add_space(margin.right as f32);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for App {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
self.env_setup_selection(ui);
|
||||||
|
ui.checkbox(&mut self.preview_channel, "Preview channel");
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(15.0);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let width_per_choice = ctx.screen_rect().width() / 4.75;
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let choices = vec![
|
||||||
|
(&mut self.msvc_version, ChoiceOption::MsvcVersion(choices!("12.3", "12.4", "12.4+extra"))),
|
||||||
|
(&mut self.sdk_version, ChoiceOption::SdkVersion(choices!("12.3", "12.4", "12.4+extra"))),
|
||||||
|
(&mut self.host_arch, ChoiceOption::HostArch(choices!(Arch::X86, Arch::X64))),
|
||||||
|
(&mut self.target_arch, ChoiceOption::TargetArch(choices!(Arch::X86, Arch::X64))),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (selection, choice) in choices {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
Self::selection_menu(selection, ui, width_per_choice, choice.clone())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(25.0);
|
||||||
|
Self::boxed(ui, "Destination Folder", |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.horizontal_centered(|ui| {
|
||||||
|
let path_selection_box = egui::TextEdit::singleline(&mut self.install_path)
|
||||||
|
.hint_text("Select folder path");
|
||||||
|
ui.add(path_selection_box);
|
||||||
|
|
||||||
|
ui.button("Browse...")
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.checkbox(&mut true, "I accept the License Agreement");
|
||||||
|
ui.add_space(ui.available_width() - 45.0);
|
||||||
|
if ui.button("Install").clicked() {
|
||||||
|
// pop out into console
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
20
src/app/arch.rs
Executable file
20
src/app/arch.rs
Executable file
|
@ -0,0 +1,20 @@
|
||||||
|
use std::env::consts;
|
||||||
|
|
||||||
|
use strum_macros::{Display, EnumString};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, EnumString, Display)]
|
||||||
|
#[strum(serialize_all = "lowercase")]
|
||||||
|
pub enum Arch {
|
||||||
|
X86,
|
||||||
|
X64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Arch {
|
||||||
|
fn default() -> Self {
|
||||||
|
match consts::ARCH {
|
||||||
|
"x86" => Self::X86,
|
||||||
|
"x86_64" => Self::X64,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
src/app/choice.rs
Executable file
42
src/app/choice.rs
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use strum_macros::Display;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! choices {
|
||||||
|
($($choice:expr),*) => {
|
||||||
|
vec![$($choice),*]
|
||||||
|
.iter()
|
||||||
|
.map(|e| e.to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Display)]
|
||||||
|
pub enum ChoiceOption<T: Into<Vec<String>>> {
|
||||||
|
#[strum(to_string = "MSVC Version")]
|
||||||
|
MsvcVersion(T),
|
||||||
|
#[strum(to_string = "SDK Version")]
|
||||||
|
SdkVersion(T),
|
||||||
|
#[strum(to_string = "Host Arch")]
|
||||||
|
HostArch(T),
|
||||||
|
#[strum(to_string = "Target Arch")]
|
||||||
|
TargetArch(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<Vec<String>>> ChoiceOption<T> {
|
||||||
|
pub fn options(self) -> Vec<String> {
|
||||||
|
match self {
|
||||||
|
Self::MsvcVersion(options)
|
||||||
|
| Self::SdkVersion(options)
|
||||||
|
| Self::HostArch(options)
|
||||||
|
| Self::TargetArch(options) => options.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<Vec<String>>> Hash for ChoiceOption<T> {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.to_string().hash(state);
|
||||||
|
}
|
||||||
|
}
|
95
src/console.rs
Executable file
95
src/console.rs
Executable file
|
@ -0,0 +1,95 @@
|
||||||
|
use std::{
|
||||||
|
env,
|
||||||
|
io::{self, ErrorKind},
|
||||||
|
os::windows::ffi::OsStrExt,
|
||||||
|
};
|
||||||
|
|
||||||
|
use tracing::instrument;
|
||||||
|
use windows::Win32::Foundation::{CloseHandle, GetLastError, HWND};
|
||||||
|
use windows::Win32::System::Console::GetConsoleWindow;
|
||||||
|
use windows::Win32::System::Threading::{
|
||||||
|
CreateProcessW, DETACHED_PROCESS, GetCurrentProcessId, PROCESS_INFORMATION, STARTUPINFOW,
|
||||||
|
};
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
||||||
|
use windows::core::{PCWSTR, PWSTR};
|
||||||
|
use windows_result::{Error, Result};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum ProcessMode {
|
||||||
|
Console(#[allow(dead_code)] HWND),
|
||||||
|
Gui,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcessMode {
|
||||||
|
#[instrument(level = "debug", name = "detect_process_mode" ret, err)] // FIXME: why no log???
|
||||||
|
pub fn from_current_process() -> Result<Self> {
|
||||||
|
unsafe {
|
||||||
|
let console = GetConsoleWindow();
|
||||||
|
if console.0 == std::ptr::null_mut() {
|
||||||
|
return Ok(ProcessMode::Gui);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pid = [0u32; 1];
|
||||||
|
if GetWindowThreadProcessId(console, Some(pid.as_mut_ptr())) == 0 {
|
||||||
|
GetLastError().ok()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if GetCurrentProcessId() != pid[0] {
|
||||||
|
Ok(ProcessMode::Console(console))
|
||||||
|
} else {
|
||||||
|
Ok(ProcessMode::Gui)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", name = "detach_to_gui", skip(self))]
|
||||||
|
pub fn detach_to_gui(self) -> Result<()> {
|
||||||
|
tracing::info!("Attempting to create a detached GUI process from the current process");
|
||||||
|
if !matches!(self, ProcessMode::Console(_)) {
|
||||||
|
return Err(Error::from(io::Error::new(
|
||||||
|
ErrorKind::Unsupported,
|
||||||
|
"Not running in console mode",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let program = env::current_exe()?
|
||||||
|
.into_os_string()
|
||||||
|
.encode_wide()
|
||||||
|
.collect::<Vec<u16>>();
|
||||||
|
|
||||||
|
let mut command_line_raw = env::args().fold(program.clone(), |acc, e| {
|
||||||
|
let mut acc = acc;
|
||||||
|
acc.extend(format!(" {e}").encode_utf16());
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut startup_info = std::mem::zeroed::<STARTUPINFOW>();
|
||||||
|
let mut process_info = std::mem::zeroed::<PROCESS_INFORMATION>();
|
||||||
|
|
||||||
|
startup_info.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
|
||||||
|
CreateProcessW(
|
||||||
|
PCWSTR::from_raw(
|
||||||
|
program
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain(std::iter::once(0))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.as_ptr(),
|
||||||
|
),
|
||||||
|
Some(PWSTR::from_raw(command_line_raw.as_mut_ptr())),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
DETACHED_PROCESS,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
&mut startup_info,
|
||||||
|
&mut process_info,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
CloseHandle(process_info.hProcess)?;
|
||||||
|
std::process::exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
src/errors.rs
Executable file
44
src/errors.rs
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use color_eyre::Result;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
pub fn init() -> Result<()> {
|
||||||
|
let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default()
|
||||||
|
.panic_section(format!(
|
||||||
|
"This is a bug. Consider reporting it at {}",
|
||||||
|
env!("CARGO_PKG_REPOSITORY")
|
||||||
|
))
|
||||||
|
.capture_span_trace_by_default(false)
|
||||||
|
.display_location_section(false)
|
||||||
|
.display_env_section(false)
|
||||||
|
.into_hooks();
|
||||||
|
eyre_hook.install()?;
|
||||||
|
std::panic::set_hook(Box::new(move |panic_info| {
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
use human_panic::{handle_dump, metadata, print_msg};
|
||||||
|
let metadata = metadata!();
|
||||||
|
let file_path = handle_dump(&metadata, panic_info);
|
||||||
|
// prints human-panic message
|
||||||
|
print_msg(file_path, &metadata)
|
||||||
|
.expect("human-panic: printing error message to console failed");
|
||||||
|
eprintln!("{}", panic_hook.panic_report(panic_info)); // prints color-eyre stack trace to stderr
|
||||||
|
}
|
||||||
|
let msg = format!("{}", panic_hook.panic_report(panic_info));
|
||||||
|
error!("Error: {}", strip_ansi_escapes::strip_str(msg));
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
// Better Panic stacktrace that is only enabled when debugging.
|
||||||
|
better_panic::Settings::auto()
|
||||||
|
.most_recent_first(false)
|
||||||
|
.lineno_suffix(true)
|
||||||
|
.verbosity(better_panic::Verbosity::Full)
|
||||||
|
.create_panic_handler()(panic_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::process::exit(libc::EXIT_FAILURE);
|
||||||
|
}));
|
||||||
|
Ok(())
|
||||||
|
}
|
86
src/logging.rs
Executable file
86
src/logging.rs
Executable file
|
@ -0,0 +1,86 @@
|
||||||
|
use std::{env, io::stderr};
|
||||||
|
|
||||||
|
use color_eyre::{Result, eyre::eyre};
|
||||||
|
use tracing_error::ErrorLayer;
|
||||||
|
use tracing_subscriber::{EnvFilter, fmt, prelude::*, util::TryInitError};
|
||||||
|
|
||||||
|
use crate::console::ProcessMode;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref LOG_ENV: String = format!("{}_LOG", env!("CARGO_PKG_NAME").to_uppercase());
|
||||||
|
pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME"));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(process_mode: ProcessMode) -> Result<()> {
|
||||||
|
//
|
||||||
|
// File initialization
|
||||||
|
//
|
||||||
|
|
||||||
|
let directory = env::current_dir()?;
|
||||||
|
std::fs::create_dir_all(directory.clone())?;
|
||||||
|
|
||||||
|
let log_path = directory.join(LOG_FILE.clone());
|
||||||
|
let log_file = match process_mode {
|
||||||
|
ProcessMode::Console(_) => std::fs::File::open("NUL")?,
|
||||||
|
ProcessMode::Gui => std::fs::File::create(log_path)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Filtering
|
||||||
|
//
|
||||||
|
|
||||||
|
// Stage 1: Construct base filter
|
||||||
|
let env_filter = EnvFilter::builder().with_default_directive(
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
tracing::Level::DEBUG
|
||||||
|
} else {
|
||||||
|
tracing::Level::INFO
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Stage 2: Attempt to read from {RUST|CRATE_NAME}_LOG env var or ignore
|
||||||
|
let env_filter = env_filter.try_from_env().unwrap_or_else(|_| {
|
||||||
|
env_filter
|
||||||
|
.with_env_var(LOG_ENV.to_string())
|
||||||
|
.from_env_lossy()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stage 3: Enable directives to reduce verbosity for release mode builds
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
let env_filter = env_filter
|
||||||
|
.add_directive("egui=info".parse().unwrap())
|
||||||
|
.add_directive("eframe=info".parse().unwrap())
|
||||||
|
.add_directive("epaint=info".parse().unwrap())
|
||||||
|
.add_directive("windows=info".parse().unwrap());
|
||||||
|
|
||||||
|
//
|
||||||
|
// Subscription
|
||||||
|
//
|
||||||
|
|
||||||
|
// Build the subscriber and apply it
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(env_filter)
|
||||||
|
.with(
|
||||||
|
// Logging to file
|
||||||
|
fmt::layer()
|
||||||
|
.with_writer(log_file)
|
||||||
|
.with_target(true)
|
||||||
|
.with_ansi(false),
|
||||||
|
)
|
||||||
|
.with({
|
||||||
|
// Logging to stderr
|
||||||
|
let layer = fmt::layer()
|
||||||
|
.with_writer(stderr)
|
||||||
|
.with_timer(tracing_subscriber::fmt::time())
|
||||||
|
.with_ansi(true);
|
||||||
|
|
||||||
|
// Enable compact mode for release logs
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
let layer = layer.compact();
|
||||||
|
layer
|
||||||
|
})
|
||||||
|
.with(ErrorLayer::default())
|
||||||
|
.try_init()
|
||||||
|
.map_err(|err: TryInitError| eyre!(err))
|
||||||
|
}
|
44
src/main.rs
Executable file
44
src/main.rs
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
use app::App;
|
||||||
|
use color_eyre::eyre;
|
||||||
|
use console::ProcessMode;
|
||||||
|
use eframe::NativeOptions;
|
||||||
|
use egui::ViewportBuilder;
|
||||||
|
|
||||||
|
mod app;
|
||||||
|
pub(crate) mod console;
|
||||||
|
mod errors;
|
||||||
|
mod logging;
|
||||||
|
|
||||||
|
fn main() -> eyre::Result<()> {
|
||||||
|
let process_mode = ProcessMode::from_current_process()?;
|
||||||
|
|
||||||
|
crate::errors::init()?;
|
||||||
|
crate::logging::init(process_mode)?;
|
||||||
|
tracing::info!(concat!(
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
" v",
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
));
|
||||||
|
|
||||||
|
match process_mode {
|
||||||
|
ProcessMode::Console(_) => process_mode.detach_to_gui()?,
|
||||||
|
ProcessMode::Gui => {
|
||||||
|
tracing::info!("Attempting to natively render UI");
|
||||||
|
eframe::run_native(
|
||||||
|
"portable-msvc-rs",
|
||||||
|
NativeOptions {
|
||||||
|
viewport: ViewportBuilder::default()
|
||||||
|
.with_resizable(false)
|
||||||
|
.with_inner_size(egui::vec2(420.0, 200.0))
|
||||||
|
.with_maximize_button(false)
|
||||||
|
.with_minimize_button(false),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Box::new(|_cc| Ok(Box::new(App::default()))),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue