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