Skip to content

Commit

Permalink
Add tag to conveniently insert marker components (#4)
Browse files Browse the repository at this point in the history
* Add builder pattern to construct settings, make fonts optional

* Add way to register marker components

* Apply marker tags to text sections

* Use new m tag for dynamic example

* Improve documentation
  • Loading branch information
TimJentzsch authored Jul 22, 2024
1 parent ca775dc commit f05e5a0
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 52 deletions.
47 changes: 20 additions & 27 deletions examples/dynamic.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
//! This example demonstrates how parts of the text can be efficiently updated dynamically.
//! To do this, we use the special `[m]` tag, which allows us to assign a marker component to the contained text.
//! We can then query for the marker component as usual and apply our edits.

use bevy::prelude::*;
use bevy_mod_bbcode::{Bbcode, BbcodeBundle, BbcodePlugin, BbcodeSettings};
use bevy_mod_bbcode::{BbcodeBundle, BbcodePlugin, BbcodeSettings};

#[derive(Debug, Component)]
struct Marker;
#[derive(Component, Clone)]
struct TimeMarker;

fn main() {
App::new()
Expand All @@ -15,31 +19,20 @@ fn main() {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2dBundle::default());

commands.spawn((
BbcodeBundle::from_content(
// The text will be added later
"",
BbcodeSettings {
regular_font: asset_server.load("fonts/FiraSans-Regular.ttf"),
bold_font: asset_server.load("fonts/FiraSans-Bold.ttf"),
italic_font: asset_server.load("fonts/FiraSans-Italic.ttf"),

font_size: 40.,
color: Color::WHITE,
},
),
Marker,
commands.spawn(BbcodeBundle::from_content(
"Time passed: [m=time]0.0[/m] s",
BbcodeSettings::new(40., Color::WHITE)
.with_regular_font(asset_server.load("fonts/FiraSans-Regular.ttf"))
.with_bold_font(asset_server.load("fonts/FiraSans-Bold.ttf"))
.with_italic_font(asset_server.load("fonts/FiraSans-Italic.ttf"))
// Register the marker component for the `m=time` tag
.with_marker("time", TimeMarker),
));
}

fn update(time: Res<Time>, mut query: Query<(&mut Bbcode, &mut BbcodeSettings), With<Marker>>) {
let (mut bbcode, mut settings) = query.single_mut();

// Dynamically change the text to the elapsed time
bbcode.content = format!(
"Time passed: [b][c=#ffffff]{:.2}[/c][/b]",
time.elapsed_seconds()
);
// Dynamically change the default text color
settings.color = Hsla::hsl((time.elapsed_seconds() * 20.) % 360., 1., 0.7).into();
fn update(time: Res<Time>, mut query: Query<&mut Text, With<TimeMarker>>) {
for mut text in query.iter_mut() {
// We can directly query for the `Text` component and update it, without the BBCode being parsed again
text.sections[0].value = format!("{:.0}", time.elapsed_seconds());
}
}
12 changes: 4 additions & 8 deletions examples/static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {

commands.spawn(BbcodeBundle::from_content(
"test [b]bold[/b] with [i]italic[/i] and [c=#ff00ff]color[/c]",
BbcodeSettings {
regular_font: asset_server.load("fonts/FiraSans-Regular.ttf"),
bold_font: asset_server.load("fonts/FiraSans-Bold.ttf"),
italic_font: asset_server.load("fonts/FiraSans-Italic.ttf"),

font_size: 40.,
color: Color::WHITE,
},
BbcodeSettings::new(40., Color::WHITE)
.with_regular_font(asset_server.load("fonts/FiraSans-Regular.ttf"))
.with_bold_font(asset_server.load("fonts/FiraSans-Bold.ttf"))
.with_italic_font(asset_server.load("fonts/FiraSans-Italic.ttf")),
));
}
69 changes: 63 additions & 6 deletions src/bevy/bbcode.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use bevy::{prelude::*, ui::FocusPolicy};
use std::sync::Arc;

use bevy::{ecs::system::EntityCommands, prelude::*, ui::FocusPolicy, utils::HashMap};

#[derive(Debug, Clone, Component, Default)]

Expand All @@ -7,14 +9,69 @@ pub struct Bbcode {
pub content: String,
}

#[derive(Debug, Clone, Component)]
pub struct BbcodeSettings {
pub regular_font: Handle<Font>,
pub bold_font: Handle<Font>,
pub italic_font: Handle<Font>,
type ModifierFn = dyn Fn(&mut EntityCommands) + Send + Sync;

#[derive(Clone, Default)]
pub(crate) struct Modifiers {
pub(crate) modifier_map: HashMap<String, Arc<ModifierFn>>,
}

#[derive(Clone, Component)]
pub struct BbcodeSettings {
pub font_size: f32,
pub color: Color,

pub(crate) regular_font: Option<Handle<Font>>,
pub(crate) bold_font: Option<Handle<Font>>,
pub(crate) italic_font: Option<Handle<Font>>,

pub(crate) modifiers: Modifiers,
}

impl BbcodeSettings {
pub fn new(font_size: f32, color: Color) -> Self {
Self {
font_size,
color,
regular_font: None,
bold_font: None,
italic_font: None,
modifiers: Default::default(),
}
}

/// Add a font to use for regular text.
pub fn with_regular_font(mut self, handle: Handle<Font>) -> Self {
self.regular_font = Some(handle);
self
}

/// Add a font to use for bold text.
pub fn with_bold_font(mut self, handle: Handle<Font>) -> Self {
self.bold_font = Some(handle);
self
}

/// Add a font to use for italic text.
pub fn with_italic_font(mut self, handle: Handle<Font>) -> Self {
self.italic_font = Some(handle);
self
}

/// Register a marker component for the `[m]` tag.
pub fn with_marker<N: Into<String>, M: Component + Clone>(
mut self,
tag_name: N,
marker: M,
) -> Self {
self.modifiers.modifier_map.insert(
tag_name.into(),
Arc::new(move |commands| {
commands.insert(marker.clone());
}),
);
self
}
}

#[derive(Bundle)]
Expand Down
58 changes: 47 additions & 11 deletions src/bevy/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@ use crate::bbcode::{parser::parse_bbcode, BbcodeNode, BbcodeTag};
use super::bbcode::{Bbcode, BbcodeSettings};

#[derive(Debug, Clone)]
struct BbcodeStyle {
struct BbcodeContext {
/// Whether the text should be written **bold**.
is_bold: bool,
/// Whether the text should be written *italic*.
is_italic: bool,
/// The color of the text.
color: Color,

/// Marker components to apply to the spawned `Text`s.
markers: Vec<String>,
}

impl BbcodeStyle {
impl BbcodeContext {
/// Change the style according to the tag.
fn apply_tag(&self, tag: &BbcodeTag) -> Self {
match tag.name() {
Expand Down Expand Up @@ -41,6 +47,20 @@ impl BbcodeStyle {
self.clone()
}
}
"m" | "marker" => {
if let Some(marker) = tag.simple_param() {
let mut markers = self.markers.clone();
markers.push(marker.clone());

Self {
markers,
..self.clone()
}
} else {
warn!("Missing marker name on [{}] tag", tag.name());
self.clone()
}
}
_ => self.clone(),
}
}
Expand Down Expand Up @@ -72,10 +92,11 @@ pub fn convert_bbcode(

construct_recursively(
&mut entity_commands,
BbcodeStyle {
BbcodeContext {
is_bold: false,
is_italic: false,
color: settings.color,
markers: Vec::new(),
},
&settings,
&nodes,
Expand All @@ -85,34 +106,49 @@ pub fn convert_bbcode(

fn construct_recursively(
entity_commands: &mut EntityCommands,
style: BbcodeStyle,
context: BbcodeContext,
settings: &BbcodeSettings,
nodes: &Vec<Arc<BbcodeNode>>,
) {
let default_font = settings.regular_font.clone().unwrap_or_default();

for node in nodes {
match **node {
BbcodeNode::Text(ref text) => {
let font = match (style.is_bold, style.is_italic) {
(true, _) => settings.bold_font.clone(),
(_, true) => settings.italic_font.clone(),
(false, false) => settings.regular_font.clone(),
let font = match (context.is_bold, context.is_italic) {
(true, _) => default_font.clone(),
(_, true) => settings
.italic_font
.clone()
.unwrap_or_else(|| default_font.clone()),
(false, false) => settings
.regular_font
.clone()
.unwrap_or_else(|| default_font.clone()),
};

entity_commands.with_children(|builder| {
builder.spawn(TextBundle::from_section(
let mut text_commands = builder.spawn(TextBundle::from_section(
text.clone(),
TextStyle {
font,
font_size: settings.font_size,
color: style.color,
color: context.color,
},
));

// Apply marker components
for marker in &context.markers {
if let Some(modifier) = settings.modifiers.modifier_map.get(marker) {
modifier(&mut text_commands);
}
}
});
}

BbcodeNode::Tag(ref tag) => construct_recursively(
entity_commands,
style.apply_tag(tag),
context.apply_tag(tag),
settings,
tag.children(),
),
Expand Down

0 comments on commit f05e5a0

Please sign in to comment.