use bevy::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DevicePlatform {
Android,
IOS,
MacCatalyst,
MacOS,
WinUI,
Tizen,
Linux,
Web,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviceIdiom {
Phone,
Tablet,
Desktop,
TV,
Watch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelectorKind {
Platform,
Idiom,
}
impl SelectorKind {
pub fn arm_names(self) -> &'static [&'static str] {
match self {
SelectorKind::Platform => &[
"Android",
"iOS",
"MacCatalyst",
"macOS",
"WinUI",
"Tizen",
"UWP",
"GTK",
"WPF",
"Linux",
"Web",
],
SelectorKind::Idiom => &["Phone", "Tablet", "Desktop", "TV", "Watch"],
}
}
pub fn extension_name(self) -> &'static str {
match self {
SelectorKind::Platform => "OnPlatform",
SelectorKind::Idiom => "OnIdiom",
}
}
}
impl DevicePlatform {
pub const fn current() -> Self {
if cfg!(target_os = "android") {
DevicePlatform::Android
} else if cfg!(target_os = "ios") {
DevicePlatform::IOS
} else if cfg!(target_os = "macos") {
DevicePlatform::MacOS
} else if cfg!(target_os = "windows") {
DevicePlatform::WinUI
} else if cfg!(target_family = "wasm") {
DevicePlatform::Web
} else if cfg!(target_os = "linux") {
DevicePlatform::Linux
} else {
DevicePlatform::Other
}
}
pub fn matches(self, name: &str) -> bool {
match name {
"Android" => self == DevicePlatform::Android,
"iOS" => self == DevicePlatform::IOS,
"macOS" | "MacCatalyst" => self == DevicePlatform::MacOS,
"WinUI" => self == DevicePlatform::WinUI,
"Tizen" => self == DevicePlatform::Tizen,
"Linux" => self == DevicePlatform::Linux,
"Web" => self == DevicePlatform::Web,
"UWP" | "GTK" | "WPF" => false,
_ => false,
}
}
}
impl DeviceIdiom {
pub const fn current() -> Self {
if cfg!(target_os = "android") || cfg!(target_os = "ios") {
DeviceIdiom::Phone
} else {
DeviceIdiom::Desktop
}
}
pub fn matches(self, name: &str) -> bool {
match name {
"Phone" => self == DeviceIdiom::Phone,
"Tablet" => self == DeviceIdiom::Tablet,
"Desktop" => self == DeviceIdiom::Desktop,
"TV" => self == DeviceIdiom::TV,
"Watch" => self == DeviceIdiom::Watch,
_ => false,
}
}
}
#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq)]
pub struct PfDevice {
pub platform: DevicePlatform,
pub idiom: DeviceIdiom,
}
impl Default for PfDevice {
fn default() -> Self {
PfDevice {
platform: DevicePlatform::current(),
idiom: DeviceIdiom::current(),
}
}
}
impl PfDevice {
pub fn matches(&self, kind: SelectorKind, name: &str) -> bool {
match kind {
SelectorKind::Platform => self.platform.matches(name),
SelectorKind::Idiom => self.idiom.matches(name),
}
}
}
pub fn device(world: &World) -> PfDevice {
world
.get_resource::<PfDevice>()
.copied()
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn platform_names_are_mauis_and_only_one_can_match() {
let mac = DevicePlatform::MacOS;
assert!(mac.matches("macOS"));
assert!(
mac.matches("MacCatalyst"),
"MAUI markup must port unchanged"
);
assert!(!mac.matches("Android"));
assert!(!mac.matches("iOS"));
assert!(!mac.matches("WinUI"));
for host in ["UWP", "GTK", "WPF"] {
for p in [
DevicePlatform::Android,
DevicePlatform::IOS,
DevicePlatform::MacOS,
DevicePlatform::WinUI,
DevicePlatform::Linux,
DevicePlatform::Web,
] {
assert!(!p.matches(host), "{host} must never match {p:?}");
}
}
}
#[test]
fn every_advertised_arm_name_is_understood() {
for kind in [SelectorKind::Platform, SelectorKind::Idiom] {
for name in kind.arm_names() {
let recognised = match kind {
SelectorKind::Platform => [
DevicePlatform::Android,
DevicePlatform::IOS,
DevicePlatform::MacOS,
DevicePlatform::WinUI,
DevicePlatform::Tizen,
DevicePlatform::Linux,
DevicePlatform::Web,
]
.iter()
.any(|p| p.matches(name)),
SelectorKind::Idiom => [
DeviceIdiom::Phone,
DeviceIdiom::Tablet,
DeviceIdiom::Desktop,
DeviceIdiom::TV,
DeviceIdiom::Watch,
]
.iter()
.any(|i| i.matches(name)),
};
let host_only = matches!(*name, "UWP" | "GTK" | "WPF");
assert!(
recognised || host_only,
"`{name}` is advertised by {} but matches nothing",
kind.extension_name()
);
}
}
}
#[test]
fn the_default_device_describes_this_build() {
let device = PfDevice::default();
assert_eq!(device.platform, DevicePlatform::current());
let hits = SelectorKind::Platform
.arm_names()
.iter()
.filter(|n| device.platform.matches(n))
.count();
assert!(
hits <= 2,
"at most macOS + MacCatalyst may both match, got {hits}"
);
}
}