use dioxus_core::Event;
use std::ops::Range;
pub type SelectionEvent = Event<SelectionData>;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", serde(rename_all = "lowercase"))]
pub enum SelectionDirection {
#[default]
None,
Forward,
Backward,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TextSelection {
range: Range<usize>,
direction: SelectionDirection,
}
impl TextSelection {
pub fn new(range: Range<usize>, direction: SelectionDirection) -> Self {
Self { range, direction }
}
pub fn range(&self) -> Range<usize> {
self.range.clone()
}
pub fn direction(&self) -> SelectionDirection {
self.direction
}
pub fn is_collapsed(&self) -> bool {
self.range.is_empty()
}
}
pub struct SelectionData {
inner: Box<dyn HasSelectionData>,
}
impl SelectionData {
pub fn new(inner: impl HasSelectionData + 'static) -> Self {
Self {
inner: Box::new(inner),
}
}
pub fn selection(&self) -> Option<TextSelection> {
self.inner.selection()
}
#[inline(always)]
pub fn downcast<T: 'static>(&self) -> Option<&T> {
self.inner.as_any().downcast_ref::<T>()
}
}
impl<E: HasSelectionData> From<E> for SelectionData {
fn from(e: E) -> Self {
Self { inner: Box::new(e) }
}
}
impl std::fmt::Debug for SelectionData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SelectionData")
.field("selection", &self.selection())
.finish()
}
}
impl PartialEq for SelectionData {
fn eq(&self, other: &Self) -> bool {
self.selection() == other.selection()
}
}
#[cfg(feature = "serialize")]
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
pub struct SerializedSelectionData {
#[serde(default)]
pub selection_start: Option<usize>,
#[serde(default)]
pub selection_end: Option<usize>,
#[serde(default)]
pub selection_direction: Option<SelectionDirection>,
}
#[cfg(feature = "serialize")]
impl SerializedSelectionData {
pub fn new(
selection_start: Option<usize>,
selection_end: Option<usize>,
selection_direction: Option<SelectionDirection>,
) -> Self {
Self {
selection_start,
selection_end,
selection_direction,
}
}
}
#[cfg(feature = "serialize")]
impl Default for SerializedSelectionData {
fn default() -> Self {
Self::new(None, None, None)
}
}
#[cfg(feature = "serialize")]
impl From<&SelectionData> for SerializedSelectionData {
fn from(data: &SelectionData) -> Self {
let Some(selection) = data.selection() else {
return Self::default();
};
let range = selection.range();
Self::new(
Some(range.start),
Some(range.end),
Some(selection.direction()),
)
}
}
#[cfg(feature = "serialize")]
impl HasSelectionData for SerializedSelectionData {
fn selection(&self) -> Option<TextSelection> {
let start = self.selection_start?;
let end = self.selection_end?;
Some(TextSelection::new(
start..end,
self.selection_direction.unwrap_or_default(),
))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[cfg(feature = "serialize")]
impl serde::Serialize for SelectionData {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
SerializedSelectionData::from(self).serialize(serializer)
}
}
#[cfg(feature = "serialize")]
impl<'de> serde::Deserialize<'de> for SelectionData {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let data = SerializedSelectionData::deserialize(deserializer)?;
Ok(Self {
inner: Box::new(data),
})
}
}
pub trait HasSelectionData: std::any::Any {
fn selection(&self) -> Option<TextSelection> {
None
}
fn as_any(&self) -> &dyn std::any::Any;
}
#[cfg(all(test, feature = "serialize"))]
mod tests {
use super::*;
#[test]
fn serialized_selection_data_deserializes_missing_fields() {
let data: SerializedSelectionData = serde_json::from_str("{}").unwrap();
assert_eq!(data, SerializedSelectionData::default());
}
#[test]
fn selection_data_exposes_serialized_fields() {
let event = SelectionData::new(SerializedSelectionData::new(
Some(1),
Some(4),
Some(SelectionDirection::Forward),
));
assert_eq!(
event.selection(),
Some(TextSelection::new(1..4, SelectionDirection::Forward))
);
}
}