Skip to content
Snippets Groups Projects
Unverified Commit 1aff2fc2 authored by sam's avatar sam Committed by GitHub
Browse files

refactor and split input fn (#69)

* refactor and split input fn

Input has been decoupled into input_mouse and input_kb

Moved `lib.rs` around, ordered as enums > structs > plugin > functions

* allow selection and copy on readonly buffers

* remove clicktimer

* fix clippy
parent 7bfc712d
No related branches found
No related tags found
No related merge requests found
#![allow(clippy::too_many_arguments, clippy::type_complexity)]
use std::time::Duration;
use bevy::{
input::mouse::{MouseScrollUnit, MouseWheel},
prelude::*,
window::PrimaryWindow,
};
use cosmic_text::{Action, AttrsList, BufferLine, Cursor, Edit, Shaping};
use crate::{
get_node_cursor_pos, get_timestamp, get_x_offset_center, get_y_offset_center,
save_edit_history, CosmicAttrs, CosmicEditHistory, CosmicEditor, CosmicFontSystem,
CosmicMaxChars, CosmicMaxLines, CosmicTextChanged, CosmicTextPosition, Focus, ReadOnly,
XOffset,
};
pub(crate) fn input_mouse(
windows: Query<&Window, With<PrimaryWindow>>, // Mouse
active_editor: Res<Focus>, // Both
keys: Res<Input<KeyCode>>, // Both
buttons: Res<Input<MouseButton>>, // Mouse
mut cosmic_edit_query: Query<(
&mut CosmicEditor, // Both
&GlobalTransform, // Mouse
&CosmicTextPosition, // Mouse, to determine point
Entity, // Both
&XOffset, // Mouse
Option<&mut Node>,
Option<&mut Sprite>,
)>,
mut font_system: ResMut<CosmicFontSystem>, // Both
mut scroll_evr: EventReader<MouseWheel>, // Mouse
camera_q: Query<(&Camera, &GlobalTransform)>, // Mouse
) {
if active_editor.0.is_none() {
return;
}
let primary_window = windows.single();
let scale_factor = primary_window.scale_factor() as f32;
let (camera, camera_transform) = camera_q.iter().find(|(c, _)| c.is_active).unwrap();
for (mut editor, node_transform, text_position, entity, x_offset, node_opt, sprite_opt) in
&mut cosmic_edit_query.iter_mut()
{
if active_editor.0 != Some(entity) {
continue;
}
let (width, height, is_ui_node) = match node_opt {
Some(node) => (node.size().x, node.size().y, true),
None => {
let sprite = sprite_opt.unwrap();
let size = sprite.custom_size.unwrap();
(size.x, size.y, false)
}
};
let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);
// if shift key is pressed
let already_has_selection = editor.0.select_opt().is_some();
if shift && !already_has_selection {
let cursor = editor.0.cursor();
editor.0.set_select_opt(Some(cursor));
}
let (padding_x, padding_y) = match text_position {
CosmicTextPosition::Center => (
get_x_offset_center(width * scale_factor, editor.0.buffer()),
get_y_offset_center(height * scale_factor, editor.0.buffer()),
),
CosmicTextPosition::TopLeft { padding } => (*padding, *padding),
CosmicTextPosition::Left { padding } => (
*padding,
get_y_offset_center(height * scale_factor, editor.0.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) {
if let Some(node_cursor_pos) = get_node_cursor_pos(
primary_window,
node_transform,
(width, height),
is_ui_node,
camera,
camera_transform,
) {
let (mut x, y) = point(node_cursor_pos);
x += x_offset.0.unwrap_or((0., 0.)).0 as i32;
if shift {
editor.0.action(&mut font_system.0, Action::Drag { x, y });
} else {
editor.0.action(&mut font_system.0, Action::Click { x, y });
}
}
return;
}
if buttons.pressed(MouseButton::Left) {
if let Some(node_cursor_pos) = get_node_cursor_pos(
primary_window,
node_transform,
(width, height),
is_ui_node,
camera,
camera_transform,
) {
let (mut x, y) = point(node_cursor_pos);
x += x_offset.0.unwrap_or((0., 0.)).0 as i32;
if active_editor.is_changed() && !shift {
editor.0.action(&mut font_system.0, Action::Click { x, y });
} else {
editor.0.action(&mut font_system.0, Action::Drag { x, y });
}
}
return;
}
for ev in scroll_evr.iter() {
match ev.unit {
MouseScrollUnit::Line => {
editor.0.action(
&mut font_system.0,
Action::Scroll {
lines: -ev.y as i32,
},
);
}
MouseScrollUnit::Pixel => {
let line_height = editor.0.buffer().metrics().line_height;
editor.0.action(
&mut font_system.0,
Action::Scroll {
lines: -(ev.y / line_height) as i32,
},
);
}
}
}
}
}
/// Handles undo/redo, copy/paste and char input
pub(crate) fn input_kb(
active_editor: Res<Focus>, // Both
keys: Res<Input<KeyCode>>, // Both
mut char_evr: EventReader<ReceivedCharacter>, // Kb
mut cosmic_edit_query: Query<(
&mut CosmicEditor, // Both
&mut CosmicEditHistory, // Kb - Undo
&CosmicAttrs, // Kb - Undo
&CosmicMaxLines, // Kb
&CosmicMaxChars, // Kb
Entity, // Both
Option<&ReadOnly>,
)>,
mut evw_changed: EventWriter<CosmicTextChanged>, // Kb
mut font_system: ResMut<CosmicFontSystem>, // Both
mut is_deleting: Local<bool>, // Kb
mut edits_duration: Local<Option<Duration>>, // Kb - Undo
mut undoredo_duration: Local<Option<Duration>>, // Kb - Undo
) {
for (mut editor, mut edit_history, attrs, max_lines, max_chars, entity, readonly_opt) in
&mut cosmic_edit_query.iter_mut()
{
if active_editor.0 != Some(entity) {
continue;
}
let readonly = readonly_opt.is_some();
let attrs = &attrs.0;
let now_ms = get_timestamp();
#[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]);
let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);
#[cfg(target_os = "macos")]
let option = keys.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]);
// if shift key is pressed
let already_has_selection = editor.0.select_opt().is_some();
if shift && !already_has_selection {
let cursor = editor.0.cursor();
editor.0.set_select_opt(Some(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::Left) {
editor.0.action(&mut font_system.0, Action::PreviousWord);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if should_jump && keys.just_pressed(KeyCode::Right) {
editor.0.action(&mut font_system.0, Action::NextWord);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if should_jump && keys.just_pressed(KeyCode::Home) {
editor.0.action(&mut font_system.0, Action::BufferStart);
// there's a bug with cosmic text where it doesn't update the visual cursor for this action
// TODO: fix upstream
editor.0.buffer_mut().set_redraw(true);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if should_jump && keys.just_pressed(KeyCode::End) {
editor.0.action(&mut font_system.0, Action::BufferEnd);
// there's a bug with cosmic text where it doesn't update the visual cursor for this action
// TODO: fix upstream
editor.0.buffer_mut().set_redraw(true);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::Left) {
editor.0.action(&mut font_system.0, Action::Left);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::Right) {
editor.0.action(&mut font_system.0, Action::Right);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::Up) {
editor.0.action(&mut font_system.0, Action::Up);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::Down) {
editor.0.action(&mut font_system.0, Action::Down);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::Back) {
#[cfg(target_arch = "wasm32")]
editor.0.action(&mut font_system.0, Action::Backspace);
*is_deleting = true;
}
if keys.just_released(KeyCode::Back) {
*is_deleting = false;
}
if keys.just_pressed(KeyCode::Delete) {
editor.0.action(&mut font_system.0, Action::Delete);
}
if keys.just_pressed(KeyCode::Escape) {
editor.0.action(&mut font_system.0, Action::Escape);
}
if command && keys.just_pressed(KeyCode::A) {
editor.0.action(&mut font_system.0, Action::BufferEnd);
let current_cursor = editor.0.cursor();
editor.0.set_select_opt(Some(Cursor {
line: 0,
index: 0,
affinity: current_cursor.affinity,
color: current_cursor.color,
}));
return;
}
if keys.just_pressed(KeyCode::Home) {
editor.0.action(&mut font_system.0, Action::Home);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::End) {
editor.0.action(&mut font_system.0, Action::End);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::PageUp) {
editor.0.action(&mut font_system.0, Action::PageUp);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::PageDown) {
editor.0.action(&mut font_system.0, Action::PageDown);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
// redo
#[cfg(not(target_os = "windows"))]
let requested_redo = command && shift && keys.just_pressed(KeyCode::Z) && !readonly;
#[cfg(target_os = "windows")]
let requested_redo = command && keys.just_pressed(KeyCode::Y);
if requested_redo {
let edits = &edit_history.edits;
if edits.is_empty() {
return;
}
if edit_history.current_edit == edits.len() - 1 {
return;
}
let idx = edit_history.current_edit + 1;
if let Some(current_edit) = edits.get(idx) {
editor.0.buffer_mut().lines.clear();
for line in current_edit.lines.iter() {
let mut line_text = String::new();
let mut attrs_list = AttrsList::new(attrs.as_attrs());
for (text, attrs) in line.iter() {
let start = line_text.len();
line_text.push_str(text);
let end = line_text.len();
attrs_list.add_span(start..end, attrs.as_attrs());
}
editor.0.buffer_mut().lines.push(BufferLine::new(
line_text,
attrs_list,
Shaping::Advanced,
));
}
editor.0.set_cursor(current_edit.cursor);
editor.0.buffer_mut().set_redraw(true);
edit_history.current_edit += 1;
}
*undoredo_duration = Some(Duration::from_millis(now_ms as u64));
evw_changed.send(CosmicTextChanged((entity, editor.get_text())));
return;
}
// undo
let requested_undo = command && keys.just_pressed(KeyCode::Z) && !readonly;
if requested_undo {
let edits = &edit_history.edits;
if edits.is_empty() {
return;
}
if edit_history.current_edit <= 1 {
return;
}
let idx = edit_history.current_edit - 1;
if let Some(current_edit) = edits.get(idx) {
editor.0.buffer_mut().lines.clear();
for line in current_edit.lines.iter() {
let mut line_text = String::new();
let mut attrs_list = AttrsList::new(attrs.as_attrs());
for (text, attrs) in line.iter() {
let start = line_text.len();
line_text.push_str(text);
let end = line_text.len();
attrs_list.add_span(start..end, attrs.as_attrs());
}
editor.0.buffer_mut().lines.push(BufferLine::new(
line_text,
attrs_list,
Shaping::Advanced,
));
}
editor.0.set_cursor(current_edit.cursor);
editor.0.buffer_mut().set_redraw(true);
edit_history.current_edit -= 1;
}
*undoredo_duration = Some(Duration::from_millis(now_ms as u64));
evw_changed.send(CosmicTextChanged((entity, editor.get_text())));
return;
}
let mut is_clipboard = false;
#[cfg(not(target_arch = "wasm32"))]
{
if let Ok(mut clipboard) = arboard::Clipboard::new() {
if command && keys.just_pressed(KeyCode::C) {
if let Some(text) = editor.0.copy_selection() {
clipboard.set_text(text).unwrap();
return;
}
}
if command && keys.just_pressed(KeyCode::X) && !readonly {
if let Some(text) = editor.0.copy_selection() {
clipboard.set_text(text).unwrap();
editor.0.delete_selection();
}
is_clipboard = true;
}
if command && keys.just_pressed(KeyCode::V) && !readonly {
if let Ok(text) = clipboard.get_text() {
for c in text.chars() {
if max_chars.0 == 0 || editor.get_text().len() < max_chars.0 {
if c == 0xA as char {
if max_lines.0 == 0
|| editor.0.buffer().lines.len() < max_lines.0
{
editor.0.action(&mut font_system.0, Action::Insert(c));
}
} else {
editor.0.action(&mut font_system.0, Action::Insert(c));
}
}
}
}
is_clipboard = true;
}
}
}
// fix for issue #8
if let Some(select) = editor.0.select_opt() {
if editor.0.cursor().line == select.line && editor.0.cursor().index == select.index {
editor.0.set_select_opt(None);
}
}
let mut is_edit = is_clipboard;
let mut is_return = false;
if keys.just_pressed(KeyCode::Return) && !readonly {
is_return = true;
if (max_lines.0 == 0 || editor.0.buffer().lines.len() < max_lines.0)
&& (max_chars.0 == 0 || editor.get_text().len() < max_chars.0)
{
// to have new line on wasm rather than E
is_edit = true;
editor.0.action(&mut font_system.0, Action::Insert('\n'));
}
}
if !(is_clipboard || is_return || readonly) {
for char_ev in char_evr.iter() {
is_edit = true;
if *is_deleting {
editor.0.action(&mut font_system.0, Action::Backspace);
} else if max_chars.0 == 0 || editor.get_text().len() < max_chars.0 {
editor
.0
.action(&mut font_system.0, Action::Insert(char_ev.char));
}
}
}
if !is_edit || readonly {
return;
}
evw_changed.send(CosmicTextChanged((entity, editor.get_text())));
if let Some(last_edit_duration) = *edits_duration {
if Duration::from_millis(now_ms as u64) - last_edit_duration
> Duration::from_millis(150)
{
save_edit_history(&mut editor.0, attrs, &mut edit_history);
*edits_duration = Some(Duration::from_millis(now_ms as u64));
}
} else {
save_edit_history(&mut editor.0, attrs, &mut edit_history);
*edits_duration = Some(Duration::from_millis(now_ms as u64));
}
}
}
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
mod cursor; mod cursor;
mod input;
use std::{collections::VecDeque, path::PathBuf, time::Duration}; use std::{collections::VecDeque, path::PathBuf, time::Duration};
use bevy::{ use bevy::{
asset::HandleId, asset::HandleId,
input::mouse::{MouseScrollUnit, MouseWheel},
prelude::*, prelude::*,
render::{render_resource::Extent3d, texture::DEFAULT_IMAGE_HANDLE}, render::{render_resource::Extent3d, texture::DEFAULT_IMAGE_HANDLE},
transform::TransformSystem, transform::TransformSystem,
...@@ -23,6 +23,7 @@ use cosmic_text::{ ...@@ -23,6 +23,7 @@ use cosmic_text::{
use cursor::{change_cursor, hover_sprites, hover_ui}; use cursor::{change_cursor, hover_sprites, hover_ui};
pub use cursor::{TextHoverIn, TextHoverOut}; pub use cursor::{TextHoverIn, TextHoverOut};
use image::{imageops::FilterType, GenericImageView}; use image::{imageops::FilterType, GenericImageView};
use input::{input_kb, input_mouse};
#[derive(Clone, Component, PartialEq, Debug)] #[derive(Clone, Component, PartialEq, Debug)]
pub enum CosmicText { pub enum CosmicText {
...@@ -30,6 +31,12 @@ pub enum CosmicText { ...@@ -30,6 +31,12 @@ pub enum CosmicText {
MultiStyle(Vec<Vec<(String, AttrsOwned)>>), MultiStyle(Vec<Vec<(String, AttrsOwned)>>),
} }
impl Default for CosmicText {
fn default() -> Self {
Self::OneStyle(String::new())
}
}
#[derive(Clone, Component, PartialEq, Default)] #[derive(Clone, Component, PartialEq, Default)]
pub enum CosmicMode { pub enum CosmicMode {
InfiniteLine, InfiniteLine,
...@@ -38,10 +45,12 @@ pub enum CosmicMode { ...@@ -38,10 +45,12 @@ pub enum CosmicMode {
Wrap, Wrap,
} }
impl Default for CosmicText { #[derive(Default)]
fn default() -> Self { pub enum CursorConfig {
Self::OneStyle(String::new()) #[default]
} Default,
Events,
None,
} }
/// Enum representing the position of the cosmic text. /// Enum representing the position of the cosmic text.
...@@ -160,46 +169,6 @@ impl CosmicEditor { ...@@ -160,46 +169,6 @@ impl CosmicEditor {
} }
} }
/// Adds the font system to each editor when added
fn cosmic_editor_builder(
mut added_editors: Query<(Entity, &CosmicMetrics), Added<CosmicText>>,
mut font_system: ResMut<CosmicFontSystem>,
mut commands: Commands,
) {
for (entity, metrics) in added_editors.iter_mut() {
let buffer = Buffer::new(
&mut font_system.0,
Metrics::new(metrics.font_size, metrics.line_height).scale(metrics.scale_factor),
);
// buffer.set_wrap(&mut font_system.0, cosmic_text::Wrap::None);
let editor = Editor::new(buffer);
commands.entity(entity).insert(CosmicEditor(editor));
commands.entity(entity).insert(CosmicEditHistory::default());
commands.entity(entity).insert(XOffset(None));
}
}
/// Updates editor buffer when text component changes
fn update_buffer_text(
mut editor_q: Query<
(
&mut CosmicEditor,
&mut CosmicText,
&CosmicAttrs,
&CosmicMaxChars,
&CosmicMaxLines,
),
Changed<CosmicText>,
>,
mut font_system: ResMut<CosmicFontSystem>,
) {
for (mut editor, text, attrs, max_chars, max_lines) in editor_q.iter_mut() {
let text = trim_text(text.to_owned(), max_chars.0, max_lines.0);
editor.set_text(text, attrs.0.clone(), &mut font_system.0);
}
}
#[derive(Component)] #[derive(Component)]
pub struct CosmicAttrs(pub AttrsOwned); pub struct CosmicAttrs(pub AttrsOwned);
...@@ -373,14 +342,40 @@ pub struct CosmicEditHistory { ...@@ -373,14 +342,40 @@ pub struct CosmicEditHistory {
pub current_edit: usize, pub current_edit: usize,
} }
#[derive(Default)] /// Resource struct that keeps track of the currently active editor entity.
pub enum CursorConfig { #[derive(Resource, Default, Deref, DerefMut)]
#[default] pub struct Focus(pub Option<Entity>);
Default,
Events, /// Resource struct that holds configuration options for cosmic fonts.
None, #[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 {
load_system_fonts: false,
font_bytes: Some(vec![fallback_font]),
fonts_dir_path: None,
}
}
}
#[derive(Resource)]
struct SwashCacheState {
swash_cache: SwashCache,
} }
#[derive(Resource)]
struct CursorBlinkTimer(pub Timer);
#[derive(Resource)]
struct CursorVisibility(pub bool);
/// Plugin struct that adds systems and initializes resources related to cosmic edit functionality. /// Plugin struct that adds systems and initializes resources related to cosmic edit functionality.
#[derive(Default)] #[derive(Default)]
pub struct CosmicEditPlugin { pub struct CosmicEditPlugin {
...@@ -397,7 +392,8 @@ impl Plugin for CosmicEditPlugin { ...@@ -397,7 +392,8 @@ impl Plugin for CosmicEditPlugin {
.add_systems( .add_systems(
Update, Update,
( (
cosmic_edit_bevy_events, input_kb,
input_mouse,
blink_cursor, blink_cursor,
freeze_cursor_blink, freeze_cursor_blink,
hide_inactive_or_readonly_cursor, hide_inactive_or_readonly_cursor,
...@@ -438,32 +434,140 @@ impl Plugin for CosmicEditPlugin { ...@@ -438,32 +434,140 @@ impl Plugin for CosmicEditPlugin {
} }
} }
/// Resource struct that keeps track of the currently active editor entity. fn save_edit_history(
#[derive(Resource, Default, Deref, DerefMut)] editor: &mut Editor,
pub struct Focus(pub Option<Entity>); attrs: &AttrsOwned,
edit_history: &mut CosmicEditHistory,
) {
let edits = &edit_history.edits;
let current_lines = get_text_spans(editor.buffer(), attrs.clone());
let current_edit = edit_history.current_edit;
let mut new_edits = VecDeque::new();
new_edits.extend(edits.iter().take(current_edit + 1).cloned());
// remove old edits
if new_edits.len() > 1000 {
new_edits.drain(0..100);
}
new_edits.push_back(EditHistoryItem {
cursor: editor.cursor(),
lines: current_lines,
});
let len = new_edits.len();
*edit_history = CosmicEditHistory {
edits: new_edits,
current_edit: len - 1,
};
}
/// Resource struct that holds configuration options for cosmic fonts. /// Adds the font system to each editor when added
#[derive(Resource, Clone)] fn cosmic_editor_builder(
pub struct CosmicFontConfig { mut added_editors: Query<(Entity, &CosmicMetrics), Added<CosmicText>>,
pub fonts_dir_path: Option<PathBuf>, mut font_system: ResMut<CosmicFontSystem>,
pub font_bytes: Option<Vec<&'static [u8]>>, mut commands: Commands,
pub load_system_fonts: bool, // caution: this can be relatively slow ) {
for (entity, metrics) in added_editors.iter_mut() {
let buffer = Buffer::new(
&mut font_system.0,
Metrics::new(metrics.font_size, metrics.line_height).scale(metrics.scale_factor),
);
// buffer.set_wrap(&mut font_system.0, cosmic_text::Wrap::None);
let editor = Editor::new(buffer);
commands.entity(entity).insert(CosmicEditor(editor));
commands.entity(entity).insert(CosmicEditHistory::default());
commands.entity(entity).insert(XOffset(None));
}
} }
impl Default for CosmicFontConfig { fn create_cosmic_font_system(cosmic_font_config: CosmicFontConfig) -> FontSystem {
fn default() -> Self { let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
let fallback_font = include_bytes!("./font/FiraMono-Regular-subset.ttf"); let mut db = cosmic_text::fontdb::Database::new();
Self { if let Some(dir_path) = cosmic_font_config.fonts_dir_path.clone() {
load_system_fonts: false, db.load_fonts_dir(dir_path);
font_bytes: Some(vec![fallback_font]), }
fonts_dir_path: None, 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)
} }
#[derive(Resource)] fn on_scale_factor_change(
struct SwashCacheState { mut scale_factor_changed: EventReader<WindowScaleFactorChanged>,
swash_cache: SwashCache, mut cosmic_query: Query<(&mut CosmicEditor, &mut CosmicMetrics)>,
mut font_system: ResMut<CosmicFontSystem>,
) {
if !scale_factor_changed.is_empty() {
let new_scale_factor = scale_factor_changed.iter().last().unwrap().scale_factor as f32;
for (mut editor, metrics) in &mut cosmic_query.iter_mut() {
let font_system = &mut font_system.0;
let metrics =
Metrics::new(metrics.font_size, metrics.line_height).scale(new_scale_factor);
editor.0.buffer_mut().set_metrics(font_system, metrics);
editor.0.buffer_mut().set_redraw(true);
}
}
}
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
}
})
}
})
}
/// Updates editor buffer when text component changes
fn update_buffer_text(
mut editor_q: Query<
(
&mut CosmicEditor,
&mut CosmicText,
&CosmicAttrs,
&CosmicMaxChars,
&CosmicMaxLines,
),
Changed<CosmicText>,
>,
mut font_system: ResMut<CosmicFontSystem>,
) {
for (mut editor, text, attrs, max_chars, max_lines) in editor_q.iter_mut() {
let text = trim_text(text.to_owned(), max_chars.0, max_lines.0);
editor.set_text(text, attrs.0.clone(), &mut font_system.0);
}
} }
fn trim_text(text: CosmicText, max_chars: usize, max_lines: usize) -> CosmicText { fn trim_text(text: CosmicText, max_chars: usize, max_lines: usize) -> CosmicText {
...@@ -548,78 +652,6 @@ fn trim_text(text: CosmicText, max_chars: usize, max_lines: usize) -> CosmicText ...@@ -548,78 +652,6 @@ fn trim_text(text: CosmicText, max_chars: usize, max_lines: usize) -> CosmicText
} }
} }
} }
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)
}
fn on_scale_factor_change(
mut scale_factor_changed: EventReader<WindowScaleFactorChanged>,
mut cosmic_query: Query<(&mut CosmicEditor, &mut CosmicMetrics)>,
mut font_system: ResMut<CosmicFontSystem>,
) {
if !scale_factor_changed.is_empty() {
let new_scale_factor = scale_factor_changed.iter().last().unwrap().scale_factor as f32;
for (mut editor, metrics) in &mut cosmic_query.iter_mut() {
let font_system = &mut font_system.0;
let metrics =
Metrics::new(metrics.font_size, metrics.line_height).scale(new_scale_factor);
editor.0.buffer_mut().set_metrics(font_system, metrics);
editor.0.buffer_mut().set_redraw(true);
}
}
}
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
}
})
}
})
}
/// Returns texts from a MultiStyle buffer /// Returns texts from a MultiStyle buffer
pub fn get_text_spans( pub fn get_text_spans(
buffer: &Buffer, buffer: &Buffer,
...@@ -659,750 +691,28 @@ pub fn get_text_spans( ...@@ -659,750 +691,28 @@ pub fn get_text_spans(
spans spans
} }
fn save_edit_history( fn get_text_size(buffer: &Buffer) -> (f32, f32) {
editor: &mut Editor, if buffer.layout_runs().count() == 0 {
attrs: &AttrsOwned, return (0., buffer.metrics().line_height);
edit_history: &mut CosmicEditHistory,
) {
let edits = &edit_history.edits;
let current_lines = get_text_spans(editor.buffer(), attrs.clone());
let current_edit = edit_history.current_edit;
let mut new_edits = VecDeque::new();
new_edits.extend(edits.iter().take(current_edit + 1).cloned());
// remove old edits
if new_edits.len() > 1000 {
new_edits.drain(0..100);
}
new_edits.push_back(EditHistoryItem {
cursor: editor.cursor(),
lines: current_lines,
});
let len = new_edits.len();
*edit_history = CosmicEditHistory {
edits: new_edits,
current_edit: len - 1,
};
}
fn get_text_size(buffer: &Buffer) -> (f32, f32) {
if buffer.layout_runs().count() == 0 {
return (0., buffer.metrics().line_height);
}
let width = buffer
.layout_runs()
.map(|run| run.line_w)
.reduce(f32::max)
.unwrap();
let height = buffer.layout_runs().count() as f32 * buffer.metrics().line_height;
(width, height)
}
pub fn get_y_offset_center(widget_height: f32, buffer: &Buffer) -> i32 {
let (_, text_height) = get_text_size(buffer);
((widget_height - text_height) / 2.0) as i32
}
pub fn get_x_offset_center(widget_width: f32, buffer: &Buffer) -> i32 {
let (text_width, _) = get_text_size(buffer);
((widget_width - text_width) / 2.0) as i32
}
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
// the meat of the input management
fn cosmic_edit_bevy_events(
windows: Query<&Window, With<PrimaryWindow>>,
active_editor: Res<Focus>,
keys: Res<Input<KeyCode>>,
mut char_evr: EventReader<ReceivedCharacter>,
buttons: Res<Input<MouseButton>>,
mut cosmic_edit_query: Query<
(
&mut CosmicEditor,
&mut CosmicEditHistory,
&GlobalTransform,
&CosmicAttrs,
&CosmicTextPosition,
&CosmicMaxLines,
&CosmicMaxChars,
Entity,
&XOffset,
),
With<CosmicEditor>,
>,
mut evw_changed: EventWriter<CosmicTextChanged>,
readonly_query: Query<&ReadOnly>,
node_query: Query<&mut Node>,
sprite_query: Query<&mut Sprite>,
mut font_system: ResMut<CosmicFontSystem>,
mut is_deleting: Local<bool>,
mut scroll_evr: EventReader<MouseWheel>,
mut edits_duration: Local<Option<Duration>>,
mut undoredo_duration: Local<Option<Duration>>,
camera_q: Query<(&Camera, &GlobalTransform)>,
) {
let primary_window = windows.single();
let scale_factor = primary_window.scale_factor() as f32;
let (camera, camera_transform) = camera_q.iter().find(|(c, _)| c.is_active).unwrap();
for (
mut editor,
mut edit_history,
node_transform,
attrs,
text_position,
max_lines,
max_chars,
entity,
x_offset,
) in &mut cosmic_edit_query.iter_mut()
{
let readonly = readonly_query.get(entity).is_ok();
let (width, height, is_ui_node) = match node_query.get(entity) {
Ok(node) => (node.size().x, node.size().y, true),
Err(_) => {
let sprite = sprite_query.get(entity).unwrap();
let size = sprite.custom_size.unwrap();
(size.x, size.y, false)
}
};
let attrs = &attrs.0;
if active_editor.0 == Some(entity) {
let now_ms = get_timestamp();
#[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]);
let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);
#[cfg(target_os = "macos")]
let option = keys.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]);
// if shift key is pressed
let already_has_selection = editor.0.select_opt().is_some();
if shift && !already_has_selection {
let cursor = editor.0.cursor();
editor.0.set_select_opt(Some(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::Left) {
editor.0.action(&mut font_system.0, Action::PreviousWord);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if should_jump && keys.just_pressed(KeyCode::Right) {
editor.0.action(&mut font_system.0, Action::NextWord);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if should_jump && keys.just_pressed(KeyCode::Home) {
editor.0.action(&mut font_system.0, Action::BufferStart);
// there's a bug with cosmic text where it doesn't update the visual cursor for this action
// TODO: fix upstream
editor.0.buffer_mut().set_redraw(true);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if should_jump && keys.just_pressed(KeyCode::End) {
editor.0.action(&mut font_system.0, Action::BufferEnd);
// there's a bug with cosmic text where it doesn't update the visual cursor for this action
// TODO: fix upstream
editor.0.buffer_mut().set_redraw(true);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::Left) {
editor.0.action(&mut font_system.0, Action::Left);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::Right) {
editor.0.action(&mut font_system.0, Action::Right);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::Up) {
editor.0.action(&mut font_system.0, Action::Up);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::Down) {
editor.0.action(&mut font_system.0, Action::Down);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if !readonly && keys.just_pressed(KeyCode::Back) {
#[cfg(target_arch = "wasm32")]
editor.0.action(&mut font_system.0, Action::Backspace);
*is_deleting = true;
}
if !readonly && keys.just_released(KeyCode::Back) {
*is_deleting = false;
}
if !readonly && keys.just_pressed(KeyCode::Delete) {
editor.0.action(&mut font_system.0, Action::Delete);
}
if keys.just_pressed(KeyCode::Escape) {
editor.0.action(&mut font_system.0, Action::Escape);
}
if command && keys.just_pressed(KeyCode::A) {
editor.0.action(&mut font_system.0, Action::BufferEnd);
let current_cursor = editor.0.cursor();
editor.0.set_select_opt(Some(Cursor {
line: 0,
index: 0,
affinity: current_cursor.affinity,
color: current_cursor.color,
}));
return;
}
if keys.just_pressed(KeyCode::Home) {
editor.0.action(&mut font_system.0, Action::Home);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::End) {
editor.0.action(&mut font_system.0, Action::End);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::PageUp) {
editor.0.action(&mut font_system.0, Action::PageUp);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
if keys.just_pressed(KeyCode::PageDown) {
editor.0.action(&mut font_system.0, Action::PageDown);
if !shift {
editor.0.set_select_opt(None);
}
return;
}
// redo
#[cfg(not(target_os = "windows"))]
let requested_redo = command && shift && keys.just_pressed(KeyCode::Z);
#[cfg(target_os = "windows")]
let requested_redo = command && keys.just_pressed(KeyCode::Y);
if !readonly && requested_redo {
let edits = &edit_history.edits;
if edits.is_empty() {
return;
}
if edit_history.current_edit == edits.len() - 1 {
return;
}
let idx = edit_history.current_edit + 1;
if let Some(current_edit) = edits.get(idx) {
editor.0.buffer_mut().lines.clear();
for line in current_edit.lines.iter() {
let mut line_text = String::new();
let mut attrs_list = AttrsList::new(attrs.as_attrs());
for (text, attrs) in line.iter() {
let start = line_text.len();
line_text.push_str(text);
let end = line_text.len();
attrs_list.add_span(start..end, attrs.as_attrs());
}
editor.0.buffer_mut().lines.push(BufferLine::new(
line_text,
attrs_list,
Shaping::Advanced,
));
}
editor.0.set_cursor(current_edit.cursor);
editor.0.buffer_mut().set_redraw(true);
edit_history.current_edit += 1;
}
*undoredo_duration = Some(Duration::from_millis(now_ms as u64));
evw_changed.send(CosmicTextChanged((entity, editor.get_text())));
return;
}
// undo
let requested_undo = command && keys.just_pressed(KeyCode::Z);
if !readonly && requested_undo {
let edits = &edit_history.edits;
if edits.is_empty() {
return;
}
if edit_history.current_edit <= 1 {
return;
}
let idx = edit_history.current_edit - 1;
if let Some(current_edit) = edits.get(idx) {
editor.0.buffer_mut().lines.clear();
for line in current_edit.lines.iter() {
let mut line_text = String::new();
let mut attrs_list = AttrsList::new(attrs.as_attrs());
for (text, attrs) in line.iter() {
let start = line_text.len();
line_text.push_str(text);
let end = line_text.len();
attrs_list.add_span(start..end, attrs.as_attrs());
}
editor.0.buffer_mut().lines.push(BufferLine::new(
line_text,
attrs_list,
Shaping::Advanced,
));
}
editor.0.set_cursor(current_edit.cursor);
editor.0.buffer_mut().set_redraw(true);
edit_history.current_edit -= 1;
}
*undoredo_duration = Some(Duration::from_millis(now_ms as u64));
evw_changed.send(CosmicTextChanged((entity, editor.get_text())));
return;
}
let mut is_clipboard = false;
#[cfg(not(target_arch = "wasm32"))]
{
if let Ok(mut clipboard) = arboard::Clipboard::new() {
if command && keys.just_pressed(KeyCode::C) {
if let Some(text) = editor.0.copy_selection() {
clipboard.set_text(text).unwrap();
return;
}
}
if !readonly && command && keys.just_pressed(KeyCode::X) {
if let Some(text) = editor.0.copy_selection() {
clipboard.set_text(text).unwrap();
editor.0.delete_selection();
}
is_clipboard = true;
}
if !readonly && command && keys.just_pressed(KeyCode::V) {
if let Ok(text) = clipboard.get_text() {
for c in text.chars() {
if max_chars.0 == 0 || editor.get_text().len() < max_chars.0 {
if c == 0xA as char {
if max_lines.0 == 0
|| editor.0.buffer().lines.len() < max_lines.0
{
editor.0.action(&mut font_system.0, Action::Insert(c));
}
} else {
editor.0.action(&mut font_system.0, Action::Insert(c));
}
}
}
}
is_clipboard = true;
}
}
}
let (padding_x, padding_y) = match text_position {
CosmicTextPosition::Center => (
get_x_offset_center(width * scale_factor, editor.0.buffer()),
get_y_offset_center(height * scale_factor, editor.0.buffer()),
),
CosmicTextPosition::TopLeft { padding } => (*padding, *padding),
CosmicTextPosition::Left { padding } => (
*padding,
get_y_offset_center(height * scale_factor, editor.0.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) {
if let Some(node_cursor_pos) = get_node_cursor_pos(
primary_window,
node_transform,
(width, height),
is_ui_node,
camera,
camera_transform,
) {
let (mut x, y) = point(node_cursor_pos);
x += x_offset.0.unwrap_or((0., 0.)).0 as i32;
if shift {
editor.0.action(&mut font_system.0, Action::Drag { x, y });
} else {
editor.0.action(&mut font_system.0, Action::Click { x, y });
}
}
return;
}
if buttons.pressed(MouseButton::Left) {
if let Some(node_cursor_pos) = get_node_cursor_pos(
primary_window,
node_transform,
(width, height),
is_ui_node,
camera,
camera_transform,
) {
let (mut x, y) = point(node_cursor_pos);
x += x_offset.0.unwrap_or((0., 0.)).0 as i32;
if active_editor.is_changed() && !shift {
editor.0.action(&mut font_system.0, Action::Click { x, y });
} else {
editor.0.action(&mut font_system.0, Action::Drag { x, y });
}
}
return;
}
for ev in scroll_evr.iter() {
match ev.unit {
MouseScrollUnit::Line => {
editor.0.action(
&mut font_system.0,
Action::Scroll {
lines: -ev.y as i32,
},
);
}
MouseScrollUnit::Pixel => {
let line_height = editor.0.buffer().metrics().line_height;
editor.0.action(
&mut font_system.0,
Action::Scroll {
lines: -(ev.y / line_height) as i32,
},
);
}
}
}
if readonly {
return;
}
// fix for issue #8
if let Some(select) = editor.0.select_opt() {
if editor.0.cursor().line == select.line && editor.0.cursor().index == select.index
{
editor.0.set_select_opt(None);
}
}
let mut is_edit = is_clipboard;
let mut is_return = false;
if keys.just_pressed(KeyCode::Return) {
is_return = true;
if (max_lines.0 == 0 || editor.0.buffer().lines.len() < max_lines.0)
&& (max_chars.0 == 0 || editor.get_text().len() < max_chars.0)
{
// to have new line on wasm rather than E
is_edit = true;
editor.0.action(&mut font_system.0, Action::Insert('\n'));
}
}
if !(is_clipboard || is_return) {
for char_ev in char_evr.iter() {
is_edit = true;
if *is_deleting {
editor.0.action(&mut font_system.0, Action::Backspace);
} else if max_chars.0 == 0 || editor.get_text().len() < max_chars.0 {
editor
.0
.action(&mut font_system.0, Action::Insert(char_ev.char));
}
}
}
if !is_edit {
return;
}
evw_changed.send(CosmicTextChanged((entity, editor.get_text())));
if let Some(last_edit_duration) = *edits_duration {
if Duration::from_millis(now_ms as u64) - last_edit_duration
> Duration::from_millis(150)
{
save_edit_history(&mut editor.0, attrs, &mut edit_history);
*edits_duration = Some(Duration::from_millis(now_ms as u64));
}
} else {
save_edit_history(&mut editor.0, attrs, &mut edit_history);
*edits_duration = Some(Duration::from_millis(now_ms as u64));
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn redraw_buffer_common(
mode: &CosmicMode,
x_offset: &mut XOffset,
images: &mut ResMut<Assets<Image>>,
swash_cache_state: &mut ResMut<SwashCacheState>,
editor: &mut Editor,
attrs: &CosmicAttrs,
background_image: Option<Handle<Image>>,
fill_color: Color,
cosmic_canvas_img_handle: &mut Handle<Image>,
text_position: &CosmicTextPosition,
font_system: &mut ResMut<CosmicFontSystem>,
scale_factor: f32,
original_width: f32,
original_height: f32,
) {
let widget_width = original_width * scale_factor;
let widget_height = original_height * scale_factor;
let swash_cache = &mut swash_cache_state.swash_cache;
let mut cursor_x = 0.;
if mode == &CosmicMode::InfiniteLine {
if let Some(line) = editor.buffer().layout_runs().next() {
for (idx, glyph) in line.glyphs.iter().enumerate() {
if editor.cursor().affinity == Affinity::Before {
if idx <= editor.cursor().index {
cursor_x += glyph.w;
}
} else if idx < editor.cursor().index {
cursor_x += glyph.w;
} else {
break;
}
}
}
}
if mode == &CosmicMode::InfiniteLine && x_offset.0.is_none() {
let padding_x = match text_position {
CosmicTextPosition::Center => get_x_offset_center(widget_width, editor.buffer()),
CosmicTextPosition::TopLeft { padding } => *padding,
CosmicTextPosition::Left { padding } => *padding,
};
*x_offset = XOffset(Some((0., widget_width - 2. * padding_x as f32)));
}
if let Some((x_min, x_max)) = x_offset.0 {
if cursor_x > x_max {
let diff = cursor_x - x_max;
*x_offset = XOffset(Some((x_min + diff, cursor_x)));
}
if cursor_x < x_min {
let diff = x_min - cursor_x;
*x_offset = XOffset(Some((cursor_x, x_max - diff)));
}
}
let font_color = attrs
.0
.color_opt
.unwrap_or(cosmic_text::Color::rgb(0, 0, 0));
let mut pixels = vec![0; widget_width as usize * widget_height as usize * 4];
if let Some(bg_image) = background_image {
if let Some(image) = images.get(&bg_image) {
let mut dynamic_image = image.clone().try_into_dynamic().unwrap();
if image.size().x != widget_width || image.size().y != widget_height {
dynamic_image = dynamic_image.resize_to_fill(
widget_width as u32,
widget_height as u32,
FilterType::Triangle,
);
}
for (i, (_, _, rgba)) in dynamic_image.pixels().enumerate() {
if let Some(p) = pixels.get_mut(i * 4..(i + 1) * 4) {
p[0] = rgba[0];
p[1] = rgba[1];
p[2] = rgba[2];
p[3] = rgba[3];
}
}
}
} else {
let bg = fill_color;
for pixel in pixels.chunks_exact_mut(4) {
pixel[0] = (bg.r() * 255.) as u8; // Red component
pixel[1] = (bg.g() * 255.) as u8; // Green component
pixel[2] = (bg.b() * 255.) as u8; // Blue component
pixel[3] = (bg.a() * 255.) as u8; // Alpha component
}
}
let (padding_x, padding_y) = match text_position {
CosmicTextPosition::Center => (
get_x_offset_center(widget_width, editor.buffer()),
get_y_offset_center(widget_height, editor.buffer()),
),
CosmicTextPosition::TopLeft { padding } => (*padding, *padding),
CosmicTextPosition::Left { padding } => (
*padding,
get_y_offset_center(widget_height, editor.buffer()),
),
};
editor.draw(
&mut font_system.0,
swash_cache,
font_color,
|x, y, w, h, color| {
for row in 0..h as i32 {
for col in 0..w as i32 {
draw_pixel(
&mut pixels,
widget_width as i32,
widget_height as i32,
x + col + padding_x - x_offset.0.unwrap_or((0., 0.)).0 as i32,
y + row + padding_y,
color,
);
}
}
},
);
if let Some(prev_image) = images.get_mut(cosmic_canvas_img_handle) {
if *cosmic_canvas_img_handle == bevy::render::texture::DEFAULT_IMAGE_HANDLE.typed() {
let mut prev_image = prev_image.clone();
prev_image.data.clear();
prev_image.data.extend_from_slice(pixels.as_slice());
prev_image.resize(Extent3d {
width: widget_width as u32,
height: widget_height as u32,
depth_or_array_layers: 1,
});
let handle_id: HandleId = HandleId::random::<Image>();
let new_handle: Handle<Image> = Handle::weak(handle_id);
let new_handle = images.set(new_handle, prev_image);
*cosmic_canvas_img_handle = new_handle;
} else {
prev_image.data.clear();
prev_image.data.extend_from_slice(pixels.as_slice());
prev_image.resize(Extent3d {
width: widget_width as u32,
height: widget_height as u32,
depth_or_array_layers: 1,
});
}
}
editor.buffer_mut().set_redraw(false);
}
fn cosmic_edit_redraw_buffer_ui(
windows: Query<&Window, With<PrimaryWindow>>,
mut images: ResMut<Assets<Image>>,
mut swash_cache_state: ResMut<SwashCacheState>,
mut cosmic_edit_query: Query<(
&mut CosmicEditor,
&CosmicAttrs,
&CosmicBackground,
&FillColor,
&CosmicTextPosition,
&mut UiImage,
&Node,
&mut XOffset,
&mut Style,
&CosmicMode,
)>,
mut font_system: ResMut<CosmicFontSystem>,
) {
let primary_window = windows.single();
let scale = primary_window.scale_factor() as f32;
for (
mut editor,
attrs,
background_image,
fill_color,
text_position,
mut img,
node,
mut x_offset,
mut style,
mode,
) in &mut cosmic_edit_query.iter_mut()
{
editor.0.shape_as_needed(&mut font_system.0);
if !editor.0.buffer().redraw() {
continue;
}
let width = node.size().x;
let mut height = node.size().y;
let widget_height = height * scale;
let widget_width = width * scale;
let (buffer_width, buffer_height) = match mode {
CosmicMode::InfiniteLine => (f32::MAX, widget_height),
CosmicMode::AutoHeight => (widget_width, (i32::MAX / 2) as f32),
CosmicMode::Wrap => (widget_width, widget_height),
};
editor
.0
.buffer_mut()
.set_size(&mut font_system.0, buffer_width, buffer_height);
if mode == &CosmicMode::AutoHeight {
let text_size = get_text_size(editor.0.buffer());
let text_height = (text_size.1 / primary_window.scale_factor() as f32) + 30.;
if text_height > height {
height = text_height;
style.height = Val::Px(height);
}
}
redraw_buffer_common(
mode,
&mut x_offset,
&mut images,
&mut swash_cache_state,
&mut editor.0,
attrs,
background_image.0.clone(),
fill_color.0,
&mut img.texture,
text_position,
&mut font_system,
scale,
width,
height,
);
} }
let width = buffer
.layout_runs()
.map(|run| run.line_w)
.reduce(f32::max)
.unwrap();
let height = buffer.layout_runs().count() as f32 * buffer.metrics().line_height;
(width, height)
} }
#[derive(Resource)] pub fn get_y_offset_center(widget_height: f32, buffer: &Buffer) -> i32 {
struct CursorBlinkTimer(pub Timer); let (_, text_height) = get_text_size(buffer);
((widget_height - text_height) / 2.0) as i32
}
#[derive(Resource)] pub fn get_x_offset_center(widget_width: f32, buffer: &Buffer) -> i32 {
struct CursorVisibility(pub bool); let (text_width, _) = get_text_size(buffer);
((widget_width - text_width) / 2.0) as i32
}
fn blink_cursor( fn blink_cursor(
mut visibility: ResMut<CursorVisibility>, mut visibility: ResMut<CursorVisibility>,
...@@ -1511,6 +821,88 @@ fn clear_inactive_selection( ...@@ -1511,6 +821,88 @@ fn clear_inactive_selection(
} }
} }
fn cosmic_edit_redraw_buffer_ui(
windows: Query<&Window, With<PrimaryWindow>>,
mut images: ResMut<Assets<Image>>,
mut swash_cache_state: ResMut<SwashCacheState>,
mut cosmic_edit_query: Query<(
&mut CosmicEditor,
&CosmicAttrs,
&CosmicBackground,
&FillColor,
&CosmicTextPosition,
&mut UiImage,
&Node,
&mut XOffset,
&mut Style,
&CosmicMode,
)>,
mut font_system: ResMut<CosmicFontSystem>,
) {
let primary_window = windows.single();
let scale = primary_window.scale_factor() as f32;
for (
mut editor,
attrs,
background_image,
fill_color,
text_position,
mut img,
node,
mut x_offset,
mut style,
mode,
) in &mut cosmic_edit_query.iter_mut()
{
editor.0.shape_as_needed(&mut font_system.0);
if !editor.0.buffer().redraw() {
continue;
}
let width = node.size().x;
let mut height = node.size().y;
let widget_height = height * scale;
let widget_width = width * scale;
let (buffer_width, buffer_height) = match mode {
CosmicMode::InfiniteLine => (f32::MAX, widget_height),
CosmicMode::AutoHeight => (widget_width, (i32::MAX / 2) as f32),
CosmicMode::Wrap => (widget_width, widget_height),
};
editor
.0
.buffer_mut()
.set_size(&mut font_system.0, buffer_width, buffer_height);
if mode == &CosmicMode::AutoHeight {
let text_size = get_text_size(editor.0.buffer());
let text_height = (text_size.1 / primary_window.scale_factor() as f32) + 30.;
if text_height > height {
height = text_height;
style.height = Val::Px(height);
}
}
redraw_buffer_common(
mode,
&mut x_offset,
&mut images,
&mut swash_cache_state,
&mut editor.0,
attrs,
background_image.0.clone(),
fill_color.0,
&mut img.texture,
text_position,
&mut font_system,
scale,
width,
height,
);
}
}
fn cosmic_edit_redraw_buffer( fn cosmic_edit_redraw_buffer(
windows: Query<&Window, With<PrimaryWindow>>, windows: Query<&Window, With<PrimaryWindow>>,
mut images: ResMut<Assets<Image>>, mut images: ResMut<Assets<Image>>,
...@@ -1590,6 +982,158 @@ fn cosmic_edit_redraw_buffer( ...@@ -1590,6 +982,158 @@ fn cosmic_edit_redraw_buffer(
} }
} }
#[allow(clippy::too_many_arguments)]
fn redraw_buffer_common(
mode: &CosmicMode,
x_offset: &mut XOffset,
images: &mut ResMut<Assets<Image>>,
swash_cache_state: &mut ResMut<SwashCacheState>,
editor: &mut Editor,
attrs: &CosmicAttrs,
background_image: Option<Handle<Image>>,
fill_color: Color,
cosmic_canvas_img_handle: &mut Handle<Image>,
text_position: &CosmicTextPosition,
font_system: &mut ResMut<CosmicFontSystem>,
scale_factor: f32,
original_width: f32,
original_height: f32,
) {
let widget_width = original_width * scale_factor;
let widget_height = original_height * scale_factor;
let swash_cache = &mut swash_cache_state.swash_cache;
let mut cursor_x = 0.;
if mode == &CosmicMode::InfiniteLine {
if let Some(line) = editor.buffer().layout_runs().next() {
for (idx, glyph) in line.glyphs.iter().enumerate() {
if editor.cursor().affinity == Affinity::Before {
if idx <= editor.cursor().index {
cursor_x += glyph.w;
}
} else if idx < editor.cursor().index {
cursor_x += glyph.w;
} else {
break;
}
}
}
}
if mode == &CosmicMode::InfiniteLine && x_offset.0.is_none() {
let padding_x = match text_position {
CosmicTextPosition::Center => get_x_offset_center(widget_width, editor.buffer()),
CosmicTextPosition::TopLeft { padding } => *padding,
CosmicTextPosition::Left { padding } => *padding,
};
*x_offset = XOffset(Some((0., widget_width - 2. * padding_x as f32)));
}
if let Some((x_min, x_max)) = x_offset.0 {
if cursor_x > x_max {
let diff = cursor_x - x_max;
*x_offset = XOffset(Some((x_min + diff, cursor_x)));
}
if cursor_x < x_min {
let diff = x_min - cursor_x;
*x_offset = XOffset(Some((cursor_x, x_max - diff)));
}
}
let font_color = attrs
.0
.color_opt
.unwrap_or(cosmic_text::Color::rgb(0, 0, 0));
let mut pixels = vec![0; widget_width as usize * widget_height as usize * 4];
if let Some(bg_image) = background_image {
if let Some(image) = images.get(&bg_image) {
let mut dynamic_image = image.clone().try_into_dynamic().unwrap();
if image.size().x != widget_width || image.size().y != widget_height {
dynamic_image = dynamic_image.resize_to_fill(
widget_width as u32,
widget_height as u32,
FilterType::Triangle,
);
}
for (i, (_, _, rgba)) in dynamic_image.pixels().enumerate() {
if let Some(p) = pixels.get_mut(i * 4..(i + 1) * 4) {
p[0] = rgba[0];
p[1] = rgba[1];
p[2] = rgba[2];
p[3] = rgba[3];
}
}
}
} else {
let bg = fill_color;
for pixel in pixels.chunks_exact_mut(4) {
pixel[0] = (bg.r() * 255.) as u8; // Red component
pixel[1] = (bg.g() * 255.) as u8; // Green component
pixel[2] = (bg.b() * 255.) as u8; // Blue component
pixel[3] = (bg.a() * 255.) as u8; // Alpha component
}
}
let (padding_x, padding_y) = match text_position {
CosmicTextPosition::Center => (
get_x_offset_center(widget_width, editor.buffer()),
get_y_offset_center(widget_height, editor.buffer()),
),
CosmicTextPosition::TopLeft { padding } => (*padding, *padding),
CosmicTextPosition::Left { padding } => (
*padding,
get_y_offset_center(widget_height, editor.buffer()),
),
};
editor.draw(
&mut font_system.0,
swash_cache,
font_color,
|x, y, w, h, color| {
for row in 0..h as i32 {
for col in 0..w as i32 {
draw_pixel(
&mut pixels,
widget_width as i32,
widget_height as i32,
x + col + padding_x - x_offset.0.unwrap_or((0., 0.)).0 as i32,
y + row + padding_y,
color,
);
}
}
},
);
if let Some(prev_image) = images.get_mut(cosmic_canvas_img_handle) {
if *cosmic_canvas_img_handle == bevy::render::texture::DEFAULT_IMAGE_HANDLE.typed() {
let mut prev_image = prev_image.clone();
prev_image.data.clear();
prev_image.data.extend_from_slice(pixels.as_slice());
prev_image.resize(Extent3d {
width: widget_width as u32,
height: widget_height as u32,
depth_or_array_layers: 1,
});
let handle_id: HandleId = HandleId::random::<Image>();
let new_handle: Handle<Image> = Handle::weak(handle_id);
let new_handle = images.set(new_handle, prev_image);
*cosmic_canvas_img_handle = new_handle;
} else {
prev_image.data.clear();
prev_image.data.extend_from_slice(pixels.as_slice());
prev_image.resize(Extent3d {
width: widget_width as u32,
height: widget_height as u32,
depth_or_array_layers: 1,
});
}
}
editor.buffer_mut().set_redraw(false);
}
fn draw_pixel( fn draw_pixel(
buffer: &mut [u8], buffer: &mut [u8],
width: i32, width: i32,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment