You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

252 lines
8.4 KiB
Rust

use crate::editor::prelude::*;
use bevy::{
asset::{AssetLoader, LoadContext, LoadedAsset},
reflect::{TypePath, TypeUuid},
ui::FocusPolicy,
utils::BoxedFuture,
};
use serde::Deserialize;
#[derive(Debug, Default)]
pub struct EditorMonologuePlugin;
impl Plugin for EditorMonologuePlugin {
fn build(&self, app: &mut App) {
app.add_asset::<Monologue>()
.init_asset_loader::<MonologueLoader>()
.add_event::<ControlMonologue>()
.add_systems(Update, sync_asset_buttons::<Monologue>)
.add_systems(Update, sync_remove_asset_buttons::<Monologue>)
.add_systems(Update, control_active_gltf)
.add_systems(Update, control_monologue)
.add_systems(Update, ui_control_monologue)
.add_systems(Update, ui_active::<Monologue>)
.add_systems(Update, ui_inactive::<Monologue>)
.add_systems(Update, sync_monologue_font)
.add_systems(Startup, init_texts_ui)
.add_systems(Update, texts_ui);
}
}
#[derive(Debug, Component, Default)]
pub struct MonologueWidget;
#[derive(Debug, Deserialize, TypeUuid, TypePath, PartialEq)]
#[uuid = "216a570b-d142-4026-baed-d7feb0250458"]
pub struct Monologue {
text: String,
}
#[derive(Debug, Event)]
pub enum ControlMonologue {
Show(Handle<Monologue>),
Hide,
}
#[derive(Debug, Component)]
pub struct MonologueModal;
#[derive(Debug, Component)]
pub struct MonologueContainer;
#[derive(Default)]
pub struct MonologueLoader;
impl AssetLoader for MonologueLoader {
fn load<'a>(
&'a self,
bytes: &'a [u8],
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<(), bevy::asset::Error>> {
Box::pin(async move {
let asset = Monologue {
text: String::from_utf8(bytes.to_vec())?,
};
info!("!!! Loading Monologue !!!");
load_context.set_default_asset(LoadedAsset::new(asset));
Ok(())
})
}
fn extensions(&self) -> &[&str] {
&["monologue.txt"]
}
}
pub fn init_texts_ui(mut commands: Commands) {
commands.spawn((
NodeBundle {
style: Style {
width: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
focus_policy: FocusPolicy::Pass,
..default()
},
MonologueContainer,
));
}
pub fn texts_ui(
mut events: EventReader<AssetEvent<Monologue>>,
mut commands: Commands,
widget: Query<Entity, With<MonologueWidget>>,
current: Query<(Entity, &ui::TargetAsset<Monologue>)>,
server: Res<AssetServer>,
) {
events.iter().for_each(|event| match event {
AssetEvent::Created { handle } => {
info!("Monologue created! {:?}", event);
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
}
AssetEvent::Removed { handle } => {
info!("Monologue removed! {:?}", event);
destroy_asset_button(
&current,
&mut commands,
&ui::TargetAsset {
handle: handle.clone(),
},
);
}
AssetEvent::Modified { handle } => {
info!("Monologue modified! {:?}", event);
destroy_asset_button(
&current,
&mut commands,
&ui::TargetAsset {
handle: handle.clone(),
},
);
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
}
});
}
pub fn control_monologue(
mut events: EventReader<ControlMonologue>,
monologues: Res<Assets<Monologue>>,
container: Query<Entity, With<MonologueContainer>>,
mut commands: Commands,
font: Res<FontInfo>, // Not convinced we need this...
) {
events.iter().for_each(|event| match event {
ControlMonologue::Hide => {
commands.entity(container.single()).despawn_descendants();
}
ControlMonologue::Show(handle) => {
monologues.get(handle).iter().for_each(|&monologue| {
commands
.entity(container.single())
.despawn_descendants()
.with_children(|parent| {
parent
.spawn(NodeBundle {
style: Style {
max_width: Val::Percent(50.0),
padding: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
border: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
})
.with_children(|parent| {
parent.spawn((
ui::TitleBarBase::new(Color::VIOLET).bundle(),
ui::Title {
text: "Monologue".into(),
..default()
},
ui::Close {
target: parent.parent_entity(),
},
ui::Sorting(0),
));
let style = match &font.default {
Some(handle) => TextStyle {
color: Color::BLACK.into(),
font_size: 16.0,
font: handle.clone(),
..default()
},
None => TextStyle {
color: Color::BLACK.into(),
font_size: 16.0,
..default()
},
};
parent.spawn((
TextBundle::from_section(monologue.text.clone(), style),
handle.clone(),
));
});
});
});
}
});
}
pub fn ui_control_monologue(
events: Query<
(
&Interaction,
&ui::TargetAsset<Monologue>,
Option<&ui::Active>,
),
(With<Button>, Changed<Interaction>),
>,
mut writer: EventWriter<ControlMonologue>,
) {
events
.iter()
.filter_map(
|(interaction, ui::TargetAsset { handle }, active)| match interaction {
Interaction::Pressed => Some((handle, active)),
_ => None,
},
)
.for_each(|(handle, active)| match active {
Some(_) => writer.send(ControlMonologue::Hide),
None => writer.send(ControlMonologue::Show(handle.clone())),
});
}
// TODO: Sync Handle<Monologue> and TextStyle components to automagically generate and sync text
pub fn sync_monologue_font(
mut texts: Query<&mut Text, With<Handle<Monologue>>>,
font: Res<FontInfo>,
) {
if font.is_changed() || font.is_added() {
texts.iter_mut().for_each(|mut text| {
text.sections
.iter_mut()
.for_each(|section| match &font.default {
Some(handle) => section.style.font = handle.clone(),
None => section.style.font = Handle::default(),
});
});
}
}