pbt-rs/src/app.rs

229 lines
7.4 KiB
Rust
Executable file

use std::{fmt::Display, string::ToString, sync::Arc};
use arch::{Arch, ArchList};
use choice::ChoiceOption;
use egui::{Galley, Rect, Ui};
use versions::ChoiceHolder;
pub(crate) mod arch;
#[macro_use]
pub(crate) mod choice;
mod versions;
#[derive(Debug, Clone)]
pub struct App {
env_setup_level: EnvSetupLevel,
preview_channel: bool,
msvc: ChoiceHolder<String, Vec<String>>,
sdk: ChoiceHolder<String, Vec<String>>,
host_arch: ChoiceHolder<Arch, ArchList>,
target_arch: ChoiceHolder<Arch, ArchList>,
install_path: String,
}
impl Default for App {
fn default() -> Self {
Self {
env_setup_level: EnvSetupLevel::default(),
preview_channel: false,
msvc: ChoiceHolder::new(ChoiceOption::MsvcVersion(choices!("2019", "2022"))).unwrap(),
sdk: ChoiceHolder::new(ChoiceOption::SdkVersion(choices!("10.0.22621.0"))).unwrap(),
host_arch: ChoiceHolder::new(ChoiceOption::HostArch(ArchList::default())).unwrap(),
target_arch: ChoiceHolder::new(ChoiceOption::TargetArch(ArchList::default())).unwrap(),
install_path: String::new(),
}
}
}
#[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<T: ToString + From<String> + PartialEq, V: Into<Vec<String>>>(
selected: &mut T,
ui: &mut Ui,
width: f32,
component: ChoiceOption<V>,
) {
let name = component.to_string();
ui.label(name);
egui::ComboBox::from_id_salt(&component)
.width(width)
.selected_text(selected.to_string()) // FIXME
.show_ui(ui, |ui| {
component.options().iter().for_each(|opt| {
ui.selectable_value(selected.into(), opt.clone().into(), 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;
macro_rules! selection_menus {
($($choice:expr),*) => {
$(ui.vertical(|ui| {
let x = $choice.available.clone();
Self::selection_menu($choice.selected_mut(), ui, width_per_choice, x)
});)*
};
}
selection_menus![
self.msvc,
self.sdk,
self.host_arch,
self.target_arch
];
});
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
}
});
});
}
}