Newer
Older
#![allow(clippy::type_complexity)]
use bevy::{prelude::*, transform::TransformSystem};
use buffer::{
add_font_system, set_editor_redraw, set_initial_scale, set_redraw, swap_target_handle,
pub use buffer::{get_x_offset_center, get_y_offset_center, CosmicBuffer};
pub use cosmic_text::{
Action, Attrs, AttrsOwned, Color as CosmicColor, Cursor, Edit, Family, Metrics, Shaping,
Style as FontStyle, Weight as FontWeight,
use cosmic_text::{Buffer, Editor, FontSystem, SwashCache};
use cursor::{change_cursor, hover_sprites, hover_ui};
pub use cursor::{TextHoverIn, TextHoverOut};
use input::{input_mouse, kb_clipboard, kb_input_text, kb_move_cursor, ClickTimer};
#[cfg(target_arch = "wasm32")]
use input::{poll_wasm_paste, WasmPaste, WasmPasteAsyncChannel};
new_image_from_default, reshape, set_buffer_size, set_padding, set_sprite_size_from_ui,
set_widget_size, set_x_offset, CosmicPadding, CosmicWidgetSize,
use render::{blink_cursor, render_texture, SwashCacheState};
#[cfg(feature = "multicam")]
#[derive(Component)]
pub struct CosmicPrimaryCamera;
#[derive(Clone, Component, PartialEq, Default)]
pub enum CosmicMode {
InfiniteLine,
#[default]
Wrap,
}
#[derive(Default)]
pub enum CursorConfig {
#[default]
Default,
Events,
None,
/// Enum representing the position of the cosmic text.
#[derive(Clone, Component)]
Center { padding: i32 },
TopLeft { padding: i32 },
Left { padding: i32 },
}
impl Default for CosmicTextPosition {
fn default() -> Self {
CosmicTextPosition::Center { padding: 5 }
}
#[derive(Event, Debug)]
pub struct CosmicTextChanged(pub (Entity, String));
pub struct CosmicFontSystem(pub FontSystem);
#[derive(Component)]
pub struct ReadOnly; // tag component
pub left: f32,
pub width: f32,
#[derive(Component, Deref, DerefMut)]
pub struct CosmicEditor {
#[deref]
pub editor: Editor<'static>,
pub cursor_visible: bool,
pub cursor_timer: Timer,
}
fn new(editor: Editor<'static>) -> Self {
Self {
editor,
cursor_visible: true,
cursor_timer: Timer::new(Duration::from_millis(530), TimerMode::Repeating),
#[derive(Component, Deref, DerefMut)]
pub struct DefaultAttrs(pub AttrsOwned);
}
}
#[derive(Component, Default)]
pub struct CosmicBackground(pub Option<Handle<Image>>);
pub struct FillColor(pub Color);
#[derive(Component, Default, Deref)]
pub struct CursorColor(pub Color);
#[derive(Component, Default, Deref)]
pub struct SelectionColor(pub Color);
#[derive(Component, Default)]
pub struct CosmicMaxLines(pub usize);
#[derive(Component, Default)]
pub struct CosmicMaxChars(pub usize);
pub cursor_color: CursorColor,
pub selection_color: SelectionColor,
pub default_attrs: DefaultAttrs,
pub background_image: CosmicBackground,
pub sprite_bundle: SpriteBundle,
// restriction bits
pub max_lines: CosmicMaxLines,
pub max_chars: CosmicMaxChars,
pub padding: CosmicPadding,
pub widget_size: CosmicWidgetSize,
}
impl Default for CosmicEditBundle {
fn default() -> Self {
CosmicEditBundle {
cursor_color: CursorColor(Color::BLACK),
selection_color: SelectionColor(Color::GRAY),
background_image: Default::default(),
max_lines: Default::default(),
max_chars: Default::default(),
mode: Default::default(),
sprite_bundle: SpriteBundle {
sprite: Sprite {
custom_size: Some(Vec2::ONE * 128.0),
..default()
},
visibility: Visibility::Hidden,
..default()
},
padding: Default::default(),
widget_size: Default::default(),
}
}
/// Resource struct that holds configuration options for cosmic fonts.
#[derive(Resource, Clone)]
pub struct CosmicFontConfig {
pub fonts_dir_path: Option<PathBuf>,
pub font_bytes: Option<Vec<&'static [u8]>>,
pub load_system_fonts: bool, // caution: this can be relatively slow
}
impl Default for CosmicFontConfig {
fn default() -> Self {
let fallback_font = include_bytes!("./font/FiraMono-Regular-subset.ttf");
Self {
font_bytes: Some(vec![fallback_font]),
fonts_dir_path: None,
}
}
}
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct KbInput;
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Render;
/// Plugin struct that adds systems and initializes resources related to cosmic edit functionality.
#[derive(Default)]
pub struct CosmicEditPlugin {
pub font_config: CosmicFontConfig,
impl Plugin for CosmicEditPlugin {
fn build(&self, app: &mut App) {
let font_system = create_cosmic_font_system(self.font_config.clone());
let layout_systems = (
(new_image_from_default, set_sprite_size_from_ui),
set_widget_size,
set_buffer_size,
set_padding,
set_redraw,
set_editor_redraw,
swap_target_handle,
(
(kb_move_cursor, kb_input_text, kb_clipboard, reshape)
.chain()
.in_set(KbInput),
blink_cursor,
),
layout_systems,
drop_editor_unfocused,
add_editor_to_focused,
render_texture,
.after(TransformSystem::TransformPropagate),
)
.insert_resource(SwashCacheState {
swash_cache: SwashCache::new(),
})
.insert_resource(CosmicFontSystem(font_system))
.insert_resource(ClickTimer(Timer::from_seconds(0.5, TimerMode::Once)))
.add_event::<CosmicTextChanged>();
match self.change_cursor {
CursorConfig::Default => {
app.add_systems(Update, (hover_sprites, hover_ui, change_cursor))
.add_event::<TextHoverIn>()
.add_event::<TextHoverOut>();
}
CursorConfig::Events => {
app.add_systems(Update, (hover_sprites, hover_ui))
.add_event::<TextHoverIn>()
.add_event::<TextHoverOut>();
}
CursorConfig::None => {}
}
#[cfg(target_arch = "wasm32")]
{
let (tx, rx) = crossbeam_channel::bounded::<WasmPaste>(1);
app.insert_resource(WasmPasteAsyncChannel { tx, rx })
.add_systems(Update, poll_wasm_paste);
}
fn add_feature_plugins(app: &mut App) -> &mut App {
app.add_plugins(plugins::placeholder::PlaceholderPlugin);
app.add_plugins(plugins::password::PasswordPlugin);
fn create_cosmic_font_system(cosmic_font_config: CosmicFontConfig) -> FontSystem {
let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
let mut db = cosmic_text::fontdb::Database::new();
if let Some(dir_path) = cosmic_font_config.fonts_dir_path.clone() {
db.load_fonts_dir(dir_path);
}
if let Some(custom_font_data) = &cosmic_font_config.font_bytes {
for elem in custom_font_data {
db.load_font_data(elem.to_vec());
if cosmic_font_config.load_system_fonts {
db.load_system_fonts();
}
cosmic_text::FontSystem::new_with_locale_and_db(locale, db)
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
pub fn get_node_cursor_pos(
window: &Window,
node_transform: &GlobalTransform,
size: (f32, f32),
is_ui_node: bool,
camera: &Camera,
camera_transform: &GlobalTransform,
) -> Option<(f32, f32)> {
let (x_min, y_min, x_max, y_max) = (
node_transform.affine().translation.x - size.0 / 2.,
node_transform.affine().translation.y - size.1 / 2.,
node_transform.affine().translation.x + size.0 / 2.,
node_transform.affine().translation.y + size.1 / 2.,
);
window.cursor_position().and_then(|pos| {
if is_ui_node {
if x_min < pos.x && pos.x < x_max && y_min < pos.y && pos.y < y_max {
Some((pos.x - x_min, pos.y - y_min))
} else {
None
}
} else {
camera
.viewport_to_world_2d(camera_transform, pos)
.and_then(|pos| {
if x_min < pos.x && pos.x < x_max && y_min < pos.y && pos.y < y_max {
Some((pos.x - x_min, y_max - pos.y))
} else {
None
}
})
}
})
}
#[cfg(target_arch = "wasm32")]
pub fn get_timestamp() -> f64 {
js_sys::Date::now()
}
#[cfg(not(target_arch = "wasm32"))]
pub fn get_timestamp() -> f64 {
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
duration.as_millis() as f64
}
#[cfg(test)]
mod tests {
use crate::*;
use self::buffer::CosmicBuffer;
fn test_spawn_cosmic_edit_system(
mut commands: Commands,
mut font_system: ResMut<CosmicFontSystem>,
) {
let attrs = Attrs::new();
buffer: CosmicBuffer::new(&mut font_system, Metrics::new(20., 20.)).with_rich_text(
&mut font_system,
vec![("Blah", attrs)],
attrs,
),
}
#[test]
fn test_spawn_cosmic_edit() {
let mut app = App::new();
app.add_plugins(TaskPoolPlugin::default());
app.add_plugins(AssetPlugin::default());
app.insert_resource(CosmicFontSystem(create_cosmic_font_system(
CosmicFontConfig::default(),
)));
app.add_systems(Update, test_spawn_cosmic_edit_system);
let mouse_input: ButtonInput<MouseButton> = ButtonInput::<MouseButton>::default();
app.insert_resource(mouse_input);
app.add_event::<ReceivedCharacter>();
app.update();
let mut text_nodes_query = app.world.query::<&CosmicBuffer>();