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>, sdk: ChoiceHolder>, host_arch: ChoiceHolder, target_arch: ChoiceHolder, 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 + PartialEq, V: Into>>( selected: &mut T, ui: &mut Ui, width: f32, component: ChoiceOption, ) { 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, ) { 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(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 } }); }); } }