Newer
Older
#![allow(clippy::too_many_arguments, clippy::type_complexity)]
#[cfg(target_arch = "wasm32")]
use bevy::tasks::AsyncComputeTaskPool;
input::mouse::{MouseMotion, MouseScrollUnit, MouseWheel},
use cosmic_text::{Action, Cursor, Edit, Motion, Selection};
#[cfg(target_arch = "wasm32")]
use crate::DefaultAttrs;
#[cfg(target_arch = "wasm32")]
use js_sys::Promise;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::JsFuture;
buffer::{get_x_offset_center, get_y_offset_center, BufferExtras},
get_node_cursor_pos, CosmicBuffer, CosmicEditor, CosmicFontSystem, CosmicMaxChars,
CosmicMaxLines, CosmicSource, CosmicTextChanged, CosmicTextPosition, FocusedWidget, ReadOnly,
XOffset,
#[derive(Resource)]
pub struct ClickTimer(pub Timer);
// TODO: hide this behind #cfg wasm, depends on wasm having own copy/paste fn
#[allow(dead_code)]
pub struct WasmPaste {
text: String,
entity: Entity,
}
#[derive(Resource)]
pub struct WasmPasteAsyncChannel {
pub tx: crossbeam_channel::Sender<WasmPaste>,
pub rx: crossbeam_channel::Receiver<WasmPaste>,
}
keys: Res<ButtonInput<KeyCode>>,
buttons: Res<ButtonInput<MouseButton>>,
&mut CosmicEditor,
&GlobalTransform,
&CosmicTextPosition,
Entity,
&XOffset,
node_q: Query<(&Node, &GlobalTransform, &CosmicSource)>,
mut font_system: ResMut<CosmicFontSystem>,
mut scroll_evr: EventReader<MouseWheel>,
camera_q: Query<(&Camera, &GlobalTransform)>,
mut click_timer: ResMut<ClickTimer>,
mut click_count: Local<usize>,
time: Res<Time>,
evr_mouse_motion: EventReader<MouseMotion>,

Grim
committed
let Some(active_editor_entity) = active_editor.0 else {

Grim
committed
};
if click_timer.0.finished() || !evr_mouse_motion.is_empty() {
*click_count = 0;
}
if buttons.just_pressed(MouseButton::Left) {
click_timer.0.reset();
*click_count += 1;
}
if *click_count > 3 {
*click_count = 0;
}

Grim
committed
let Ok(primary_window) = windows.get_single() else {

Grim
committed
};

Grim
committed
let Some((camera, camera_transform)) = camera_q.iter().find(|(c, _)| c.is_active) else {
return;
};

Grim
committed
if let Ok((mut editor, sprite_transform, text_position, entity, x_offset, sprite)) =
editor_q.get_mut(active_editor_entity)
let buffer = editor.with_buffer(|b| b.clone());
let mut is_ui_node = false;
let mut transform = sprite_transform;
let (mut width, mut height) =
(sprite.custom_size.unwrap().x, sprite.custom_size.unwrap().y);
// TODO: this is bad loop nesting, rethink system with relationships in mind
for (node, node_transform, source) in node_q.iter() {
if source.0 != entity {
continue;
is_ui_node = true;
transform = node_transform;
width = node.size().x;
height = node.size().y;
}
let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);
// if shift key is pressed
let already_has_selection = editor.selection() != Selection::None;
let cursor = editor.cursor();
editor.set_selection(Selection::Normal(cursor));
}
let (padding_x, padding_y) = match text_position {
CosmicTextPosition::Center { padding: _ } => (
get_x_offset_center(width * scale_factor, &buffer),
get_y_offset_center(height * scale_factor, &buffer),
),
CosmicTextPosition::TopLeft { padding } => (*padding, *padding),
CosmicTextPosition::Left { padding } => (
*padding,
get_y_offset_center(height * scale_factor, &buffer),
),
};
let point = |node_cursor_pos: (f32, f32)| {
(
(node_cursor_pos.0 * scale_factor) as i32 - padding_x,
(node_cursor_pos.1 * scale_factor) as i32 - padding_y,
)
};
if buttons.just_pressed(MouseButton::Left) {
editor.cursor_visible = true;
editor.cursor_timer.reset();
if let Some(node_cursor_pos) = get_node_cursor_pos(
primary_window,
(width, height),
is_ui_node,
camera,
camera_transform,
) {
let (mut x, y) = point(node_cursor_pos);
editor.action(&mut font_system.0, Action::Drag { x, y });
editor.action(&mut font_system.0, Action::Click { x, y });
editor.action(&mut font_system.0, Action::Motion(Motion::LeftWord));
let cursor = editor.cursor();
editor.set_selection(Selection::Normal(cursor));
editor.action(&mut font_system.0, Action::Motion(Motion::RightWord));
editor
.action(&mut font_system.0, Action::Motion(Motion::ParagraphStart));
let cursor = editor.cursor();
editor.set_selection(Selection::Normal(cursor));
editor.action(&mut font_system.0, Action::Motion(Motion::ParagraphEnd));
if buttons.pressed(MouseButton::Left) && *click_count == 0 {
if let Some(node_cursor_pos) = get_node_cursor_pos(
primary_window,
(width, height),
is_ui_node,
camera,
camera_transform,
) {
let (mut x, y) = point(node_cursor_pos);
editor.action(&mut font_system.0, Action::Click { x, y });
editor.action(&mut font_system.0, Action::Drag { x, y });
&mut font_system.0,
Action::Scroll {
lines: -ev.y as i32,
},
);
}
MouseScrollUnit::Pixel => {
let line_height = buffer.metrics().line_height;
editor.action(
&mut font_system.0,
Action::Scroll {
lines: -(ev.y / line_height) as i32,
},
);
}
}
}
}
}
mut cosmic_edit_query: Query<(&mut CosmicEditor,)>,

Grim
committed
let Some(active_editor_entity) = active_editor.0 else {
return;
};
if let Ok((mut editor,)) = cosmic_edit_query.get_mut(active_editor_entity) {
if keys.get_just_pressed().len() != 0 {
editor.cursor_visible = true;
editor.cursor_timer.reset();
}
#[cfg(target_arch = "wasm32")]
let command = if web_sys::window()
.unwrap()
.navigator()
.user_agent()
.unwrap_or("NoUA".into())
.contains("Macintosh")
{
keys.any_pressed([KeyCode::SuperLeft, KeyCode::SuperRight])
} else {
command
};
#[cfg(target_os = "macos")]
let option = keys.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]);
let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);
let already_has_selection = editor.selection() != Selection::None;
let cursor = editor.cursor();
editor.set_selection(Selection::Normal(cursor));
}
#[cfg(target_os = "macos")]
let should_jump = command && option;
#[cfg(not(target_os = "macos"))]
let should_jump = command;
if should_jump && keys.just_pressed(KeyCode::ArrowLeft) {
editor.action(&mut font_system.0, Action::Motion(Motion::PreviousWord));
if should_jump && keys.just_pressed(KeyCode::ArrowRight) {
editor.action(&mut font_system.0, Action::Motion(Motion::NextWord));
}
return;
}
if should_jump && keys.just_pressed(KeyCode::Home) {
editor.action(&mut font_system.0, Action::Motion(Motion::BufferStart));
}
return;
}
if should_jump && keys.just_pressed(KeyCode::End) {
editor.action(&mut font_system.0, Action::Motion(Motion::BufferEnd));
editor.action(&mut font_system.0, Action::Motion(Motion::Left));
editor.action(&mut font_system.0, Action::Motion(Motion::Right));
editor.action(&mut font_system.0, Action::Motion(Motion::Up));
editor.action(&mut font_system.0, Action::Motion(Motion::Down));
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
if keys.just_pressed(KeyCode::Escape) {
editor.action(&mut font_system.0, Action::Escape);
}
if command && keys.just_pressed(KeyCode::KeyA) {
editor.action(&mut font_system.0, Action::Motion(Motion::BufferEnd));
let current_cursor = editor.cursor();
editor.set_selection(Selection::Normal(Cursor {
line: 0,
index: 0,
affinity: current_cursor.affinity,
}));
return;
}
if keys.just_pressed(KeyCode::Home) {
editor.action(&mut font_system.0, Action::Motion(Motion::Home));
if !shift {
editor.set_selection(Selection::None);
}
return;
}
if keys.just_pressed(KeyCode::End) {
editor.action(&mut font_system.0, Action::Motion(Motion::End));
if !shift {
editor.set_selection(Selection::None);
}
return;
}
if keys.just_pressed(KeyCode::PageUp) {
editor.action(&mut font_system.0, Action::Motion(Motion::PageUp));
if !shift {
editor.set_selection(Selection::None);
}
return;
}
if keys.just_pressed(KeyCode::PageDown) {
editor.action(&mut font_system.0, Action::Motion(Motion::PageDown));
if !shift {
editor.set_selection(Selection::None);
}
}
}
}
pub(crate) fn kb_input_text(
active_editor: Res<FocusedWidget>,
keys: Res<ButtonInput<KeyCode>>,
mut char_evr: EventReader<ReceivedCharacter>,
mut cosmic_edit_query: Query<(
&mut CosmicEditor,
&mut CosmicBuffer,
&CosmicMaxLines,
&CosmicMaxChars,
Entity,
Option<&ReadOnly>,
)>,
mut evw_changed: EventWriter<CosmicTextChanged>,
mut font_system: ResMut<CosmicFontSystem>,
mut is_deleting: Local<bool>,
) {
let Some(active_editor_entity) = active_editor.0 else {
return;
};
if let Ok((mut editor, buffer, max_lines, max_chars, entity, readonly_opt)) =
cosmic_edit_query.get_mut(active_editor_entity)
{
let command = keypress_command(&keys);
if keys.get_just_pressed().len() != 0 {
editor.cursor_visible = true;
editor.cursor_timer.reset();
}
let readonly = readonly_opt.is_some();
match select {
Selection::Line(cursor) => {
if editor.cursor().line == cursor.line && editor.cursor().index == cursor.index
{
editor.set_selection(Selection::None);
}
}
Selection::Normal(cursor) => {
if editor.cursor().line == cursor.line && editor.cursor().index == cursor.index
{
editor.set_selection(Selection::None);
}
}
Selection::Word(cursor) => {
if editor.cursor().line == cursor.line && editor.cursor().index == cursor.index
{
editor.set_selection(Selection::None);
}
}
Selection::None => {}
#[cfg(target_arch = "wasm32")]
editor.action(&mut font_system.0, Action::Backspace);
if keys.just_pressed(KeyCode::Delete) && !readonly {
editor.action(&mut font_system.0, Action::Delete);
editor.with_buffer_mut(|b| b.set_redraw(true));
let mut is_edit = false;
let mut is_return = false;
if keys.just_pressed(KeyCode::Enter) {
is_return = true;
if (max_lines.0 == 0 || buffer.lines.len() < max_lines.0)
&& (max_chars.0 == 0 || buffer.get_text().len() < max_chars.0)
{
// to have new line on wasm rather than E
is_edit = true;
editor.action(&mut font_system.0, Action::Insert('\n'));
if !is_return {
for char_ev in char_evr.read() {
is_edit = true;
if *is_deleting {
editor.action(&mut font_system.0, Action::Backspace);
} else if !command && (max_chars.0 == 0 || buffer.get_text().len() < max_chars.0) {
let b = char_ev.char.as_bytes();
for c in b {
let c: char = (*c).into();
editor.action(&mut font_system.0, Action::Insert(c));
}
}
evw_changed.send(CosmicTextChanged((entity, buffer.get_text())));
}
}
pub fn kb_clipboard(
active_editor: Res<FocusedWidget>,
keys: Res<ButtonInput<KeyCode>>,
mut evw_changed: EventWriter<CosmicTextChanged>,
mut font_system: ResMut<CosmicFontSystem>,
mut cosmic_edit_query: Query<(
&mut CosmicEditor,
&mut CosmicBuffer,
&CosmicMaxLines,
&CosmicMaxChars,
Entity,
Option<&ReadOnly>,
)>,
) {
let Some(active_editor_entity) = active_editor.0 else {
return;
};
if let Ok((mut editor, buffer, max_lines, max_chars, entity, readonly_opt)) =
cosmic_edit_query.get_mut(active_editor_entity)
{
let command = keypress_command(&keys);
let readonly = readonly_opt.is_some();
let mut is_clipboard = false;
#[cfg(not(target_arch = "wasm32"))]
{
if let Ok(mut clipboard) = arboard::Clipboard::new() {
clipboard.set_text(text).unwrap();
return;
}
}
if command && keys.just_pressed(KeyCode::KeyX) && !readonly {
if command && keys.just_pressed(KeyCode::KeyV) && !readonly {
if let Ok(text) = clipboard.get_text() {
for c in text.chars() {
if max_chars.0 == 0 || buffer.get_text().len() < max_chars.0 {
if max_lines.0 == 0 || buffer.lines.len() < max_lines.0 {
editor.action(&mut font_system.0, Action::Insert(c));
editor.action(&mut font_system.0, Action::Insert(c));
}
}
}
}
is_clipboard = true;
}
}
}
write_clipboard_wasm(text.as_str());
return;
}
}
if command && keys.just_pressed(KeyCode::KeyX) && !readonly {
if command && keys.just_pressed(KeyCode::KeyV) && !readonly {
let tx = _channel.unwrap().tx.clone();
let _task = AsyncComputeTaskPool::get().spawn(async move {
let promise = read_clipboard_wasm();
let result = JsFuture::from(promise).await;
if let Ok(js_text) = result {
if let Some(text) = js_text.as_string() {
let _ = tx.try_send(WasmPaste { text, entity });
}
}
});
return;
}
}
evw_changed.send(CosmicTextChanged((entity, buffer.get_text())));
fn keypress_command(keys: &ButtonInput<KeyCode>) -> bool {
#[cfg(target_os = "macos")]
let command = keys.any_pressed([KeyCode::SuperLeft, KeyCode::SuperRight]);
#[cfg(not(target_os = "macos"))]
let command = keys.any_pressed([KeyCode::ControlLeft, KeyCode::ControlRight]);
#[cfg(target_arch = "wasm32")]
let command = if web_sys::window()
.unwrap()
.navigator()
.user_agent()
.unwrap_or("NoUA".into())
.contains("Macintosh")
{
keys.any_pressed([KeyCode::SuperLeft, KeyCode::SuperRight])
} else {
command
};
command
}
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn write_clipboard_wasm(text: &str) {
let clipboard = web_sys::window()
.unwrap()
.navigator()
.clipboard()
.expect("Clipboard not found!");
let _result = clipboard.write_text(text);
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn read_clipboard_wasm() -> Promise {
let clipboard = web_sys::window()
.unwrap()
.navigator()
.clipboard()
.expect("Clipboard not found!");
clipboard.read_text()
}
#[cfg(target_arch = "wasm32")]
pub fn poll_wasm_paste(
channel: Res<WasmPasteAsyncChannel>,
mut editor_q: Query<
(
&mut CosmicEditor,
&CosmicMaxChars,
&CosmicMaxChars,
),
Without<ReadOnly>,
>,
mut evw_changed: EventWriter<CosmicTextChanged>,
mut font_system: ResMut<CosmicFontSystem>,
) {
let inlet = channel.rx.try_recv();
match inlet {
Ok(inlet) => {
let entity = inlet.entity;
if let Ok((mut editor, mut buffer, attrs, max_chars, max_lines)) =
editor_q.get_mut(entity)
{
let text = inlet.text;
let attrs = &attrs.0;
for c in text.chars() {
if max_chars.0 == 0 || buffer.get_text().len() < max_chars.0 {
if max_lines.0 == 0 || buffer.lines.len() < max_lines.0 {
editor.action(&mut font_system.0, Action::Insert(c));
editor.action(&mut font_system.0, Action::Insert(c));
evw_changed.send(CosmicTextChanged((entity, buffer.get_text())));