use std::collections::{BTreeMap, HashMap};
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use crate::script::navigation::NavigationRequest;
use crate::state::StoredValue;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum UiEffect {
PlaySfx {
name: String,
#[serde(default = "default_ui_effect_volume")]
volume: f32,
},
OpenUi {
role: String,
},
CloseUi,
Save {
slot: String,
},
Load {
slot: String,
},
NextDialogue,
Navigate(NavigationRequest),
}
fn default_ui_effect_volume() -> f32 {
1.0
}
#[derive(Clone, Debug)]
pub struct UiReactiveBinding {
pub(crate) program: hiraku_script::LinkedProgram,
pub(crate) getter: hiraku_script::Value,
pub(crate) setter: Option<hiraku_script::Value>,
pub(crate) globals: BTreeMap<String, hiraku_script::Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UiPhaseAnimation {
pub phases: Vec<crate::script::AnimationPhase>,
pub spec: crate::script::AnimationSpec,
#[serde(default)]
pub continuous_rotation: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ScreenSpec {
pub title: Option<String>,
#[serde(default = "default_screen_panel")]
pub panel: bool,
#[serde(default)]
pub width: Option<f32>,
#[serde(default)]
pub background_texture: Option<ScreenTexture>,
#[serde(default)]
pub xalign: f32,
#[serde(default)]
pub yalign: f32,
#[serde(default)]
pub padding: f32,
#[serde(default)]
pub gap: f32,
#[serde(default)]
pub overlay: Option<[f32; 4]>,
#[serde(default)]
pub background: Option<[f32; 4]>,
#[serde(default)]
pub border: Option<[f32; 4]>,
#[serde(default)]
pub children: Vec<ScreenNode>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ScreenNode {
Text(TextNode),
Button(ButtonNode),
Image(ScreenImageNode),
ImageButton(ScreenImageButtonNode),
Bar(BarNode),
Row(ContainerNode),
Column(ContainerNode),
Scrollable(ScrollableNode),
Toggle(ToggleNode),
Spacer(SpacerNode),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ScrollableNode {
pub children: Vec<ScreenNode>,
pub speed: f32,
#[serde(default)]
pub layout: ScreenLayout,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ToggleNode {
pub unchecked: ScreenImageNode,
pub checked: ScreenImageNode,
pub value: bool,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ScreenLayout {
#[serde(default)]
pub hidden: bool,
#[serde(default)]
pub visible_binding: Option<String>,
#[serde(skip)]
pub reactive_visibility: Option<UiReactiveBinding>,
#[serde(default)]
pub animation: Option<crate::script::AnimationSpec>,
#[serde(default)]
pub phase_animation: Option<UiPhaseAnimation>,
#[serde(default)]
pub width: Option<f32>,
#[serde(default)]
pub height: Option<f32>,
#[serde(default)]
pub width_percent: Option<f32>,
#[serde(default)]
pub height_percent: Option<f32>,
#[serde(default)]
pub min_width: Option<f32>,
#[serde(default)]
pub left: Option<f32>,
#[serde(default)]
pub left_percent: Option<f32>,
#[serde(default)]
pub right: Option<f32>,
#[serde(default)]
pub right_percent: Option<f32>,
#[serde(default)]
pub top: Option<f32>,
#[serde(default)]
pub top_percent: Option<f32>,
#[serde(default)]
pub bottom: Option<f32>,
#[serde(default)]
pub bottom_percent: Option<f32>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TextNode {
pub text: String,
#[serde(default)]
pub binding: Option<String>,
#[serde(skip)]
pub reactive_text: Option<UiReactiveBinding>,
#[serde(default = "default_text_size")]
pub size: f32,
#[serde(default)]
pub color: Option<[f32; 4]>,
#[serde(default)]
pub align: Option<f32>,
#[serde(default)]
pub layout: ScreenLayout,
}
impl StoredValue {
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Bool(value) => Some(*value),
_ => None,
}
}
pub fn as_number(&self) -> Option<f64> {
match self {
Self::Int(value) => Some(*value as f64),
Self::Float(value) => Some(*value),
_ => None,
}
}
pub(crate) fn display(&self) -> String {
match self {
Self::Bool(value) => value.to_string(),
Self::Int(value) => value.to_string(),
Self::Float(value) => value.to_string(),
Self::String(value) => value.clone(),
Self::Array(_) | Self::Map(_) => "<object>".to_string(),
}
}
}
#[derive(Resource, Default)]
pub struct UiModels {
roots: BTreeMap<String, StoredValue>,
revision: u64,
}
impl UiModels {
pub fn set(&mut self, name: impl Into<String>, value: StoredValue) {
let name = name.into();
assert!(
!name.contains('.'),
"UI model names must be root identifiers"
);
if self.roots.get(&name) == Some(&value) {
return;
}
self.roots.insert(name, value);
self.revision = self
.revision
.checked_add(1)
.expect("UI model revision must not be exhausted");
}
pub fn remove(&mut self, name: &str) {
if self.roots.remove(name).is_some() {
self.revision = self
.revision
.checked_add(1)
.expect("UI model revision must not be exhausted");
}
}
pub fn get(&self, path: &str) -> Option<&StoredValue> {
let mut segments = path.split('.');
let mut value = self.roots.get(segments.next()?)?;
for segment in segments {
let StoredValue::Map(fields) = value else {
return None;
};
value = fields.get(segment)?;
}
Some(value)
}
pub fn revision(&self) -> u64 {
self.revision
}
pub(crate) fn roots(&self) -> impl Iterator<Item = (&str, &StoredValue)> {
self.roots
.iter()
.map(|(name, value)| (name.as_str(), value))
}
}
#[derive(Component)]
pub(crate) struct UiTextBinding {
pub(crate) template: String,
pub(crate) rendered_revision: u64,
}
#[derive(Component)]
pub(crate) struct UiVisibilityBinding {
pub(crate) signal: String,
pub(crate) rendered_revision: u64,
}
#[derive(Component)]
pub(crate) struct UiEnabledBinding {
pub(crate) signal: String,
pub(crate) rendered_revision: u64,
}
#[derive(Component)]
pub(crate) struct UiProgressBinding {
pub(crate) signal: String,
pub(crate) min: f32,
pub(crate) max: f32,
pub(crate) rendered_revision: u64,
}
#[derive(Component)]
pub(crate) struct UiReactiveTextBinding {
pub(crate) expression: UiReactiveBinding,
pub(crate) rendered_revision: u64,
}
#[derive(Component)]
pub(crate) struct UiReactiveVisibilityBinding {
pub(crate) expression: UiReactiveBinding,
pub(crate) rendered_revision: u64,
}
#[derive(Component)]
pub(crate) struct UiReactiveEnabledBinding {
pub(crate) expression: UiReactiveBinding,
pub(crate) rendered_revision: u64,
}
#[derive(Component)]
pub(crate) struct UiReactiveProgressBinding {
pub(crate) expression: UiReactiveBinding,
pub(crate) min: f32,
pub(crate) max: f32,
pub(crate) rendered_revision: u64,
}
#[derive(Component)]
pub(crate) struct UiAnimationPlayer {
pub(crate) spec: crate::script::AnimationSpec,
pub(crate) elapsed: f32,
pub(crate) phases: Option<Vec<crate::script::AnimationPhase>>,
pub(crate) continuous_rotation: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ButtonNode {
pub text: String,
#[serde(default)]
pub value: Option<StoredValue>,
#[serde(default)]
pub click_effects: Vec<UiEffect>,
#[serde(default = "default_button_enabled")]
pub enabled: bool,
#[serde(default)]
pub enabled_binding: Option<String>,
#[serde(skip)]
pub reactive_enabled: Option<UiReactiveBinding>,
#[serde(default = "default_button_size")]
pub size: f32,
#[serde(default)]
pub color: Option<[f32; 4]>,
#[serde(default)]
pub hovered_color: Option<[f32; 4]>,
#[serde(default)]
pub pressed_color: Option<[f32; 4]>,
#[serde(default)]
pub insensitive_color: Option<[f32; 4]>,
#[serde(default)]
pub background: Option<[f32; 4]>,
#[serde(default)]
pub border: Option<[f32; 4]>,
#[serde(default)]
pub hovered_background: Option<[f32; 4]>,
#[serde(default)]
pub pressed_background: Option<[f32; 4]>,
#[serde(default)]
pub background_texture: Option<ScreenTexture>,
#[serde(default)]
pub hovered_background_texture: Option<ScreenTexture>,
#[serde(default = "default_interaction_scale")]
pub hover_scale: f32,
#[serde(default = "default_interaction_scale")]
pub press_scale: f32,
#[serde(default)]
pub align: Option<f32>,
#[serde(default)]
pub padding_x: Option<f32>,
#[serde(default)]
pub padding_y: Option<f32>,
#[serde(default)]
pub border_width: Option<f32>,
#[serde(default)]
pub radius: Option<f32>,
#[serde(default)]
pub layout: ScreenLayout,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ScreenImageNode {
pub texture: ScreenTexture,
#[serde(default)]
pub layout: ScreenLayout,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ScreenTexture {
pub path: String,
#[serde(default)]
pub rect: Option<[f32; 4]>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ScreenImageButtonNode {
pub texture: ScreenTexture,
#[serde(default)]
pub hovered_texture: Option<ScreenTexture>,
#[serde(default)]
pub hovered_layout: Option<ScreenLayout>,
#[serde(default = "default_interaction_scale")]
pub hover_scale: f32,
#[serde(default = "default_interaction_scale")]
pub press_scale: f32,
#[serde(default)]
pub value: Option<StoredValue>,
#[serde(default)]
pub click_effects: Vec<UiEffect>,
#[serde(default = "default_button_enabled")]
pub enabled: bool,
#[serde(default)]
pub enabled_binding: Option<String>,
#[serde(skip)]
pub reactive_enabled: Option<UiReactiveBinding>,
#[serde(default)]
pub hovered_when_disabled: bool,
#[serde(default)]
pub layout: ScreenLayout,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BarNode {
pub value: f32,
#[serde(default)]
pub binding: Option<String>,
#[serde(skip)]
pub reactive_value: Option<UiReactiveBinding>,
#[serde(default)]
pub min: f32,
#[serde(default = "default_bar_max")]
pub max: f32,
#[serde(default = "default_bar_width")]
pub width: f32,
#[serde(default = "default_bar_height")]
pub height: f32,
#[serde(default)]
pub background: Option<[f32; 4]>,
#[serde(default)]
pub fill: Option<[f32; 4]>,
#[serde(default)]
pub border: Option<[f32; 4]>,
#[serde(default)]
pub layout: ScreenLayout,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ContainerNode {
#[serde(default)]
pub gap: f32,
#[serde(default)]
pub padding: f32,
#[serde(default)]
pub background: Option<[f32; 4]>,
#[serde(default)]
pub border: Option<[f32; 4]>,
#[serde(default)]
pub justify: Option<String>,
#[serde(default)]
pub align_items: Option<String>,
#[serde(default)]
pub layout: ScreenLayout,
#[serde(default)]
pub children: Vec<ScreenNode>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SpacerNode {
#[serde(default)]
pub width: f32,
#[serde(default)]
pub height: f32,
#[serde(default)]
pub layout: ScreenLayout,
}
#[derive(Resource, Default)]
pub struct ScreenUiState {
pub active_root: Option<Entity>,
pub pending_root: Option<PendingScreenRoot>,
pub stale_roots: Vec<StaleScreenRoot>,
pub waiting: Option<crate::script::ScriptRequestId>,
}
#[derive(Resource, Default)]
pub struct OverlayUiState {
pub roots: HashMap<String, Entity>,
}
pub struct PendingScreenRoot {
pub entity: Entity,
pub previous: Option<Entity>,
pub wait_images: Vec<Handle<Image>>,
pub ready_frames_remaining: u8,
pub done: Option<crate::script::ScriptRequestId>,
}
#[derive(Clone, Debug)]
pub struct StaleScreenRoot {
pub entity: Entity,
pub frames_remaining: u8,
pub wait_images: Vec<Handle<Image>>,
}
#[derive(Component)]
pub struct ScreenUiRoot;
#[derive(Component)]
pub struct ScreenUiNode;
#[derive(Component, Clone)]
pub struct ScreenUiButton {
pub root: Entity,
pub value: Option<StoredValue>,
pub click_effects: Vec<UiEffect>,
pub enabled: bool,
pub text_entity: Entity,
pub normal_background: Color,
pub hovered_background: Color,
pub pressed_background: Color,
pub insensitive_background: Color,
pub normal_text_color: Color,
pub hovered_text_color: Color,
pub pressed_text_color: Color,
pub insensitive_text_color: Color,
pub hover_scale: f32,
pub press_scale: f32,
pub normal_texture: Option<Handle<Image>>,
pub normal_atlas: Option<TextureAtlas>,
pub normal_rect: Option<Rect>,
pub hovered_texture: Option<Handle<Image>>,
pub hovered_atlas: Option<TextureAtlas>,
pub hovered_rect: Option<Rect>,
}
#[derive(Component)]
pub struct ScreenUiButtonText;
#[derive(Component, Clone)]
pub struct ScreenUiImageButton {
pub root: Entity,
pub value: Option<StoredValue>,
pub click_effects: Vec<UiEffect>,
pub enabled: bool,
pub hovered_when_disabled: bool,
pub normal_rect: Option<Rect>,
pub normal_texture: Handle<Image>,
pub normal_atlas: Option<TextureAtlas>,
pub hovered_rect: Option<Rect>,
pub hovered_texture: Option<Handle<Image>>,
pub hovered_atlas: Option<TextureAtlas>,
pub hovered_node: Option<Node>,
pub normal_node: Node,
pub hover_scale: f32,
pub press_scale: f32,
}
#[derive(Component)]
pub struct ScreenUiScrollable {
pub speed: f32,
}
#[derive(Component)]
pub struct ScreenUiToggle {
pub checked: bool,
pub unchecked_node: Node,
pub checked_node: Node,
pub unchecked_texture: Handle<Image>,
pub unchecked_atlas: Option<TextureAtlas>,
pub unchecked_rect: Option<Rect>,
pub checked_texture: Handle<Image>,
pub checked_atlas: Option<TextureAtlas>,
pub checked_rect: Option<Rect>,
}
fn default_interaction_scale() -> f32 {
1.0
}
fn default_screen_panel() -> bool {
true
}
fn default_text_size() -> f32 {
26.0
}
fn default_button_size() -> f32 {
28.0
}
fn default_button_enabled() -> bool {
true
}
fn default_bar_max() -> f32 {
1.0
}
fn default_bar_width() -> f32 {
320.0
}
fn default_bar_height() -> f32 {
18.0
}
pub fn color_from_rgba(rgba: [f32; 4]) -> Color {
Color::srgba(rgba[0], rgba[1], rgba[2], rgba[3])
}