refactor(app): implement ChoiceHolder abstraction

This commit is contained in:
Erica Marigold 2025-05-03 06:55:28 +01:00
parent b94972e183
commit 10e0c5a0d3
Signed by: DevComp
SSH key fingerprint: SHA256:jD3oMT4WL3WHPJQbrjC3l5feNCnkv7ndW8nYaHX5wFw
6 changed files with 268 additions and 36 deletions

80
Cargo.lock generated
View file

@ -873,6 +873,35 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "cookie"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"percent-encoding",
"time",
"version_check",
]
[[package]]
name = "cookie_store"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9"
dependencies = [
"cookie",
"document-features",
"idna",
"indexmap 2.9.0",
"log",
"serde",
"serde_derive",
"serde_json",
"time",
"url",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -2981,6 +3010,7 @@ dependencies = [
"tracing", "tracing",
"tracing-error", "tracing-error",
"tracing-subscriber", "tracing-subscriber",
"ureq",
"url", "url",
"uuid", "uuid",
"windows 0.61.1", "windows 0.61.1",
@ -3379,7 +3409,9 @@ version = "0.23.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
dependencies = [ dependencies = [
"log",
"once_cell", "once_cell",
"ring",
"rustls-pki-types", "rustls-pki-types",
"rustls-webpki", "rustls-webpki",
"subtle", "subtle",
@ -4300,6 +4332,39 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7a3e9af6113ecd57b8c63d3cd76a385b2e3881365f1f489e54f49801d0c83ea"
dependencies = [
"base64",
"cookie_store",
"flate2",
"log",
"percent-encoding",
"rustls",
"rustls-pemfile",
"rustls-pki-types",
"serde",
"serde_json",
"ureq-proto",
"utf-8",
"webpki-roots",
]
[[package]]
name = "ureq-proto"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fadf18427d33828c311234884b7ba2afb57143e6e7e69fda7ee883b624661e36"
dependencies = [
"base64",
"http",
"httparse",
"log",
]
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.4" version = "2.5.4"
@ -4312,6 +4377,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "utf16_iter" name = "utf16_iter"
version = "1.0.5" version = "1.0.5"
@ -4618,6 +4689,15 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "webpki-roots"
version = "0.26.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37493cadf42a2a939ed404698ded7fb378bf301b5011f973361779a3a74f8c93"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "weezl" name = "weezl"
version = "0.1.8" version = "0.1.8"

View file

@ -47,3 +47,4 @@ uuid = { version = "1.16.0", features = ["serde"] }
url = { version = "2.5.4", features = ["serde"] } url = { version = "2.5.4", features = ["serde"] }
langtag = { version = "0.4.0", features = ["serde"] } langtag = { version = "0.4.0", features = ["serde"] }
serde_with = "3.12.0" serde_with = "3.12.0"
ureq = { version = "3.0.11", features = ["json"] }

View file

@ -1,21 +1,23 @@
use std::{fmt::Display, string::ToString, sync::Arc}; use std::{fmt::Display, string::ToString, sync::Arc};
use arch::Arch; use arch::{Arch, ArchList};
use choice::ChoiceOption; use choice::ChoiceOption;
use egui::{Galley, Rect, Ui}; use egui::{Galley, Rect, Ui};
use versions::ChoiceHolder;
pub(crate) mod arch; pub(crate) mod arch;
#[macro_use] #[macro_use]
pub(crate) mod choice; pub(crate) mod choice;
mod versions;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct App { pub struct App {
env_setup_level: EnvSetupLevel, env_setup_level: EnvSetupLevel,
preview_channel: bool, preview_channel: bool,
msvc_version: String, msvc: ChoiceHolder<String, Vec<String>>,
sdk_version: String, sdk: ChoiceHolder<String, Vec<String>>,
host_arch: String, host_arch: ChoiceHolder<Arch, ArchList>,
target_arch: String, target_arch: ChoiceHolder<Arch, ArchList>,
install_path: String, install_path: String,
} }
@ -23,16 +25,15 @@ impl Default for App {
fn default() -> Self { fn default() -> Self {
Self { Self {
env_setup_level: EnvSetupLevel::default(), env_setup_level: EnvSetupLevel::default(),
preview_channel: bool::default(), preview_channel: false,
msvc_version: String::default(), msvc: ChoiceHolder::new(ChoiceOption::MsvcVersion(choices!("2019", "2022"))).unwrap(),
sdk_version: String::default(), sdk: ChoiceHolder::new(ChoiceOption::SdkVersion(choices!("10.0.22621.0"))).unwrap(),
host_arch: Arch::default().to_string(), host_arch: ChoiceHolder::new(ChoiceOption::HostArch(ArchList::default())).unwrap(),
target_arch: Arch::default().to_string(), target_arch: ChoiceHolder::new(ChoiceOption::TargetArch(ArchList::default())).unwrap(),
install_path: String::default(), install_path: String::new(),
} }
} }
} }
#[derive(Debug, Clone, Copy, Default, PartialEq)] #[derive(Debug, Clone, Copy, Default, PartialEq)]
pub enum EnvSetupLevel { pub enum EnvSetupLevel {
#[default] #[default]
@ -69,21 +70,21 @@ impl App {
}); });
} }
fn selection_menu( fn selection_menu<T: ToString + From<String> + PartialEq, V: Into<Vec<String>>>(
selected: &mut String, selected: &mut T,
ui: &mut Ui, ui: &mut Ui,
width: f32, width: f32,
component: ChoiceOption<Vec<String>>, component: ChoiceOption<V>,
) { ) {
let name = component.to_string(); let name = component.to_string();
ui.label(name); ui.label(name);
egui::ComboBox::from_id_salt(&component) egui::ComboBox::from_id_salt(&component)
.width(width) .width(width)
.selected_text(selected.clone()) // FIXME .selected_text(selected.to_string()) // FIXME
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
component.options().iter().for_each(|opt| { component.options().iter().for_each(|opt| {
ui.selectable_value(selected, opt.to_string(), opt.to_string()); ui.selectable_value(selected.into(), opt.clone().into(), opt.to_string());
}); });
}); });
} }
@ -186,19 +187,21 @@ impl eframe::App for App {
ui.add_space(15.0); ui.add_space(15.0);
ui.horizontal(|ui| { ui.horizontal(|ui| {
let width_per_choice = ctx.screen_rect().width() / 4.75; let width_per_choice = ctx.screen_rect().width() / 4.75;
#[rustfmt::skip] macro_rules! selection_menus {
let choices = vec![ ($($choice:expr),*) => {
(&mut self.msvc_version, ChoiceOption::MsvcVersion(choices!("12.3", "12.4", "12.4+extra"))), $(ui.vertical(|ui| {
(&mut self.sdk_version, ChoiceOption::SdkVersion(choices!("12.3", "12.4", "12.4+extra"))), let x = $choice.available.clone();
(&mut self.host_arch, ChoiceOption::HostArch(choices!(Arch::X86, Arch::X64))), Self::selection_menu($choice.selected_mut(), ui, width_per_choice, x)
(&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())
});
} }
selection_menus![
self.msvc,
self.sdk,
self.host_arch,
self.target_arch
];
}); });
ui.add_space(25.0); ui.add_space(25.0);

View file

@ -1,22 +1,80 @@
use std::env::consts; use std::{env::consts, ops::Deref, str::FromStr};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumString}; use strum_macros::{Display, EnumString};
#[derive(Debug, Clone, Copy, EnumString, Display, Deserialize, Serialize)] #[derive(Debug, Clone, Copy, EnumString, Display, Deserialize, Serialize, PartialEq)]
#[strum(serialize_all = "lowercase")] #[strum(serialize_all = "lowercase")]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum Arch { pub enum Arch {
X86, X86,
#[strum(to_string = "x64", serialize = "x86_64")]
X64, X64,
} }
impl Arch {
pub fn all() -> [Arch; 2] {
match Arch::default() {
Arch::X86 => [Arch::X86, Arch::X64],
Arch::X64 => [Arch::X64, Arch::X86],
}
}
}
impl Default for Arch { impl Default for Arch {
fn default() -> Self { fn default() -> Self {
match consts::ARCH { Self::from_str(consts::ARCH).unwrap()
"x86" => Self::X86, }
"x86_64" => Self::X64, }
_ => unreachable!(),
} impl From<String> for Arch {
fn from(s: String) -> Self {
Self::from_str(s.as_str()).unwrap_or_default()
}
}
impl Into<String> for Arch {
fn into(self) -> String {
self.to_string()
}
}
#[derive(Debug, Clone)]
pub struct ArchList(pub Vec<Arch>);
impl From<ArchList> for Vec<String> {
fn from(archs: ArchList) -> Vec<String> {
archs.0.into_iter().map(|arch| arch.to_string()).collect()
}
}
impl From<Vec<String>> for ArchList {
fn from(archs: Vec<String>) -> ArchList {
ArchList(archs.into_iter().map(|arch| arch.into()).collect())
}
}
impl From<Vec<Arch>> for ArchList {
fn from(archs: Vec<Arch>) -> ArchList {
ArchList(archs)
}
}
impl Deref for ArchList {
type Target = Vec<Arch>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Into<Vec<Arch>> for ArchList {
fn into(self) -> Vec<Arch> {
self.0
}
}
impl Default for ArchList {
fn default() -> Self {
Self(Arch::all().to_vec())
} }
} }

View file

@ -40,3 +40,11 @@ impl<T: Into<Vec<String>>> Hash for ChoiceOption<T> {
self.to_string().hash(state); self.to_string().hash(state);
} }
} }
impl<T: Into<Vec<String>>> IntoIterator for ChoiceOption<T> {
type Item = String;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.options().into_iter()
}
}

82
src/app/versions.rs Executable file
View file

@ -0,0 +1,82 @@
use std::str::FromStr;
use super::{
arch::{Arch, ArchList},
choice::ChoiceOption,
};
use color_eyre::{Result, eyre::eyre};
#[derive(Debug, Clone)]
pub struct ChoiceHolder<T, V: Into<Vec<T>>>
where
Vec<String>: From<V>,
{
pub available: ChoiceOption<V>,
pub selected: T,
}
impl<T: Default + Clone, V: Clone + Into<Vec<T>>> ChoiceHolder<T, V>
where
Vec<String>: From<V>,
T: From<String>,
{
pub fn new(available: ChoiceOption<V>) -> Result<Self> {
Ok(Self {
available: available.clone(),
selected: Into::<T>::into(
available
.options()
.first()
.ok_or(eyre!("Available options must not be an empty Vec"))?
.clone(),
),
})
}
pub fn selected_mut(&mut self) -> &mut T {
&mut self.selected
}
}
// FIXME: Following code is very ugly, consider using a macro
impl Into<ChoiceHolder<String, Vec<String>>> for ChoiceHolder<Arch, ArchList> {
fn into(self) -> ChoiceHolder<String, Vec<String>> {
match self.available {
ChoiceOption::HostArch(archs) => ChoiceHolder::new(ChoiceOption::HostArch(
archs.clone().iter().map(|arch| arch.to_string()).collect(),
))
.unwrap(),
ChoiceOption::TargetArch(archs) => ChoiceHolder::new(ChoiceOption::TargetArch(
archs.clone().iter().map(|arch| arch.to_string()).collect(),
))
.unwrap(),
_ => unreachable!(),
}
}
}
impl From<ChoiceHolder<String, Vec<String>>> for ChoiceHolder<Arch, ArchList> {
fn from(value: ChoiceHolder<String, Vec<String>>) -> Self {
match value.available {
ChoiceOption::HostArch(archs) => ChoiceHolder::new(ChoiceOption::HostArch(
archs
.iter()
.map(|arch| Arch::from_str(arch.as_str()).unwrap())
.collect::<Vec<Arch>>()
.into(),
))
.unwrap(),
ChoiceOption::TargetArch(archs) => ChoiceHolder::new(ChoiceOption::TargetArch(
archs
.iter()
.map(|arch| Arch::from_str(arch.as_str()).unwrap())
.collect::<Vec<Arch>>()
.into(),
))
.unwrap(),
_ => unreachable!(),
}
}
}