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.
283 lines
9.7 KiB
Rust
283 lines
9.7 KiB
Rust
use std::time::Duration;
|
|
|
|
use bevy::prelude::*;
|
|
|
|
use monologue_trees::*;
|
|
|
|
const LOREM: [&str; 5] = [
|
|
"Ullam nostrum aut amet adipisci consequuntur quisquam nemo consequatur. Vel eum et ullam ullam aperiam earum voluptas consequuntur. Blanditiis earum voluptatem voluptas animi dolorum fuga aliquam ea.\n",
|
|
"Velit ratione consequatur modi. Dolores quo quisquam occaecati veniam maxime totam minus et. Laudantium unde optio vel. Et cumque voluptatum dolorem. Odit tempore dolores quibusdam aspernatur vitae labore occaecati. Omnis quia tempora tenetur repellat in.\n",
|
|
"Dolores perspiciatis tempore in consequuntur in minus autem. Voluptatem quas dignissimos quae ut necessitatibus illo ducimus. Quis fuga nisi non ut sunt velit.\n",
|
|
"Sit quia officia alias hic. Incidunt sed vitae optio cumque rerum corrupti perferendis enim. Adipisci necessitatibus illum vero placeat saepe aut et.\n",
|
|
"Velit perspiciatis doloribus consectetur. Doloremque ea non optio itaque. Voluptatem sint voluptatum minus.\n",
|
|
];
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
|
primary_window: Some(Window {
|
|
title: "Text Inspect".into(),
|
|
resolution: (640., 480.).into(),
|
|
..default()
|
|
}),
|
|
..default()
|
|
}))
|
|
.add_plugin(debug::DebugInfo)
|
|
.add_startup_system(load_fonts)
|
|
.add_startup_system(init_ui)
|
|
.add_system(manage_buttons)
|
|
.add_system(update_font)
|
|
.add_system(mouse_cursor)
|
|
.add_system(play)
|
|
.run();
|
|
}
|
|
|
|
#[derive(Resource)]
|
|
struct Fonts(Vec<Handle<Font>>);
|
|
|
|
#[derive(Component)]
|
|
struct Marker;
|
|
|
|
#[derive(Component)]
|
|
struct PreviewText;
|
|
|
|
#[derive(Component)]
|
|
struct ButtonShelf;
|
|
|
|
#[derive(Component)]
|
|
struct FontButton(Handle<Font>);
|
|
|
|
fn load_fonts(mut commands: Commands, server: Res<AssetServer>) {
|
|
let handles = server
|
|
.load_folder("fonts")
|
|
.expect("Load fonts folder")
|
|
.iter()
|
|
.map(|untyped| untyped.clone().typed::<Font>())
|
|
.collect();
|
|
commands.insert_resource(Fonts(handles));
|
|
}
|
|
|
|
fn init_ui(mut commands: Commands) {
|
|
commands.spawn(Camera2dBundle { ..default() });
|
|
|
|
commands
|
|
.spawn((
|
|
NodeBundle {
|
|
style: Style {
|
|
size: Size::all(Val::Percent(100.0)),
|
|
..default()
|
|
},
|
|
background_color: BackgroundColor(Color::BLACK),
|
|
..default()
|
|
},
|
|
Marker,
|
|
))
|
|
.with_children(|parent| {
|
|
parent.spawn((
|
|
NodeBundle {
|
|
style: Style {
|
|
flex_direction: FlexDirection::Column,
|
|
align_items: AlignItems::Center,
|
|
size: Size {
|
|
width: Val::Px(200.0),
|
|
height: Val::Undefined,
|
|
},
|
|
align_content: AlignContent::SpaceEvenly,
|
|
..default()
|
|
},
|
|
background_color: BackgroundColor(Color::BLACK),
|
|
..default()
|
|
},
|
|
Marker,
|
|
ButtonShelf,
|
|
));
|
|
|
|
parent.spawn((
|
|
TextBundle::from_sections(LOREM.iter().map(|§ion| TextSection {
|
|
value: section.into(),
|
|
style: TextStyle {
|
|
font_size: 20.0,
|
|
..default()
|
|
},
|
|
}))
|
|
.with_style(Style {
|
|
max_size: Size {
|
|
width: Val::Px(450.),
|
|
height: Val::Undefined,
|
|
},
|
|
..default()
|
|
}),
|
|
Marker,
|
|
PreviewText,
|
|
));
|
|
});
|
|
}
|
|
|
|
// Add/remove buttons from shelf with font name(s)
|
|
fn manage_buttons(
|
|
mut commands: Commands,
|
|
fonts: Res<Fonts>,
|
|
server: Res<AssetServer>,
|
|
query: Query<Entity, (With<Marker>, With<ButtonShelf>)>,
|
|
) {
|
|
if fonts.is_added() || fonts.is_changed() {
|
|
let root = query.get_single().expect("Fetching root UI node");
|
|
let mut root_cmd = commands.get_entity(root).expect("Root UI node commands");
|
|
|
|
root_cmd.clear_children();
|
|
root_cmd.with_children(|root| {
|
|
fonts.0.iter().for_each(|font| {
|
|
let handle_path = server
|
|
.get_handle_path(font.clone())
|
|
.expect("Get handle path");
|
|
let path = handle_path.path().to_str().expect("Convert path to str");
|
|
let fname = path
|
|
.split("/")
|
|
.last()
|
|
.expect("Extracting filename")
|
|
.strip_suffix(".otf")
|
|
.expect("Stripping prefix");
|
|
|
|
let style = TextStyle {
|
|
font: font.clone(),
|
|
color: Color::BLACK,
|
|
font_size: 16.0,
|
|
};
|
|
|
|
root.spawn((
|
|
ButtonBundle {
|
|
style: Style {
|
|
align_content: AlignContent::Center,
|
|
// size: Size {
|
|
// width: Val::Undefined,
|
|
// height: Val::Undefined,
|
|
// },
|
|
padding: UiRect::all(Val::Px(5.0)),
|
|
..default()
|
|
},
|
|
..default()
|
|
},
|
|
FontButton(font.clone()),
|
|
Marker,
|
|
))
|
|
.with_children(|parent| {
|
|
info!("Adding {} button", fname);
|
|
parent.spawn((TextBundle::from_section(fname, style), Marker));
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
fn update_font(
|
|
mut texts: Query<&mut Text, (With<Marker>, With<PreviewText>)>,
|
|
interaction: Query<(&Interaction, &FontButton), Changed<Interaction>>,
|
|
) {
|
|
for (i, f) in interaction.iter() {
|
|
match (i, f) {
|
|
(Interaction::Clicked, FontButton(font)) => {
|
|
texts.single_mut().sections.iter_mut().for_each(|section| {
|
|
section.style.font = font.clone();
|
|
});
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn mouse_cursor(
|
|
mut windows: Query<&mut Window>,
|
|
interactions: Query<&Interaction, (Changed<Interaction>, With<Button>)>,
|
|
) {
|
|
for interaction in interactions.iter() {
|
|
let mut window = windows.single_mut();
|
|
|
|
window.cursor.icon = match interaction {
|
|
Interaction::Hovered | Interaction::Clicked => CursorIcon::Hand,
|
|
Interaction::None => CursorIcon::Arrow,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn play(
|
|
mut texts: Query<&mut Text, (With<Marker>, With<PreviewText>)>,
|
|
interactions: Query<&Interaction, (Changed<Interaction>, With<FontButton>)>,
|
|
time: Res<Time>,
|
|
mut duration: Local<Duration>,
|
|
mut desired: Local<Vec<String>>,
|
|
) {
|
|
for interaction in interactions.iter() {
|
|
match interaction {
|
|
Interaction::Clicked => {
|
|
if *duration == Duration::ZERO {
|
|
// Get end result desired strings
|
|
*desired = texts
|
|
.single_mut()
|
|
.sections
|
|
.iter()
|
|
.map(|section| section.value.clone())
|
|
.collect();
|
|
// Clear current text
|
|
texts
|
|
.single_mut()
|
|
.sections
|
|
.iter_mut()
|
|
.for_each(|section| section.value = String::new());
|
|
}
|
|
*duration = Duration::from_secs(30);
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
// PERF: Why does this slow down immediatly?
|
|
if *duration > Duration::ZERO {
|
|
*duration = duration.saturating_sub(time.delta());
|
|
|
|
// info!("Delta: {:?}", time.delta());
|
|
|
|
let percentage = 1.0 - (duration.as_secs_f32() / 30.0);
|
|
|
|
let mut text = texts.single_mut();
|
|
|
|
let len_total = desired.iter().fold(0, |acc, curr| acc + curr.len());
|
|
let mut len_total = ((len_total as f32 * percentage) as usize).min(len_total as usize);
|
|
|
|
for (section, desired) in text.sections.iter_mut().zip(desired.iter()) {
|
|
// This section is empty, initialize with capacity
|
|
if len_total == 0 {
|
|
break;
|
|
|
|
// Total amount of remaining text is greater than this section
|
|
// Set this text block to the full section
|
|
} else if len_total >= desirted.len() {
|
|
// info!("len_total >= desired.len()");
|
|
len_total -= desired.len();
|
|
if section.value.len() != desired.len() {
|
|
// info!("value != desired");
|
|
section.value = desired.clone();
|
|
}
|
|
|
|
// Total remaining text is less than this section
|
|
// Set to a sub-string of text
|
|
} else if len_total < desired.len() {
|
|
// info!("len total < desired len");
|
|
// Difference between current value and desired length
|
|
let diff = desired
|
|
.split_at(section.value.len())
|
|
.1
|
|
.split_at(len_total - section.value.len())
|
|
.0
|
|
.into();
|
|
// info!("adding value {}", diff);
|
|
section.value.push_str(diff);
|
|
len_total = 0;
|
|
|
|
// Undefined behavior
|
|
} else {
|
|
info!("Unexpected text animation situation");
|
|
}
|
|
}
|
|
}
|
|
}
|