use serde::de;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use ts_rs::TS;
use super::base_types::Linear3D;
use super::primitives::{CenterMode, EasingType, SdfPrimitive};
#[derive(Clone, Debug, PartialEq, Default, Deserialize, Serialize, TS)]
pub struct NavigationState {
#[serde(default)]
pub bubble: Option<BubbleNavigationState>,
}
#[derive(Copy, Clone, Debug, PartialEq, Default, Deserialize, Serialize, TS)]
pub struct BubbleNavigationState {
#[serde(default)]
pub center: Option<Linear3D>,
#[serde(default)]
pub velocity_zone_width: Option<f32>,
#[serde(default)]
pub collision_extra_inflate: Option<f32>,
}
#[derive(Clone, Debug, PartialEq, Default, Deserialize, Serialize, TS)]
pub struct NavigationStatusInfo {
#[serde(default)]
pub mode: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Default, Deserialize, Serialize, TS)]
pub struct NavigationConfigSnapshot {
#[serde(default)]
pub mode: Option<String>,
#[serde(default)]
pub bubble: Option<BubbleNavigationSettings>,
}
#[derive(Clone, Debug, PartialEq, Default, Deserialize, Serialize, TS)]
pub struct NavigationFullResponse {
#[serde(default)]
pub config: Option<BubbleNavigationSettings>,
#[serde(default)]
pub state: Option<BubbleNavigationState>,
#[serde(default)]
pub status: Option<NavigationStatusInfo>,
}
#[derive(Clone, Debug, PartialEq, Default, Deserialize, Serialize, TS)]
pub struct NavigationConfigure {
pub mode: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub bubble: Option<BubbleNavigationSettings>,
}
#[derive(Clone, Debug, PartialEq, Default, Deserialize, Serialize, TS)]
pub struct BubbleCenterSettings {
#[serde(skip_serializing_if = "Option::is_none")]
pub position: Option<Linear3D>,
#[serde(skip_serializing_if = "Option::is_none")]
pub relative: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub follow: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub speed: Option<f32>,
}
#[derive(Copy, Clone, Debug, PartialEq, Default, Deserialize, Serialize, TS)]
pub struct CollisionDetectionSettings {
#[serde(skip_serializing_if = "Option::is_none")]
pub enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub force_threshold: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub inflate_ratio: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exit_ratio: Option<f32>,
}
#[derive(Clone, Debug, PartialEq, Default, Serialize, TS)]
pub struct BubbleNavigationSettings {
#[serde(skip_serializing_if = "Option::is_none")]
pub center: Option<BubbleCenterSettings>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shape: Option<SdfPrimitive>,
#[serde(skip_serializing_if = "Option::is_none")]
pub velocity_zone_width: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_velocity: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub velocity_ease: Option<EasingType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reset_velocity_on_entry: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bump_width: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bump_stiffness: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spring_inner: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spring_surface: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spring_outer: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub wall_stiffness: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub damping_inner: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub damping_surface: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub damping_outer: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rotation_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scale_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub collision_detection: Option<CollisionDetectionSettings>,
}
#[derive(Clone, Debug, PartialEq, Default, Deserialize)]
struct BubbleNavigationSettingsSerde {
#[serde(default)]
center: Option<Value>,
#[serde(default)]
shape: Option<SdfPrimitive>,
#[serde(default)]
velocity_zone_width: Option<f32>,
#[serde(default)]
max_velocity: Option<f32>,
#[serde(default)]
velocity_ease: Option<EasingType>,
#[serde(default)]
reset_velocity_on_entry: Option<bool>,
#[serde(default)]
bump_width: Option<f32>,
#[serde(default)]
bump_stiffness: Option<f32>,
#[serde(default)]
spring_inner: Option<f32>,
#[serde(default)]
spring_surface: Option<f32>,
#[serde(default)]
spring_outer: Option<f32>,
#[serde(default)]
wall_stiffness: Option<f32>,
#[serde(default)]
damping_inner: Option<f32>,
#[serde(default)]
damping_surface: Option<f32>,
#[serde(default)]
damping_outer: Option<f32>,
#[serde(default)]
rotation_enabled: Option<bool>,
#[serde(default)]
scale_enabled: Option<bool>,
#[serde(default)]
collision_detection: Option<CollisionDetectionSettings>,
#[serde(default)]
center_mode: Option<CenterMode>,
#[serde(default)]
center_drift_speed: Option<f32>,
#[serde(default)]
stop_at_collision: Option<bool>,
#[serde(default)]
collision_threshold: Option<f32>,
#[serde(default)]
collision_inflate_scale: Option<f32>,
}
fn parse_center_compat(value: Option<Value>) -> Result<Option<BubbleCenterSettings>, serde_json::Error> {
let Some(center_value) = value else {
return Ok(None);
};
if center_value.is_null() {
return Ok(None);
}
match center_value {
Value::Object(map) => {
let has_new_center_shape =
map.contains_key("position") ||
map.contains_key("relative") ||
map.contains_key("follow") ||
map.contains_key("speed");
let has_legacy_linear_shape =
map.contains_key("x") &&
map.contains_key("y") &&
map.contains_key("z");
if has_new_center_shape {
let parsed: BubbleCenterSettings = serde_json::from_value(Value::Object(map))?;
Ok(Some(parsed))
} else if has_legacy_linear_shape {
let pos: Linear3D = serde_json::from_value(Value::Object(map))?;
Ok(Some(BubbleCenterSettings {
position: Some(pos),
..Default::default()
}))
} else {
let parsed: BubbleCenterSettings = serde_json::from_value(Value::Object(map))?;
Ok(Some(parsed))
}
}
other => {
let pos: Linear3D = serde_json::from_value(other)?;
Ok(Some(BubbleCenterSettings {
position: Some(pos),
..Default::default()
}))
}
}
}
impl<'de> Deserialize<'de> for BubbleNavigationSettings {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let helper = BubbleNavigationSettingsSerde::deserialize(deserializer)?;
let mut center = parse_center_compat(helper.center).map_err(de::Error::custom)?;
if let Some(legacy_mode) = helper.center_mode {
let center_cfg = center.get_or_insert_with(Default::default);
match legacy_mode {
CenterMode::AutoFollow => {
if center_cfg.follow.is_none() {
center_cfg.follow = Some(true);
}
if center_cfg.speed.is_none() {
center_cfg.speed = helper.center_drift_speed;
}
}
CenterMode::Fixed => {
if center_cfg.follow.is_none() {
center_cfg.follow = Some(false);
}
}
CenterMode::TrackCursor => {
if center_cfg.follow.is_none() {
center_cfg.follow = Some(true);
}
if center_cfg.speed.is_none() {
center_cfg.speed = Some(0.0);
}
}
}
}
if helper.center_drift_speed.is_some() {
let center_cfg = center.get_or_insert_with(Default::default);
if center_cfg.speed.is_none() {
center_cfg.speed = helper.center_drift_speed;
}
}
let mut collision_detection = helper.collision_detection;
if
helper.stop_at_collision.is_some() ||
helper.collision_threshold.is_some() ||
helper.collision_inflate_scale.is_some()
{
let collision_cfg = collision_detection.get_or_insert_with(Default::default);
if collision_cfg.enabled.is_none() {
collision_cfg.enabled = helper.stop_at_collision;
}
if collision_cfg.force_threshold.is_none() {
collision_cfg.force_threshold = helper.collision_threshold;
}
if collision_cfg.inflate_ratio.is_none() {
collision_cfg.inflate_ratio = helper.collision_inflate_scale;
}
}
Ok(BubbleNavigationSettings {
center,
shape: helper.shape,
velocity_zone_width: helper.velocity_zone_width,
max_velocity: helper.max_velocity,
velocity_ease: helper.velocity_ease,
reset_velocity_on_entry: helper.reset_velocity_on_entry,
bump_width: helper.bump_width,
bump_stiffness: helper.bump_stiffness,
spring_inner: helper.spring_inner,
spring_surface: helper.spring_surface,
spring_outer: helper.spring_outer,
wall_stiffness: helper.wall_stiffness,
damping_inner: helper.damping_inner,
damping_surface: helper.damping_surface,
damping_outer: helper.damping_outer,
rotation_enabled: helper.rotation_enabled,
scale_enabled: helper.scale_enabled,
collision_detection,
})
}
}