@ -1,47 +1,52 @@
// Monolog u e Trees Editor
// Monolog e Trees Editor
//
//
// Editor for creating Monologue Trees levels
// Editor for creating Monologue Trees levels
//
//
// BUGS:
// BUGS:
// * Cannot view scene when selected, WTF
// * Camera order ambiguity
// * Scene and Animation tabs are whacked
// * Multi-GLTF UX is bad.
// * Consider GLTF hierarchy (GLTF1 > Scene1a/Scene1b, GlTF2 > Scene2a/Scene2b, etc)
// * Easy despawn when de-selecting gltf
//
//
// TODO:
// TODO:
// * (medium) Spawn gltf scene
// * (medium) Load default scene when gltf selected
// * (medium) Set gltf to active/inactive
// * (medium) Play individual animation(s)
// * Only select one at a time.
// * (hard) Better Colorscheme
// * (medium) Visual errors for bad GLTFs
// * (medium) Spawn clicked scene
// * (medium) Play clicked animation
// * (easy) Play all animations
// * (easy) Play all animations
// * (medium) Add fonts similar to Audios based on inspect-fonts
// * (easy) Clear button to wipe spawned scene
// * (hard) Add Dialogs (requires text box UI, saving, loading).
// * (brutal) export level
// * (hard) import level
//
//
// Asset types:
// Asset types:
// * Audios (done)
// * Audios (done)
// * Loop individual
// * Loop individual (done)
// * Stop all
// * Gltfs (doing)
// * Gltfs (doing)
// * Scenes
// * Scenes
// * Animations
// * Animations
// * Play/Pause all
// * Play/Pause all
// * Fonts
// * Fonts (done)
// * Monologues
// * Monologues (done)
use bevy ::{
use bevy ::{
asset ::{ Asset , Assets } ,
asset ::{ Asset , Assets } ,
asset ::{ AssetLoader , LoadContext , LoadedAsset } ,
asset ::{ AssetLoader , LoadContext , LoadedAsset } ,
audio ::PlaybackMode ,
audio ::PlaybackMode ,
gltf ::Gltf ,
gltf ::Gltf ,
input ::{ keyboard ::KeyboardInput , ButtonState } ,
prelude ::* ,
prelude ::* ,
utils ::BoxedFuture ,
utils ::BoxedFuture ,
} ;
} ;
use monologue_trees ::{ debug ::* , ui } ;
use monologue_trees ::{ debug ::* , ui } ;
const WELCOME_MESSAGES : & ' static [ & ' static str ] = & [
"Welcome to the Monologue Trees editor!" ,
concat! (
"Import assets by dragging and dropping files into the editor\n" ,
"\n" ,
"Supported file types (for now):\n" ,
"* 3D: .gltf, .glb\n" ,
"* Audio: .ogg\n" ,
"* Font: .ttf, .otf\n" ,
"* Monologues: .monologue.txt" ,
) ,
] ;
fn main ( ) {
fn main ( ) {
App ::new ( )
App ::new ( )
. add_plugins ( (
. add_plugins ( (
@ -61,25 +66,41 @@ fn main() {
. init_asset_loader ::< MonologueLoader > ( )
. init_asset_loader ::< MonologueLoader > ( )
. add_event ::< CustomAssetEvent < Scene > > ( )
. add_event ::< CustomAssetEvent < Scene > > ( )
. add_event ::< CustomAssetEvent < AnimationClip > > ( )
. add_event ::< CustomAssetEvent < AnimationClip > > ( )
. add_systems ( Startup , ( initialize_ui , welcome_message ) )
. add_systems ( Startup , ( initialize_ui , init_texts_ui , welcome_message ) )
. add_systems (
Update ,
(
manage_gltf_animation_ui ,
init_animations_ui ,
animations_ui ,
play_all_animations ,
play_animation ,
) ,
)
. add_systems (
Update ,
( manage_gltf_scene_ui , scenes_ui , control_active_scenes ) ,
)
. add_systems (
Update ,
(
cameras_ui ,
manage_active_camera ,
control_active_camera ,
fallback_camera ,
) ,
)
. add_systems ( Update , ( audio_ui , play_audio ) )
. add_systems (
. add_systems (
Update ,
Update ,
(
(
import_files ,
import_files ,
audio_ui ,
gltf_ui ,
gltf_ui ,
fonts_ui ,
fonts_ui ,
texts_ui ,
texts_ui ,
cameras_ui ,
manage_active_gltf ,
manage_active_gltf ,
manage_gltf_scene_ui ,
show_preview_text ,
manage_gltf_animation_ui ,
sync_monologue_font ,
scenes_ui ,
animations_ui ,
spawn_scenes ,
manage_camera ,
play_animation ,
play_audio ,
) ,
) ,
)
)
. run ( ) ;
. run ( ) ;
@ -106,13 +127,14 @@ pub struct EditorCamera;
fn initialize_ui ( mut commands : Commands ) {
fn initialize_ui ( mut commands : Commands ) {
// Empty entity for populating the level being edited
// Empty entity for populating the level being edited
commands . spawn ( ( Transform Bundle { .. default ( ) } , LevelRoot ) ) ;
commands . spawn ( ( Spatial Bundle { .. default ( ) } , LevelRoot ) ) ;
commands . spawn ( (
commands . spawn ( (
Camera 3 dBundle { .. default ( ) } ,
Camera 2 dBundle { .. default ( ) } ,
UiCameraConfig { show_ui : true } ,
UiCameraConfig { show_ui : true } ,
Name ::new ( "Editor Camera" ) ,
Name ::new ( "Editor Camera" ) ,
EditorCamera ,
EditorCamera ,
ui ::Active ,
) ) ;
) ) ;
let base_style = Style {
let base_style = Style {
@ -184,6 +206,11 @@ fn initialize_ui(mut commands: Commands) {
parent ,
parent ,
ui ::Select ::Single ,
ui ::Select ::Single ,
) ) ;
) ) ;
content_containers . push ( spawn_tab_container ::< MonologueWidget > (
"Monologue" ,
parent ,
ui ::Select ::Single ,
) ) ;
content_containers . push ( spawn_tab_container ::< AudioWidget > (
content_containers . push ( spawn_tab_container ::< AudioWidget > (
"Audio" ,
"Audio" ,
parent ,
parent ,
@ -197,7 +224,7 @@ fn initialize_ui(mut commands: Commands) {
content_containers . push ( spawn_tab_container ::< SceneWidget > (
content_containers . push ( spawn_tab_container ::< SceneWidget > (
"Scene" ,
"Scene" ,
parent ,
parent ,
ui ::Select ::Single ,
ui ::Select ::Multi ,
) ) ;
) ) ;
content_containers . push ( spawn_tab_container ::< AnimationWidget > (
content_containers . push ( spawn_tab_container ::< AnimationWidget > (
"Animation" ,
"Animation" ,
@ -242,7 +269,10 @@ fn initialize_ui(mut commands: Commands) {
content_containers . iter ( ) . for_each ( | ( name , target ) | {
content_containers . iter ( ) . for_each ( | ( name , target ) | {
parent . spawn ( (
parent . spawn ( (
b . clone ( ) ,
b . clone ( ) ,
ui ::Title { text : name . clone ( ) } ,
ui ::Title {
text : name . clone ( ) ,
.. default ( )
} ,
ui ::Collapse { target : * target } ,
ui ::Collapse { target : * target } ,
) ) ;
) ) ;
} ) ;
} ) ;
@ -250,24 +280,10 @@ fn initialize_ui(mut commands: Commands) {
} )
} )
. id ( ) ;
. id ( ) ;
parent . spawn ( (
parent . spawn ( (
NodeBundle {
ui ::TitleBarBase ::new ( Color ::WHITE ) . bundle ( ) ,
style : Style {
border : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
margin : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
padding : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
flex_direction : FlexDirection ::Row ,
overflow : Overflow ::clip ( ) ,
align_items : AlignItems ::Center ,
align_content : AlignContent ::Center ,
justify_content : JustifyContent ::SpaceBetween ,
.. default ( )
} ,
background_color : Color ::ALICE_BLUE . into ( ) ,
border_color : Color ::BLACK . into ( ) ,
.. default ( )
} ,
ui ::Title {
ui ::Title {
text : "Assets" . into ( ) ,
text : "Assets" . into ( ) ,
.. default ( )
} ,
} ,
ui ::Minimize { target : container } ,
ui ::Minimize { target : container } ,
ui ::Sorting ( 0 ) ,
ui ::Sorting ( 0 ) ,
@ -276,21 +292,9 @@ fn initialize_ui(mut commands: Commands) {
}
}
fn welcome_message ( mut writer : EventWriter < ui ::Alert > ) {
fn welcome_message ( mut writer : EventWriter < ui ::Alert > ) {
writer . send ( ui ::Alert ::Info (
WELCOME_MESSAGES
"Welcome to the Monologue Trees editor!" . into ( ) ,
. iter ( )
) ) ;
. for_each ( | & msg | writer . send ( ui ::Alert ::Info ( msg . into ( ) ) ) )
writer . send ( ui ::Alert ::Info (
[
"Import assets by dragging and dropping files into the editor" ,
"" ,
"Supported file types (for now):" ,
"* 3D: .gltf, .glb" ,
"* Audio: .ogg" ,
"* Font: .ttf, .otf" ,
]
. join ( "\n" )
. into ( ) ,
) ) ;
}
}
fn spawn_tab_container < T : Default + Component > (
fn spawn_tab_container < T : Default + Component > (
@ -361,16 +365,7 @@ mod audio {
current : Query < ( Entity , & ui ::TargetAsset < AudioSource > ) > ,
current : Query < ( Entity , & ui ::TargetAsset < AudioSource > ) > ,
server : Res < AssetServer > ,
server : Res < AssetServer > ,
) {
) {
events
events . iter ( ) . for_each ( | event | match event {
. iter ( )
. filter ( | & event | match event {
AssetEvent ::Created { handle }
| AssetEvent ::Removed { handle }
| AssetEvent ::Modified { handle } = > {
has_extensions ( & server , handle . clone ( ) , & [ "ogg" ] )
}
} )
. for_each ( | event | match event {
AssetEvent ::Created { handle } = > {
AssetEvent ::Created { handle } = > {
info ! ( "Asset created! {:?}" , event ) ;
info ! ( "Asset created! {:?}" , event ) ;
let id = create_asset_button (
let id = create_asset_button (
@ -380,6 +375,7 @@ mod audio {
handle : handle . clone ( ) ,
handle : handle . clone ( ) ,
} ,
} ,
get_asset_name ( & server , handle . clone ( ) ) ,
get_asset_name ( & server , handle . clone ( ) ) ,
None ,
) ;
) ;
commands . entity ( id ) . insert ( AudioSourceBundle {
commands . entity ( id ) . insert ( AudioSourceBundle {
source : handle . clone ( ) ,
source : handle . clone ( ) ,
@ -416,6 +412,7 @@ mod audio {
handle : handle . clone ( ) ,
handle : handle . clone ( ) ,
} ,
} ,
get_asset_name ( & server , handle . clone ( ) ) ,
get_asset_name ( & server , handle . clone ( ) ) ,
None ,
) ;
) ;
commands . entity ( id ) . insert ( AudioSourceBundle {
commands . entity ( id ) . insert ( AudioSourceBundle {
source : handle . clone ( ) ,
source : handle . clone ( ) ,
@ -473,6 +470,7 @@ mod assets {
commands : & mut Commands ,
commands : & mut Commands ,
target : ui ::TargetAsset < A > ,
target : ui ::TargetAsset < A > ,
name : String ,
name : String ,
font : Option < Handle < Font > > ,
) -> Entity {
) -> Entity {
commands
commands
. spawn ( (
. spawn ( (
@ -487,7 +485,10 @@ mod assets {
border_color : Color ::BLACK . into ( ) ,
border_color : Color ::BLACK . into ( ) ,
.. default ( )
.. default ( )
} ,
} ,
ui ::Title { text : name } ,
ui ::Title {
text : name ,
font : font . clone ( ) ,
} ,
) )
) )
. set_parent ( root . single ( ) )
. set_parent ( root . single ( ) )
. id ( )
. id ( )
@ -512,7 +513,10 @@ mod assets {
border_color : Color ::BLACK . into ( ) ,
border_color : Color ::BLACK . into ( ) ,
.. default ( )
.. default ( )
} ,
} ,
ui ::Title { text : name } ,
ui ::Title {
text : name ,
.. default ( )
} ,
) )
) )
. set_parent ( root . single ( ) )
. set_parent ( root . single ( ) )
. id ( )
. id ( )
@ -534,6 +538,22 @@ mod assets {
}
}
}
}
pub fn destroy_entity_button (
current : & Query < ( Entity , & ui ::TargetEntity ) > ,
commands : & mut Commands ,
target : & ui ::TargetEntity ,
) {
if let Some ( entity ) = current . iter ( ) . find_map ( | ( entity , this ) | {
if this . entity = = target . entity {
Some ( entity )
} else {
None
}
} ) {
commands . entity ( entity ) . despawn_recursive ( ) ;
}
}
pub fn has_extensions < T : Asset > (
pub fn has_extensions < T : Asset > (
server : & AssetServer ,
server : & AssetServer ,
handle : Handle < T > ,
handle : Handle < T > ,
@ -566,16 +586,7 @@ mod gltf {
current : Query < ( Entity , & ui ::TargetAsset < Gltf > ) > ,
current : Query < ( Entity , & ui ::TargetAsset < Gltf > ) > ,
server : Res < AssetServer > ,
server : Res < AssetServer > ,
) {
) {
events
events . iter ( ) . for_each ( | event | match event {
. iter ( )
. filter ( | & event | match event {
AssetEvent ::Created { handle }
| AssetEvent ::Removed { handle }
| AssetEvent ::Modified { handle } = > {
has_extensions ( & server , handle . clone ( ) , & [ "gltf" , "glb" ] )
}
} )
. for_each ( | event | match event {
AssetEvent ::Created { handle } = > {
AssetEvent ::Created { handle } = > {
info ! ( "Asset created! {:?}" , event ) ;
info ! ( "Asset created! {:?}" , event ) ;
create_asset_button (
create_asset_button (
@ -585,6 +596,7 @@ mod gltf {
handle : handle . clone ( ) ,
handle : handle . clone ( ) ,
} ,
} ,
get_asset_name ( & server , handle . clone ( ) ) ,
get_asset_name ( & server , handle . clone ( ) ) ,
None ,
) ;
) ;
}
}
AssetEvent ::Removed { handle } = > {
AssetEvent ::Removed { handle } = > {
@ -613,6 +625,7 @@ mod gltf {
handle : handle . clone ( ) ,
handle : handle . clone ( ) ,
} ,
} ,
get_asset_name ( & server , handle . clone ( ) ) ,
get_asset_name ( & server , handle . clone ( ) ) ,
None ,
) ;
) ;
}
}
} ) ;
} ) ;
@ -751,18 +764,25 @@ mod scenes {
current : Query < ( Entity , & ui ::TargetAsset < Scene > ) > ,
current : Query < ( Entity , & ui ::TargetAsset < Scene > ) > ,
) {
) {
events . iter ( ) . for_each ( | event | {
events . iter ( ) . for_each ( | event | {
let empty = current . iter ( ) . len ( ) = = 0 ;
match event {
match event {
CustomAssetEvent ::Add { name , handle } = > {
CustomAssetEvent ::Add { name , handle } = > {
info ! ( "Asset loading! {:?}({:?})" , name , handle ) ;
info ! ( "Asset loading! {:?}({:?})" , name , handle ) ;
// Spawn new tree
// Spawn new tree
create_asset_button (
let e = create_asset_button (
& widget ,
& widget ,
& mut commands ,
& mut commands ,
ui ::TargetAsset {
ui ::TargetAsset {
handle : handle . clone ( ) ,
handle : handle . clone ( ) ,
} ,
} ,
name . clone ( ) ,
name . clone ( ) ,
None ,
) ;
) ;
// If this is the first scene being added, set it as active
if empty {
commands . entity ( e ) . insert ( ui ::Active ) ;
}
}
}
CustomAssetEvent ::Remove { handle } = > {
CustomAssetEvent ::Remove { handle } = > {
destroy_asset_button (
destroy_asset_button (
@ -780,25 +800,42 @@ mod scenes {
} ) ;
} ) ;
}
}
pub fn spawn _scenes(
pub fn control_active _scenes(
events: Query <
added: Query < Entity , ( With < Button > , Added < ui ::Active > ) > ,
( & Interaction , & ui ::TargetAsset < Scene > ) ,
mut removed : RemovedComponents < ui ::Active > ,
( With < Button > , Changed < Interaction > ) ,
scene_refs : Query < & ui ::TargetAsset < Scene > > ,
> ,
scenes : Query < ( Entity , & Handle < Scene > ) > ,
level_root : Query < Entity , With < LevelRoot > > ,
level_root : Query < Entity , With < LevelRoot > > ,
mut commands : Commands ,
mut commands : Commands ,
) {
) {
events
// A scene button was marked inactive
. iter ( )
removed . iter ( ) . for_each ( | entity | {
. filter ( | ( & interaction , _ ) | interaction = = Interaction ::Pressed )
// Get the handle associated with that button
. for_each ( | ( _ , ui ::TargetAsset { handle } ) | {
if let Ok ( ui ::TargetAsset { handle } ) = scene_refs . get ( entity ) {
info ! ( "Spawning {:?}" , handle ) ;
if let Some ( entity ) = scenes . iter ( ) . find_map ( | ( entity , this_handle ) | {
if this_handle = = handle {
Some ( entity )
} else {
None
}
} ) {
commands . entity ( entity ) . despawn_recursive ( ) ;
}
}
} ) ;
added . iter ( ) . for_each ( | entity | {
if let Ok ( ui ::TargetAsset { handle } ) = scene_refs . get ( entity ) {
info ! ( "Spawning Scene {:?}" , handle ) ;
commands
commands
. entity ( level_root . single ( ) )
. entity ( level_root . single ( ) )
. with_children ( | parent | {
. with_children ( | parent | {
parent . spawn ( ( handle . clone ( ) , TransformBundle { .. default ( ) } ) ) ;
parent . spawn ( SceneBundle {
scene : handle . clone ( ) ,
.. default ( )
} ) ;
} ) ;
}
} ) ;
} ) ;
} )
}
}
}
}
@ -811,6 +848,36 @@ mod animations {
#[ derive(Debug, Component, Default) ]
#[ derive(Debug, Component, Default) ]
pub struct AnimationWidget ;
pub struct AnimationWidget ;
#[ derive(Debug, Component) ]
pub struct AnimationPlayAll ;
pub fn init_animations_ui (
events : Query < Entity , Added < AnimationWidget > > ,
mut commands : Commands ,
) {
events . iter ( ) . for_each ( | entity | {
commands . entity ( entity ) . with_children ( | parent | {
parent . spawn ( (
AnimationPlayAll ,
ButtonBundle {
style : Style {
border : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
margin : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
padding : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
.. default ( )
} ,
border_color : Color ::BLACK . into ( ) ,
.. default ( )
} ,
ui ::Title {
text : "Play All" . into ( ) ,
.. default ( )
} ,
) ) ;
} ) ;
} )
}
pub fn animations_ui (
pub fn animations_ui (
mut events : EventReader < CustomAssetEvent < AnimationClip > > ,
mut events : EventReader < CustomAssetEvent < AnimationClip > > ,
mut commands : Commands ,
mut commands : Commands ,
@ -829,6 +896,7 @@ mod animations {
handle : handle . clone ( ) ,
handle : handle . clone ( ) ,
} ,
} ,
name . clone ( ) ,
name . clone ( ) ,
None ,
) ;
) ;
}
}
CustomAssetEvent ::Remove { handle } = > {
CustomAssetEvent ::Remove { handle } = > {
@ -847,25 +915,62 @@ mod animations {
} ) ;
} ) ;
}
}
pub fn play_all_animations (
start : Query < Entity , ( With < Button > , Added < ui ::Active > ) > ,
mut stop : RemovedComponents < ui ::Active > ,
play_all_btn : Query < Entity , With < AnimationPlayAll > > ,
clip_btns : Query < Entity , With < ui ::TargetAsset < AnimationClip > > > ,
mut commands : Commands ,
) {
stop . iter ( )
. filter ( | & entity | play_all_btn . contains ( entity ) )
. for_each ( | _ | {
clip_btns . iter ( ) . for_each ( | entity | {
commands . entity ( entity ) . remove ::< ui ::Active > ( ) ;
} )
} ) ;
start
. iter ( )
. filter ( | & entity | play_all_btn . contains ( entity ) )
. for_each ( | _ | {
clip_btns . iter ( ) . for_each ( | entity | {
commands . entity ( entity ) . insert ( ui ::Active ) ;
} )
} ) ;
}
pub fn play_animation (
pub fn play_animation (
events : Query <
start : Query < Entity , ( With < Button > , Added < ui ::Active > ) > ,
( & Interaction , & ui ::TargetAsset < AnimationClip > ) ,
mut stop : RemovedComponents < ui ::Active > ,
( With < Button > , Changed < Interaction > ) ,
clip_refs : Query < & ui ::TargetAsset < AnimationClip > > ,
> ,
mut targets : Query < ( & mut AnimationPlayer , & Name ) , With < Transform > > ,
mut targets : Query < ( & mut AnimationPlayer , & Name ) , With < Transform > > ,
clips : Res < Assets < AnimationClip > > ,
clips : Res < Assets < AnimationClip > > ,
) {
) {
events
stop . iter ( ) . for_each ( | entity | {
. iter ( )
if let Ok ( ui ::TargetAsset { handle } ) = clip_refs . get ( entity ) {
. filter ( | ( & interaction , _ ) | interaction = = Interaction ::Pressed )
let clip = clips . get ( & handle ) . expect ( "Load animation clip" ) ;
. for_each ( | ( _ , ui ::TargetAsset { handle } ) | {
let clip = clips . get ( handle ) . expect ( "Load animation clip" ) ;
targets
targets
. iter_mut ( )
. iter_mut ( )
. filter ( | ( _ , name ) | clip . compatible_with ( name ) )
. filter ( | ( _ , name ) | clip . compatible_with ( name ) )
. for_each ( | ( mut player , _ ) | {
. for_each ( | ( mut player , _ ) | {
player . pause ( ) ;
} )
}
} ) ;
start . iter ( ) . for_each ( | entity | {
if let Ok ( ui ::TargetAsset { handle } ) = clip_refs . get ( entity ) {
let clip = clips . get ( & handle ) . expect ( "Load animation clip" ) ;
targets
. iter_mut ( )
. filter ( | ( _ , name ) | clip . compatible_with ( name ) )
. for_each ( | ( mut player , _ ) | {
if player . is_paused ( ) {
player . resume ( ) ;
} else {
player . play ( handle . clone ( ) ) . repeat ( ) ;
player . play ( handle . clone ( ) ) . repeat ( ) ;
}
} )
} )
}
} ) ;
} ) ;
}
}
}
}
@ -877,7 +982,6 @@ mod fonts {
#[ derive(Debug, Component, Default) ]
#[ derive(Debug, Component, Default) ]
pub struct FontWidget ;
pub struct FontWidget ;
// TODO: Make each button have the font
pub fn fonts_ui (
pub fn fonts_ui (
mut events : EventReader < AssetEvent < Font > > ,
mut events : EventReader < AssetEvent < Font > > ,
mut commands : Commands ,
mut commands : Commands ,
@ -885,16 +989,7 @@ mod fonts {
current : Query < ( Entity , & ui ::TargetAsset < Font > ) > ,
current : Query < ( Entity , & ui ::TargetAsset < Font > ) > ,
server : Res < AssetServer > ,
server : Res < AssetServer > ,
) {
) {
events
events . iter ( ) . for_each ( | event | match event {
. iter ( )
. filter ( | & event | match event {
AssetEvent ::Created { handle }
| AssetEvent ::Removed { handle }
| AssetEvent ::Modified { handle } = > {
has_extensions ( & server , handle . clone ( ) , & [ "ttf" , "otf" ] )
}
} )
. for_each ( | event | match event {
AssetEvent ::Created { handle } = > {
AssetEvent ::Created { handle } = > {
info ! ( "Asset created! {:?}" , event ) ;
info ! ( "Asset created! {:?}" , event ) ;
create_asset_button (
create_asset_button (
@ -904,6 +999,7 @@ mod fonts {
handle : handle . clone ( ) ,
handle : handle . clone ( ) ,
} ,
} ,
get_asset_name ( & server , handle . clone ( ) ) ,
get_asset_name ( & server , handle . clone ( ) ) ,
Some ( handle . clone ( ) ) ,
) ;
) ;
}
}
AssetEvent ::Removed { handle } = > {
AssetEvent ::Removed { handle } = > {
@ -932,6 +1028,7 @@ mod fonts {
handle : handle . clone ( ) ,
handle : handle . clone ( ) ,
} ,
} ,
get_asset_name ( & server , handle . clone ( ) ) ,
get_asset_name ( & server , handle . clone ( ) ) ,
Some ( handle . clone ( ) ) ,
) ;
) ;
}
}
} ) ;
} ) ;
@ -941,7 +1038,10 @@ mod fonts {
use monologues ::* ;
use monologues ::* ;
mod monologues {
mod monologues {
use super ::* ;
use super ::* ;
use bevy ::reflect ::{ TypePath , TypeUuid } ;
use bevy ::{
reflect ::{ TypePath , TypeUuid } ,
ui ::FocusPolicy ,
} ;
use serde ::Deserialize ;
use serde ::Deserialize ;
#[ derive(Debug, Component, Default) ]
#[ derive(Debug, Component, Default) ]
@ -953,6 +1053,12 @@ mod monologues {
text : String ,
text : String ,
}
}
#[ derive(Debug, Component) ]
pub struct MonologueModal ;
#[ derive(Debug, Component) ]
pub struct MonologueContainer ;
#[ derive(Default) ]
#[ derive(Default) ]
pub struct MonologueLoader ;
pub struct MonologueLoader ;
@ -963,29 +1069,160 @@ mod monologues {
load_context : & ' a mut LoadContext ,
load_context : & ' a mut LoadContext ,
) -> BoxedFuture < ' a , Result < ( ) , bevy ::asset ::Error > > {
) -> BoxedFuture < ' a , Result < ( ) , bevy ::asset ::Error > > {
Box ::pin ( async move {
Box ::pin ( async move {
load_context . set_default_asset ( LoadedAsset ::new (
let asset = Monologue {
String ::from_utf8 ( bytes . to_vec ( ) ) . expect ( "Convert bytes to String" ) ,
text : String ::from_utf8 ( bytes . to_vec ( ) ) ? ,
) ) ;
} ;
load_context . set_default_asset ( LoadedAsset ::new ( asset ) ) ;
Ok ( ( ) )
Ok ( ( ) )
} )
} )
}
}
fn extensions ( & self ) -> & [ & str ] {
fn extensions ( & self ) -> & [ & str ] {
& [ " . monologue.txt"]
& [ " 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 ,
) ) ;
}
// TODO: Load .txt files for monologues
// TODO: Load .txt files for monologues
pub fn texts_ui (
pub fn texts_ui (
mut events : EventReader < AssetEvent < Monologue > > ,
mut events : EventReader < AssetEvent < Monologue > > ,
mut _commands : Commands ,
mut commands : Commands ,
_widget : Query < Entity , With < MonologueWidget > > ,
widget : Query < Entity , With < MonologueWidget > > ,
_current : Query < ( Entity , & ui ::TargetAsset < Monologue > ) > ,
current : Query < ( Entity , & ui ::TargetAsset < Monologue > ) > ,
_server : Res < AssetServer > ,
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 ,
) ;
}
} ) ;
}
// TODO(BUG): Better handle hide/close monologue
pub fn show_preview_text (
added : Query < Entity , ( With < Button > , Added < ui ::Active > ) > ,
mut removed : RemovedComponents < ui ::Active > ,
monologue_handles : Query < & ui ::TargetAsset < Monologue > > ,
monologues : Res < Assets < Monologue > > ,
container : Query < Entity , With < MonologueContainer > > ,
mut commands : Commands ,
) {
) {
events . iter ( ) . for_each ( | _event | {
added
info ! ( "Loading monologue" ) ;
. iter ( )
. filter_map ( | entity | monologue_handles . get ( entity ) . ok ( ) )
. for_each ( | ui ::TargetAsset { handle } | {
let monologue = monologues . get ( handle ) . expect ( "Preview loaded 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 ) ,
) ) ;
parent . spawn ( (
TextBundle ::from_section (
monologue . text . clone ( ) ,
TextStyle {
color : Color ::BLACK . into ( ) ,
font_size : 16.0 ,
.. default ( )
} ,
) ,
handle . clone ( ) ,
) ) ;
} ) ;
} ) ;
} ) ;
}
// TODO: Sync Handle<Monologue> and TextStyle components to automagically generate and sync text
pub fn sync_monologue_font (
events : Query < & ui ::TargetAsset < Font > , Added < ui ::Active > > ,
mut texts : Query < & mut Text , With < Handle < Monologue > > > ,
) {
events . iter ( ) . for_each ( | ui ::TargetAsset { handle } | {
texts . iter_mut ( ) . for_each ( | mut text | {
text . sections . iter_mut ( ) . for_each ( | section | {
section . style . font = handle . clone ( ) ;
} ) ;
} ) ;
} ) ;
}
}
}
}
@ -998,33 +1235,90 @@ mod cameras {
// TODO: Despawn camera button when camera removed
// TODO: Despawn camera button when camera removed
pub fn cameras_ui (
pub fn cameras_ui (
mut events : Query < ( Entity , & mut Camera , & Name ) , ( Added < Camera > , Without < EditorCamera > ) > ,
mut added : Query < ( Entity , & mut Camera , & Name ) , ( Added < Camera > , Without < EditorCamera > ) > ,
mut removed : RemovedComponents < Camera > ,
widget : Query < Entity , With < CameraWidget > > ,
widget : Query < Entity , With < CameraWidget > > ,
current : Query < ( Entity , & ui ::TargetEntity ) > ,
mut commands : Commands ,
mut commands : Commands ,
) {
) {
events . iter_mut ( ) . for_each ( | ( entity , mut camera , name ) | {
removed . iter ( ) . for_each ( | entity | {
info ! ( "Destroy button for {:?}" , entity ) ;
destroy_entity_button ( & current , & mut commands , & ui ::TargetEntity { entity } ) ;
} ) ;
added . iter_mut ( ) . for_each ( | ( entity , mut camera , name ) | {
let empty = current . iter ( ) . len ( ) = = 0 ;
info ! ( "Camera added {:?} {:?}" , entity , name ) ;
info ! ( "Camera added {:?} {:?}" , entity , name ) ;
create_entity_button (
let e = create_entity_button (
& widget ,
& widget ,
& mut commands ,
& mut commands ,
ui ::TargetEntity { entity } ,
ui ::TargetEntity { entity } ,
name . as_str ( ) . into ( ) ,
name . as_str ( ) . into ( ) ,
) ;
) ;
if empty {
commands . entity ( e ) . insert ( ui ::Active ) ;
} else {
camera . is_active = false ;
camera . is_active = false ;
} )
}
} ) ;
}
}
pub fn manage_camera (
/// Set the camera active component based on button clicks
pub fn manage_active_camera (
events : Query < ( & Interaction , & ui ::TargetEntity ) , Changed < Interaction > > ,
events : Query < ( & Interaction , & ui ::TargetEntity ) , Changed < Interaction > > ,
mut cameras : Query < ( Entity , & mut Camera ) > ,
cameras : Query < Entity , With < Camera > > ,
mut commands : Commands ,
) {
) {
events
events
. iter ( )
. iter ( )
. filter ( | ( & interaction , _ ) | interaction = = Interaction ::Pressed )
. filter ( | ( & interaction , _ ) | interaction = = Interaction ::Pressed )
. for_each ( | ( _ , ui ::TargetEntity { entity } ) | {
. for_each ( | ( _ , ui ::TargetEntity { entity } ) | {
cameras . iter_mut ( ) . for_each ( | ( this_entity , mut camera ) | {
cameras . iter ( ) . for_each ( | this_entity | {
camera . is_active = this_entity = = * entity ;
if this_entity = = * entity {
info ! ( "Marking {:?} as active camera" , entity ) ;
commands . entity ( this_entity ) . insert ( ui ::Active ) ;
} else {
info ! ( "Marking {:?} as inactive camera" , entity ) ;
commands . entity ( this_entity ) . remove ::< ui ::Active > ( ) ;
}
} ) ;
} ) ;
} ) ;
} ) ;
}
}
/// Set the active camera based on the Active marker component
pub fn control_active_camera (
added : Query < Entity , ( Added < ui ::Active > , With < Camera > ) > ,
mut removed : RemovedComponents < ui ::Active > ,
mut cameras : Query < & mut Camera > ,
) {
removed . iter ( ) . for_each ( | entity | {
info ! ( "Setting {:?} to inactive camera" , entity ) ;
if let Ok ( mut camera ) = cameras . get_mut ( entity ) {
camera . is_active = false ;
}
} ) ;
added . iter ( ) . for_each ( | entity | {
info ! ( "Setting {:?} to active camera" , entity ) ;
if let Ok ( mut camera ) = cameras . get_mut ( entity ) {
camera . is_active = true ;
}
} ) ;
}
// In the event that an active camera is despawned, fall back to the editor camera
pub fn fallback_camera (
modified : Query < Entity , ( Changed < Camera > , Without < EditorCamera > ) > ,
mut removed : RemovedComponents < Camera > ,
other_cameras : Query < & Camera , Without < EditorCamera > > ,
mut editor_camera : Query < & mut Camera , With < EditorCamera > > ,
) {
// Any time a camera is modified
modified . iter ( ) . chain ( removed . iter ( ) ) . for_each ( | _ | {
// If no other cameras are active
if ! other_cameras . iter ( ) . any ( | camera | camera . is_active ) {
// Make the editor camera active
editor_camera . single_mut ( ) . is_active = true ;
}
} )
}
}
}