use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicU64, Ordering};
use crate::dom::{DomId, DomNodeId, NodeId};
use crate::geom::{LogicalPosition, LogicalRect};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ContentIndex {
pub run_index: u32,
pub item_index: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub struct GraphemeClusterId {
pub source_run: u32,
pub start_byte_in_run: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
#[repr(C)]
pub enum CursorAffinity {
Leading,
Trailing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
#[repr(C)]
pub struct TextCursor {
pub cluster_id: GraphemeClusterId,
pub affinity: CursorAffinity,
}
impl_option!(
TextCursor,
OptionTextCursor,
[Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd]
);
#[derive(Debug, PartialOrd, Ord, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct SelectionRange {
pub start: TextCursor,
pub end: TextCursor,
}
impl_option!(
SelectionRange,
OptionSelectionRange,
[Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd]
);
impl_vec!(SelectionRange, SelectionRangeVec, SelectionRangeVecDestructor, SelectionRangeVecDestructorType, SelectionRangeVecSlice, OptionSelectionRange);
impl_vec_debug!(SelectionRange, SelectionRangeVec);
impl_vec_clone!(
SelectionRange,
SelectionRangeVec,
SelectionRangeVecDestructor
);
impl_vec_partialeq!(SelectionRange, SelectionRangeVec);
impl_vec_partialord!(SelectionRange, SelectionRangeVec);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C, u8)]
pub enum Selection {
Cursor(TextCursor),
Range(SelectionRange),
}
impl_option!(
Selection,
OptionSelection,
[Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord]
);
impl_vec!(Selection, SelectionVec, SelectionVecDestructor, SelectionVecDestructorType, SelectionVecSlice, OptionSelection);
impl_vec_debug!(Selection, SelectionVec);
impl_vec_clone!(Selection, SelectionVec, SelectionVecDestructor);
impl_vec_partialeq!(Selection, SelectionVec);
impl_vec_partialord!(Selection, SelectionVec);
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct SelectionState {
pub selections: SelectionVec,
pub node_id: DomNodeId,
}
impl SelectionState {
pub fn add(&mut self, new_selection: Selection) {
let mut selections: Vec<Selection> = self.selections.as_ref().to_vec();
selections.push(new_selection);
selections.sort_unstable();
selections.dedup(); self.selections = selections.into();
}
}
impl_option!(
SelectionState,
OptionSelectionState,
copy = false,
clone = false,
[Debug, Clone, PartialEq]
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub struct SelectionId {
pub inner: u64,
}
impl SelectionId {
pub fn new() -> Self {
static COUNTER: AtomicU64 = AtomicU64::new(1);
SelectionId { inner: COUNTER.fetch_add(1, Ordering::Relaxed) }
}
}
impl Default for SelectionId {
fn default() -> Self {
Self::new()
}
}
impl_option!(
SelectionId,
OptionSelectionId,
[Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord]
);
impl_vec!(SelectionId, SelectionIdVec, SelectionIdVecDestructor, SelectionIdVecDestructorType, SelectionIdVecSlice, OptionSelectionId);
impl_vec_debug!(SelectionId, SelectionIdVec);
impl_vec_clone!(SelectionId, SelectionIdVec, SelectionIdVecDestructor);
impl_vec_partialeq!(SelectionId, SelectionIdVec);
impl_vec_partialord!(SelectionId, SelectionIdVec);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct IdentifiedSelection {
pub id: SelectionId,
pub selection: Selection,
}
impl_option!(
IdentifiedSelection,
OptionIdentifiedSelection,
[Debug, Clone, Copy, PartialEq, Eq, Hash]
);
impl_vec!(IdentifiedSelection, IdentifiedSelectionVec, IdentifiedSelectionVecDestructor, IdentifiedSelectionVecDestructorType, IdentifiedSelectionVecSlice, OptionIdentifiedSelection);
impl_vec_debug!(IdentifiedSelection, IdentifiedSelectionVec);
impl_vec_clone!(IdentifiedSelection, IdentifiedSelectionVec, IdentifiedSelectionVecDestructor);
impl_vec_partialeq!(IdentifiedSelection, IdentifiedSelectionVec);
#[derive(Debug, Clone, PartialEq)]
pub struct MultiCursorState {
pub selections: Vec<IdentifiedSelection>,
pub node_id: DomNodeId,
pub contenteditable_key: u64,
}
impl MultiCursorState {
pub fn new_with_cursor(cursor: TextCursor, node_id: DomNodeId, contenteditable_key: u64) -> Self {
let id = SelectionId::new();
Self {
selections: vec![IdentifiedSelection {
id,
selection: Selection::Cursor(cursor),
}],
node_id,
contenteditable_key,
}
}
#[must_use]
pub fn add_cursor(&mut self, cursor: TextCursor) -> SelectionId {
let id = SelectionId::new();
self.selections.push(IdentifiedSelection {
id,
selection: Selection::Cursor(cursor),
});
self.merge_overlapping();
id
}
#[must_use]
pub fn add_selection(&mut self, range: SelectionRange) -> SelectionId {
let id = SelectionId::new();
self.selections.push(IdentifiedSelection {
id,
selection: Selection::Range(range),
});
self.merge_overlapping();
id
}
#[must_use]
pub fn remove_selection(&mut self, id: SelectionId) -> bool {
let len_before = self.selections.len();
self.selections.retain(|s| s.id != id);
self.selections.len() < len_before
}
pub fn get_primary(&self) -> Option<&IdentifiedSelection> {
self.selections.last()
}
pub fn get_primary_mut(&mut self) -> Option<&mut IdentifiedSelection> {
self.selections.last_mut()
}
pub fn get_primary_cursor(&self) -> Option<TextCursor> {
self.get_primary().map(|s| match &s.selection {
Selection::Cursor(c) => *c,
Selection::Range(r) => r.end,
})
}
pub fn to_selections(&self) -> Vec<Selection> {
self.selections.iter().map(|s| s.selection).collect()
}
pub fn update_from_edit_result(&mut self, new_selections: &[Selection]) {
let old_ids: Vec<SelectionId> = self.selections.iter().map(|s| s.id).collect();
self.selections.clear();
for (i, sel) in new_selections.iter().enumerate() {
let id = old_ids.get(i).copied().unwrap_or_else(SelectionId::new);
self.selections.push(IdentifiedSelection {
id,
selection: *sel,
});
}
}
pub fn set_single_cursor(&mut self, cursor: TextCursor) {
let id = if let Some(primary) = self.selections.last() {
primary.id
} else {
SelectionId::new()
};
self.selections.clear();
self.selections.push(IdentifiedSelection {
id,
selection: Selection::Cursor(cursor),
});
}
pub fn set_single_range(&mut self, range: SelectionRange) {
let id = if let Some(primary) = self.selections.last() {
primary.id
} else {
SelectionId::new()
};
self.selections.clear();
self.selections.push(IdentifiedSelection {
id,
selection: Selection::Range(range),
});
}
pub fn len(&self) -> usize {
self.selections.len()
}
pub fn is_empty(&self) -> bool {
self.selections.is_empty()
}
pub fn merge_overlapping(&mut self) {
if self.selections.len() <= 1 {
return;
}
self.selections.sort_by(|a, b| {
let pos_a = selection_start_pos(&a.selection);
let pos_b = selection_start_pos(&b.selection);
pos_a.cmp(&pos_b)
});
let mut merged: Vec<IdentifiedSelection> = Vec::with_capacity(self.selections.len());
for sel in self.selections.drain(..) {
if let Some(last) = merged.last_mut() {
let last_end = selection_end_pos(&last.selection);
let cur_start = selection_start_pos(&sel.selection);
if cur_start <= last_end {
let new_start = selection_start_pos(&last.selection);
let cur_end = selection_end_pos(&sel.selection);
let new_end = if cur_end > last_end { cur_end } else { last_end };
if new_start == new_end {
last.selection = Selection::Cursor(new_start);
} else {
last.selection = Selection::Range(SelectionRange {
start: new_start,
end: new_end,
});
}
last.id = sel.id;
continue;
}
}
merged.push(sel);
}
self.selections = merged;
}
pub fn move_all_cursors(
&mut self,
extend_selection: bool,
move_fn: impl Fn(&TextCursor) -> TextCursor,
) {
for sel in self.selections.iter_mut() {
match &sel.selection {
Selection::Cursor(c) => {
let new_cursor = move_fn(c);
if extend_selection {
if *c != new_cursor {
sel.selection = Selection::Range(SelectionRange {
start: *c,
end: new_cursor,
});
}
} else {
sel.selection = Selection::Cursor(new_cursor);
}
}
Selection::Range(r) => {
if extend_selection {
let new_end = move_fn(&r.end);
if r.start == new_end {
sel.selection = Selection::Cursor(r.start);
} else {
sel.selection = Selection::Range(SelectionRange {
start: r.start,
end: new_end,
});
}
} else {
let new_cursor = move_fn(&r.end);
sel.selection = Selection::Cursor(new_cursor);
}
}
}
}
self.merge_overlapping();
}
pub fn remap_node_ids(
&mut self,
dom_id: DomId,
node_id_map: &alloc::collections::BTreeMap<crate::dom::NodeId, crate::dom::NodeId>,
) {
if self.node_id.dom != dom_id {
return;
}
if let Some(old_node_id) = self.node_id.node.into_crate_internal() {
if let Some(&new_node_id) = node_id_map.get(&old_node_id) {
self.node_id.node = crate::styled_dom::NodeHierarchyItemId::from_crate_internal(Some(new_node_id));
} else {
self.selections.clear();
}
}
}
}
fn selection_start_pos(sel: &Selection) -> TextCursor {
match sel {
Selection::Cursor(c) => *c,
Selection::Range(r) => {
if r.start <= r.end { r.start } else { r.end }
}
}
}
fn selection_end_pos(sel: &Selection) -> TextCursor {
match sel {
Selection::Cursor(c) => *c,
Selection::Range(r) => {
if r.end >= r.start { r.end } else { r.start }
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SelectionAnchor {
pub ifc_root_node_id: NodeId,
pub cursor: TextCursor,
pub char_bounds: LogicalRect,
pub mouse_position: LogicalPosition,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SelectionFocus {
pub ifc_root_node_id: NodeId,
pub cursor: TextCursor,
pub mouse_position: LogicalPosition,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TextSelection {
pub dom_id: DomId,
pub anchor: SelectionAnchor,
pub focus: SelectionFocus,
pub affected_nodes: BTreeMap<NodeId, SelectionRange>,
pub is_forward: bool,
}
impl TextSelection {
pub fn new_collapsed(
dom_id: DomId,
ifc_root_node_id: NodeId,
cursor: TextCursor,
char_bounds: LogicalRect,
mouse_position: LogicalPosition,
) -> Self {
let anchor = SelectionAnchor {
ifc_root_node_id,
cursor,
char_bounds,
mouse_position,
};
let focus = SelectionFocus {
ifc_root_node_id,
cursor,
mouse_position,
};
let mut affected_nodes = BTreeMap::new();
affected_nodes.insert(ifc_root_node_id, SelectionRange {
start: cursor,
end: cursor,
});
TextSelection {
dom_id,
anchor,
focus,
affected_nodes,
is_forward: true, }
}
pub fn is_collapsed(&self) -> bool {
self.anchor.ifc_root_node_id == self.focus.ifc_root_node_id
&& self.anchor.cursor == self.focus.cursor
}
pub fn get_range_for_node(&self, ifc_root_node_id: &NodeId) -> Option<&SelectionRange> {
self.affected_nodes.get(ifc_root_node_id)
}
}
impl_option!(
TextSelection,
OptionTextSelection,
copy = false,
clone = false,
[Debug, Clone, PartialEq]
);