use crate::core::input::Action;
use bevy::color::Srgba;
use bevy::prelude::{
Bundle, Color, Component, Entity, Name, Resource, Sprite, Transform, Vec2, Vec3,
};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
#[cfg(feature = "debug")]
use bevy::reflect::Reflect;
use bevy_rich_text3d::{TextAlign, TextAnchor};
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub struct UILayer(Cow<'static, str>);
impl UILayer {
pub const BACKPACK_MENU: UILayer = UILayer::new_static("BackpackMenu");
pub const BACKPACK_ITEM: UILayer = UILayer::new_static("BackpackItem");
pub const BACKPACK_ITEM_CHOOSES: UILayer = UILayer::new_static("BackpackItemOptions");
pub const BACKPACK_STATUS: UILayer = UILayer::new_static("BackpackStatus");
pub const BACKPACK_MENU_OPTIONS: &'static [UILayer] =
&[Self::BACKPACK_ITEM, Self::BACKPACK_STATUS];
const fn new_static(name: &'static str) -> UILayer {
UILayer(Cow::Borrowed(name))
}
pub fn new(name: impl Into<Cow<'static, str>>) -> UILayer {
UILayer(name.into())
}
#[allow(dead_code)]
pub fn name(&self) -> &str {
&self.0
}
}
impl fmt::Display for UILayer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(usize)]
pub enum BackpackItemOption {
Use = 0,
Info = 1,
Drop = 2,
}
impl BackpackItemOption {
pub const ALL: &'static [Self] = &[Self::Use, Self::Info, Self::Drop];
pub const fn count() -> usize {
Self::ALL.len()
}
}
#[derive(Component, Debug, Clone)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) struct UIAnimationState {
pub(crate) state_name: String,
}
#[derive(Component, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) struct RonUI {
layer: UILayer,
index: usize,
max_index: usize,
}
impl RonUI {
pub(crate) fn new(layer: UILayer, max_index: usize) -> Self {
Self {
layer,
index: 0,
max_index,
}
}
pub(crate) fn layer(&self) -> &UILayer {
&self.layer
}
pub(crate) fn index(&self) -> usize {
self.index
}
#[allow(dead_code)]
pub(crate) fn max_index(&self) -> usize {
self.max_index
}
pub(crate) fn set_layer(&mut self, layer: UILayer, max_index: usize) {
if self.layer != layer {
self.layer = layer;
self.index = 0;
}
self.max_index = max_index;
if self.index > self.max_index {
self.index = self.max_index;
}
}
pub(crate) fn set_index(&mut self, idx: usize) {
self.index = idx.min(self.max_index);
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) enum UIFont {
DeterminationMono,
DeterminationSans,
Hud,
BattleHud,
}
impl UIFont {
pub(crate) fn font_name(&self) -> &'static str {
match self {
UIFont::DeterminationMono => "Determination Mono SimSun",
UIFont::DeterminationSans => "Determination Sans SimSun",
UIFont::Hud => "Crypt of Tomorrow Fusion",
UIFont::BattleHud => "Mars Needs Cunnilingus",
}
}
pub(crate) fn default_size(&self) -> f32 {
128.
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) struct UITextConfig {
pub(crate) name: Name,
pub(crate) content: String,
pub(crate) template: Option<String>,
pub(crate) font: UIFont,
pub(crate) world_scale: Vec2,
pub(crate) color: Srgba,
pub(crate) transform: Transform,
pub(crate) align: TextAlign,
pub(crate) anchor: TextAnchor,
pub(crate) line_height: f32,
}
impl Default for UITextConfig {
fn default() -> Self {
Self {
name: Name::new("Text"),
content: "Text".to_string(),
template: None,
font: UIFont::DeterminationMono,
world_scale: Vec2::splat(13.),
color: Srgba::WHITE,
transform: Transform::default(),
align: TextAlign::Left,
anchor: TextAnchor::BOTTOM_RIGHT,
line_height: 1.0,
}
}
}
#[derive(Component, Debug, Clone)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) struct UITextTemplate(pub(crate) String);
#[derive(Component, Debug)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) struct CameraAnchored {
pub(crate) offset: Vec3,
}
impl CameraAnchored {
pub(crate) fn new(offset: Vec3) -> Self {
Self { offset }
}
}
#[derive(Component, Debug, Clone)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) struct CameraAnchoredDynamic {
pub(crate) x_expression: Option<String>,
pub(crate) y_expression: Option<String>,
pub(crate) z_expression: Option<String>,
}
#[derive(Bundle, Debug)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) struct CameraAnchoredBundle {
anchor: CameraAnchored,
transform: Transform,
}
impl CameraAnchoredBundle {
pub(crate) fn from_camera_transform(camera_transform: &Transform, offset: Vec3) -> Self {
Self {
anchor: CameraAnchored::new(offset),
transform: Transform::from_translation(camera_transform.translation + offset),
}
}
}
#[derive(Component, Debug)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) struct UIBox {
pub(crate) width: f32,
pub(crate) height: f32,
pub(crate) border_width: f32,
pub(crate) texts: Vec<UITextConfig>,
#[cfg_attr(feature = "debug", reflect(ignore))]
pub(crate) fill_shader: Option<String>,
#[cfg_attr(feature = "debug", reflect(ignore))]
pub(crate) structure_file: Option<String>,
pub(crate) fill_color: Color,
}
impl UIBox {
#[allow(dead_code)]
pub(crate) fn new(width: f32, height: f32, border_width: f32) -> Self {
Self {
width,
height,
border_width,
texts: Vec::new(),
fill_shader: None,
structure_file: None,
fill_color: Color::BLACK,
}
}
#[allow(dead_code)]
pub(crate) fn new_with_texts(
width: f32,
height: f32,
border_width: f32,
texts: Vec<UITextConfig>,
) -> Self {
Self {
width,
height,
border_width,
texts,
fill_shader: None,
structure_file: None,
fill_color: Color::BLACK,
}
}
pub(crate) fn new_full(
width: f32,
height: f32,
border_width: f32,
texts: Vec<UITextConfig>,
fill_shader: Option<String>,
structure_file: Option<String>,
fill_color: Color,
) -> Self {
Self {
width,
height,
border_width,
texts,
fill_shader,
structure_file,
fill_color,
}
}
pub(crate) fn width(&self) -> f32 {
self.width
}
pub(crate) fn height(&self) -> f32 {
self.height
}
pub(crate) fn border_width(&self) -> f32 {
self.border_width
}
#[allow(dead_code)]
pub(crate) fn fill_shader(&self) -> Option<&str> {
self.fill_shader.as_deref()
}
#[allow(dead_code)]
pub(crate) fn structure_file(&self) -> Option<&str> {
self.structure_file.as_deref()
}
#[allow(dead_code)]
pub(crate) fn fill_color(&self) -> Color {
self.fill_color
}
#[allow(dead_code)]
pub(crate) fn set_dimensions(&mut self, width: f32, height: f32) {
self.width = width;
self.height = height;
}
#[allow(dead_code)]
pub(crate) fn set_border_width(&mut self, border_width: f32) {
self.border_width = border_width;
}
}
#[derive(Component, Debug)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) struct UIBoxVisibility {
rule: UILayerVisibilityRule,
}
impl UIBoxVisibility {
pub(crate) fn new(rule: UILayerVisibilityRule) -> Self {
Self { rule }
}
pub(crate) fn rule(&self) -> &UILayerVisibilityRule {
&self.rule
}
pub(crate) fn is_visible_for(&self, layer: &UILayer) -> bool {
self.rule.is_visible_for(layer)
}
}
#[derive(Component)]
pub(crate) struct BoxCursorSprite;
#[derive(Component, Copy, Clone)]
pub(crate) struct BoxCursorOwner(pub Entity);
#[derive(Component, Copy, Clone)]
pub(crate) struct BoxCursorReady;
#[derive(Component)]
pub(crate) struct UIBoxFiller;
#[derive(Component, Debug)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) struct UIContainer;
#[derive(Component, Debug)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) struct UIContainerVisibility {
rule: UILayerVisibilityRule,
}
impl UIContainerVisibility {
pub(crate) fn new(rule: UILayerVisibilityRule) -> Self {
Self { rule }
}
pub(crate) fn rule(&self) -> &UILayerVisibilityRule {
&self.rule
}
pub(crate) fn is_visible_for(&self, layer: &UILayer) -> bool {
self.rule.is_visible_for(layer)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "debug", derive(Reflect))]
#[derive(Default)]
pub(crate) enum UILayerVisibilityRule {
#[default]
Always,
AlwaysHidden,
OnlyIn(Vec<UILayer>),
Except(Vec<UILayer>),
}
impl UILayerVisibilityRule {
pub(crate) fn is_visible_for(&self, layer: &UILayer) -> bool {
match self {
UILayerVisibilityRule::Always => true,
UILayerVisibilityRule::AlwaysHidden => false,
UILayerVisibilityRule::OnlyIn(layers) => layers.iter().any(|l| l == layer),
UILayerVisibilityRule::Except(layers) => layers.iter().all(|l| l != layer),
}
}
}
pub(crate) use UILayerVisibilityRule as BoxCursorVisibility;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) enum BoxCursorPosition {
Static(Vec3),
Linear { origin: Vec3, step: Vec3 },
Custom(Vec<Vec3>),
}
impl Default for BoxCursorPosition {
fn default() -> Self {
BoxCursorPosition::Static(Vec3::ZERO)
}
}
impl BoxCursorPosition {
#[allow(dead_code)]
pub(crate) fn fixed(position: Vec3) -> Self {
Self::Static(position)
}
#[allow(dead_code)]
pub(crate) fn linear(origin: Vec3, step: Vec3) -> Self {
Self::Linear { origin, step }
}
#[allow(dead_code)]
pub(crate) fn custom(positions: Vec<Vec3>) -> Self {
Self::Custom(positions)
}
pub(crate) fn position_for_index(&self, index: usize) -> Vec3 {
match self {
BoxCursorPosition::Static(position) => *position,
BoxCursorPosition::Linear { origin, step } => *origin + *step * index as f32,
BoxCursorPosition::Custom(positions) => {
if positions.is_empty() {
Vec3::ZERO
} else {
positions[index.min(positions.len() - 1)]
}
}
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) struct BoxCursorPlacement {
pub(crate) default: BoxCursorPosition,
pub(crate) overrides: HashMap<UILayer, BoxCursorPosition>,
}
impl BoxCursorPlacement {
pub(crate) fn new(default: BoxCursorPosition) -> Self {
Self {
default,
overrides: HashMap::new(),
}
}
pub(crate) fn with_override(mut self, layer: UILayer, position: BoxCursorPosition) -> Self {
self.overrides.insert(layer, position);
self
}
pub(crate) fn get(&self, layer: &UILayer) -> &BoxCursorPosition {
self.overrides.get(layer).unwrap_or(&self.default)
}
}
impl From<BoxCursorPosition> for BoxCursorPlacement {
fn from(position: BoxCursorPosition) -> Self {
Self::new(position)
}
}
#[derive(Component, Debug)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub(crate) struct BoxCursor {
pub(crate) sprite: Sprite,
pub(crate) visibility: BoxCursorVisibility,
pub(crate) placement: BoxCursorPlacement,
pub(crate) transform: Transform,
hidden: bool,
last_index: Option<usize>,
last_layer: Option<UILayer>,
}
impl BoxCursor {
pub(crate) fn new(
sprite: Sprite,
visibility: BoxCursorVisibility,
placement: impl Into<BoxCursorPlacement>,
transform: Transform,
) -> Self {
Self {
sprite,
visibility,
placement: placement.into(),
transform,
hidden: false,
last_index: None,
last_layer: None,
}
}
pub(crate) fn sprite(&self) -> Sprite {
self.sprite.clone()
}
pub(crate) fn visibility(&self) -> &BoxCursorVisibility {
&self.visibility
}
pub(crate) fn desired_translation(&self, layer: &UILayer, index: usize) -> Vec3 {
let position_rule = self.placement.get(layer);
self.transform.translation + position_rule.position_for_index(index)
}
pub(crate) fn transform(&self) -> Transform {
self.transform
}
pub(crate) fn translation_for_index(&mut self, layer: &UILayer, index: usize) -> Option<Vec3> {
if self.last_index == Some(index) && self.last_layer.as_ref() == Some(layer) {
return None;
}
self.last_index = Some(index);
self.last_layer = Some(layer.clone());
Some(self.desired_translation(layer, index))
}
#[allow(dead_code)]
pub(crate) fn hide(&mut self) {
self.hidden = true;
}
#[allow(dead_code)]
pub(crate) fn show(&mut self) {
self.hidden = false;
}
pub(crate) fn is_hidden(&self) -> bool {
self.hidden
}
}
#[derive(Debug, Clone)]
pub(crate) struct UILayerNavigationRule {
adjustments: HashMap<Action, isize>,
looping: bool,
min_index: Option<IndexBound>,
max_index: Option<IndexBound>,
sound_on_navigate: Option<String>,
}
#[derive(Debug, Clone)]
pub(crate) enum IndexBound {
Static(usize),
Dynamic(String),
}
impl UILayerNavigationRule {
pub(crate) fn new(pairs: impl IntoIterator<Item = (Action, isize)>) -> Self {
Self {
adjustments: pairs.into_iter().collect::<HashMap<_, _>>(),
looping: false,
min_index: None,
max_index: None,
sound_on_navigate: None,
}
}
pub(crate) fn new_with_bounds(
pairs: impl IntoIterator<Item = (Action, isize)>,
looping: bool,
min_index: Option<IndexBound>,
max_index: Option<IndexBound>,
sound_on_navigate: Option<String>,
) -> Self {
Self {
adjustments: pairs.into_iter().collect::<HashMap<_, _>>(),
looping,
min_index,
max_index,
sound_on_navigate,
}
}
pub(crate) fn delta_for(&self, action: Action) -> Option<isize> {
self.adjustments.get(&action).copied()
}
pub(crate) fn looping(&self) -> bool {
self.looping
}
pub(crate) fn min_index(&self) -> &Option<IndexBound> {
&self.min_index
}
pub(crate) fn max_index(&self) -> &Option<IndexBound> {
&self.max_index
}
pub(crate) fn sound_on_navigate(&self) -> Option<&str> {
self.sound_on_navigate.as_deref()
}
}
#[derive(Resource, Debug, Default)]
pub(crate) struct UILayerNavigationConfig {
rules: HashMap<UILayer, UILayerNavigationRule>,
}
impl UILayerNavigationConfig {
pub(crate) fn get(&self, layer: &UILayer) -> Option<&UILayerNavigationRule> {
self.rules.get(layer)
}
pub(crate) fn set_rule(&mut self, layer: UILayer, rule: UILayerNavigationRule) {
self.rules.insert(layer, rule);
}
}
impl Default for UILayerNavigationRule {
fn default() -> Self {
Self::new([])
}
}
#[derive(Resource, Debug, Default)]
pub(crate) struct UILayerTransitionConfig {
transitions: HashMap<UILayer, LayerTransitions>,
}
#[derive(Debug, Clone)]
pub(crate) struct LayerTransitions {
pub(crate) on_confirm: Vec<TransitionRule>,
pub(crate) on_cancel: Option<TransitionAction>,
pub(crate) sound_on_confirm: Option<String>,
pub(crate) sound_on_cancel: Option<String>,
}
#[derive(Debug, Clone)]
pub(crate) struct TransitionRule {
pub(crate) condition: Option<String>,
pub(crate) action: TransitionAction,
}
#[derive(Debug, Clone)]
pub(crate) enum TransitionAction {
GotoLayer(UILayer),
PopState,
PushState(String),
}
impl UILayerTransitionConfig {
#[allow(dead_code)]
pub(crate) fn new() -> Self {
Self {
transitions: HashMap::new(),
}
}
pub(crate) fn set_transitions(&mut self, layer: UILayer, transitions: LayerTransitions) {
self.transitions.insert(layer, transitions);
}
pub(crate) fn get(&self, layer: &UILayer) -> Option<&LayerTransitions> {
self.transitions.get(layer)
}
}
#[derive(Component)]
pub struct HPBarSprite {
pub shader_params: Color,
}
#[derive(Component, Clone)]
pub struct DynamicUIElement {
pub sprite_def: Option<crate::core::ui::layout::SpriteDef>,
pub text_def: Option<crate::core::ui::layout::TextDef>,
}
#[derive(Component, Debug, Clone)]
#[cfg_attr(feature = "debug", derive(Reflect))]
pub struct HPBarLag {
pub lag_hp_ratio: f32,
pub last_hp_ratio: f32,
pub start_lag_ratio: f32, pub delay_timer: f32, pub anim_progress: f32, }
impl Default for HPBarLag {
fn default() -> Self {
Self {
lag_hp_ratio: 1.0,
last_hp_ratio: 1.0,
start_lag_ratio: 1.0,
delay_timer: 0.0,
anim_progress: 0.0,
}
}
}
impl HPBarLag {
pub fn new(hp_ratio: f32) -> Self {
Self {
lag_hp_ratio: hp_ratio,
last_hp_ratio: hp_ratio,
start_lag_ratio: hp_ratio,
delay_timer: 0.0,
anim_progress: 0.5, }
}
}