use std::rc::Rc;
use dioxus::prelude::*;
use freya_core::platform::CursorIcon;
use freya_elements::{
self as dioxus_elements,
events::KeyboardEvent,
MouseEvent,
};
use freya_hooks::{
use_editable,
use_focus,
use_platform,
EditableConfig,
EditableEvent,
EditableMode,
TextEditor,
};
#[derive(Debug, Default, PartialEq, Clone, Copy)]
pub enum SelectableTextStatus {
#[default]
Idle,
Hovering,
}
#[component]
pub fn SelectableText(value: ReadOnlySignal<String>) -> Element {
let platform = use_platform();
let mut editable = use_editable(
move || EditableConfig::new(value()).with_allow_changes(false),
EditableMode::MultipleLinesSingleEditor,
);
let mut status = use_signal(SelectableTextStatus::default);
let mut focus = use_focus();
let mut drag_origin = use_signal(|| None);
if &*value.read() != editable.editor().read().rope() {
editable.editor_mut().write().set(&value.read());
editable.editor_mut().write().editor_history().clear();
}
use_drop(move || {
if *status.peek() == SelectableTextStatus::Hovering {
platform.set_cursor(CursorIcon::default());
}
});
let a11y_id = focus.attribute();
let cursor_reference = editable.cursor_attr();
let highlights = editable.highlights_attr(0);
let onmousedown = move |e: MouseEvent| {
e.stop_propagation();
drag_origin.set(Some(e.get_screen_coordinates() - e.element_coordinates));
editable.process_event(&EditableEvent::MouseDown(e.data, 0));
focus.request_focus();
};
let onglobalmousemove = move |mut e: MouseEvent| {
if focus.is_focused() {
if let Some(drag_origin) = drag_origin() {
let data = Rc::get_mut(&mut e.data).unwrap();
data.element_coordinates.x -= drag_origin.x;
data.element_coordinates.y -= drag_origin.y;
editable.process_event(&EditableEvent::MouseMove(e.data, 0));
}
}
};
let onmouseenter = move |_| {
platform.set_cursor(CursorIcon::Text);
*status.write() = SelectableTextStatus::Hovering;
};
let onmouseleave = move |_| {
platform.set_cursor(CursorIcon::default());
*status.write() = SelectableTextStatus::default();
};
let onclick = move |_: MouseEvent| {
editable.process_event(&EditableEvent::Click);
};
let onkeydown = move |e: KeyboardEvent| {
editable.process_event(&EditableEvent::KeyDown(e.data));
};
let onkeyup = move |e: KeyboardEvent| {
editable.process_event(&EditableEvent::KeyUp(e.data));
};
let onglobalclick = move |_| {
match *status.read() {
SelectableTextStatus::Idle if focus.is_focused() => {
editable.process_event(&EditableEvent::Click);
}
SelectableTextStatus::Hovering => {
editable.process_event(&EditableEvent::Click);
}
_ => {}
};
if focus.is_focused() {
if drag_origin.read().is_some() {
drag_origin.set(None);
} else {
editable.editor_mut().write().clear_selection();
focus.request_unfocus();
}
}
};
rsx!(
paragraph {
a11y_focusable: "true",
a11y_id,
cursor_id: "0",
cursor_mode: "editable",
cursor_color: "black",
highlights,
cursor_reference,
onclick,
onglobalmousemove,
onmousedown,
onmouseenter,
onmouseleave,
onglobalclick,
onkeydown,
onkeyup,
text {
"{editable.editor()}"
}
}
)
}
#[cfg(test)]
mod test {
use freya::prelude::*;
use freya_testing::prelude::*;
#[tokio::test]
pub async fn selectable_text() {
fn selectable_text_app() -> Element {
rsx!(SelectableText {
value: "Hello, World!"
})
}
let mut utils = launch_test(selectable_text_app);
let root = utils.root().get(0);
assert_eq!(root.get(0).get(0).text(), Some("Hello, World!"));
utils.wait_for_update().await;
utils.wait_for_update().await;
utils.push_event(TestEvent::Mouse {
name: EventName::MouseDown,
cursor: (3.0, 3.0).into(),
button: Some(MouseButton::Left),
});
utils.wait_for_update().await;
utils.wait_for_update().await;
utils.push_event(TestEvent::Mouse {
name: EventName::MouseMove,
cursor: (55.0, 3.0).into(),
button: Some(MouseButton::Left),
});
utils.wait_for_update().await;
utils.wait_for_update().await;
let root = utils.root().get(0);
let highlights = root.state().cursor.highlights.clone();
#[cfg(not(target_os = "macos"))]
assert_eq!(highlights, Some(vec![(0, 8)]));
#[cfg(target_os = "macos")]
assert_eq!(highlights, Some(vec![(0, 7)]));
}
}