Merge pull request #940 from Demonthos/make-widgets-cross-framework
This commit is contained in:
44 files changed
+3232
-1185
No files matched your search
+82
-73
@@ -13,7 +13,9 @@ use dioxus_html::geometry::{
|
||||
use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers};
|
||||
use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
|
||||
use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
|
||||
use dioxus_html::{event_bubbles, EventData, FocusData, KeyboardData, MouseData, WheelData};
|
||||
use dioxus_html::{event_bubbles, FocusData, KeyboardData, MouseData, WheelData};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
rc::Rc,
|
||||
@@ -24,15 +26,64 @@ use taffy::{prelude::Layout, Taffy};
|
||||
|
||||
use crate::focus::{Focus, Focused};
|
||||
use crate::layout::TaffyLayout;
|
||||
use crate::{layout_to_screen_space, FocusState};
|
||||
use crate::{get_abs_layout, layout_to_screen_space, FocusState};
|
||||
|
||||
pub(crate) struct Event {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Event {
|
||||
pub id: NodeId,
|
||||
pub name: &'static str,
|
||||
pub data: Rc<EventData>,
|
||||
pub data: EventData,
|
||||
pub bubbles: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum EventData {
|
||||
Mouse(MouseData),
|
||||
Keyboard(KeyboardData),
|
||||
Focus(FocusData),
|
||||
Wheel(WheelData),
|
||||
Form(FormData),
|
||||
}
|
||||
|
||||
impl EventData {
|
||||
pub fn into_any(self) -> Rc<dyn Any> {
|
||||
match self {
|
||||
EventData::Mouse(m) => Rc::new(m),
|
||||
EventData::Keyboard(k) => Rc::new(k),
|
||||
EventData::Focus(f) => Rc::new(f),
|
||||
EventData::Wheel(w) => Rc::new(w),
|
||||
EventData::Form(f) => Rc::new(f.into_html()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct FormData {
|
||||
pub value: String,
|
||||
|
||||
pub values: HashMap<String, Vec<String>>,
|
||||
|
||||
pub files: Option<Files>,
|
||||
}
|
||||
|
||||
impl FormData {
|
||||
fn into_html(self) -> dioxus_html::FormData {
|
||||
dioxus_html::FormData {
|
||||
value: self.value,
|
||||
values: self.values,
|
||||
files: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Files {
|
||||
files: FxHashMap<String, File>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct File {}
|
||||
|
||||
type EventCore = (&'static str, EventData);
|
||||
|
||||
const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
|
||||
@@ -148,13 +199,13 @@ impl InnerInputState {
|
||||
resolved_events.push(Event {
|
||||
name: "focus",
|
||||
id,
|
||||
data: Rc::new(EventData::Focus(FocusData {})),
|
||||
data: EventData::Focus(FocusData {}),
|
||||
bubbles: event_bubbles("focus"),
|
||||
});
|
||||
resolved_events.push(Event {
|
||||
name: "focusin",
|
||||
id,
|
||||
data: Rc::new(EventData::Focus(FocusData {})),
|
||||
data: EventData::Focus(FocusData {}),
|
||||
bubbles: event_bubbles("focusin"),
|
||||
});
|
||||
}
|
||||
@@ -162,7 +213,7 @@ impl InnerInputState {
|
||||
resolved_events.push(Event {
|
||||
name: "focusout",
|
||||
id,
|
||||
data: Rc::new(EventData::Focus(FocusData {})),
|
||||
data: EventData::Focus(FocusData {}),
|
||||
bubbles: event_bubbles("focusout"),
|
||||
});
|
||||
}
|
||||
@@ -198,7 +249,7 @@ impl InnerInputState {
|
||||
|
||||
fn try_create_event(
|
||||
name: &'static str,
|
||||
data: Rc<EventData>,
|
||||
data: EventData,
|
||||
will_bubble: &mut FxHashSet<NodeId>,
|
||||
resolved_events: &mut Vec<Event>,
|
||||
node: NodeRef,
|
||||
@@ -273,7 +324,7 @@ impl InnerInputState {
|
||||
if old_pos != Some(new_pos) {
|
||||
let mut will_bubble = FxHashSet::default();
|
||||
for node in dom.get_listening_sorted("mousemove") {
|
||||
let node_layout = get_abs_layout(node, dom, layout);
|
||||
let node_layout = get_abs_layout(node, layout);
|
||||
let previously_contained = old_pos
|
||||
.filter(|pos| layout_contains_point(&node_layout, *pos))
|
||||
.is_some();
|
||||
@@ -282,10 +333,7 @@ impl InnerInputState {
|
||||
if currently_contains && previously_contained {
|
||||
try_create_event(
|
||||
"mousemove",
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
@@ -300,7 +348,7 @@ impl InnerInputState {
|
||||
// mouseenter
|
||||
let mut will_bubble = FxHashSet::default();
|
||||
for node in dom.get_listening_sorted("mouseenter") {
|
||||
let node_layout = get_abs_layout(node, dom, layout);
|
||||
let node_layout = get_abs_layout(node, layout);
|
||||
let previously_contained = old_pos
|
||||
.filter(|pos| layout_contains_point(&node_layout, *pos))
|
||||
.is_some();
|
||||
@@ -309,7 +357,7 @@ impl InnerInputState {
|
||||
if currently_contains && !previously_contained {
|
||||
try_create_event(
|
||||
"mouseenter",
|
||||
Rc::new(dioxus_html::EventData::Mouse(mouse_data.clone())),
|
||||
EventData::Mouse(mouse_data.clone()),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
@@ -323,7 +371,7 @@ impl InnerInputState {
|
||||
// mouseover
|
||||
let mut will_bubble = FxHashSet::default();
|
||||
for node in dom.get_listening_sorted("mouseover") {
|
||||
let node_layout = get_abs_layout(node, dom, layout);
|
||||
let node_layout = get_abs_layout(node, layout);
|
||||
let previously_contained = old_pos
|
||||
.filter(|pos| layout_contains_point(&node_layout, *pos))
|
||||
.is_some();
|
||||
@@ -332,10 +380,7 @@ impl InnerInputState {
|
||||
if currently_contains && !previously_contained {
|
||||
try_create_event(
|
||||
"mouseover",
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
@@ -349,16 +394,13 @@ impl InnerInputState {
|
||||
if was_pressed {
|
||||
let mut will_bubble = FxHashSet::default();
|
||||
for node in dom.get_listening_sorted("mousedown") {
|
||||
let node_layout = get_abs_layout(node, dom, layout);
|
||||
let node_layout = get_abs_layout(node, layout);
|
||||
let currently_contains = layout_contains_point(&node_layout, new_pos);
|
||||
|
||||
if currently_contains {
|
||||
try_create_event(
|
||||
"mousedown",
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
@@ -373,16 +415,13 @@ impl InnerInputState {
|
||||
if was_released {
|
||||
let mut will_bubble = FxHashSet::default();
|
||||
for node in dom.get_listening_sorted("mouseup") {
|
||||
let node_layout = get_abs_layout(node, dom, layout);
|
||||
let node_layout = get_abs_layout(node, layout);
|
||||
let currently_contains = layout_contains_point(&node_layout, new_pos);
|
||||
|
||||
if currently_contains {
|
||||
try_create_event(
|
||||
"mouseup",
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
@@ -398,16 +437,13 @@ impl InnerInputState {
|
||||
if mouse_data.trigger_button() == Some(DioxusMouseButton::Primary) && was_released {
|
||||
let mut will_bubble = FxHashSet::default();
|
||||
for node in dom.get_listening_sorted("click") {
|
||||
let node_layout = get_abs_layout(node, dom, layout);
|
||||
let node_layout = get_abs_layout(node, layout);
|
||||
let currently_contains = layout_contains_point(&node_layout, new_pos);
|
||||
|
||||
if currently_contains {
|
||||
try_create_event(
|
||||
"click",
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
@@ -424,16 +460,13 @@ impl InnerInputState {
|
||||
{
|
||||
let mut will_bubble = FxHashSet::default();
|
||||
for node in dom.get_listening_sorted("contextmenu") {
|
||||
let node_layout = get_abs_layout(node, dom, layout);
|
||||
let node_layout = get_abs_layout(node, layout);
|
||||
let currently_contains = layout_contains_point(&node_layout, new_pos);
|
||||
|
||||
if currently_contains {
|
||||
try_create_event(
|
||||
"contextmenu",
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
@@ -450,14 +483,14 @@ impl InnerInputState {
|
||||
if was_scrolled {
|
||||
let mut will_bubble = FxHashSet::default();
|
||||
for node in dom.get_listening_sorted("wheel") {
|
||||
let node_layout = get_abs_layout(node, dom, layout);
|
||||
let node_layout = get_abs_layout(node, layout);
|
||||
|
||||
let currently_contains = layout_contains_point(&node_layout, new_pos);
|
||||
|
||||
if currently_contains {
|
||||
try_create_event(
|
||||
"wheel",
|
||||
Rc::new(EventData::Wheel(w.clone())),
|
||||
EventData::Wheel(w.clone()),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
@@ -473,7 +506,7 @@ impl InnerInputState {
|
||||
// mouseleave
|
||||
let mut will_bubble = FxHashSet::default();
|
||||
for node in dom.get_listening_sorted("mouseleave") {
|
||||
let node_layout = get_abs_layout(node, dom, layout);
|
||||
let node_layout = get_abs_layout(node, layout);
|
||||
let previously_contained = old_pos
|
||||
.filter(|pos| layout_contains_point(&node_layout, *pos))
|
||||
.is_some();
|
||||
@@ -482,10 +515,7 @@ impl InnerInputState {
|
||||
if !currently_contains && previously_contained {
|
||||
try_create_event(
|
||||
"mouseleave",
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
@@ -499,7 +529,7 @@ impl InnerInputState {
|
||||
// mouseout
|
||||
let mut will_bubble = FxHashSet::default();
|
||||
for node in dom.get_listening_sorted("mouseout") {
|
||||
let node_layout = get_abs_layout(node, dom, layout);
|
||||
let node_layout = get_abs_layout(node, layout);
|
||||
let previously_contained = old_pos
|
||||
.filter(|pos| layout_contains_point(&node_layout, *pos))
|
||||
.is_some();
|
||||
@@ -508,10 +538,7 @@ impl InnerInputState {
|
||||
if !currently_contains && previously_contained {
|
||||
try_create_event(
|
||||
"mouseout",
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
@@ -546,24 +573,6 @@ impl InnerInputState {
|
||||
// }
|
||||
}
|
||||
|
||||
fn get_abs_layout(node: NodeRef, dom: &RealDom, taffy: &Taffy) -> Layout {
|
||||
let mut node_layout = *taffy
|
||||
.layout(node.get::<TaffyLayout>().unwrap().node.unwrap())
|
||||
.unwrap();
|
||||
let mut current = node;
|
||||
|
||||
while let Some(parent) = current.parent_id() {
|
||||
let parent = dom.get(parent).unwrap();
|
||||
current = parent;
|
||||
let parent_layout = taffy
|
||||
.layout(parent.get::<TaffyLayout>().unwrap().node.unwrap())
|
||||
.unwrap();
|
||||
node_layout.location.x += parent_layout.location.x;
|
||||
node_layout.location.y += parent_layout.location.y;
|
||||
}
|
||||
node_layout
|
||||
}
|
||||
|
||||
pub struct RinkInputHandler {
|
||||
state: Rc<RefCell<InnerInputState>>,
|
||||
queued_events: Rc<RefCell<Vec<EventCore>>>,
|
||||
@@ -628,12 +637,12 @@ impl RinkInputHandler {
|
||||
})
|
||||
.map(|evt| (evt.0, evt.1));
|
||||
|
||||
let mut hm: FxHashMap<&'static str, Vec<Rc<EventData>>> = FxHashMap::default();
|
||||
let mut hm: FxHashMap<&'static str, Vec<EventData>> = FxHashMap::default();
|
||||
for (event, data) in events {
|
||||
if let Some(v) = hm.get_mut(event) {
|
||||
v.push(Rc::new(data));
|
||||
v.push(data);
|
||||
} else {
|
||||
hm.insert(event, vec![Rc::new(data)]);
|
||||
hm.insert(event, vec![data]);
|
||||
}
|
||||
}
|
||||
for (event, datas) in hm {
|
||||
|
||||
@@ -25,12 +25,6 @@ impl<T> PossiblyUninitalized<T> {
|
||||
_ => panic!("uninitalized"),
|
||||
}
|
||||
}
|
||||
pub fn ok(self) -> Option<T> {
|
||||
match self {
|
||||
Self::Initialized(i) => Some(i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Default for PossiblyUninitalized<T> {
|
||||
fn default() -> Self {
|
||||
@@ -54,6 +48,9 @@ impl State for TaffyLayout {
|
||||
.with_attrs(AttributeMaskBuilder::Some(SORTED_LAYOUT_ATTRS))
|
||||
.with_text();
|
||||
|
||||
// The layout state should be effected by the shadow dom
|
||||
const TRAVERSE_SHADOW_DOM: bool = true;
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
node_view: NodeView,
|
||||
|
||||
+98
-19
@@ -6,8 +6,7 @@ use crossterm::{
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use dioxus_html::EventData;
|
||||
use dioxus_native_core::prelude::*;
|
||||
use dioxus_native_core::{prelude::*, tree::TreeRef};
|
||||
use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, SendAnyMap};
|
||||
use focus::FocusState;
|
||||
use futures::{channel::mpsc::UnboundedSender, pin_mut, Future, StreamExt};
|
||||
@@ -21,22 +20,22 @@ use std::{
|
||||
};
|
||||
use std::{rc::Rc, sync::RwLock};
|
||||
use style_attributes::StyleModifier;
|
||||
use taffy::Taffy;
|
||||
pub use taffy::{geometry::Point, prelude::*};
|
||||
use tokio::select;
|
||||
use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
use widgets::{register_widgets, RinkWidgetResponder, RinkWidgetTraitObject};
|
||||
|
||||
mod config;
|
||||
mod focus;
|
||||
mod hooks;
|
||||
mod layout;
|
||||
pub mod prelude;
|
||||
mod prevent_default;
|
||||
pub mod query;
|
||||
mod render;
|
||||
mod style;
|
||||
mod style_attributes;
|
||||
mod widget;
|
||||
mod widgets;
|
||||
|
||||
pub use config::*;
|
||||
pub use hooks::*;
|
||||
@@ -91,11 +90,14 @@ pub fn render<R: Driver>(
|
||||
PreventDefault::to_type_erased(),
|
||||
]);
|
||||
|
||||
let (handler, mut register_event) = RinkInputHandler::create(&mut rdom);
|
||||
|
||||
// Setup input handling
|
||||
|
||||
// The event channel for fully resolved events
|
||||
let (event_tx, mut event_reciever) = unbounded();
|
||||
let event_tx_clone = event_tx.clone();
|
||||
|
||||
// The event channel for raw terminal events
|
||||
let (raw_event_tx, mut raw_event_reciever) = unbounded();
|
||||
let event_tx_clone = raw_event_tx.clone();
|
||||
if !cfg.headless {
|
||||
std::thread::spawn(move || {
|
||||
// Timeout after 10ms when waiting for events
|
||||
@@ -103,7 +105,10 @@ pub fn render<R: Driver>(
|
||||
loop {
|
||||
if crossterm::event::poll(tick_rate).unwrap() {
|
||||
let evt = crossterm::event::read().unwrap();
|
||||
if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() {
|
||||
if raw_event_tx
|
||||
.unbounded_send(InputEvent::UserInput(evt))
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -111,10 +116,21 @@ pub fn render<R: Driver>(
|
||||
});
|
||||
}
|
||||
|
||||
register_widgets(&mut rdom, event_tx);
|
||||
|
||||
let (handler, mut register_event) = RinkInputHandler::create(&mut rdom);
|
||||
|
||||
let rdom = Arc::new(RwLock::new(rdom));
|
||||
let taffy = Arc::new(Mutex::new(Taffy::new()));
|
||||
let mut renderer = create_renderer(&rdom, &taffy, event_tx_clone);
|
||||
|
||||
// insert the query engine into the rdom
|
||||
let query_engine = Query::new(rdom.clone(), taffy.clone());
|
||||
{
|
||||
let mut rdom = rdom.write().unwrap();
|
||||
rdom.raw_world_mut().add_unique(query_engine);
|
||||
}
|
||||
|
||||
{
|
||||
renderer.update(&rdom);
|
||||
let mut any_map = SendAnyMap::new();
|
||||
@@ -160,7 +176,7 @@ pub fn render<R: Driver>(
|
||||
|
||||
if !to_rerender.is_empty() || updated {
|
||||
updated = false;
|
||||
fn resize(dims: Rect, taffy: &mut Taffy, rdom: &RealDom) {
|
||||
fn resize(dims: tui::layout::Rect, taffy: &mut Taffy, rdom: &RealDom) {
|
||||
let width = screen_to_layout_space(dims.width);
|
||||
let height = screen_to_layout_space(dims.height);
|
||||
let root_node = rdom
|
||||
@@ -202,7 +218,7 @@ pub fn render<R: Driver>(
|
||||
} else {
|
||||
let rdom = rdom.read().unwrap();
|
||||
resize(
|
||||
Rect {
|
||||
tui::layout::Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1000,
|
||||
@@ -214,6 +230,7 @@ pub fn render<R: Driver>(
|
||||
}
|
||||
}
|
||||
|
||||
let mut event_recieved = None;
|
||||
{
|
||||
let wait = renderer.poll_async();
|
||||
|
||||
@@ -223,7 +240,7 @@ pub fn render<R: Driver>(
|
||||
_ = wait => {
|
||||
|
||||
},
|
||||
evt = event_reciever.next() => {
|
||||
evt = raw_event_reciever.next() => {
|
||||
match evt.as_ref().unwrap() {
|
||||
InputEvent::UserInput(event) => match event {
|
||||
TermEvent::Key(key) => {
|
||||
@@ -244,21 +261,32 @@ pub fn render<R: Driver>(
|
||||
register_event(evt);
|
||||
}
|
||||
},
|
||||
Some(evt) = event_reciever.next() => {
|
||||
event_recieved = Some(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if let Some(evt) = event_recieved {
|
||||
renderer.handle_event(
|
||||
&rdom,
|
||||
evt.id,
|
||||
evt.name,
|
||||
Rc::new(evt.data),
|
||||
evt.bubbles,
|
||||
);
|
||||
}
|
||||
{
|
||||
let evts = {
|
||||
handler.get_events(
|
||||
&taffy.lock().expect("taffy lock poisoned"),
|
||||
&mut rdom.write().unwrap(),
|
||||
)
|
||||
};
|
||||
let evts = handler.get_events(
|
||||
&taffy.lock().expect("taffy lock poisoned"),
|
||||
&mut rdom.write().unwrap(),
|
||||
);
|
||||
updated |= handler.state().focus_state.clean();
|
||||
|
||||
for e in evts {
|
||||
renderer.handle_event(&rdom, e.id, e.name, e.data, e.bubbles);
|
||||
bubble_event_to_widgets(&mut rdom.write().unwrap(), &e);
|
||||
renderer.handle_event(&rdom, e.id, e.name, Rc::new(e.data), e.bubbles);
|
||||
}
|
||||
}
|
||||
// updates the dom's nodes
|
||||
@@ -310,3 +338,54 @@ pub trait Driver {
|
||||
);
|
||||
fn poll_async(&mut self) -> Pin<Box<dyn Future<Output = ()> + '_>>;
|
||||
}
|
||||
|
||||
/// Before sending the event to drivers, we need to bubble it up the tree to any widgets that are listening
|
||||
fn bubble_event_to_widgets(rdom: &mut RealDom, event: &Event) {
|
||||
let id = event.id;
|
||||
let mut node = Some(id);
|
||||
|
||||
while let Some(node_id) = node {
|
||||
let parent_id = {
|
||||
let tree = rdom.tree_ref();
|
||||
tree.parent_id_advanced(node_id, true)
|
||||
};
|
||||
|
||||
{
|
||||
// println!("@ bubbling event to node {:?}", node_id);
|
||||
let mut node_mut = rdom.get_mut(node_id).unwrap();
|
||||
if let Some(mut widget) = node_mut
|
||||
.get_mut::<RinkWidgetTraitObject>()
|
||||
.map(|w| w.clone())
|
||||
{
|
||||
widget.handle_event(event, node_mut)
|
||||
}
|
||||
}
|
||||
|
||||
if !event.bubbles {
|
||||
// println!("event does not bubble");
|
||||
break;
|
||||
}
|
||||
node = parent_id;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_abs_layout(node: NodeRef, taffy: &Taffy) -> Layout {
|
||||
let mut node_layout = *taffy
|
||||
.layout(node.get::<TaffyLayout>().unwrap().node.unwrap())
|
||||
.unwrap();
|
||||
let mut current = node;
|
||||
|
||||
let dom = node.real_dom();
|
||||
let tree = dom.tree_ref();
|
||||
|
||||
while let Some(parent) = tree.parent_id_advanced(current.id(), true) {
|
||||
let parent = dom.get(parent).unwrap();
|
||||
current = parent;
|
||||
let parent_layout = taffy
|
||||
.layout(parent.get::<TaffyLayout>().unwrap().node.unwrap())
|
||||
.unwrap();
|
||||
node_layout.location.x += parent_layout.location.x;
|
||||
node_layout.location.y += parent_layout.location.y;
|
||||
}
|
||||
node_layout
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
#[cfg(feature = "dioxus-bindings")]
|
||||
pub use crate::widgets::*;
|
||||
+19
-15
@@ -1,13 +1,14 @@
|
||||
use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard};
|
||||
|
||||
use dioxus_native_core::prelude::*;
|
||||
use shipyard::Unique;
|
||||
use taffy::{
|
||||
geometry::Point,
|
||||
prelude::{Layout, Size},
|
||||
Taffy,
|
||||
};
|
||||
|
||||
use crate::{layout::TaffyLayout, layout_to_screen_space};
|
||||
use crate::{get_abs_layout, layout_to_screen_space};
|
||||
|
||||
/// Allows querying the layout of nodes after rendering. It will only provide a correct value after a node is rendered.
|
||||
/// Provided as a root context for all tui applictions.
|
||||
@@ -40,7 +41,7 @@ use crate::{layout::TaffyLayout, layout_to_screen_space};
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Unique)]
|
||||
pub struct Query {
|
||||
pub(crate) rdom: Arc<RwLock<RealDom>>,
|
||||
pub(crate) stretch: Arc<Mutex<Taffy>>,
|
||||
@@ -69,7 +70,7 @@ pub struct ElementRef<'a> {
|
||||
}
|
||||
|
||||
impl<'a> ElementRef<'a> {
|
||||
fn new(
|
||||
pub(crate) fn new(
|
||||
inner: RwLockReadGuard<'a, RealDom>,
|
||||
stretch: MutexGuard<'a, Taffy>,
|
||||
id: NodeId,
|
||||
@@ -89,17 +90,20 @@ impl<'a> ElementRef<'a> {
|
||||
}
|
||||
|
||||
pub fn layout(&self) -> Option<Layout> {
|
||||
let layout = self
|
||||
.stretch
|
||||
.layout(self.inner.get(self.id)?.get::<TaffyLayout>()?.node.ok()?)
|
||||
.ok();
|
||||
layout.map(|layout| Layout {
|
||||
order: layout.order,
|
||||
size: layout.size.map(layout_to_screen_space),
|
||||
location: Point {
|
||||
x: layout_to_screen_space(layout.location.x),
|
||||
y: layout_to_screen_space(layout.location.y),
|
||||
},
|
||||
})
|
||||
get_layout(self.inner.get(self.id).unwrap(), &self.stretch)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_layout(node: NodeRef, stretch: &Taffy) -> Option<Layout> {
|
||||
let layout = get_abs_layout(node, stretch);
|
||||
let pos = layout.location;
|
||||
|
||||
Some(Layout {
|
||||
order: layout.order,
|
||||
size: layout.size.map(layout_to_screen_space),
|
||||
location: Point {
|
||||
x: layout_to_screen_space(pos.x).round(),
|
||||
y: layout_to_screen_space(pos.y).round(),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use dioxus_native_core::prelude::*;
|
||||
use dioxus_native_core::{prelude::*, tree::TreeRef};
|
||||
use std::io::Stdout;
|
||||
use taffy::{
|
||||
geometry::Point,
|
||||
@@ -83,7 +83,10 @@ pub(crate) fn render_vnode(
|
||||
frame.render_widget(WidgetWithContext::new(node, cfg), area);
|
||||
}
|
||||
|
||||
for c in node.children() {
|
||||
let node_id = node.id();
|
||||
let rdom = node.real_dom();
|
||||
for child_id in rdom.tree_ref().children_ids_advanced(node_id, true) {
|
||||
let c = rdom.get(child_id).unwrap();
|
||||
render_vnode(frame, layout, c, cfg, location);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dioxus_html::input_data::keyboard_types::Key;
|
||||
use dioxus_native_core::{
|
||||
custom_element::CustomElement,
|
||||
node::OwnedAttributeDiscription,
|
||||
node_ref::AttributeMask,
|
||||
prelude::NodeType,
|
||||
real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut, RealDom},
|
||||
NodeId,
|
||||
};
|
||||
use shipyard::UniqueView;
|
||||
|
||||
use crate::FormData;
|
||||
|
||||
use super::{RinkWidget, WidgetContext};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Button {
|
||||
text_id: NodeId,
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
fn width(el: &ElementNodeMut) -> String {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "width".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
value
|
||||
} else {
|
||||
"1px".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn height(el: &ElementNodeMut) -> String {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "height".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
value
|
||||
} else {
|
||||
"1px".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn update_size_attr(&mut self, el: &mut ElementNodeMut) {
|
||||
let width = Self::width(el);
|
||||
let height = Self::height(el);
|
||||
let single_char = width == "1px" || height == "1px";
|
||||
let border_style = if single_char { "none" } else { "solid" };
|
||||
el.set_attribute(
|
||||
OwnedAttributeDiscription {
|
||||
name: "border-style".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
border_style.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
fn update_value_attr(&mut self, el: &ElementNodeMut) {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "value".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
self.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
fn write_value(&self, rdom: &mut RealDom) {
|
||||
if let Some(mut text) = rdom.get_mut(self.text_id) {
|
||||
let node_type = text.node_type_mut();
|
||||
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") };
|
||||
*text.text_mut() = self.value.clone();
|
||||
}
|
||||
}
|
||||
|
||||
fn switch(&mut self, ctx: &mut WidgetContext, node: NodeMut) {
|
||||
let data = FormData {
|
||||
value: self.value.to_string(),
|
||||
values: HashMap::new(),
|
||||
files: None,
|
||||
};
|
||||
ctx.send(crate::Event {
|
||||
id: node.id(),
|
||||
name: "input",
|
||||
data: crate::EventData::Form(data),
|
||||
bubbles: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomElement for Button {
|
||||
const NAME: &'static str = "input";
|
||||
|
||||
fn roots(&self) -> Vec<NodeId> {
|
||||
vec![self.text_id]
|
||||
}
|
||||
|
||||
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
|
||||
let node_type = root.node_type();
|
||||
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") };
|
||||
|
||||
let value = el
|
||||
.attributes
|
||||
.get(&OwnedAttributeDiscription {
|
||||
name: "value".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string());
|
||||
|
||||
drop(node_type);
|
||||
|
||||
let rdom = root.real_dom_mut();
|
||||
let text = rdom.create_node(value.clone().unwrap_or_default());
|
||||
let text_id = text.id();
|
||||
|
||||
root.add_event_listener("keydown");
|
||||
root.add_event_listener("click");
|
||||
|
||||
Self {
|
||||
text_id,
|
||||
value: value.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn attributes_changed(
|
||||
&mut self,
|
||||
mut root: dioxus_native_core::real_dom::NodeMut,
|
||||
attributes: &dioxus_native_core::node_ref::AttributeMask,
|
||||
) {
|
||||
match attributes {
|
||||
AttributeMask::All => {
|
||||
{
|
||||
let node_type = root.node_type_mut();
|
||||
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
|
||||
self.update_value_attr(&el);
|
||||
self.update_size_attr(&mut el);
|
||||
}
|
||||
self.write_value(root.real_dom_mut());
|
||||
}
|
||||
AttributeMask::Some(attrs) => {
|
||||
{
|
||||
let node_type = root.node_type_mut();
|
||||
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
|
||||
if attrs.contains("width") || attrs.contains("height") {
|
||||
self.update_size_attr(&mut el);
|
||||
}
|
||||
if attrs.contains("value") {
|
||||
self.update_value_attr(&el);
|
||||
}
|
||||
}
|
||||
if attrs.contains("value") {
|
||||
self.write_value(root.real_dom_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RinkWidget for Button {
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
event: &crate::Event,
|
||||
mut node: dioxus_native_core::real_dom::NodeMut,
|
||||
) {
|
||||
let mut ctx: WidgetContext = {
|
||||
node.real_dom_mut()
|
||||
.raw_world_mut()
|
||||
.borrow::<UniqueView<WidgetContext>>()
|
||||
.expect("expected widget context")
|
||||
.clone()
|
||||
};
|
||||
|
||||
match event.name {
|
||||
"click" => self.switch(&mut ctx, node),
|
||||
"keydown" => {
|
||||
if let crate::EventData::Keyboard(data) = &event.data {
|
||||
if !data.is_auto_repeating()
|
||||
&& match data.key() {
|
||||
Key::Character(c) if c == " " => true,
|
||||
Key::Enter => true,
|
||||
_ => false,
|
||||
}
|
||||
{
|
||||
self.switch(&mut ctx, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dioxus_html::input_data::keyboard_types::Key;
|
||||
use dioxus_native_core::{
|
||||
custom_element::CustomElement,
|
||||
node::OwnedAttributeDiscription,
|
||||
node_ref::AttributeMask,
|
||||
prelude::NodeType,
|
||||
real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut},
|
||||
NodeId,
|
||||
};
|
||||
use shipyard::UniqueView;
|
||||
|
||||
use crate::FormData;
|
||||
|
||||
use super::{RinkWidget, WidgetContext};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct CheckBox {
|
||||
div_id: NodeId,
|
||||
text_id: NodeId,
|
||||
value: String,
|
||||
checked: bool,
|
||||
}
|
||||
|
||||
impl CheckBox {
|
||||
fn width(el: &ElementNodeMut) -> String {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "width".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
value
|
||||
} else {
|
||||
"1px".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn height(el: &ElementNodeMut) -> String {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "height".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
value
|
||||
} else {
|
||||
"1px".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn update_size_attr(&mut self, el: &mut ElementNodeMut) {
|
||||
let width = Self::width(el);
|
||||
let height = Self::height(el);
|
||||
let single_char = width == "1px" || height == "1px";
|
||||
let border_style = if single_char { "none" } else { "solid" };
|
||||
el.set_attribute(
|
||||
OwnedAttributeDiscription {
|
||||
name: "border-style".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
border_style.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
fn update_value_attr(&mut self, el: &ElementNodeMut) {
|
||||
self.value = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "value".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_else(|| "on".to_string());
|
||||
}
|
||||
|
||||
fn update_checked_attr(&mut self, el: &ElementNodeMut) {
|
||||
self.checked = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "checked".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_else(|| "false".to_string())
|
||||
== "true";
|
||||
}
|
||||
|
||||
fn write_value(&self, mut root: NodeMut) {
|
||||
let single_char = {
|
||||
let node_type = root.node_type_mut();
|
||||
let NodeTypeMut::Element( el) = node_type else { panic!("input must be an element") };
|
||||
Self::width(&el) == "1px" || Self::height(&el) == "1px"
|
||||
};
|
||||
let rdom = root.real_dom_mut();
|
||||
|
||||
if let Some(mut text) = rdom.get_mut(self.text_id) {
|
||||
let node_type = text.node_type_mut();
|
||||
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") };
|
||||
let value = if single_char {
|
||||
if self.checked {
|
||||
"☑"
|
||||
} else {
|
||||
"☐"
|
||||
}
|
||||
} else if self.checked {
|
||||
"✓"
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
*text.text_mut() = value.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
fn switch(&mut self, mut node: NodeMut) {
|
||||
let new_state = !self.checked;
|
||||
|
||||
let data = FormData {
|
||||
value: new_state
|
||||
.then(|| self.value.to_string())
|
||||
.unwrap_or_default(),
|
||||
values: HashMap::new(),
|
||||
files: None,
|
||||
};
|
||||
{
|
||||
let ctx: UniqueView<WidgetContext> = node
|
||||
.real_dom_mut()
|
||||
.raw_world_mut()
|
||||
.borrow()
|
||||
.expect("expected widget context");
|
||||
ctx.send(crate::Event {
|
||||
id: self.div_id,
|
||||
name: "input",
|
||||
data: crate::EventData::Form(data),
|
||||
bubbles: true,
|
||||
});
|
||||
}
|
||||
|
||||
self.checked = new_state;
|
||||
|
||||
self.write_value(node);
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomElement for CheckBox {
|
||||
const NAME: &'static str = "input";
|
||||
|
||||
fn roots(&self) -> Vec<NodeId> {
|
||||
vec![self.text_id]
|
||||
}
|
||||
|
||||
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
|
||||
let node_type = root.node_type();
|
||||
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") };
|
||||
|
||||
let value = el
|
||||
.attributes
|
||||
.get(&OwnedAttributeDiscription {
|
||||
name: "value".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string());
|
||||
|
||||
drop(node_type);
|
||||
|
||||
let rdom = root.real_dom_mut();
|
||||
let text = rdom.create_node(String::new());
|
||||
let text_id = text.id();
|
||||
|
||||
root.add_event_listener("click");
|
||||
root.add_event_listener("keydown");
|
||||
let div_id = root.id();
|
||||
|
||||
let myself = Self {
|
||||
div_id,
|
||||
text_id,
|
||||
value: value.unwrap_or_default(),
|
||||
checked: false,
|
||||
};
|
||||
myself.write_value(root);
|
||||
|
||||
myself
|
||||
}
|
||||
|
||||
fn attributes_changed(
|
||||
&mut self,
|
||||
mut root: dioxus_native_core::real_dom::NodeMut,
|
||||
attributes: &dioxus_native_core::node_ref::AttributeMask,
|
||||
) {
|
||||
match attributes {
|
||||
AttributeMask::All => {
|
||||
{
|
||||
let node_type = root.node_type_mut();
|
||||
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
|
||||
self.update_value_attr(&el);
|
||||
self.update_size_attr(&mut el);
|
||||
self.update_checked_attr(&el);
|
||||
}
|
||||
self.write_value(root);
|
||||
}
|
||||
AttributeMask::Some(attrs) => {
|
||||
{
|
||||
let node_type = root.node_type_mut();
|
||||
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
|
||||
if attrs.contains("width") || attrs.contains("height") {
|
||||
self.update_size_attr(&mut el);
|
||||
}
|
||||
if attrs.contains("value") {
|
||||
self.update_value_attr(&el);
|
||||
}
|
||||
if attrs.contains("checked") {
|
||||
self.update_checked_attr(&el);
|
||||
}
|
||||
}
|
||||
if attrs.contains("checked") {
|
||||
self.write_value(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RinkWidget for CheckBox {
|
||||
fn handle_event(&mut self, event: &crate::Event, node: dioxus_native_core::real_dom::NodeMut) {
|
||||
match event.name {
|
||||
"click" => self.switch(node),
|
||||
"keydown" => {
|
||||
if let crate::EventData::Keyboard(data) = &event.data {
|
||||
if !data.is_auto_repeating()
|
||||
&& match data.key() {
|
||||
Key::Character(c) if c == " " => true,
|
||||
Key::Enter => true,
|
||||
_ => false,
|
||||
}
|
||||
{
|
||||
self.switch(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
use dioxus_native_core::{
|
||||
custom_element::CustomElement, node::OwnedAttributeDiscription, prelude::NodeType,
|
||||
real_dom::NodeImmutable,
|
||||
};
|
||||
|
||||
use super::{
|
||||
checkbox::CheckBox, number::Number, password::Password, slider::Slider, textbox::TextBox,
|
||||
RinkWidget,
|
||||
};
|
||||
use crate::widgets::button::Button;
|
||||
|
||||
pub(crate) enum Input {
|
||||
Button(Button),
|
||||
CheckBox(CheckBox),
|
||||
TextBox(TextBox),
|
||||
Password(Password),
|
||||
Number(Number),
|
||||
Slider(Slider),
|
||||
}
|
||||
|
||||
impl CustomElement for Input {
|
||||
const NAME: &'static str = "input";
|
||||
|
||||
fn roots(&self) -> Vec<dioxus_native_core::NodeId> {
|
||||
match self {
|
||||
Input::Button(button) => button.roots(),
|
||||
Input::CheckBox(checkbox) => checkbox.roots(),
|
||||
Input::TextBox(textbox) => textbox.roots(),
|
||||
Input::Password(password) => password.roots(),
|
||||
Input::Number(number) => number.roots(),
|
||||
Input::Slider(slider) => slider.roots(),
|
||||
}
|
||||
}
|
||||
|
||||
fn slot(&self) -> Option<dioxus_native_core::NodeId> {
|
||||
match self {
|
||||
Input::Button(button) => button.slot(),
|
||||
Input::CheckBox(checkbox) => checkbox.slot(),
|
||||
Input::TextBox(textbox) => textbox.slot(),
|
||||
Input::Password(password) => password.slot(),
|
||||
Input::Number(number) => number.slot(),
|
||||
Input::Slider(slider) => slider.slot(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
|
||||
{
|
||||
// currently widgets are not allowed to have children
|
||||
let children = root.child_ids();
|
||||
let rdom = root.real_dom_mut();
|
||||
for child in children {
|
||||
if let Some(mut child) = rdom.get_mut(child) {
|
||||
child.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let node_type = root.node_type();
|
||||
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") };
|
||||
let input_type = el
|
||||
.attributes
|
||||
.get(&OwnedAttributeDiscription {
|
||||
name: "type".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text());
|
||||
match input_type
|
||||
.map(|type_| type_.trim().to_lowercase())
|
||||
.as_deref()
|
||||
{
|
||||
Some("button") => {
|
||||
drop(node_type);
|
||||
Input::Button(Button::create(root))
|
||||
}
|
||||
Some("checkbox") => {
|
||||
drop(node_type);
|
||||
Input::CheckBox(CheckBox::create(root))
|
||||
}
|
||||
Some("textbox") => {
|
||||
drop(node_type);
|
||||
Input::TextBox(TextBox::create(root))
|
||||
}
|
||||
Some("password") => {
|
||||
drop(node_type);
|
||||
Input::Password(Password::create(root))
|
||||
}
|
||||
Some("number") => {
|
||||
drop(node_type);
|
||||
Input::Number(Number::create(root))
|
||||
}
|
||||
Some("range") => {
|
||||
drop(node_type);
|
||||
Input::Slider(Slider::create(root))
|
||||
}
|
||||
_ => {
|
||||
drop(node_type);
|
||||
Input::TextBox(TextBox::create(root))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn attributes_changed(
|
||||
&mut self,
|
||||
root: dioxus_native_core::real_dom::NodeMut,
|
||||
attributes: &dioxus_native_core::node_ref::AttributeMask,
|
||||
) {
|
||||
match self {
|
||||
Input::Button(button) => {
|
||||
button.attributes_changed(root, attributes);
|
||||
}
|
||||
Input::CheckBox(checkbox) => {
|
||||
checkbox.attributes_changed(root, attributes);
|
||||
}
|
||||
Input::TextBox(textbox) => {
|
||||
textbox.attributes_changed(root, attributes);
|
||||
}
|
||||
Input::Password(password) => {
|
||||
password.attributes_changed(root, attributes);
|
||||
}
|
||||
Input::Number(number) => {
|
||||
number.attributes_changed(root, attributes);
|
||||
}
|
||||
Input::Slider(slider) => {
|
||||
slider.attributes_changed(root, attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RinkWidget for Input {
|
||||
fn handle_event(&mut self, event: &crate::Event, node: dioxus_native_core::real_dom::NodeMut) {
|
||||
match self {
|
||||
Input::Button(button) => {
|
||||
button.handle_event(event, node);
|
||||
}
|
||||
Input::CheckBox(checkbox) => {
|
||||
checkbox.handle_event(event, node);
|
||||
}
|
||||
Input::TextBox(textbox) => {
|
||||
textbox.handle_event(event, node);
|
||||
}
|
||||
Input::Password(password) => {
|
||||
password.handle_event(event, node);
|
||||
}
|
||||
Input::Number(number) => {
|
||||
number.handle_event(event, node);
|
||||
}
|
||||
Input::Slider(slider) => {
|
||||
slider.handle_event(event, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
mod button;
|
||||
mod checkbox;
|
||||
mod input;
|
||||
mod number;
|
||||
mod password;
|
||||
mod slider;
|
||||
mod text_like;
|
||||
mod textbox;
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use dioxus_native_core::{
|
||||
custom_element::{CustomElement, CustomElementUpdater},
|
||||
real_dom::{NodeMut, RealDom},
|
||||
};
|
||||
use futures_channel::mpsc::UnboundedSender;
|
||||
use shipyard::{Component, Unique};
|
||||
|
||||
use crate::Event;
|
||||
|
||||
pub(crate) fn register_widgets(rdom: &mut RealDom, sender: UnboundedSender<Event>) {
|
||||
// inject the widget context
|
||||
rdom.raw_world().add_unique(WidgetContext { sender });
|
||||
|
||||
rdom.register_custom_element::<RinkWidgetWrapper<input::Input>>();
|
||||
}
|
||||
|
||||
trait RinkWidget: Sync + Send + CustomElement + 'static {
|
||||
fn handle_event(&mut self, event: &Event, node: dioxus_native_core::real_dom::NodeMut);
|
||||
}
|
||||
|
||||
pub trait RinkWidgetResponder: CustomElementUpdater {
|
||||
fn handle_event(&mut self, event: &Event, node: dioxus_native_core::real_dom::NodeMut);
|
||||
}
|
||||
|
||||
impl<W: RinkWidget> RinkWidgetResponder for W {
|
||||
fn handle_event(&mut self, event: &Event, node: dioxus_native_core::real_dom::NodeMut) {
|
||||
RinkWidget::handle_event(self, event, node)
|
||||
}
|
||||
}
|
||||
|
||||
struct RinkWidgetWrapper<W: RinkWidget> {
|
||||
inner: RinkWidgetTraitObject,
|
||||
_marker: std::marker::PhantomData<W>,
|
||||
}
|
||||
|
||||
impl<W: RinkWidget> CustomElement for RinkWidgetWrapper<W> {
|
||||
const NAME: &'static str = W::NAME;
|
||||
|
||||
const NAMESPACE: Option<&'static str> = W::NAMESPACE;
|
||||
|
||||
fn create(mut node: NodeMut) -> Self {
|
||||
let myself = RinkWidgetTraitObject {
|
||||
widget: Arc::new(RwLock::new(W::create(node.reborrow()))),
|
||||
};
|
||||
|
||||
// Insert the widget as an arbitrary data node so that it can be recognized when bubbling events
|
||||
node.insert(myself.clone());
|
||||
|
||||
RinkWidgetWrapper {
|
||||
inner: myself,
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn attributes_changed(
|
||||
&mut self,
|
||||
root: dioxus_native_core::real_dom::NodeMut,
|
||||
attributes: &dioxus_native_core::node_ref::AttributeMask,
|
||||
) {
|
||||
let mut widget = self.inner.widget.write().unwrap();
|
||||
widget.attributes_changed(root, attributes);
|
||||
}
|
||||
|
||||
fn roots(&self) -> Vec<dioxus_native_core::NodeId> {
|
||||
let widget = self.inner.widget.read().unwrap();
|
||||
widget.roots()
|
||||
}
|
||||
|
||||
fn slot(&self) -> Option<dioxus_native_core::NodeId> {
|
||||
let widget = self.inner.widget.read().unwrap();
|
||||
widget.slot()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Component)]
|
||||
pub(crate) struct RinkWidgetTraitObject {
|
||||
widget: Arc<RwLock<dyn RinkWidgetResponder + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl CustomElementUpdater for RinkWidgetTraitObject {
|
||||
fn attributes_changed(
|
||||
&mut self,
|
||||
light_root: dioxus_native_core::real_dom::NodeMut,
|
||||
attributes: &dioxus_native_core::node_ref::AttributeMask,
|
||||
) {
|
||||
let mut widget = self.widget.write().unwrap();
|
||||
widget.attributes_changed(light_root, attributes);
|
||||
}
|
||||
|
||||
fn roots(&self) -> Vec<dioxus_native_core::NodeId> {
|
||||
let widget = self.widget.read().unwrap();
|
||||
widget.roots()
|
||||
}
|
||||
|
||||
fn slot(&self) -> Option<dioxus_native_core::NodeId> {
|
||||
let widget = self.widget.read().unwrap();
|
||||
widget.slot()
|
||||
}
|
||||
}
|
||||
|
||||
impl RinkWidgetResponder for RinkWidgetTraitObject {
|
||||
fn handle_event(&mut self, event: &Event, node: dioxus_native_core::real_dom::NodeMut) {
|
||||
let mut widget = self.widget.write().unwrap();
|
||||
widget.handle_event(event, node);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Unique, Clone)]
|
||||
pub(crate) struct WidgetContext {
|
||||
sender: UnboundedSender<Event>,
|
||||
}
|
||||
|
||||
impl WidgetContext {
|
||||
pub(crate) fn send(&self, event: Event) {
|
||||
self.sender.unbounded_send(event).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
use dioxus_html::input_data::keyboard_types::Key;
|
||||
use dioxus_native_core::{
|
||||
custom_element::CustomElement,
|
||||
real_dom::{NodeImmutable, RealDom},
|
||||
NodeId,
|
||||
};
|
||||
|
||||
use crate::EventData;
|
||||
|
||||
use super::{text_like::TextLike, RinkWidget};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Number {
|
||||
text: TextLike,
|
||||
}
|
||||
|
||||
impl Number {
|
||||
fn increase(&mut self, rdom: &mut RealDom, id: NodeId) {
|
||||
let num = self.text.text().parse::<f64>().unwrap_or(0.0);
|
||||
self.text.set_text((num + 1.0).to_string(), rdom, id);
|
||||
}
|
||||
|
||||
fn decrease(&mut self, rdom: &mut RealDom, id: NodeId) {
|
||||
let num = self.text.text().parse::<f64>().unwrap_or(0.0);
|
||||
self.text.set_text((num - 1.0).to_string(), rdom, id);
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomElement for Number {
|
||||
const NAME: &'static str = "input";
|
||||
|
||||
fn roots(&self) -> Vec<NodeId> {
|
||||
self.text.roots()
|
||||
}
|
||||
|
||||
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
|
||||
Number {
|
||||
text: TextLike::create(root.reborrow()),
|
||||
}
|
||||
}
|
||||
|
||||
fn attributes_changed(
|
||||
&mut self,
|
||||
root: dioxus_native_core::real_dom::NodeMut,
|
||||
attributes: &dioxus_native_core::node_ref::AttributeMask,
|
||||
) {
|
||||
self.text.attributes_changed(root, attributes)
|
||||
}
|
||||
}
|
||||
|
||||
impl RinkWidget for Number {
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
event: &crate::Event,
|
||||
mut node: dioxus_native_core::real_dom::NodeMut,
|
||||
) {
|
||||
if event.name == "keydown" {
|
||||
if let EventData::Keyboard(data) = &event.data {
|
||||
let key = data.key();
|
||||
let is_num_like = match key.clone() {
|
||||
Key::ArrowLeft | Key::ArrowRight | Key::Backspace => true,
|
||||
Key::Character(c)
|
||||
if c == "." || c == "-" || c.chars().all(|c| c.is_numeric()) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if is_num_like {
|
||||
self.text.handle_event(event, node)
|
||||
} else {
|
||||
let id = node.id();
|
||||
let rdom = node.real_dom_mut();
|
||||
match key {
|
||||
Key::ArrowUp => {
|
||||
self.increase(rdom, id);
|
||||
}
|
||||
Key::ArrowDown => {
|
||||
self.decrease(rdom, id);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.text.handle_event(event, node)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use super::text_like::{TextLike, TextLikeController};
|
||||
|
||||
pub(crate) type Password = TextLike<PasswordController>;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct PasswordController;
|
||||
|
||||
impl TextLikeController for PasswordController {
|
||||
fn display_text(&self, text: &str) -> String {
|
||||
text.chars().map(|_| '.').collect()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dioxus_html::{input_data::keyboard_types::Key, KeyboardData, MouseData};
|
||||
use dioxus_native_core::{
|
||||
custom_element::CustomElement,
|
||||
node::{OwnedAttributeDiscription, OwnedAttributeValue},
|
||||
node_ref::AttributeMask,
|
||||
prelude::{ElementNode, NodeType},
|
||||
real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut, RealDom},
|
||||
NodeId,
|
||||
};
|
||||
use shipyard::UniqueView;
|
||||
|
||||
use super::{RinkWidget, WidgetContext};
|
||||
use crate::{query::get_layout, Event, EventData, FormData, Query};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Slider {
|
||||
div_wrapper: NodeId,
|
||||
pre_cursor_div: NodeId,
|
||||
post_cursor_div: NodeId,
|
||||
min: f64,
|
||||
max: f64,
|
||||
step: Option<f64>,
|
||||
value: f64,
|
||||
border: bool,
|
||||
}
|
||||
|
||||
impl Default for Slider {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
div_wrapper: Default::default(),
|
||||
pre_cursor_div: Default::default(),
|
||||
post_cursor_div: Default::default(),
|
||||
min: 0.0,
|
||||
max: 100.0,
|
||||
step: None,
|
||||
value: 0.0,
|
||||
border: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Slider {
|
||||
fn size(&self) -> f64 {
|
||||
self.max - self.min
|
||||
}
|
||||
|
||||
fn step(&self) -> f64 {
|
||||
self.step.unwrap_or(self.size() / 10.0)
|
||||
}
|
||||
|
||||
fn width(el: &ElementNodeMut) -> String {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "width".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
value
|
||||
} else {
|
||||
"1px".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn height(el: &ElementNodeMut) -> String {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "height".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
value
|
||||
} else {
|
||||
"1px".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn update_min_attr(&mut self, el: &ElementNodeMut) {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "min".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
self.min = value.parse().ok().unwrap_or(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_max_attr(&mut self, el: &ElementNodeMut) {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "max".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
self.max = value.parse().ok().unwrap_or(100.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_step_attr(&mut self, el: &ElementNodeMut) {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "step".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
self.step = value.parse().ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn update_size_attr(&mut self, el: &mut ElementNodeMut) {
|
||||
let width = Self::width(el);
|
||||
let height = Self::height(el);
|
||||
let single_char = width
|
||||
.strip_prefix("px")
|
||||
.and_then(|n| n.parse::<u32>().ok().filter(|num| *num > 3))
|
||||
.is_some()
|
||||
|| height
|
||||
.strip_prefix("px")
|
||||
.and_then(|n| n.parse::<u32>().ok().filter(|num| *num > 3))
|
||||
.is_some();
|
||||
self.border = !single_char;
|
||||
let border_style = if self.border { "solid" } else { "none" };
|
||||
el.set_attribute(
|
||||
OwnedAttributeDiscription {
|
||||
name: "border-style".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
border_style.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
fn update_value(&mut self, new: f64) {
|
||||
self.value = new.clamp(self.min, self.max);
|
||||
}
|
||||
|
||||
fn update_value_attr(&mut self, el: &ElementNodeMut) {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "value".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
self.update_value(value.parse().ok().unwrap_or(0.0));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_value(&self, rdom: &mut RealDom, id: NodeId) {
|
||||
let value_percent = (self.value - self.min) / self.size() * 100.0;
|
||||
|
||||
if let Some(mut div) = rdom.get_mut(self.pre_cursor_div) {
|
||||
let node_type = div.node_type_mut();
|
||||
let NodeTypeMut::Element(mut element) = node_type else { panic!("input must be an element") };
|
||||
element.set_attribute(
|
||||
OwnedAttributeDiscription {
|
||||
name: "width".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
format!("{}%", value_percent),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(mut div) = rdom.get_mut(self.post_cursor_div) {
|
||||
let node_type = div.node_type_mut();
|
||||
let NodeTypeMut::Element(mut element) = node_type else { panic!("input must be an element") };
|
||||
element.set_attribute(
|
||||
OwnedAttributeDiscription {
|
||||
name: "width".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
format!("{}%", 100.0 - value_percent),
|
||||
);
|
||||
}
|
||||
|
||||
// send the event
|
||||
let world = rdom.raw_world_mut();
|
||||
|
||||
{
|
||||
let ctx: UniqueView<WidgetContext> = world.borrow().expect("expected widget context");
|
||||
|
||||
let data = FormData {
|
||||
value: self.value.to_string(),
|
||||
values: HashMap::new(),
|
||||
files: None,
|
||||
};
|
||||
ctx.send(Event {
|
||||
id,
|
||||
name: "input",
|
||||
data: EventData::Form(data),
|
||||
bubbles: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_keydown(&mut self, mut root: NodeMut, data: &KeyboardData) {
|
||||
let key = data.key();
|
||||
|
||||
let step = self.step();
|
||||
match key {
|
||||
Key::ArrowDown | Key::ArrowLeft => {
|
||||
self.update_value(self.value - step);
|
||||
}
|
||||
Key::ArrowUp | Key::ArrowRight => {
|
||||
self.update_value(self.value + step);
|
||||
}
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let id = root.id();
|
||||
|
||||
let rdom = root.real_dom_mut();
|
||||
self.write_value(rdom, id);
|
||||
}
|
||||
|
||||
fn handle_mousemove(&mut self, mut root: NodeMut, data: &MouseData) {
|
||||
if !data.held_buttons().is_empty() {
|
||||
let id = root.id();
|
||||
let rdom = root.real_dom_mut();
|
||||
let world = rdom.raw_world_mut();
|
||||
let taffy = {
|
||||
let query: UniqueView<Query> = world.borrow().unwrap();
|
||||
query.stretch.clone()
|
||||
};
|
||||
|
||||
let taffy = taffy.lock().unwrap();
|
||||
|
||||
let layout = get_layout(rdom.get(self.div_wrapper).unwrap(), &taffy).unwrap();
|
||||
|
||||
let width = layout.size.width as f64;
|
||||
let offset = data.element_coordinates();
|
||||
self.update_value(self.min + self.size() * offset.x / width);
|
||||
|
||||
self.write_value(rdom, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomElement for Slider {
|
||||
const NAME: &'static str = "input";
|
||||
|
||||
fn roots(&self) -> Vec<NodeId> {
|
||||
vec![self.div_wrapper]
|
||||
}
|
||||
|
||||
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
|
||||
let node_type = root.node_type();
|
||||
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") };
|
||||
|
||||
let value = el.attributes.get(&OwnedAttributeDiscription {
|
||||
name: "value".to_string(),
|
||||
namespace: None,
|
||||
});
|
||||
let value = value
|
||||
.and_then(|value| match value {
|
||||
OwnedAttributeValue::Text(text) => text.as_str().parse().ok(),
|
||||
OwnedAttributeValue::Float(float) => Some(*float),
|
||||
OwnedAttributeValue::Int(int) => Some(*int as f64),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(0.0);
|
||||
|
||||
drop(node_type);
|
||||
|
||||
let rdom = root.real_dom_mut();
|
||||
|
||||
let pre_cursor_div = rdom.create_node(NodeType::Element(ElementNode {
|
||||
tag: "div".to_string(),
|
||||
attributes: [(
|
||||
OwnedAttributeDiscription {
|
||||
name: "background-color".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
"rgba(10,10,10,0.5)".to_string().into(),
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}));
|
||||
let pre_cursor_div_id = pre_cursor_div.id();
|
||||
|
||||
let cursor_text = rdom.create_node("|".to_string());
|
||||
let cursor_text_id = cursor_text.id();
|
||||
let mut cursor_span = rdom.create_node(NodeType::Element(ElementNode {
|
||||
tag: "div".to_string(),
|
||||
attributes: [].into_iter().collect(),
|
||||
..Default::default()
|
||||
}));
|
||||
cursor_span.add_child(cursor_text_id);
|
||||
let cursor_span_id = cursor_span.id();
|
||||
|
||||
let post_cursor_div = rdom.create_node(NodeType::Element(ElementNode {
|
||||
tag: "span".to_string(),
|
||||
attributes: [
|
||||
(
|
||||
OwnedAttributeDiscription {
|
||||
name: "width".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
"100%".to_string().into(),
|
||||
),
|
||||
(
|
||||
OwnedAttributeDiscription {
|
||||
name: "background-color".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
"rgba(10,10,10,0.5)".to_string().into(),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}));
|
||||
let post_cursor_div_id = post_cursor_div.id();
|
||||
|
||||
let mut div_wrapper = rdom.create_node(NodeType::Element(ElementNode {
|
||||
tag: "div".to_string(),
|
||||
attributes: [
|
||||
(
|
||||
OwnedAttributeDiscription {
|
||||
name: "display".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
"flex".to_string().into(),
|
||||
),
|
||||
(
|
||||
OwnedAttributeDiscription {
|
||||
name: "flex-direction".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
"row".to_string().into(),
|
||||
),
|
||||
(
|
||||
OwnedAttributeDiscription {
|
||||
name: "width".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
"100%".to_string().into(),
|
||||
),
|
||||
(
|
||||
OwnedAttributeDiscription {
|
||||
name: "height".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
"100%".to_string().into(),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}));
|
||||
let div_wrapper_id = div_wrapper.id();
|
||||
div_wrapper.add_child(pre_cursor_div_id);
|
||||
div_wrapper.add_child(cursor_span_id);
|
||||
div_wrapper.add_child(post_cursor_div_id);
|
||||
|
||||
root.add_event_listener("mousemove");
|
||||
root.add_event_listener("mousedown");
|
||||
root.add_event_listener("keydown");
|
||||
|
||||
Self {
|
||||
pre_cursor_div: pre_cursor_div_id,
|
||||
post_cursor_div: post_cursor_div_id,
|
||||
div_wrapper: div_wrapper_id,
|
||||
value,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn attributes_changed(
|
||||
&mut self,
|
||||
mut root: dioxus_native_core::real_dom::NodeMut,
|
||||
attributes: &dioxus_native_core::node_ref::AttributeMask,
|
||||
) {
|
||||
match attributes {
|
||||
AttributeMask::All => {
|
||||
{
|
||||
let node_type = root.node_type_mut();
|
||||
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
|
||||
self.update_value_attr(&el);
|
||||
self.update_size_attr(&mut el);
|
||||
self.update_max_attr(&el);
|
||||
self.update_min_attr(&el);
|
||||
self.update_step_attr(&el);
|
||||
}
|
||||
let id = root.id();
|
||||
self.write_value(root.real_dom_mut(), id);
|
||||
}
|
||||
AttributeMask::Some(attrs) => {
|
||||
{
|
||||
let node_type = root.node_type_mut();
|
||||
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
|
||||
if attrs.contains("width") || attrs.contains("height") {
|
||||
self.update_size_attr(&mut el);
|
||||
}
|
||||
if attrs.contains("max") {
|
||||
self.update_max_attr(&el);
|
||||
}
|
||||
if attrs.contains("min") {
|
||||
self.update_min_attr(&el);
|
||||
}
|
||||
if attrs.contains("step") {
|
||||
self.update_step_attr(&el);
|
||||
}
|
||||
if attrs.contains("value") {
|
||||
self.update_value_attr(&el);
|
||||
}
|
||||
}
|
||||
if attrs.contains("value") {
|
||||
let id = root.id();
|
||||
self.write_value(root.real_dom_mut(), id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RinkWidget for Slider {
|
||||
fn handle_event(&mut self, event: &crate::Event, node: dioxus_native_core::real_dom::NodeMut) {
|
||||
match event.name {
|
||||
"keydown" => {
|
||||
if let EventData::Keyboard(data) = &event.data {
|
||||
self.handle_keydown(node, data);
|
||||
}
|
||||
}
|
||||
|
||||
"mousemove" => {
|
||||
if let EventData::Mouse(data) = &event.data {
|
||||
self.handle_mousemove(node, data);
|
||||
}
|
||||
}
|
||||
|
||||
"mousedown" => {
|
||||
if let EventData::Mouse(data) = &event.data {
|
||||
self.handle_mousemove(node, data);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
use std::{collections::HashMap, io::stdout};
|
||||
|
||||
use crossterm::{cursor::MoveTo, execute};
|
||||
use dioxus_html::{input_data::keyboard_types::Key, KeyboardData, MouseData};
|
||||
use dioxus_native_core::{
|
||||
custom_element::CustomElement,
|
||||
node::OwnedAttributeDiscription,
|
||||
node_ref::AttributeMask,
|
||||
prelude::{ElementNode, NodeType},
|
||||
real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut, RealDom},
|
||||
utils::cursor::{Cursor, Pos},
|
||||
NodeId,
|
||||
};
|
||||
use shipyard::UniqueView;
|
||||
use taffy::geometry::Point;
|
||||
|
||||
use crate::{query::get_layout, Event, EventData, FormData, Query};
|
||||
|
||||
use super::{RinkWidget, WidgetContext};
|
||||
|
||||
pub(crate) trait TextLikeController {
|
||||
fn display_text(&self, text: &str) -> String {
|
||||
text.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct EmptyController;
|
||||
|
||||
impl TextLikeController for EmptyController {}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct TextLike<C: TextLikeController = EmptyController> {
|
||||
text: String,
|
||||
div_wrapper: NodeId,
|
||||
pre_cursor_text: NodeId,
|
||||
highlighted_text: NodeId,
|
||||
post_cursor_text: NodeId,
|
||||
cursor: Cursor,
|
||||
dragging: bool,
|
||||
border: bool,
|
||||
max_len: Option<usize>,
|
||||
controller: C,
|
||||
}
|
||||
|
||||
impl<C: TextLikeController> TextLike<C> {
|
||||
fn width(el: &ElementNodeMut) -> String {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "width".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
value
|
||||
} else {
|
||||
"1px".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn height(el: &ElementNodeMut) -> String {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "height".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
value
|
||||
} else {
|
||||
"1px".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn update_max_width_attr(&mut self, el: &ElementNodeMut) {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "maxlength".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
if let Ok(max_len) = value.parse::<usize>() {
|
||||
self.max_len = Some(max_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_size_attr(&mut self, el: &mut ElementNodeMut) {
|
||||
let width = Self::width(el);
|
||||
let height = Self::height(el);
|
||||
let single_char = width
|
||||
.strip_prefix("px")
|
||||
.and_then(|n| n.parse::<u32>().ok().filter(|num| *num > 3))
|
||||
.is_some()
|
||||
|| height
|
||||
.strip_prefix("px")
|
||||
.and_then(|n| n.parse::<u32>().ok().filter(|num| *num > 3))
|
||||
.is_some();
|
||||
self.border = !single_char;
|
||||
let border_style = if self.border { "solid" } else { "none" };
|
||||
el.set_attribute(
|
||||
OwnedAttributeDiscription {
|
||||
name: "border-style".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
border_style.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
fn update_value_attr(&mut self, el: &ElementNodeMut) {
|
||||
if let Some(value) = el
|
||||
.get_attribute(&OwnedAttributeDiscription {
|
||||
name: "value".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string())
|
||||
{
|
||||
self.text = value;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_text(&mut self, text: String, rdom: &mut RealDom, id: NodeId) {
|
||||
self.text = text;
|
||||
self.write_value(rdom, id);
|
||||
}
|
||||
|
||||
pub(crate) fn text(&self) -> &str {
|
||||
self.text.as_str()
|
||||
}
|
||||
|
||||
fn write_value(&self, rdom: &mut RealDom, id: NodeId) {
|
||||
let start_highlight = self.cursor.first().idx(self.text.as_str());
|
||||
let end_highlight = self.cursor.last().idx(self.text.as_str());
|
||||
let (text_before_first_cursor, text_after_first_cursor) =
|
||||
self.text.split_at(start_highlight);
|
||||
let (text_highlighted, text_after_second_cursor) =
|
||||
text_after_first_cursor.split_at(end_highlight - start_highlight);
|
||||
|
||||
if let Some(mut text) = rdom.get_mut(self.pre_cursor_text) {
|
||||
let node_type = text.node_type_mut();
|
||||
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") };
|
||||
*text.text_mut() = self.controller.display_text(text_before_first_cursor);
|
||||
}
|
||||
|
||||
if let Some(mut text) = rdom.get_mut(self.highlighted_text) {
|
||||
let node_type = text.node_type_mut();
|
||||
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") };
|
||||
*text.text_mut() = self.controller.display_text(text_highlighted);
|
||||
}
|
||||
|
||||
if let Some(mut text) = rdom.get_mut(self.post_cursor_text) {
|
||||
let node_type = text.node_type_mut();
|
||||
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") };
|
||||
*text.text_mut() = self.controller.display_text(text_after_second_cursor);
|
||||
}
|
||||
|
||||
// send the event
|
||||
{
|
||||
let world = rdom.raw_world_mut();
|
||||
let data: FormData = FormData {
|
||||
value: self.text.clone(),
|
||||
values: HashMap::new(),
|
||||
files: None,
|
||||
};
|
||||
let ctx: UniqueView<WidgetContext> = world.borrow().expect("expected widget context");
|
||||
|
||||
ctx.send(Event {
|
||||
id,
|
||||
name: "input",
|
||||
data: EventData::Form(data),
|
||||
bubbles: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_keydown(&mut self, mut root: NodeMut, data: &KeyboardData) {
|
||||
let key = data.key();
|
||||
let modifiers = data.modifiers();
|
||||
let code = data.code();
|
||||
|
||||
if key == Key::Enter {
|
||||
return;
|
||||
}
|
||||
self.cursor.handle_input(
|
||||
&code,
|
||||
&key,
|
||||
&modifiers,
|
||||
&mut self.text,
|
||||
self.max_len.unwrap_or(1000),
|
||||
);
|
||||
|
||||
let id = root.id();
|
||||
|
||||
let rdom = root.real_dom_mut();
|
||||
self.write_value(rdom, id);
|
||||
let world = rdom.raw_world_mut();
|
||||
|
||||
// move cursor to new position
|
||||
let taffy = {
|
||||
let query: UniqueView<Query> = world.borrow().unwrap();
|
||||
query.stretch.clone()
|
||||
};
|
||||
|
||||
let taffy = taffy.lock().unwrap();
|
||||
|
||||
let layout = get_layout(rdom.get(self.div_wrapper).unwrap(), &taffy).unwrap();
|
||||
let Point { x, y } = layout.location;
|
||||
|
||||
let Pos { col, row } = self.cursor.start;
|
||||
let (x, y) = (col as u16 + x as u16, row as u16 + y as u16);
|
||||
if let Ok(pos) = crossterm::cursor::position() {
|
||||
if pos != (x, y) {
|
||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
||||
}
|
||||
} else {
|
||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mousemove(&mut self, mut root: NodeMut, data: &MouseData) {
|
||||
if self.dragging {
|
||||
let id = root.id();
|
||||
let offset = data.element_coordinates();
|
||||
let mut new = Pos::new(offset.x as usize, offset.y as usize);
|
||||
|
||||
// textboxs are only one line tall
|
||||
new.row = 0;
|
||||
|
||||
if new != self.cursor.start {
|
||||
self.cursor.end = Some(new);
|
||||
}
|
||||
let rdom = root.real_dom_mut();
|
||||
self.write_value(rdom, id);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mousedown(&mut self, mut root: NodeMut, data: &MouseData) {
|
||||
let offset = data.element_coordinates();
|
||||
let mut new = Pos::new(offset.x as usize, offset.y as usize);
|
||||
|
||||
// textboxs are only one line tall
|
||||
new.row = 0;
|
||||
|
||||
new.realize_col(self.text.as_str());
|
||||
self.cursor = Cursor::from_start(new);
|
||||
self.dragging = true;
|
||||
|
||||
let id = root.id();
|
||||
|
||||
// move cursor to new position
|
||||
let rdom = root.real_dom_mut();
|
||||
let world = rdom.raw_world_mut();
|
||||
let taffy = {
|
||||
let query: UniqueView<Query> = world.borrow().unwrap();
|
||||
query.stretch.clone()
|
||||
};
|
||||
|
||||
let taffy = taffy.lock().unwrap();
|
||||
|
||||
let layout = get_layout(rdom.get(self.div_wrapper).unwrap(), &taffy).unwrap();
|
||||
let Point { x, y } = layout.location;
|
||||
|
||||
let Pos { col, row } = self.cursor.start;
|
||||
let (x, y) = (col as u16 + x as u16, row as u16 + y as u16);
|
||||
if let Ok(pos) = crossterm::cursor::position() {
|
||||
if pos != (x, y) {
|
||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
||||
}
|
||||
} else {
|
||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
||||
}
|
||||
|
||||
self.write_value(rdom, id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: TextLikeController + Send + Sync + Default + 'static> CustomElement for TextLike<C> {
|
||||
const NAME: &'static str = "input";
|
||||
|
||||
fn roots(&self) -> Vec<NodeId> {
|
||||
vec![self.div_wrapper]
|
||||
}
|
||||
|
||||
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
|
||||
let node_type = root.node_type();
|
||||
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") };
|
||||
|
||||
let value = el
|
||||
.attributes
|
||||
.get(&OwnedAttributeDiscription {
|
||||
name: "value".to_string(),
|
||||
namespace: None,
|
||||
})
|
||||
.and_then(|value| value.as_text())
|
||||
.map(|value| value.to_string());
|
||||
|
||||
drop(node_type);
|
||||
|
||||
let rdom = root.real_dom_mut();
|
||||
|
||||
let pre_text = rdom.create_node(String::new());
|
||||
let pre_text_id = pre_text.id();
|
||||
let highlighted_text = rdom.create_node(String::new());
|
||||
let highlighted_text_id = highlighted_text.id();
|
||||
let mut highlighted_text_span = rdom.create_node(NodeType::Element(ElementNode {
|
||||
tag: "span".to_string(),
|
||||
attributes: [(
|
||||
OwnedAttributeDiscription {
|
||||
name: "background-color".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
"rgba(255, 255, 255, 50%)".to_string().into(),
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}));
|
||||
highlighted_text_span.add_child(highlighted_text_id);
|
||||
let highlighted_text_span_id = highlighted_text_span.id();
|
||||
let post_text = rdom.create_node(value.clone().unwrap_or_default());
|
||||
let post_text_id = post_text.id();
|
||||
let mut div_wrapper = rdom.create_node(NodeType::Element(ElementNode {
|
||||
tag: "div".to_string(),
|
||||
attributes: [(
|
||||
OwnedAttributeDiscription {
|
||||
name: "display".to_string(),
|
||||
namespace: Some("style".to_string()),
|
||||
},
|
||||
"flex".to_string().into(),
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}));
|
||||
let div_wrapper_id = div_wrapper.id();
|
||||
div_wrapper.add_child(pre_text_id);
|
||||
div_wrapper.add_child(highlighted_text_span_id);
|
||||
div_wrapper.add_child(post_text_id);
|
||||
|
||||
div_wrapper.add_event_listener("mousemove");
|
||||
div_wrapper.add_event_listener("mousedown");
|
||||
div_wrapper.add_event_listener("mouseup");
|
||||
div_wrapper.add_event_listener("mouseleave");
|
||||
div_wrapper.add_event_listener("mouseenter");
|
||||
root.add_event_listener("keydown");
|
||||
root.add_event_listener("focusout");
|
||||
|
||||
Self {
|
||||
pre_cursor_text: pre_text_id,
|
||||
highlighted_text: highlighted_text_id,
|
||||
post_cursor_text: post_text_id,
|
||||
div_wrapper: div_wrapper_id,
|
||||
cursor: Cursor::default(),
|
||||
text: value.unwrap_or_default(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn attributes_changed(
|
||||
&mut self,
|
||||
mut root: dioxus_native_core::real_dom::NodeMut,
|
||||
attributes: &dioxus_native_core::node_ref::AttributeMask,
|
||||
) {
|
||||
match attributes {
|
||||
AttributeMask::All => {
|
||||
{
|
||||
let node_type = root.node_type_mut();
|
||||
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
|
||||
self.update_value_attr(&el);
|
||||
self.update_size_attr(&mut el);
|
||||
self.update_max_width_attr(&el);
|
||||
}
|
||||
let id = root.id();
|
||||
self.write_value(root.real_dom_mut(), id);
|
||||
}
|
||||
AttributeMask::Some(attrs) => {
|
||||
{
|
||||
let node_type = root.node_type_mut();
|
||||
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
|
||||
if attrs.contains("width") || attrs.contains("height") {
|
||||
self.update_size_attr(&mut el);
|
||||
}
|
||||
if attrs.contains("maxlength") {
|
||||
self.update_max_width_attr(&el);
|
||||
}
|
||||
if attrs.contains("value") {
|
||||
self.update_value_attr(&el);
|
||||
}
|
||||
}
|
||||
if attrs.contains("value") {
|
||||
let id = root.id();
|
||||
self.write_value(root.real_dom_mut(), id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: TextLikeController + Send + Sync + Default + 'static> RinkWidget for TextLike<C> {
|
||||
fn handle_event(&mut self, event: &crate::Event, node: NodeMut) {
|
||||
match event.name {
|
||||
"keydown" => {
|
||||
if let EventData::Keyboard(data) = &event.data {
|
||||
self.handle_keydown(node, data);
|
||||
}
|
||||
}
|
||||
|
||||
"mousemove" => {
|
||||
if let EventData::Mouse(data) = &event.data {
|
||||
self.handle_mousemove(node, data);
|
||||
}
|
||||
}
|
||||
|
||||
"mousedown" => {
|
||||
if let EventData::Mouse(data) = &event.data {
|
||||
self.handle_mousedown(node, data);
|
||||
}
|
||||
}
|
||||
|
||||
"mouseup" => {
|
||||
self.dragging = false;
|
||||
}
|
||||
|
||||
"mouseleave" => {
|
||||
self.dragging = false;
|
||||
}
|
||||
|
||||
"mouseenter" => {
|
||||
self.dragging = false;
|
||||
}
|
||||
|
||||
"focusout" => {
|
||||
execute!(stdout(), MoveTo(0, 1000)).unwrap();
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
use super::text_like::TextLike;
|
||||
|
||||
pub(crate) type TextBox = TextLike;
|
||||
Reference in new issue
Block a user