Newer
Older
#![allow(clippy::type_complexity)]
pub mod password;
pub mod placeholder;
use bevy::{prelude::*, transform::TransformSystem};
Action, Attrs, AttrsOwned, Buffer, Color as CosmicColor, Cursor, Edit, Editor, Family,
FontSystem, Metrics, Shaping, Style as FontStyle, Weight as FontWeight,
#[cfg(target_arch = "wasm32")]
use input::{poll_wasm_paste, WasmPaste, WasmPasteAsyncChannel};
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
pub use password::*;
pub use placeholder::*;
pub use render::*;
pub use widget::*;
/// Plugin struct that adds systems and initializes resources related to cosmic edit functionality.
#[derive(Default)]
pub struct CosmicEditPlugin {
pub font_config: CosmicFontConfig,
pub change_cursor: CursorConfig,
}
impl Plugin for CosmicEditPlugin {
fn build(&self, app: &mut App) {
let font_system = create_cosmic_font_system(self.font_config.clone());
app.add_plugins((
BufferPlugin,
RenderPlugin,
WidgetPlugin,
InputPlugin,
FocusPlugin,
CursorPlugin {
change_cursor: self.change_cursor.clone(),
},
PlaceholderPlugin,
PasswordPlugin,
))
.insert_resource(CosmicFontSystem(font_system))
.add_event::<CosmicTextChanged>();
#[cfg(target_arch = "wasm32")]
{
let (tx, rx) = crossbeam_channel::bounded::<WasmPaste>(1);
app.insert_resource(WasmPasteAsyncChannel { tx, rx })
.add_systems(Update, poll_wasm_paste);
}
}
}
#[cfg(feature = "multicam")]
#[derive(Component)]
pub struct CosmicPrimaryCamera;
#[derive(Clone, Component, PartialEq, Default)]
pub enum CosmicMode {
InfiniteLine,
#[default]
Wrap,
}
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,
}
}
}
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)
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
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>();