make TUI widgets cross framework
This commit is contained in:
1 parent
9a16687bdc
commit
c4c35cb046
33 files changed
+3025
-1091
No files matched your search
@@ -591,6 +591,8 @@ impl<'src> ScopeState {
|
|||||||
propagates: event.propagates,
|
propagates: event.propagates,
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
println!("Failed to downcast event data")
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_html::FormData;
|
use dioxus_tui::Config;
|
||||||
use dioxus_tui::prelude::*;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus_tui::launch_cfg(app, Config::new());
|
dioxus_tui::launch_cfg(app, Config::new());
|
||||||
@@ -18,8 +17,8 @@ fn app(cx: Scope) -> Element {
|
|||||||
align_items: "center",
|
align_items: "center",
|
||||||
justify_content: "center",
|
justify_content: "center",
|
||||||
|
|
||||||
Input{
|
input {
|
||||||
oninput: |data: FormData| if &data.value == "good"{
|
oninput: |data| if &data.value == "good"{
|
||||||
bg_green.set(true);
|
bg_green.set(true);
|
||||||
} else{
|
} else{
|
||||||
bg_green.set(false);
|
bg_green.set(false);
|
||||||
@@ -30,8 +29,8 @@ fn app(cx: Scope) -> Element {
|
|||||||
height: "10%",
|
height: "10%",
|
||||||
checked: "true",
|
checked: "true",
|
||||||
}
|
}
|
||||||
Input{
|
input {
|
||||||
oninput: |data: FormData| if &data.value == "hello world"{
|
oninput: |data| if &data.value == "hello world"{
|
||||||
bg_green.set(true);
|
bg_green.set(true);
|
||||||
} else{
|
} else{
|
||||||
bg_green.set(false);
|
bg_green.set(false);
|
||||||
@@ -40,8 +39,8 @@ fn app(cx: Scope) -> Element {
|
|||||||
height: "10%",
|
height: "10%",
|
||||||
maxlength: "11",
|
maxlength: "11",
|
||||||
}
|
}
|
||||||
Input{
|
input {
|
||||||
oninput: |data: FormData| {
|
oninput: |data| {
|
||||||
if (data.value.parse::<f32>().unwrap() - 40.0).abs() < 5.0 {
|
if (data.value.parse::<f32>().unwrap() - 40.0).abs() < 5.0 {
|
||||||
bg_green.set(true);
|
bg_green.set(true);
|
||||||
} else{
|
} else{
|
||||||
@@ -54,8 +53,8 @@ fn app(cx: Scope) -> Element {
|
|||||||
min: "20",
|
min: "20",
|
||||||
max: "80",
|
max: "80",
|
||||||
}
|
}
|
||||||
Input{
|
input {
|
||||||
oninput: |data: FormData| {
|
oninput: |data| {
|
||||||
if data.value == "10"{
|
if data.value == "10"{
|
||||||
bg_green.set(true);
|
bg_green.set(true);
|
||||||
} else{
|
} else{
|
||||||
@@ -67,8 +66,8 @@ fn app(cx: Scope) -> Element {
|
|||||||
height: "10%",
|
height: "10%",
|
||||||
maxlength: "4",
|
maxlength: "4",
|
||||||
}
|
}
|
||||||
Input{
|
input {
|
||||||
oninput: |data: FormData| {
|
oninput: |data| {
|
||||||
if data.value == "hello world"{
|
if data.value == "hello world"{
|
||||||
bg_green.set(true);
|
bg_green.set(true);
|
||||||
} else{
|
} else{
|
||||||
@@ -80,8 +79,10 @@ fn app(cx: Scope) -> Element {
|
|||||||
height: "10%",
|
height: "10%",
|
||||||
maxlength: "11",
|
maxlength: "11",
|
||||||
}
|
}
|
||||||
Input{
|
input {
|
||||||
onclick: |_: FormData| bg_green.set(true),
|
oninput: |_| {
|
||||||
|
bg_green.set(true)
|
||||||
|
},
|
||||||
r#type: "button",
|
r#type: "button",
|
||||||
value: "green",
|
value: "green",
|
||||||
width: "50%",
|
width: "50%",
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
pub mod prelude;
|
|
||||||
pub mod widgets;
|
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
@@ -8,7 +5,6 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use dioxus_core::{Component, ElementId, VirtualDom};
|
use dioxus_core::{Component, ElementId, VirtualDom};
|
||||||
use dioxus_html::EventData;
|
|
||||||
use dioxus_native_core::dioxus::{DioxusState, NodeImmutableDioxusExt};
|
use dioxus_native_core::dioxus::{DioxusState, NodeImmutableDioxusExt};
|
||||||
use dioxus_native_core::prelude::*;
|
use dioxus_native_core::prelude::*;
|
||||||
|
|
||||||
@@ -83,13 +79,14 @@ impl Driver for DioxusRenderer {
|
|||||||
rdom: &Arc<RwLock<RealDom>>,
|
rdom: &Arc<RwLock<RealDom>>,
|
||||||
id: NodeId,
|
id: NodeId,
|
||||||
event: &str,
|
event: &str,
|
||||||
value: Rc<EventData>,
|
value: Rc<rink::EventData>,
|
||||||
bubbles: bool,
|
bubbles: bool,
|
||||||
) {
|
) {
|
||||||
let id = { rdom.read().unwrap().get(id).unwrap().mounted_id() };
|
let id = { rdom.read().unwrap().get(id).unwrap().mounted_id() };
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
|
let inner_value = value.deref().clone();
|
||||||
self.vdom
|
self.vdom
|
||||||
.handle_event(event, value.deref().clone().into_any(), id, bubbles);
|
.handle_event(event, inner_value.into_any(), id, bubbles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
pub use crate::widgets::*;
|
|
||||||
pub use rink::Config;
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus_elements::input_data::keyboard_types::Key;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
use dioxus_html::FormData;
|
|
||||||
|
|
||||||
#[derive(Props)]
|
|
||||||
pub(crate) struct ButtonProps<'a> {
|
|
||||||
#[props(!optional)]
|
|
||||||
raw_onclick: Option<&'a EventHandler<'a, FormData>>,
|
|
||||||
#[props(!optional)]
|
|
||||||
value: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
width: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
height: Option<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub(crate) fn Button<'a>(cx: Scope<'a, ButtonProps>) -> Element<'a> {
|
|
||||||
let state = use_state(cx, || false);
|
|
||||||
let width = cx.props.width.unwrap_or("1px");
|
|
||||||
let height = cx.props.height.unwrap_or("1px");
|
|
||||||
|
|
||||||
let single_char = width == "1px" || height == "1px";
|
|
||||||
let text = if let Some(v) = cx.props.value { v } else { "" };
|
|
||||||
let border_style = if single_char { "none" } else { "solid" };
|
|
||||||
let update = || {
|
|
||||||
let new_state = !state.get();
|
|
||||||
if let Some(callback) = cx.props.raw_onclick {
|
|
||||||
callback.call(FormData {
|
|
||||||
value: text.to_string(),
|
|
||||||
values: HashMap::new(),
|
|
||||||
files: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
state.set(new_state);
|
|
||||||
};
|
|
||||||
render! {
|
|
||||||
div{
|
|
||||||
width: "{width}",
|
|
||||||
height: "{height}",
|
|
||||||
border_style: "{border_style}",
|
|
||||||
flex_direction: "row",
|
|
||||||
align_items: "center",
|
|
||||||
justify_content: "center",
|
|
||||||
onclick: move |_| {
|
|
||||||
update();
|
|
||||||
},
|
|
||||||
onkeydown: move |evt|{
|
|
||||||
if !evt.is_auto_repeating() && match evt.key(){ Key::Character(c) if c == " " =>true, Key::Enter=>true, _=>false } {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"{text}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus_elements::input_data::keyboard_types::Key;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
use dioxus_html::FormData;
|
|
||||||
|
|
||||||
#[derive(Props)]
|
|
||||||
pub(crate) struct CheckBoxProps<'a> {
|
|
||||||
#[props(!optional)]
|
|
||||||
raw_oninput: Option<&'a EventHandler<'a, FormData>>,
|
|
||||||
#[props(!optional)]
|
|
||||||
value: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
width: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
height: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
checked: Option<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub(crate) fn CheckBox<'a>(cx: Scope<'a, CheckBoxProps>) -> Element<'a> {
|
|
||||||
let state = use_state(cx, || cx.props.checked.filter(|&c| c == "true").is_some());
|
|
||||||
let width = cx.props.width.unwrap_or("1px");
|
|
||||||
let height = cx.props.height.unwrap_or("1px");
|
|
||||||
|
|
||||||
let single_char = width == "1px" && height == "1px";
|
|
||||||
let text = if single_char {
|
|
||||||
if *state.get() {
|
|
||||||
"☑"
|
|
||||||
} else {
|
|
||||||
"☐"
|
|
||||||
}
|
|
||||||
} else if *state.get() {
|
|
||||||
"✓"
|
|
||||||
} else {
|
|
||||||
" "
|
|
||||||
};
|
|
||||||
let border_style = if width == "1px" || height == "1px" {
|
|
||||||
"none"
|
|
||||||
} else {
|
|
||||||
"solid"
|
|
||||||
};
|
|
||||||
let update = move || {
|
|
||||||
let new_state = !state.get();
|
|
||||||
if let Some(callback) = cx.props.raw_oninput {
|
|
||||||
callback.call(FormData {
|
|
||||||
value: if let Some(value) = &cx.props.value {
|
|
||||||
if new_state {
|
|
||||||
value.to_string()
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"on".to_string()
|
|
||||||
},
|
|
||||||
values: HashMap::new(),
|
|
||||||
files: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
state.set(new_state);
|
|
||||||
};
|
|
||||||
render! {
|
|
||||||
div {
|
|
||||||
width: "{width}",
|
|
||||||
height: "{height}",
|
|
||||||
border_style: "{border_style}",
|
|
||||||
align_items: "center",
|
|
||||||
justify_content: "center",
|
|
||||||
onclick: move |_| {
|
|
||||||
update();
|
|
||||||
},
|
|
||||||
onkeydown: move |evt| {
|
|
||||||
if !evt.is_auto_repeating() && match evt.key(){ Key::Character(c) if c == " " =>true, Key::Enter=>true, _=>false } {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"{text}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus_core::prelude::fc_to_builder;
|
|
||||||
use dioxus_html::FormData;
|
|
||||||
|
|
||||||
use crate::widgets::button::Button;
|
|
||||||
use crate::widgets::checkbox::CheckBox;
|
|
||||||
use crate::widgets::number::NumbericInput;
|
|
||||||
use crate::widgets::password::Password;
|
|
||||||
use crate::widgets::slider::Slider;
|
|
||||||
use crate::widgets::textbox::TextBox;
|
|
||||||
|
|
||||||
#[derive(Props)]
|
|
||||||
pub struct InputProps<'a> {
|
|
||||||
r#type: Option<&'static str>,
|
|
||||||
oninput: Option<EventHandler<'a, FormData>>,
|
|
||||||
onclick: Option<EventHandler<'a, FormData>>,
|
|
||||||
value: Option<&'a str>,
|
|
||||||
size: Option<&'a str>,
|
|
||||||
maxlength: Option<&'a str>,
|
|
||||||
width: Option<&'a str>,
|
|
||||||
height: Option<&'a str>,
|
|
||||||
min: Option<&'a str>,
|
|
||||||
max: Option<&'a str>,
|
|
||||||
step: Option<&'a str>,
|
|
||||||
checked: Option<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub fn Input<'a>(cx: Scope<'a, InputProps<'a>>) -> Element<'a> {
|
|
||||||
cx.render(match cx.props.r#type {
|
|
||||||
Some("checkbox") => {
|
|
||||||
rsx! {
|
|
||||||
CheckBox{
|
|
||||||
raw_oninput: cx.props.oninput.as_ref(),
|
|
||||||
value: cx.props.value,
|
|
||||||
width: cx.props.width,
|
|
||||||
height: cx.props.height,
|
|
||||||
checked: cx.props.checked,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some("range") => {
|
|
||||||
rsx! {
|
|
||||||
Slider{
|
|
||||||
raw_oninput: cx.props.oninput.as_ref(),
|
|
||||||
value: cx.props.value,
|
|
||||||
width: cx.props.width,
|
|
||||||
height: cx.props.height,
|
|
||||||
max: cx.props.max,
|
|
||||||
min: cx.props.min,
|
|
||||||
step: cx.props.step,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some("button") => {
|
|
||||||
rsx! {
|
|
||||||
Button{
|
|
||||||
raw_onclick: cx.props.onclick.as_ref(),
|
|
||||||
value: cx.props.value,
|
|
||||||
width: cx.props.width,
|
|
||||||
height: cx.props.height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some("number") => {
|
|
||||||
rsx! {
|
|
||||||
NumbericInput{
|
|
||||||
raw_oninput: cx.props.oninput.as_ref(),
|
|
||||||
value: cx.props.value,
|
|
||||||
size: cx.props.size,
|
|
||||||
max_length: cx.props.maxlength,
|
|
||||||
width: cx.props.width,
|
|
||||||
height: cx.props.height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some("password") => {
|
|
||||||
rsx! {
|
|
||||||
Password{
|
|
||||||
raw_oninput: cx.props.oninput.as_ref(),
|
|
||||||
value: cx.props.value,
|
|
||||||
size: cx.props.size,
|
|
||||||
max_length: cx.props.maxlength,
|
|
||||||
width: cx.props.width,
|
|
||||||
height: cx.props.height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
rsx! {
|
|
||||||
TextBox{
|
|
||||||
raw_oninput: cx.props.oninput.as_ref(),
|
|
||||||
value: cx.props.value,
|
|
||||||
size: cx.props.size,
|
|
||||||
max_length: cx.props.maxlength,
|
|
||||||
width: cx.props.width,
|
|
||||||
height: cx.props.height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
mod button;
|
|
||||||
mod checkbox;
|
|
||||||
mod input;
|
|
||||||
mod number;
|
|
||||||
mod password;
|
|
||||||
mod slider;
|
|
||||||
mod textbox;
|
|
||||||
|
|
||||||
use dioxus_core::{RenderReturn, Scope};
|
|
||||||
use dioxus_native_core::NodeId;
|
|
||||||
pub use input::*;
|
|
||||||
|
|
||||||
use crate::DioxusElementToNodeId;
|
|
||||||
|
|
||||||
pub(crate) fn get_root_id<T>(cx: Scope<T>) -> Option<NodeId> {
|
|
||||||
if let RenderReturn::Ready(sync) = cx.root_node() {
|
|
||||||
let mapping: DioxusElementToNodeId = cx.consume_context()?;
|
|
||||||
mapping.get_node_id(sync.root_ids.get(0)?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
use crate::widgets::get_root_id;
|
|
||||||
use crossterm::{cursor::MoveTo, execute};
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus_elements::input_data::keyboard_types::Key;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
use dioxus_html::FormData;
|
|
||||||
use dioxus_native_core::utils::cursor::{Cursor, Pos};
|
|
||||||
use rink::Query;
|
|
||||||
use std::{collections::HashMap, io::stdout};
|
|
||||||
use taffy::geometry::Point;
|
|
||||||
|
|
||||||
#[derive(Props)]
|
|
||||||
pub(crate) struct NumbericInputProps<'a> {
|
|
||||||
#[props(!optional)]
|
|
||||||
raw_oninput: Option<&'a EventHandler<'a, FormData>>,
|
|
||||||
#[props(!optional)]
|
|
||||||
value: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
size: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
max_length: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
width: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
height: Option<&'a str>,
|
|
||||||
}
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a> {
|
|
||||||
let tui_query: Query = cx.consume_context().unwrap();
|
|
||||||
let tui_query_clone = tui_query.clone();
|
|
||||||
|
|
||||||
let text_ref = use_ref(cx, || {
|
|
||||||
if let Some(intial_text) = cx.props.value {
|
|
||||||
intial_text.to_string()
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let cursor = use_ref(cx, Cursor::default);
|
|
||||||
let dragging = use_state(cx, || false);
|
|
||||||
|
|
||||||
let text = text_ref.read().clone();
|
|
||||||
let start_highlight = cursor.read().first().idx(&*text);
|
|
||||||
let end_highlight = cursor.read().last().idx(&*text);
|
|
||||||
let (text_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight);
|
|
||||||
let (text_highlighted, text_after_second_cursor) =
|
|
||||||
text_after_first_cursor.split_at(end_highlight - start_highlight);
|
|
||||||
|
|
||||||
let max_len = cx
|
|
||||||
.props
|
|
||||||
.max_length
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|s| s.parse().ok())
|
|
||||||
.unwrap_or(usize::MAX);
|
|
||||||
|
|
||||||
let width = cx
|
|
||||||
.props
|
|
||||||
.width
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
// px is the same as em in tui
|
|
||||||
.or_else(|| cx.props.size.map(|s| s.to_string() + "px"))
|
|
||||||
.unwrap_or_else(|| "10px".to_string());
|
|
||||||
let height = cx.props.height.unwrap_or("3px");
|
|
||||||
|
|
||||||
// don't draw a border unless there is enough space
|
|
||||||
let border = if width
|
|
||||||
.strip_suffix("px")
|
|
||||||
.and_then(|w| w.parse::<i32>().ok())
|
|
||||||
.filter(|w| *w < 3)
|
|
||||||
.is_some()
|
|
||||||
|| height
|
|
||||||
.strip_suffix("px")
|
|
||||||
.and_then(|h| h.parse::<i32>().ok())
|
|
||||||
.filter(|h| *h < 3)
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
"none"
|
|
||||||
} else {
|
|
||||||
"solid"
|
|
||||||
};
|
|
||||||
|
|
||||||
let update = |text: String| {
|
|
||||||
if let Some(input_handler) = &cx.props.raw_oninput {
|
|
||||||
input_handler.call(FormData {
|
|
||||||
value: text,
|
|
||||||
values: HashMap::new(),
|
|
||||||
files: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let increase = move || {
|
|
||||||
let mut text = text_ref.write();
|
|
||||||
*text = (text.parse::<f64>().unwrap_or(0.0) + 1.0).to_string();
|
|
||||||
update(text.clone());
|
|
||||||
};
|
|
||||||
let decrease = move || {
|
|
||||||
let mut text = text_ref.write();
|
|
||||||
*text = (text.parse::<f64>().unwrap_or(0.0) - 1.0).to_string();
|
|
||||||
update(text.clone());
|
|
||||||
};
|
|
||||||
|
|
||||||
render! {
|
|
||||||
div{
|
|
||||||
width: "{width}",
|
|
||||||
height: "{height}",
|
|
||||||
border_style: "{border}",
|
|
||||||
|
|
||||||
onkeydown: move |k| {
|
|
||||||
let is_text = match k.key(){
|
|
||||||
Key::ArrowLeft | Key::ArrowRight | Key::Backspace => true,
|
|
||||||
Key::Character(c) if c=="." || c== "-" || c.chars().all(|c|c.is_numeric())=> true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
if is_text{
|
|
||||||
let mut text = text_ref.write();
|
|
||||||
cursor.write().handle_input(&k.code(), &k.key(), &k.modifiers(), &mut *text, max_len);
|
|
||||||
update(text.clone());
|
|
||||||
|
|
||||||
let node = tui_query.get(get_root_id(cx).unwrap());
|
|
||||||
let Point{ x, y } = node.pos().unwrap();
|
|
||||||
|
|
||||||
let Pos { col, row } = cursor.read().start;
|
|
||||||
let (x, y) = (col as u16 + x as u16 + u16::from(border != "none"), row as u16 + y as u16 + u16::from(border != "none"));
|
|
||||||
if let Ok(pos) = crossterm::cursor::position() {
|
|
||||||
if pos != (x, y){
|
|
||||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
match k.key() {
|
|
||||||
Key::ArrowUp =>{
|
|
||||||
increase();
|
|
||||||
}
|
|
||||||
Key::ArrowDown =>{
|
|
||||||
decrease();
|
|
||||||
}
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmousemove: move |evt| {
|
|
||||||
if *dragging.get() {
|
|
||||||
let offset = evt.data.element_coordinates();
|
|
||||||
let mut new = Pos::new(offset.x as usize, offset.y as usize);
|
|
||||||
if border != "none" {
|
|
||||||
new.col = new.col.saturating_sub(1);
|
|
||||||
}
|
|
||||||
// textboxs are only one line tall
|
|
||||||
new.row = 0;
|
|
||||||
|
|
||||||
if new != cursor.read().start {
|
|
||||||
cursor.write().end = Some(new);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmousedown: move |evt| {
|
|
||||||
let offset = evt.data.element_coordinates();
|
|
||||||
let mut new = Pos::new(offset.x as usize, offset.y as usize);
|
|
||||||
if border != "none" {
|
|
||||||
new.col = new.col.saturating_sub(1);
|
|
||||||
}
|
|
||||||
new.row = 0;
|
|
||||||
|
|
||||||
new.realize_col(text_ref.read().as_str());
|
|
||||||
cursor.set(Cursor::from_start(new));
|
|
||||||
dragging.set(true);
|
|
||||||
let node = tui_query_clone.get(get_root_id(cx).unwrap());
|
|
||||||
let Point{ x, y } = node.pos().unwrap();
|
|
||||||
|
|
||||||
let Pos { col, row } = cursor.read().start;
|
|
||||||
let (x, y) = (col as u16 + x as u16 + u16::from(border != "none"), row as u16 + y as u16 + u16::from(border != "none"));
|
|
||||||
if let Ok(pos) = crossterm::cursor::position() {
|
|
||||||
if pos != (x, y){
|
|
||||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmouseup: move |_| {
|
|
||||||
dragging.set(false);
|
|
||||||
},
|
|
||||||
onmouseleave: move |_| {
|
|
||||||
dragging.set(false);
|
|
||||||
},
|
|
||||||
onmouseenter: move |_| {
|
|
||||||
dragging.set(false);
|
|
||||||
},
|
|
||||||
onfocusout: |_| {
|
|
||||||
execute!(stdout(), MoveTo(0, 1000)).unwrap();
|
|
||||||
},
|
|
||||||
|
|
||||||
"{text_before_first_cursor}"
|
|
||||||
|
|
||||||
span{
|
|
||||||
background_color: "rgba(255, 255, 255, 50%)",
|
|
||||||
|
|
||||||
"{text_highlighted}"
|
|
||||||
}
|
|
||||||
|
|
||||||
"{text_after_second_cursor}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
use crate::widgets::get_root_id;
|
|
||||||
use crossterm::{cursor::*, execute};
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus_elements::input_data::keyboard_types::Key;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
use dioxus_html::FormData;
|
|
||||||
use dioxus_native_core::utils::cursor::{Cursor, Pos};
|
|
||||||
use rink::Query;
|
|
||||||
use std::{collections::HashMap, io::stdout};
|
|
||||||
use taffy::geometry::Point;
|
|
||||||
|
|
||||||
#[derive(Props)]
|
|
||||||
pub(crate) struct PasswordProps<'a> {
|
|
||||||
#[props(!optional)]
|
|
||||||
raw_oninput: Option<&'a EventHandler<'a, FormData>>,
|
|
||||||
#[props(!optional)]
|
|
||||||
value: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
size: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
max_length: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
width: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
height: Option<&'a str>,
|
|
||||||
}
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
|
|
||||||
let tui_query: Query = cx.consume_context().unwrap();
|
|
||||||
let tui_query_clone = tui_query.clone();
|
|
||||||
|
|
||||||
let text_ref = use_ref(cx, || {
|
|
||||||
if let Some(intial_text) = cx.props.value {
|
|
||||||
intial_text.to_string()
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let cursor = use_ref(cx, Cursor::default);
|
|
||||||
let dragging = use_state(cx, || false);
|
|
||||||
|
|
||||||
let text = text_ref.read().clone();
|
|
||||||
let start_highlight = cursor.read().first().idx(&*text);
|
|
||||||
let end_highlight = cursor.read().last().idx(&*text);
|
|
||||||
let (text_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight);
|
|
||||||
let (text_highlighted, text_after_second_cursor) =
|
|
||||||
text_after_first_cursor.split_at(end_highlight - start_highlight);
|
|
||||||
|
|
||||||
let text_before_first_cursor = ".".repeat(text_before_first_cursor.len());
|
|
||||||
let text_highlighted = ".".repeat(text_highlighted.len());
|
|
||||||
let text_after_second_cursor = ".".repeat(text_after_second_cursor.len());
|
|
||||||
|
|
||||||
let max_len = cx
|
|
||||||
.props
|
|
||||||
.max_length
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|s| s.parse().ok())
|
|
||||||
.unwrap_or(usize::MAX);
|
|
||||||
|
|
||||||
let width = cx
|
|
||||||
.props
|
|
||||||
.width
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
// px is the same as em in tui
|
|
||||||
.or_else(|| cx.props.size.map(|s| s.to_string() + "px"))
|
|
||||||
.unwrap_or_else(|| "10px".to_string());
|
|
||||||
let height = cx.props.height.unwrap_or("3px");
|
|
||||||
|
|
||||||
// don't draw a border unless there is enough space
|
|
||||||
let border = if width
|
|
||||||
.strip_suffix("px")
|
|
||||||
.and_then(|w| w.parse::<i32>().ok())
|
|
||||||
.filter(|w| *w < 3)
|
|
||||||
.is_some()
|
|
||||||
|| height
|
|
||||||
.strip_suffix("px")
|
|
||||||
.and_then(|h| h.parse::<i32>().ok())
|
|
||||||
.filter(|h| *h < 3)
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
"none"
|
|
||||||
} else {
|
|
||||||
"solid"
|
|
||||||
};
|
|
||||||
|
|
||||||
let onkeydown = move |k: KeyboardEvent| {
|
|
||||||
if k.key() == Key::Enter {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut text = text_ref.write();
|
|
||||||
cursor
|
|
||||||
.write()
|
|
||||||
.handle_input(&k.code(), &k.key(), &k.modifiers(), &mut *text, max_len);
|
|
||||||
if let Some(input_handler) = &cx.props.raw_oninput {
|
|
||||||
input_handler.call(FormData {
|
|
||||||
value: text.clone(),
|
|
||||||
values: HashMap::new(),
|
|
||||||
files: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let node = tui_query.get(get_root_id(cx).unwrap());
|
|
||||||
let Point { x, y } = node.pos().unwrap();
|
|
||||||
|
|
||||||
let Pos { col, row } = cursor.read().start;
|
|
||||||
let (x, y) = (
|
|
||||||
col as u16 + x as u16 + u16::from(border != "none"),
|
|
||||||
row as u16 + y as u16 + u16::from(border != "none"),
|
|
||||||
);
|
|
||||||
if let Ok(pos) = crossterm::cursor::position() {
|
|
||||||
if pos != (x, y) {
|
|
||||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render! {
|
|
||||||
div {
|
|
||||||
width: "{width}",
|
|
||||||
height: "{height}",
|
|
||||||
border_style: "{border}",
|
|
||||||
|
|
||||||
onkeydown: onkeydown,
|
|
||||||
|
|
||||||
onmousemove: move |evt| {
|
|
||||||
if *dragging.get() {
|
|
||||||
let offset = evt.data.element_coordinates();
|
|
||||||
let mut new = Pos::new(offset.x as usize, offset.y as usize);
|
|
||||||
if border != "none" {
|
|
||||||
new.col = new.col.saturating_sub(1);
|
|
||||||
}
|
|
||||||
// textboxs are only one line tall
|
|
||||||
new.row = 0;
|
|
||||||
|
|
||||||
if new != cursor.read().start {
|
|
||||||
cursor.write().end = Some(new);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onmousedown: move |evt| {
|
|
||||||
let offset = evt.data.element_coordinates();
|
|
||||||
let mut new = Pos::new(offset.x as usize, offset.y as usize);
|
|
||||||
if border != "none" {
|
|
||||||
new.col = new.col.saturating_sub(1);
|
|
||||||
}
|
|
||||||
// textboxs are only one line tall
|
|
||||||
new.row = 0;
|
|
||||||
|
|
||||||
new.realize_col(text_ref.read().as_str());
|
|
||||||
cursor.set(Cursor::from_start(new));
|
|
||||||
dragging.set(true);
|
|
||||||
let node = tui_query_clone.get(get_root_id(cx).unwrap());
|
|
||||||
let Point{ x, y } = node.pos().unwrap();
|
|
||||||
|
|
||||||
let Pos { col, row } = cursor.read().start;
|
|
||||||
let (x, y) = (col as u16 + x as u16 + u16::from(border != "none"), row as u16 + y as u16 + u16::from(border != "none"));
|
|
||||||
if let Ok(pos) = crossterm::cursor::position() {
|
|
||||||
if pos != (x, y){
|
|
||||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmouseup: move |_| {
|
|
||||||
dragging.set(false);
|
|
||||||
},
|
|
||||||
onmouseleave: move |_| {
|
|
||||||
dragging.set(false);
|
|
||||||
},
|
|
||||||
onmouseenter: move |_| {
|
|
||||||
dragging.set(false);
|
|
||||||
},
|
|
||||||
onfocusout: |_| {
|
|
||||||
execute!(stdout(), MoveTo(0, 1000)).unwrap();
|
|
||||||
},
|
|
||||||
|
|
||||||
"{text_before_first_cursor}"
|
|
||||||
|
|
||||||
span{
|
|
||||||
background_color: "rgba(255, 255, 255, 50%)",
|
|
||||||
|
|
||||||
"{text_highlighted}"
|
|
||||||
}
|
|
||||||
|
|
||||||
"{text_after_second_cursor}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::widgets::get_root_id;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus_elements::input_data::keyboard_types::Key;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
use dioxus_html::FormData;
|
|
||||||
use rink::Query;
|
|
||||||
|
|
||||||
#[derive(Props)]
|
|
||||||
pub(crate) struct SliderProps<'a> {
|
|
||||||
#[props(!optional)]
|
|
||||||
raw_oninput: Option<&'a EventHandler<'a, FormData>>,
|
|
||||||
#[props(!optional)]
|
|
||||||
value: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
width: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
height: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
min: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
max: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
step: Option<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub(crate) fn Slider<'a>(cx: Scope<'a, SliderProps>) -> Element<'a> {
|
|
||||||
let tui_query: Query = cx.consume_context().unwrap();
|
|
||||||
|
|
||||||
let value_state = use_state(cx, || 0.0);
|
|
||||||
let value: Option<f32> = cx.props.value.and_then(|v| v.parse().ok());
|
|
||||||
let width = cx.props.width.unwrap_or("20px");
|
|
||||||
let height = cx.props.height.unwrap_or("1px");
|
|
||||||
let min = cx.props.min.and_then(|v| v.parse().ok()).unwrap_or(0.0);
|
|
||||||
let max = cx.props.max.and_then(|v| v.parse().ok()).unwrap_or(100.0);
|
|
||||||
let size = max - min;
|
|
||||||
let step = cx
|
|
||||||
.props
|
|
||||||
.step
|
|
||||||
.and_then(|v| v.parse().ok())
|
|
||||||
.unwrap_or(size / 10.0);
|
|
||||||
|
|
||||||
let current_value = match value {
|
|
||||||
Some(value) => value,
|
|
||||||
None => *value_state.get(),
|
|
||||||
}
|
|
||||||
.clamp(min, max);
|
|
||||||
|
|
||||||
let fst_width = 100.0 * (current_value - min) / size;
|
|
||||||
let snd_width = 100.0 * (max - current_value) / size;
|
|
||||||
assert!(fst_width + snd_width > 99.0 && fst_width + snd_width < 101.0);
|
|
||||||
|
|
||||||
let update = |value: String| {
|
|
||||||
if let Some(oninput) = cx.props.raw_oninput {
|
|
||||||
oninput.call(FormData {
|
|
||||||
value,
|
|
||||||
values: HashMap::new(),
|
|
||||||
files: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render! {
|
|
||||||
div{
|
|
||||||
width: "{width}",
|
|
||||||
height: "{height}",
|
|
||||||
display: "flex",
|
|
||||||
flex_direction: "row",
|
|
||||||
onkeydown: move |event| {
|
|
||||||
match event.key() {
|
|
||||||
Key::ArrowLeft => {
|
|
||||||
value_state.set((current_value - step).clamp(min, max));
|
|
||||||
update(value_state.current().to_string());
|
|
||||||
}
|
|
||||||
Key::ArrowRight => {
|
|
||||||
value_state.set((current_value + step).clamp(min, max));
|
|
||||||
update(value_state.current().to_string());
|
|
||||||
}
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmousemove: move |evt| {
|
|
||||||
let mouse = evt.data;
|
|
||||||
if !mouse.held_buttons().is_empty(){
|
|
||||||
let node = tui_query.get(get_root_id(cx).unwrap());
|
|
||||||
let width = node.size().unwrap().width;
|
|
||||||
let offset = mouse.element_coordinates();
|
|
||||||
value_state.set(min + size*(offset.x as f32) / width as f32);
|
|
||||||
update(value_state.current().to_string());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
div{
|
|
||||||
width: "{fst_width}%",
|
|
||||||
background_color: "rgba(10,10,10,0.5)",
|
|
||||||
}
|
|
||||||
div{
|
|
||||||
"|"
|
|
||||||
}
|
|
||||||
div{
|
|
||||||
width: "{snd_width}%",
|
|
||||||
background_color: "rgba(10,10,10,0.5)",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
use crate::widgets::get_root_id;
|
|
||||||
use crossterm::{cursor::*, execute};
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus_elements::input_data::keyboard_types::Key;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
use dioxus_html::FormData;
|
|
||||||
use dioxus_native_core::utils::cursor::{Cursor, Pos};
|
|
||||||
use rink::Query;
|
|
||||||
use std::{collections::HashMap, io::stdout};
|
|
||||||
use taffy::geometry::Point;
|
|
||||||
|
|
||||||
#[derive(Props)]
|
|
||||||
pub(crate) struct TextBoxProps<'a> {
|
|
||||||
#[props(!optional)]
|
|
||||||
raw_oninput: Option<&'a EventHandler<'a, FormData>>,
|
|
||||||
#[props(!optional)]
|
|
||||||
value: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
size: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
max_length: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
width: Option<&'a str>,
|
|
||||||
#[props(!optional)]
|
|
||||||
height: Option<&'a str>,
|
|
||||||
}
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
|
|
||||||
let tui_query: Query = cx.consume_context().unwrap();
|
|
||||||
let tui_query_clone = tui_query.clone();
|
|
||||||
|
|
||||||
let text_ref = use_ref(cx, || {
|
|
||||||
if let Some(intial_text) = cx.props.value {
|
|
||||||
intial_text.to_string()
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let cursor = use_ref(cx, Cursor::default);
|
|
||||||
let dragging = use_state(cx, || false);
|
|
||||||
|
|
||||||
let text = text_ref.read().clone();
|
|
||||||
let start_highlight = cursor.read().first().idx(&*text);
|
|
||||||
let end_highlight = cursor.read().last().idx(&*text);
|
|
||||||
let (text_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight);
|
|
||||||
let (text_highlighted, text_after_second_cursor) =
|
|
||||||
text_after_first_cursor.split_at(end_highlight - start_highlight);
|
|
||||||
|
|
||||||
let max_len = cx
|
|
||||||
.props
|
|
||||||
.max_length
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|s| s.parse().ok())
|
|
||||||
.unwrap_or(usize::MAX);
|
|
||||||
|
|
||||||
let width = cx
|
|
||||||
.props
|
|
||||||
.width
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
// px is the same as em in tui
|
|
||||||
.or_else(|| cx.props.size.map(|s| s.to_string() + "px"))
|
|
||||||
.unwrap_or_else(|| "10px".to_string());
|
|
||||||
let height = cx.props.height.unwrap_or("3px");
|
|
||||||
|
|
||||||
// don't draw a border unless there is enough space
|
|
||||||
let border = if width
|
|
||||||
.strip_suffix("px")
|
|
||||||
.and_then(|w| w.parse::<i32>().ok())
|
|
||||||
.filter(|w| *w < 3)
|
|
||||||
.is_some()
|
|
||||||
|| height
|
|
||||||
.strip_suffix("px")
|
|
||||||
.and_then(|h| h.parse::<i32>().ok())
|
|
||||||
.filter(|h| *h < 3)
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
"none"
|
|
||||||
} else {
|
|
||||||
"solid"
|
|
||||||
};
|
|
||||||
|
|
||||||
render! {
|
|
||||||
div{
|
|
||||||
width: "{width}",
|
|
||||||
height: "{height}",
|
|
||||||
border_style: "{border}",
|
|
||||||
|
|
||||||
onkeydown: move |k| {
|
|
||||||
if k.key() == Key::Enter {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut text = text_ref.write();
|
|
||||||
cursor.write().handle_input(&k.code(), &k.key(), &k.modifiers(), &mut *text, max_len);
|
|
||||||
if let Some(input_handler) = &cx.props.raw_oninput{
|
|
||||||
input_handler.call(FormData{
|
|
||||||
value: text.clone(),
|
|
||||||
values: HashMap::new(),
|
|
||||||
files: None
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let node = tui_query.get(get_root_id(cx).unwrap());
|
|
||||||
let Point{ x, y } = node.pos().unwrap();
|
|
||||||
|
|
||||||
let Pos { col, row } = cursor.read().start;
|
|
||||||
let (x, y) = (col as u16 + x as u16 + u16::from(border != "none"), row as u16 + y as u16 + u16::from(border != "none"));
|
|
||||||
if let Ok(pos) = crossterm::cursor::position() {
|
|
||||||
if pos != (x, y){
|
|
||||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onmousemove: move |evt| {
|
|
||||||
if *dragging.get() {
|
|
||||||
let offset = evt.data.element_coordinates();
|
|
||||||
let mut new = Pos::new(offset.x as usize, offset.y as usize);
|
|
||||||
if border != "none" {
|
|
||||||
new.col = new.col.saturating_sub(1);
|
|
||||||
}
|
|
||||||
// textboxs are only one line tall
|
|
||||||
new.row = 0;
|
|
||||||
|
|
||||||
if new != cursor.read().start {
|
|
||||||
cursor.write().end = Some(new);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmousedown: move |evt| {
|
|
||||||
let offset = evt.data.element_coordinates();
|
|
||||||
let mut new = Pos::new(offset.x as usize, offset.y as usize);
|
|
||||||
if border != "none" {
|
|
||||||
new.col = new.col.saturating_sub(1);
|
|
||||||
}
|
|
||||||
// textboxs are only one line tall
|
|
||||||
new.row = 0;
|
|
||||||
|
|
||||||
new.realize_col(text_ref.read().as_str());
|
|
||||||
cursor.set(Cursor::from_start(new));
|
|
||||||
dragging.set(true);
|
|
||||||
let node = tui_query_clone.get(get_root_id(cx).unwrap());
|
|
||||||
let Point{ x, y } = node.pos().unwrap();
|
|
||||||
|
|
||||||
let Pos { col, row } = cursor.read().start;
|
|
||||||
let (x, y) = (col as u16 + x as u16 + u16::from(border != "none"), row as u16 + y as u16 + u16::from(border != "none"));
|
|
||||||
if let Ok(pos) = crossterm::cursor::position() {
|
|
||||||
if pos != (x, y){
|
|
||||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
execute!(stdout(), MoveTo(x, y)).unwrap();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmouseup: move |_| {
|
|
||||||
dragging.set(false);
|
|
||||||
},
|
|
||||||
onmouseleave: move |_| {
|
|
||||||
dragging.set(false);
|
|
||||||
},
|
|
||||||
onmouseenter: move |_| {
|
|
||||||
dragging.set(false);
|
|
||||||
},
|
|
||||||
onfocusout: |_| {
|
|
||||||
execute!(stdout(), MoveTo(0, 1000)).unwrap();
|
|
||||||
},
|
|
||||||
|
|
||||||
"{text_before_first_cursor}"
|
|
||||||
|
|
||||||
span{
|
|
||||||
background_color: "rgba(255, 255, 255, 50%)",
|
|
||||||
|
|
||||||
"{text_highlighted}"
|
|
||||||
}
|
|
||||||
|
|
||||||
"{text_after_second_cursor}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -126,7 +126,7 @@ impl AttributeMask {
|
|||||||
pub fn union(&self, other: &Self) -> Self {
|
pub fn union(&self, other: &Self) -> Self {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(AttributeMask::Some(s), AttributeMask::Some(o)) => {
|
(AttributeMask::Some(s), AttributeMask::Some(o)) => {
|
||||||
AttributeMask::Some(s.intersection(o).cloned().collect())
|
AttributeMask::Some(s.union(o).cloned().collect())
|
||||||
}
|
}
|
||||||
_ => AttributeMask::All,
|
_ => AttributeMask::All,
|
||||||
}
|
}
|
||||||
@@ -187,25 +187,50 @@ impl NodeMask {
|
|||||||
self.attritutes = self.attritutes.union(&attributes);
|
self.attritutes = self.attritutes.union(&attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the mask for the attributes
|
||||||
|
pub fn attributes(&self) -> &AttributeMask {
|
||||||
|
&self.attritutes
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the mask to view the tag
|
/// Set the mask to view the tag
|
||||||
pub fn set_tag(&mut self) {
|
pub fn set_tag(&mut self) {
|
||||||
self.tag = true;
|
self.tag = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the mask for the tag
|
||||||
|
pub fn tag(&self) -> bool {
|
||||||
|
self.tag
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the mask to view the namespace
|
/// Set the mask to view the namespace
|
||||||
pub fn set_namespace(&mut self) {
|
pub fn set_namespace(&mut self) {
|
||||||
self.namespace = true;
|
self.namespace = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the mask for the namespace
|
||||||
|
pub fn namespace(&self) -> bool {
|
||||||
|
self.namespace
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the mask to view the text
|
/// Set the mask to view the text
|
||||||
pub fn set_text(&mut self) {
|
pub fn set_text(&mut self) {
|
||||||
self.text = true;
|
self.text = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the mask for the text
|
||||||
|
pub fn text(&self) -> bool {
|
||||||
|
self.text
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the mask to view the listeners
|
/// Set the mask to view the listeners
|
||||||
pub fn set_listeners(&mut self) {
|
pub fn set_listeners(&mut self) {
|
||||||
self.listeners = true;
|
self.listeners = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the mask for the listeners
|
||||||
|
pub fn listeners(&self) -> bool {
|
||||||
|
self.listeners
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A builder for a mask that controls what attributes are visible.
|
/// A builder for a mask that controls what attributes are visible.
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
//! Helpers for watching for changes in the DOM tree.
|
//! Helpers for watching for changes in the DOM tree.
|
||||||
|
|
||||||
use crate::{node::FromAnyValue, prelude::*};
|
use crate::{node::FromAnyValue, node_ref::AttributeMask, prelude::*};
|
||||||
|
|
||||||
/// A trait for watching for changes in the DOM tree.
|
/// A trait for watching for changes in the DOM tree.
|
||||||
pub trait NodeWatcher<V: FromAnyValue + Send + Sync> {
|
pub trait NodeWatcher<V: FromAnyValue + Send + Sync> {
|
||||||
/// Called after a node is added to the tree.
|
/// Called after a node is added to the tree.
|
||||||
fn on_node_added(&self, _node: NodeMut<V>) {}
|
fn on_node_added(&mut self, _node: NodeMut<V>) {}
|
||||||
/// Called before a node is removed from the tree.
|
/// Called before a node is removed from the tree.
|
||||||
fn on_node_removed(&self, _node: NodeMut<V>) {}
|
fn on_node_removed(&mut self, _node: NodeMut<V>) {}
|
||||||
/// Called after a node is moved to a new parent.
|
/// Called after a node is moved to a new parent.
|
||||||
fn on_node_moved(&self, _node: NodeMut<V>) {}
|
fn on_node_moved(&mut self, _node: NodeMut<V>) {}
|
||||||
// /// Called after the text content of a node is changed.
|
}
|
||||||
// fn on_text_changed(&self, _node: NodeMut<V>) {}
|
|
||||||
// /// Called after an attribute of an element is changed.
|
/// A trait for watching for changes to attributes of an element.
|
||||||
// fn on_attribute_changed(&self, _node: NodeMut<V>, attribute: &str) {}
|
pub trait AttributeWatcher<V: FromAnyValue + Send + Sync> {
|
||||||
|
/// Called before update_state is called on the RealDom
|
||||||
|
fn on_attributes_changed(&self, _node: NodeMut<V>, _attributes: &AttributeMask) {}
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@ use crate::node::{
|
|||||||
ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, TextNode,
|
ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, TextNode,
|
||||||
};
|
};
|
||||||
use crate::node_ref::{NodeMask, NodeMaskBuilder};
|
use crate::node_ref::{NodeMask, NodeMaskBuilder};
|
||||||
use crate::node_watcher::NodeWatcher;
|
use crate::node_watcher::{AttributeWatcher, NodeWatcher};
|
||||||
use crate::passes::{DirtyNodeStates, TypeErasedState};
|
use crate::passes::{DirtyNodeStates, TypeErasedState};
|
||||||
use crate::prelude::AttributeMaskBuilder;
|
use crate::prelude::AttributeMaskBuilder;
|
||||||
use crate::tree::{TreeMut, TreeMutView, TreeRef, TreeRefView};
|
use crate::tree::{TreeMut, TreeMutView, TreeRef, TreeRefView};
|
||||||
@@ -49,6 +49,7 @@ impl Deref for DirtyNodesResult {
|
|||||||
pub(crate) struct NodesDirty<V: FromAnyValue + Send + Sync> {
|
pub(crate) struct NodesDirty<V: FromAnyValue + Send + Sync> {
|
||||||
passes_updated: FxHashMap<NodeId, FxHashSet<TypeId>>,
|
passes_updated: FxHashMap<NodeId, FxHashSet<TypeId>>,
|
||||||
nodes_updated: FxHashMap<NodeId, NodeMask>,
|
nodes_updated: FxHashMap<NodeId, NodeMask>,
|
||||||
|
nodes_created: FxHashSet<NodeId>,
|
||||||
pub(crate) passes: Box<[TypeErasedState<V>]>,
|
pub(crate) passes: Box<[TypeErasedState<V>]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +91,7 @@ impl<V: FromAnyValue + Send + Sync> NodesDirty<V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NodeWatchers<V> = Arc<RwLock<Vec<Box<dyn NodeWatcher<V> + Send + Sync>>>>;
|
type NodeWatchers<V> = Arc<RwLock<Vec<Box<dyn NodeWatcher<V> + Send + Sync>>>>;
|
||||||
|
type AttributeWatchers<V> = Arc<RwLock<Vec<Box<dyn AttributeWatcher<V> + Send + Sync>>>>;
|
||||||
|
|
||||||
/// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
|
/// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
|
||||||
/// The render state passes from parent to children and or accumulates state from children to parents.
|
/// The render state passes from parent to children and or accumulates state from children to parents.
|
||||||
@@ -106,6 +108,7 @@ pub struct RealDom<V: FromAnyValue + Send + Sync = ()> {
|
|||||||
nodes_listening: FxHashMap<String, FxHashSet<NodeId>>,
|
nodes_listening: FxHashMap<String, FxHashSet<NodeId>>,
|
||||||
pub(crate) dirty_nodes: NodesDirty<V>,
|
pub(crate) dirty_nodes: NodesDirty<V>,
|
||||||
node_watchers: NodeWatchers<V>,
|
node_watchers: NodeWatchers<V>,
|
||||||
|
attribute_watchers: AttributeWatchers<V>,
|
||||||
workload: ScheduledWorkload,
|
workload: ScheduledWorkload,
|
||||||
root_id: NodeId,
|
root_id: NodeId,
|
||||||
phantom: std::marker::PhantomData<V>,
|
phantom: std::marker::PhantomData<V>,
|
||||||
@@ -159,8 +162,10 @@ impl<V: FromAnyValue + Send + Sync> RealDom<V> {
|
|||||||
passes_updated,
|
passes_updated,
|
||||||
nodes_updated,
|
nodes_updated,
|
||||||
passes: tracked_states,
|
passes: tracked_states,
|
||||||
|
nodes_created: [root_id].into_iter().collect(),
|
||||||
},
|
},
|
||||||
node_watchers: Default::default(),
|
node_watchers: Default::default(),
|
||||||
|
attribute_watchers: Default::default(),
|
||||||
workload,
|
workload,
|
||||||
root_id,
|
root_id,
|
||||||
phantom: std::marker::PhantomData,
|
phantom: std::marker::PhantomData,
|
||||||
@@ -181,15 +186,16 @@ impl<V: FromAnyValue + Send + Sync> RealDom<V> {
|
|||||||
pub fn create_node(&mut self, node: impl Into<NodeType<V>>) -> NodeMut<'_, V> {
|
pub fn create_node(&mut self, node: impl Into<NodeType<V>>) -> NodeMut<'_, V> {
|
||||||
let id = self.world.add_entity(node.into());
|
let id = self.world.add_entity(node.into());
|
||||||
self.tree_mut().create_node(id);
|
self.tree_mut().create_node(id);
|
||||||
|
|
||||||
self.dirty_nodes
|
self.dirty_nodes
|
||||||
.passes_updated
|
.passes_updated
|
||||||
.entry(id)
|
.entry(id)
|
||||||
.or_default()
|
.or_default()
|
||||||
.extend(self.dirty_nodes.passes.iter().map(|x| x.this_type_id));
|
.extend(self.dirty_nodes.passes.iter().map(|x| x.this_type_id));
|
||||||
let watchers = self.node_watchers.clone();
|
self.dirty_nodes
|
||||||
for watcher in &*watchers.read().unwrap() {
|
.mark_dirty(id, NodeMaskBuilder::ALL.build());
|
||||||
watcher.on_node_added(NodeMut::new(id, self));
|
self.dirty_nodes.nodes_created.insert(id);
|
||||||
}
|
|
||||||
NodeMut::new(id, self)
|
NodeMut::new(id, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,16 +222,19 @@ impl<V: FromAnyValue + Send + Sync> RealDom<V> {
|
|||||||
self.root_id
|
self.root_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if a node exists in the dom.
|
||||||
|
pub fn contains(&self, id: NodeId) -> bool {
|
||||||
|
self.tree_ref().contains(id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a reference to a node.
|
/// Get a reference to a node.
|
||||||
pub fn get(&self, id: NodeId) -> Option<NodeRef<'_, V>> {
|
pub fn get(&self, id: NodeId) -> Option<NodeRef<'_, V>> {
|
||||||
self.tree_ref()
|
self.contains(id).then_some(NodeRef { id, dom: self })
|
||||||
.contains(id)
|
|
||||||
.then_some(NodeRef { id, dom: self })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to a node.
|
/// Get a mutable reference to a node.
|
||||||
pub fn get_mut(&mut self, id: NodeId) -> Option<NodeMut<'_, V>> {
|
pub fn get_mut(&mut self, id: NodeId) -> Option<NodeMut<'_, V>> {
|
||||||
let contains = self.tree_ref().contains(id);
|
let contains = self.contains(id);
|
||||||
contains.then(|| NodeMut::new(id, self))
|
contains.then(|| NodeMut::new(id, self))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,8 +256,41 @@ impl<V: FromAnyValue + Send + Sync> RealDom<V> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
ctx: SendAnyMap,
|
ctx: SendAnyMap,
|
||||||
) -> (FxDashSet<NodeId>, FxHashMap<NodeId, NodeMask>) {
|
) -> (FxDashSet<NodeId>, FxHashMap<NodeId, NodeMask>) {
|
||||||
|
let nodes_created = std::mem::take(&mut self.dirty_nodes.nodes_created);
|
||||||
|
|
||||||
|
// call node watchers
|
||||||
|
{
|
||||||
|
let watchers = self.node_watchers.clone();
|
||||||
|
|
||||||
|
// ignore watchers if they are already being modified
|
||||||
|
if let Ok(mut watchers) = watchers.try_write() {
|
||||||
|
for id in &nodes_created {
|
||||||
|
for watcher in &mut *watchers {
|
||||||
|
watcher.on_node_added(NodeMut::new(*id, self));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let passes = std::mem::take(&mut self.dirty_nodes.passes_updated);
|
let passes = std::mem::take(&mut self.dirty_nodes.passes_updated);
|
||||||
let nodes_updated = std::mem::take(&mut self.dirty_nodes.nodes_updated);
|
let nodes_updated = std::mem::take(&mut self.dirty_nodes.nodes_updated);
|
||||||
|
|
||||||
|
// call attribute watchers
|
||||||
|
for (node_id, mask) in &nodes_updated {
|
||||||
|
if self.contains(*node_id) {
|
||||||
|
// ignore watchers if they are already being modified
|
||||||
|
let watchers = self.attribute_watchers.clone();
|
||||||
|
if let Ok(mut watchers) = watchers.try_write() {
|
||||||
|
for watcher in &mut *watchers {
|
||||||
|
watcher.on_attributes_changed(
|
||||||
|
self.get_mut(*node_id).unwrap(),
|
||||||
|
mask.attributes(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let dirty_nodes =
|
let dirty_nodes =
|
||||||
DirtyNodeStates::with_passes(self.dirty_nodes.passes.iter().map(|p| p.this_type_id));
|
DirtyNodeStates::with_passes(self.dirty_nodes.passes.iter().map(|p| p.this_type_id));
|
||||||
let tree = self.tree_ref();
|
let tree = self.tree_ref();
|
||||||
@@ -341,6 +383,17 @@ impl<V: FromAnyValue + Send + Sync> RealDom<V> {
|
|||||||
self.node_watchers.write().unwrap().push(Box::new(watcher));
|
self.node_watchers.write().unwrap().push(Box::new(watcher));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds an [`AttributeWatcher`] to the dom. Attribute watchers are called whenever an attribute is changed.
|
||||||
|
pub fn add_attribute_watcher(
|
||||||
|
&mut self,
|
||||||
|
watcher: impl AttributeWatcher<V> + 'static + Send + Sync,
|
||||||
|
) {
|
||||||
|
self.attribute_watchers
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.push(Box::new(watcher));
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a reference to the underlying world. Any changes made to the world will not update the reactive system.
|
/// Returns a reference to the underlying world. Any changes made to the world will not update the reactive system.
|
||||||
pub fn raw_world(&self) -> &World {
|
pub fn raw_world(&self) -> &World {
|
||||||
&self.world
|
&self.world
|
||||||
@@ -399,7 +452,7 @@ impl<'a, V: Component<Tracking = Untracked> + Send + Sync> DerefMut for ViewEntr
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A immutable view of a node
|
/// A immutable view of a node
|
||||||
pub trait NodeImmutable<V: FromAnyValue + Send + Sync>: Sized {
|
pub trait NodeImmutable<V: FromAnyValue + Send + Sync = ()>: Sized {
|
||||||
/// Get the real dom this node was created in
|
/// Get the real dom this node was created in
|
||||||
fn real_dom(&self) -> &RealDom<V>;
|
fn real_dom(&self) -> &RealDom<V>;
|
||||||
|
|
||||||
@@ -573,7 +626,9 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> {
|
|||||||
.or_default()
|
.or_default()
|
||||||
.insert(TypeId::of::<T>());
|
.insert(TypeId::of::<T>());
|
||||||
let view_mut: ViewMut<T> = self.dom.borrow_raw().ok()?;
|
let view_mut: ViewMut<T> = self.dom.borrow_raw().ok()?;
|
||||||
Some(ViewEntryMut::new(view_mut, self.id))
|
view_mut
|
||||||
|
.contains(self.id)
|
||||||
|
.then_some(ViewEntryMut::new(view_mut, self.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a custom component into this node
|
/// Insert a custom component into this node
|
||||||
@@ -684,7 +739,8 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> {
|
|||||||
for child in children_ids_vec {
|
for child in children_ids_vec {
|
||||||
self.dom.get_mut(child).unwrap().remove();
|
self.dom.get_mut(child).unwrap().remove();
|
||||||
}
|
}
|
||||||
self.dom.tree_mut().remove_single(id);
|
self.dom.tree_mut().remove(id);
|
||||||
|
self.real_dom_mut().raw_world_mut().delete_entity(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace this node with a different node
|
/// Replace this node with a different node
|
||||||
@@ -758,7 +814,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> {
|
|||||||
/// mark that this node was removed for the incremental system
|
/// mark that this node was removed for the incremental system
|
||||||
fn mark_removed(&mut self) {
|
fn mark_removed(&mut self) {
|
||||||
let watchers = self.dom.node_watchers.clone();
|
let watchers = self.dom.node_watchers.clone();
|
||||||
for watcher in &*watchers.read().unwrap() {
|
for watcher in &mut *watchers.write().unwrap() {
|
||||||
watcher.on_node_removed(NodeMut::new(self.id(), self.dom));
|
watcher.on_node_removed(NodeMut::new(self.id(), self.dom));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -766,9 +822,12 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> {
|
|||||||
/// mark that this node was moved for the incremental system
|
/// mark that this node was moved for the incremental system
|
||||||
fn mark_moved(&mut self) {
|
fn mark_moved(&mut self) {
|
||||||
let watchers = self.dom.node_watchers.clone();
|
let watchers = self.dom.node_watchers.clone();
|
||||||
for watcher in &*watchers.read().unwrap() {
|
// ignore watchers if the we are inside of a watcher
|
||||||
watcher.on_node_moved(NodeMut::new(self.id(), self.dom));
|
if let Ok(mut watchers) = watchers.try_write() {
|
||||||
}
|
for watcher in &mut *watchers {
|
||||||
|
watcher.on_node_moved(NodeMut::new(self.id(), self.dom));
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to the type of the current node
|
/// Get a mutable reference to the type of the current node
|
||||||
@@ -885,6 +944,15 @@ pub struct ElementNodeMut<'a, V: FromAnyValue + Send + Sync = ()> {
|
|||||||
dirty_nodes: &'a mut NodesDirty<V>,
|
dirty_nodes: &'a mut NodesDirty<V>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for ElementNodeMut<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("ElementNodeMut")
|
||||||
|
.field("id", &self.id)
|
||||||
|
.field("element", &*self.element)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<V: FromAnyValue + Send + Sync> ElementNodeMut<'_, V> {
|
impl<V: FromAnyValue + Send + Sync> ElementNodeMut<'_, V> {
|
||||||
/// Get the current element
|
/// Get the current element
|
||||||
fn element(&self) -> &ElementNode<V> {
|
fn element(&self) -> &ElementNode<V> {
|
||||||
@@ -976,6 +1044,14 @@ impl<V: FromAnyValue + Send + Sync> ElementNodeMut<'_, V> {
|
|||||||
self.element_mut().attributes.get_mut(name)
|
self.element_mut().attributes.get_mut(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an attribute of the element
|
||||||
|
pub fn get_attribute(
|
||||||
|
&self,
|
||||||
|
name: &OwnedAttributeDiscription,
|
||||||
|
) -> Option<&OwnedAttributeValue<V>> {
|
||||||
|
self.element().attributes.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the set of all events the element is listening to
|
/// Get the set of all events the element is listening to
|
||||||
pub fn listeners(&self) -> &FxHashSet<String> {
|
pub fn listeners(&self) -> &FxHashSet<String> {
|
||||||
&self.element().listeners
|
&self.element().listeners
|
||||||
|
|||||||
@@ -5,3 +5,4 @@
|
|||||||
mod persistant_iterator;
|
mod persistant_iterator;
|
||||||
pub use persistant_iterator::*;
|
pub use persistant_iterator::*;
|
||||||
pub mod cursor;
|
pub mod cursor;
|
||||||
|
pub mod widget_watcher;
|
||||||
@@ -60,7 +60,7 @@ struct PersistantElementIterUpdater<V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<V: FromAnyValue + Sync + Send> NodeWatcher<V> for PersistantElementIterUpdater<V> {
|
impl<V: FromAnyValue + Sync + Send> NodeWatcher<V> for PersistantElementIterUpdater<V> {
|
||||||
fn on_node_moved(&self, node: NodeMut<V>) {
|
fn on_node_moved(&mut self, node: NodeMut<V>) {
|
||||||
// if any element is moved, update its parents in the stack
|
// if any element is moved, update its parents in the stack
|
||||||
let mut stack = self.stack.lock().unwrap();
|
let mut stack = self.stack.lock().unwrap();
|
||||||
let moved = node.id();
|
let moved = node.id();
|
||||||
@@ -78,7 +78,7 @@ impl<V: FromAnyValue + Sync + Send> NodeWatcher<V> for PersistantElementIterUpda
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_node_removed(&self, node: NodeMut<V>) {
|
fn on_node_removed(&mut self, node: NodeMut<V>) {
|
||||||
// if any element is removed in the chain, remove it and its children from the stack
|
// if any element is removed in the chain, remove it and its children from the stack
|
||||||
let mut stack = self.stack.lock().unwrap();
|
let mut stack = self.stack.lock().unwrap();
|
||||||
let removed = node.id();
|
let removed = node.id();
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
//! Widget utilities for defining, registering and updating widgets
|
||||||
|
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
use shipyard::Component;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
node::{FromAnyValue, NodeType},
|
||||||
|
node_ref::AttributeMask,
|
||||||
|
node_watcher::{AttributeWatcher, NodeWatcher},
|
||||||
|
prelude::{NodeImmutable, NodeMut, RealDom},
|
||||||
|
NodeId,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A watcher that handlers registering and updating widgets
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct WidgetWatcher<V: FromAnyValue + Send + Sync> {
|
||||||
|
inner: Arc<RwLock<WidgetWatcherInner<V>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: FromAnyValue + Send + Sync> NodeWatcher<V> for WidgetWatcher<V> {
|
||||||
|
fn on_node_added(&mut self, node: NodeMut<V>) {
|
||||||
|
let mut inner = self.inner.write().unwrap();
|
||||||
|
inner.on_node_added(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_node_removed(&mut self, node: NodeMut<V>) {
|
||||||
|
let mut inner = self.inner.write().unwrap();
|
||||||
|
inner.on_node_removed(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: FromAnyValue + Send + Sync> WidgetWatcher<V> {
|
||||||
|
/// Register a widget
|
||||||
|
pub fn register_widget<W: WidgetFactory<O, V> + 'static, O: WidgetUpdater<V>>(&mut self) {
|
||||||
|
let mut inner = self.inner.write().unwrap();
|
||||||
|
inner.builders.insert(
|
||||||
|
W::NAME,
|
||||||
|
WidgetBuilder {
|
||||||
|
create: |mut node| Box::new(W::create(&mut node)),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attach the widget watcher to the RealDom
|
||||||
|
pub fn attach(&self, dom: &mut RealDom<V>) {
|
||||||
|
dom.add_node_watcher(self.clone());
|
||||||
|
dom.add_attribute_watcher(self.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: FromAnyValue + Send + Sync> AttributeWatcher<V> for WidgetWatcher<V> {
|
||||||
|
fn on_attributes_changed(&self, node: NodeMut<V>, attributes: &AttributeMask) {
|
||||||
|
let mut inner = self.inner.write().unwrap();
|
||||||
|
if let Some(widget) = inner.widgets.get_mut(&node.id()) {
|
||||||
|
widget.dyn_widget.attributes_changed(node, attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct WidgetWatcherInner<V: FromAnyValue + Send + Sync> {
|
||||||
|
builders: FxHashMap<&'static str, WidgetBuilder<V>>,
|
||||||
|
widgets: FxHashMap<NodeId, BoxedWidget<V>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: FromAnyValue + Send + Sync> NodeWatcher<V> for WidgetWatcherInner<V> {
|
||||||
|
fn on_node_added(&mut self, node: NodeMut<V>) {
|
||||||
|
let node_type = node.node_type();
|
||||||
|
if let NodeType::Element(el) = &*node_type {
|
||||||
|
if let Some(builder) = self.builders.get(el.tag.as_str()) {
|
||||||
|
drop(node_type);
|
||||||
|
let id = node.id();
|
||||||
|
let widget = (builder.create)(node);
|
||||||
|
self.widgets.insert(id, BoxedWidget { dyn_widget: widget });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_node_removed(&mut self, node: NodeMut<V>) {
|
||||||
|
self.widgets.remove(&node.id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct BoxedWidget<V: FromAnyValue + Send + Sync> {
|
||||||
|
dyn_widget: Box<dyn WidgetUpdater<V>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WidgetBuilder<V: FromAnyValue + Send + Sync> {
|
||||||
|
create: fn(NodeMut<V>) -> Box<dyn WidgetUpdater<V>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A controlled element (a.k.a. widget)
|
||||||
|
pub trait Widget<V: FromAnyValue + Send + Sync = ()>: Send + Sync + 'static {
|
||||||
|
/// The tag the widget is registered under.
|
||||||
|
const NAME: &'static str;
|
||||||
|
|
||||||
|
/// Create a new widget.
|
||||||
|
fn create(root: &mut NodeMut<V>) -> Self;
|
||||||
|
|
||||||
|
/// Called when the attributes of the widget are changed.
|
||||||
|
fn attributes_changed(&mut self, _root: NodeMut<V>, _attributes: &AttributeMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A factory for creating widgets
|
||||||
|
pub trait WidgetFactory<W: WidgetUpdater<V>, V: FromAnyValue + Send + Sync = ()>:
|
||||||
|
Send + Sync + 'static
|
||||||
|
{
|
||||||
|
/// The tag the widget is registered under.
|
||||||
|
const NAME: &'static str;
|
||||||
|
|
||||||
|
/// Create a new widget.
|
||||||
|
fn create(root: &mut NodeMut<V>) -> W;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Widget<V>, V: FromAnyValue + Send + Sync> WidgetFactory<W, V> for W {
|
||||||
|
const NAME: &'static str = W::NAME;
|
||||||
|
|
||||||
|
fn create(root: &mut NodeMut<V>) -> Self {
|
||||||
|
W::create(root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for updating widgets
|
||||||
|
pub trait WidgetUpdater<V: FromAnyValue + Send + Sync = ()>: Send + Sync + 'static {
|
||||||
|
/// Called when the attributes of the widget are changed.
|
||||||
|
fn attributes_changed(&mut self, _root: NodeMut<V>, _attributes: &AttributeMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Widget<V>, V: FromAnyValue + Send + Sync> WidgetUpdater<V> for W {
|
||||||
|
fn attributes_changed(&mut self, root: NodeMut<V>, attributes: &AttributeMask) {
|
||||||
|
self.attributes_changed(root, attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
use dioxus_html::EventData;
|
|
||||||
use dioxus_native_core::{
|
use dioxus_native_core::{
|
||||||
node::TextNode,
|
node::TextNode,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
real_dom::{NodeImmutable, NodeTypeMut},
|
real_dom::{NodeImmutable, NodeTypeMut},
|
||||||
NodeId,
|
NodeId,
|
||||||
};
|
};
|
||||||
use rink::{render, Config, Driver};
|
use rink::{render, Config, Driver, EventData};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use dioxus_html::EventData;
|
|
||||||
use dioxus_native_core::{
|
use dioxus_native_core::{
|
||||||
node::TextNode,
|
node::TextNode,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
real_dom::{NodeImmutable, NodeTypeMut},
|
real_dom::{NodeImmutable, NodeTypeMut},
|
||||||
NodeId,
|
NodeId,
|
||||||
};
|
};
|
||||||
use rink::{render, Config, Driver};
|
use rink::{render, Config, Driver, EventData};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
use dioxus_native_core::{
|
||||||
|
prelude::*,
|
||||||
|
real_dom::{NodeImmutable, NodeTypeMut},
|
||||||
|
NodeId,
|
||||||
|
};
|
||||||
|
use rink::{render, Config, Driver, EventData};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Counter {
|
||||||
|
count: f64,
|
||||||
|
button_id: NodeId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Counter {
|
||||||
|
fn create(mut root: NodeMut) -> Self {
|
||||||
|
let mut myself = Self::default();
|
||||||
|
|
||||||
|
let root_id = root.id();
|
||||||
|
let rdom = root.real_dom_mut();
|
||||||
|
|
||||||
|
// create the counter
|
||||||
|
let count = myself.count;
|
||||||
|
let mut button = rdom.create_node(NodeType::Element(ElementNode {
|
||||||
|
tag: "input".to_string(),
|
||||||
|
attributes: [
|
||||||
|
// supported types: button, checkbox, textbox, password, number, range
|
||||||
|
("type".to_string().into(), "range".to_string().into()),
|
||||||
|
("display".to_string().into(), "flex".to_string().into()),
|
||||||
|
(("flex-direction", "style").into(), "row".to_string().into()),
|
||||||
|
(
|
||||||
|
("justify-content", "style").into(),
|
||||||
|
"center".to_string().into(),
|
||||||
|
),
|
||||||
|
(("align-items", "style").into(), "center".to_string().into()),
|
||||||
|
(
|
||||||
|
"value".to_string().into(),
|
||||||
|
format!("click me {count}").into(),
|
||||||
|
),
|
||||||
|
(("width", "style").into(), "50%".to_string().into()),
|
||||||
|
(("height", "style").into(), "10%".to_string().into()),
|
||||||
|
("min".to_string().into(), "20".to_string().into()),
|
||||||
|
("max".to_string().into(), "80".to_string().into()),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
}));
|
||||||
|
button.add_event_listener("input");
|
||||||
|
myself.button_id = button.id();
|
||||||
|
rdom.get_mut(root_id).unwrap().add_child(myself.button_id);
|
||||||
|
|
||||||
|
myself
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Driver for Counter {
|
||||||
|
fn update(&mut self, rdom: &Arc<RwLock<RealDom>>) {
|
||||||
|
// update the counter
|
||||||
|
let mut rdom = rdom.write().unwrap();
|
||||||
|
let mut node = rdom.get_mut(self.button_id).unwrap();
|
||||||
|
if let NodeTypeMut::Element(mut el) = node.node_type_mut() {
|
||||||
|
el.set_attribute(
|
||||||
|
("background-color", "style"),
|
||||||
|
format!("rgb({}, {}, {})", 255.0 - self.count * 2.0, 0, 0,),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_event(
|
||||||
|
&mut self,
|
||||||
|
_: &Arc<RwLock<RealDom>>,
|
||||||
|
_: NodeId,
|
||||||
|
event_type: &str,
|
||||||
|
event: Rc<EventData>,
|
||||||
|
_: bool,
|
||||||
|
) {
|
||||||
|
match event_type {
|
||||||
|
"oninput" => {
|
||||||
|
// when the button is clicked, increment the counter
|
||||||
|
if let EventData::Form(input_event) = &*event {
|
||||||
|
if let Ok(value) = input_event.value.parse::<f64>() {
|
||||||
|
self.count = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_async(&mut self) -> std::pin::Pin<Box<dyn futures::Future<Output = ()> + '_>> {
|
||||||
|
Box::pin(async move { tokio::time::sleep(std::time::Duration::from_millis(1000)).await })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
render(Config::new(), |rdom, _, _| {
|
||||||
|
let mut rdom = rdom.write().unwrap();
|
||||||
|
let root = rdom.root_id();
|
||||||
|
Counter::create(rdom.get_mut(root).unwrap())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
+71
-44
@@ -12,7 +12,9 @@ use dioxus_html::geometry::{
|
|||||||
use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers};
|
use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers};
|
||||||
use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
|
use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
|
||||||
use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
|
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::{
|
use std::{
|
||||||
cell::{RefCell, RefMut},
|
cell::{RefCell, RefMut},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
@@ -25,13 +27,62 @@ use crate::focus::{Focus, Focused};
|
|||||||
use crate::layout::TaffyLayout;
|
use crate::layout::TaffyLayout;
|
||||||
use crate::{layout_to_screen_space, FocusState};
|
use crate::{layout_to_screen_space, FocusState};
|
||||||
|
|
||||||
pub(crate) struct Event {
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Event {
|
||||||
pub id: NodeId,
|
pub id: NodeId,
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub data: Rc<EventData>,
|
pub data: EventData,
|
||||||
pub bubbles: bool,
|
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, 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);
|
type EventCore = (&'static str, EventData);
|
||||||
|
|
||||||
const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
|
const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
|
||||||
@@ -147,13 +198,13 @@ impl InnerInputState {
|
|||||||
resolved_events.push(Event {
|
resolved_events.push(Event {
|
||||||
name: "focus",
|
name: "focus",
|
||||||
id,
|
id,
|
||||||
data: Rc::new(EventData::Focus(FocusData {})),
|
data: EventData::Focus(FocusData {}),
|
||||||
bubbles: event_bubbles("focus"),
|
bubbles: event_bubbles("focus"),
|
||||||
});
|
});
|
||||||
resolved_events.push(Event {
|
resolved_events.push(Event {
|
||||||
name: "focusin",
|
name: "focusin",
|
||||||
id,
|
id,
|
||||||
data: Rc::new(EventData::Focus(FocusData {})),
|
data: EventData::Focus(FocusData {}),
|
||||||
bubbles: event_bubbles("focusin"),
|
bubbles: event_bubbles("focusin"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -161,7 +212,7 @@ impl InnerInputState {
|
|||||||
resolved_events.push(Event {
|
resolved_events.push(Event {
|
||||||
name: "focusout",
|
name: "focusout",
|
||||||
id,
|
id,
|
||||||
data: Rc::new(EventData::Focus(FocusData {})),
|
data: EventData::Focus(FocusData {}),
|
||||||
bubbles: event_bubbles("focusout"),
|
bubbles: event_bubbles("focusout"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -197,7 +248,7 @@ impl InnerInputState {
|
|||||||
|
|
||||||
fn try_create_event(
|
fn try_create_event(
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
data: Rc<EventData>,
|
data: EventData,
|
||||||
will_bubble: &mut FxHashSet<NodeId>,
|
will_bubble: &mut FxHashSet<NodeId>,
|
||||||
resolved_events: &mut Vec<Event>,
|
resolved_events: &mut Vec<Event>,
|
||||||
node: NodeRef,
|
node: NodeRef,
|
||||||
@@ -281,10 +332,7 @@ impl InnerInputState {
|
|||||||
if currently_contains && previously_contained {
|
if currently_contains && previously_contained {
|
||||||
try_create_event(
|
try_create_event(
|
||||||
"mousemove",
|
"mousemove",
|
||||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||||
mouse_data,
|
|
||||||
&node_layout,
|
|
||||||
))),
|
|
||||||
&mut will_bubble,
|
&mut will_bubble,
|
||||||
resolved_events,
|
resolved_events,
|
||||||
node,
|
node,
|
||||||
@@ -308,7 +356,7 @@ impl InnerInputState {
|
|||||||
if currently_contains && !previously_contained {
|
if currently_contains && !previously_contained {
|
||||||
try_create_event(
|
try_create_event(
|
||||||
"mouseenter",
|
"mouseenter",
|
||||||
Rc::new(dioxus_html::EventData::Mouse(mouse_data.clone())),
|
EventData::Mouse(mouse_data.clone()),
|
||||||
&mut will_bubble,
|
&mut will_bubble,
|
||||||
resolved_events,
|
resolved_events,
|
||||||
node,
|
node,
|
||||||
@@ -331,10 +379,7 @@ impl InnerInputState {
|
|||||||
if currently_contains && !previously_contained {
|
if currently_contains && !previously_contained {
|
||||||
try_create_event(
|
try_create_event(
|
||||||
"mouseover",
|
"mouseover",
|
||||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||||
mouse_data,
|
|
||||||
&node_layout,
|
|
||||||
))),
|
|
||||||
&mut will_bubble,
|
&mut will_bubble,
|
||||||
resolved_events,
|
resolved_events,
|
||||||
node,
|
node,
|
||||||
@@ -354,10 +399,7 @@ impl InnerInputState {
|
|||||||
if currently_contains {
|
if currently_contains {
|
||||||
try_create_event(
|
try_create_event(
|
||||||
"mousedown",
|
"mousedown",
|
||||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||||
mouse_data,
|
|
||||||
&node_layout,
|
|
||||||
))),
|
|
||||||
&mut will_bubble,
|
&mut will_bubble,
|
||||||
resolved_events,
|
resolved_events,
|
||||||
node,
|
node,
|
||||||
@@ -378,10 +420,7 @@ impl InnerInputState {
|
|||||||
if currently_contains {
|
if currently_contains {
|
||||||
try_create_event(
|
try_create_event(
|
||||||
"mouseup",
|
"mouseup",
|
||||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||||
mouse_data,
|
|
||||||
&node_layout,
|
|
||||||
))),
|
|
||||||
&mut will_bubble,
|
&mut will_bubble,
|
||||||
resolved_events,
|
resolved_events,
|
||||||
node,
|
node,
|
||||||
@@ -403,10 +442,7 @@ impl InnerInputState {
|
|||||||
if currently_contains {
|
if currently_contains {
|
||||||
try_create_event(
|
try_create_event(
|
||||||
"click",
|
"click",
|
||||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||||
mouse_data,
|
|
||||||
&node_layout,
|
|
||||||
))),
|
|
||||||
&mut will_bubble,
|
&mut will_bubble,
|
||||||
resolved_events,
|
resolved_events,
|
||||||
node,
|
node,
|
||||||
@@ -429,10 +465,7 @@ impl InnerInputState {
|
|||||||
if currently_contains {
|
if currently_contains {
|
||||||
try_create_event(
|
try_create_event(
|
||||||
"contextmenu",
|
"contextmenu",
|
||||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||||
mouse_data,
|
|
||||||
&node_layout,
|
|
||||||
))),
|
|
||||||
&mut will_bubble,
|
&mut will_bubble,
|
||||||
resolved_events,
|
resolved_events,
|
||||||
node,
|
node,
|
||||||
@@ -456,7 +489,7 @@ impl InnerInputState {
|
|||||||
if currently_contains {
|
if currently_contains {
|
||||||
try_create_event(
|
try_create_event(
|
||||||
"wheel",
|
"wheel",
|
||||||
Rc::new(EventData::Wheel(w.clone())),
|
EventData::Wheel(w.clone()),
|
||||||
&mut will_bubble,
|
&mut will_bubble,
|
||||||
resolved_events,
|
resolved_events,
|
||||||
node,
|
node,
|
||||||
@@ -481,10 +514,7 @@ impl InnerInputState {
|
|||||||
if !currently_contains && previously_contained {
|
if !currently_contains && previously_contained {
|
||||||
try_create_event(
|
try_create_event(
|
||||||
"mouseleave",
|
"mouseleave",
|
||||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||||
mouse_data,
|
|
||||||
&node_layout,
|
|
||||||
))),
|
|
||||||
&mut will_bubble,
|
&mut will_bubble,
|
||||||
resolved_events,
|
resolved_events,
|
||||||
node,
|
node,
|
||||||
@@ -507,10 +537,7 @@ impl InnerInputState {
|
|||||||
if !currently_contains && previously_contained {
|
if !currently_contains && previously_contained {
|
||||||
try_create_event(
|
try_create_event(
|
||||||
"mouseout",
|
"mouseout",
|
||||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
|
||||||
mouse_data,
|
|
||||||
&node_layout,
|
|
||||||
))),
|
|
||||||
&mut will_bubble,
|
&mut will_bubble,
|
||||||
resolved_events,
|
resolved_events,
|
||||||
node,
|
node,
|
||||||
@@ -627,12 +654,12 @@ impl RinkInputHandler {
|
|||||||
})
|
})
|
||||||
.map(|evt| (evt.0, evt.1));
|
.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 {
|
for (event, data) in events {
|
||||||
if let Some(v) = hm.get_mut(event) {
|
if let Some(v) = hm.get_mut(event) {
|
||||||
v.push(Rc::new(data));
|
v.push(data);
|
||||||
} else {
|
} else {
|
||||||
hm.insert(event, vec![Rc::new(data)]);
|
hm.insert(event, vec![data]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (event, datas) in hm {
|
for (event, datas) in hm {
|
||||||
|
|||||||
+63
-14
@@ -6,7 +6,6 @@ use crossterm::{
|
|||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use dioxus_html::EventData;
|
|
||||||
use dioxus_native_core::prelude::*;
|
use dioxus_native_core::prelude::*;
|
||||||
use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, SendAnyMap};
|
use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, SendAnyMap};
|
||||||
use focus::FocusState;
|
use focus::FocusState;
|
||||||
@@ -25,18 +24,19 @@ use taffy::Taffy;
|
|||||||
pub use taffy::{geometry::Point, prelude::*};
|
pub use taffy::{geometry::Point, prelude::*};
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
|
use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
|
||||||
|
use widgets::{register_widgets, RinkWidgetResponder, RinkWidgetTraitObject};
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod focus;
|
mod focus;
|
||||||
mod hooks;
|
mod hooks;
|
||||||
mod layout;
|
mod layout;
|
||||||
pub mod prelude;
|
|
||||||
mod prevent_default;
|
mod prevent_default;
|
||||||
pub mod query;
|
pub mod query;
|
||||||
mod render;
|
mod render;
|
||||||
mod style;
|
mod style;
|
||||||
mod style_attributes;
|
mod style_attributes;
|
||||||
mod widget;
|
mod widget;
|
||||||
|
mod widgets;
|
||||||
|
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
pub use hooks::*;
|
pub use hooks::*;
|
||||||
@@ -91,18 +91,24 @@ pub fn render<R: Driver>(
|
|||||||
PreventDefault::to_type_erased(),
|
PreventDefault::to_type_erased(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let (handler, mut register_event) = RinkInputHandler::create(&mut rdom);
|
|
||||||
|
|
||||||
// Setup input handling
|
// Setup input handling
|
||||||
|
|
||||||
|
// The event channel for fully resolved events
|
||||||
let (event_tx, mut event_reciever) = unbounded();
|
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 {
|
if !cfg.headless {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let tick_rate = Duration::from_millis(1000);
|
let tick_rate = Duration::from_millis(1000);
|
||||||
loop {
|
loop {
|
||||||
if crossterm::event::poll(tick_rate).unwrap() {
|
if crossterm::event::poll(tick_rate).unwrap() {
|
||||||
let evt = crossterm::event::read().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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,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 rdom = Arc::new(RwLock::new(rdom));
|
||||||
let taffy = Arc::new(Mutex::new(Taffy::new()));
|
let taffy = Arc::new(Mutex::new(Taffy::new()));
|
||||||
let mut renderer = create_renderer(&rdom, &taffy, event_tx_clone);
|
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);
|
renderer.update(&rdom);
|
||||||
let mut any_map = SendAnyMap::new();
|
let mut any_map = SendAnyMap::new();
|
||||||
@@ -213,6 +230,7 @@ pub fn render<R: Driver>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut event_recieved = None;
|
||||||
{
|
{
|
||||||
let wait = renderer.poll_async();
|
let wait = renderer.poll_async();
|
||||||
|
|
||||||
@@ -222,7 +240,7 @@ pub fn render<R: Driver>(
|
|||||||
_ = wait => {
|
_ = wait => {
|
||||||
|
|
||||||
},
|
},
|
||||||
evt = event_reciever.next() => {
|
evt = raw_event_reciever.next() => {
|
||||||
match evt.as_ref().unwrap() {
|
match evt.as_ref().unwrap() {
|
||||||
InputEvent::UserInput(event) => match event {
|
InputEvent::UserInput(event) => match event {
|
||||||
TermEvent::Key(key) => {
|
TermEvent::Key(key) => {
|
||||||
@@ -243,21 +261,32 @@ pub fn render<R: Driver>(
|
|||||||
register_event(evt);
|
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 = {
|
let evts = handler.get_events(
|
||||||
handler.get_events(
|
&taffy.lock().expect("taffy lock poisoned"),
|
||||||
&taffy.lock().expect("taffy lock poisoned"),
|
&mut rdom.write().unwrap(),
|
||||||
&mut rdom.write().unwrap(),
|
);
|
||||||
)
|
|
||||||
};
|
|
||||||
updated |= handler.state().focus_state.clean();
|
updated |= handler.state().focus_state.clean();
|
||||||
|
|
||||||
for e in evts {
|
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
|
// updates the dom's nodes
|
||||||
@@ -309,3 +338,23 @@ pub trait Driver {
|
|||||||
);
|
);
|
||||||
fn poll_async(&mut self) -> Pin<Box<dyn Future<Output = ()> + '_>>;
|
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(rdom.get_mut(id).unwrap());
|
||||||
|
|
||||||
|
while let Some(mut node_mut) = node {
|
||||||
|
let parent_id = node_mut.parent_id();
|
||||||
|
if let Some(mut widget) = node_mut
|
||||||
|
.get_mut::<RinkWidgetTraitObject>()
|
||||||
|
.map(|w| w.clone())
|
||||||
|
{
|
||||||
|
widget.handle_event(event, &mut node_mut)
|
||||||
|
}
|
||||||
|
if !event.bubbles {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node = parent_id.map(|id| rdom.get_mut(id).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
#[cfg(feature = "dioxus-bindings")]
|
|
||||||
pub use crate::widgets::*;
|
|
||||||
+31
-22
@@ -1,6 +1,7 @@
|
|||||||
use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard};
|
use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard};
|
||||||
|
|
||||||
use dioxus_native_core::prelude::*;
|
use dioxus_native_core::prelude::*;
|
||||||
|
use shipyard::Unique;
|
||||||
use taffy::{
|
use taffy::{
|
||||||
geometry::Point,
|
geometry::Point,
|
||||||
prelude::{Layout, Size},
|
prelude::{Layout, Size},
|
||||||
@@ -40,7 +41,7 @@ use crate::{layout::TaffyLayout, layout_to_screen_space};
|
|||||||
/// })
|
/// })
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Unique)]
|
||||||
pub struct Query {
|
pub struct Query {
|
||||||
pub(crate) rdom: Arc<RwLock<RealDom>>,
|
pub(crate) rdom: Arc<RwLock<RealDom>>,
|
||||||
pub(crate) stretch: Arc<Mutex<Taffy>>,
|
pub(crate) stretch: Arc<Mutex<Taffy>>,
|
||||||
@@ -69,7 +70,7 @@ pub struct ElementRef<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ElementRef<'a> {
|
impl<'a> ElementRef<'a> {
|
||||||
fn new(
|
pub(crate) fn new(
|
||||||
inner: RwLockReadGuard<'a, RealDom>,
|
inner: RwLockReadGuard<'a, RealDom>,
|
||||||
stretch: MutexGuard<'a, Taffy>,
|
stretch: MutexGuard<'a, Taffy>,
|
||||||
id: NodeId,
|
id: NodeId,
|
||||||
@@ -89,25 +90,33 @@ impl<'a> ElementRef<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn layout(&self) -> Option<Layout> {
|
pub fn layout(&self) -> Option<Layout> {
|
||||||
let layout = self
|
get_layout(self.inner.get(self.id).unwrap(), &self.stretch)
|
||||||
.stretch
|
|
||||||
.layout(
|
|
||||||
self.inner
|
|
||||||
.get(self.id)
|
|
||||||
.unwrap()
|
|
||||||
.get::<TaffyLayout>()
|
|
||||||
.unwrap()
|
|
||||||
.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),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_layout(node: NodeRef, stretch: &Taffy) -> Option<Layout> {
|
||||||
|
let layout = stretch
|
||||||
|
.layout(node.get::<TaffyLayout>().unwrap().node.ok()?)
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
let mut current_node_id = node.parent_id();
|
||||||
|
let mut pos = layout.location;
|
||||||
|
let rdom = node.real_dom();
|
||||||
|
while let Some(node) = current_node_id.and_then(|id| rdom.get(id)) {
|
||||||
|
let current_layout = stretch
|
||||||
|
.layout(node.get::<TaffyLayout>().unwrap().node.ok()?)
|
||||||
|
.ok()?;
|
||||||
|
pos.x += current_layout.location.x;
|
||||||
|
pos.y += current_layout.location.y;
|
||||||
|
current_node_id = node.parent_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use dioxus_html::input_data::keyboard_types::Key;
|
||||||
|
use dioxus_native_core::{
|
||||||
|
node::OwnedAttributeDiscription,
|
||||||
|
node_ref::AttributeMask,
|
||||||
|
prelude::NodeType,
|
||||||
|
real_dom::{ElementNodeMut, NodeImmutable, NodeTypeMut, RealDom},
|
||||||
|
utils::widget_watcher::Widget,
|
||||||
|
NodeId,
|
||||||
|
};
|
||||||
|
use shipyard::UniqueViewMut;
|
||||||
|
|
||||||
|
use crate::FormData;
|
||||||
|
|
||||||
|
use super::{RinkWidget, WidgetContext};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct Button {
|
||||||
|
div_id: NodeId,
|
||||||
|
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) {
|
||||||
|
let data = FormData {
|
||||||
|
value: self.value.to_string(),
|
||||||
|
values: HashMap::new(),
|
||||||
|
files: None,
|
||||||
|
};
|
||||||
|
ctx.send(crate::Event {
|
||||||
|
id: self.div_id,
|
||||||
|
name: "input",
|
||||||
|
data: crate::EventData::Form(data),
|
||||||
|
bubbles: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for Button {
|
||||||
|
const NAME: &'static str = "input";
|
||||||
|
|
||||||
|
fn create(root: &mut 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");
|
||||||
|
let div_id = root.id();
|
||||||
|
root.add_child(text_id);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
div_id,
|
||||||
|
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,
|
||||||
|
node: &mut dioxus_native_core::real_dom::NodeMut,
|
||||||
|
) {
|
||||||
|
let mut ctx: UniqueViewMut<WidgetContext> = node
|
||||||
|
.real_dom_mut()
|
||||||
|
.raw_world_mut()
|
||||||
|
.borrow()
|
||||||
|
.expect("expected widget context");
|
||||||
|
|
||||||
|
match event.name {
|
||||||
|
"click" => self.switch(&mut ctx),
|
||||||
|
"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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use dioxus_html::input_data::keyboard_types::Key;
|
||||||
|
use dioxus_native_core::{
|
||||||
|
node::OwnedAttributeDiscription,
|
||||||
|
node_ref::AttributeMask,
|
||||||
|
prelude::NodeType,
|
||||||
|
real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut},
|
||||||
|
utils::widget_watcher::Widget,
|
||||||
|
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, root: &mut 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, node: &mut 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 Widget for CheckBox {
|
||||||
|
const NAME: &'static str = "input";
|
||||||
|
|
||||||
|
fn create(root: &mut 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();
|
||||||
|
root.add_child(text_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(&mut 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(&mut root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RinkWidget for CheckBox {
|
||||||
|
fn handle_event(
|
||||||
|
&mut self,
|
||||||
|
event: &crate::Event,
|
||||||
|
node: &mut 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,132 @@
|
|||||||
|
use dioxus_native_core::{
|
||||||
|
node::OwnedAttributeDiscription, prelude::NodeType, real_dom::NodeImmutable,
|
||||||
|
utils::widget_watcher::Widget,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 Widget for Input {
|
||||||
|
const NAME: &'static str = "input";
|
||||||
|
|
||||||
|
fn create(root: &mut 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 {
|
||||||
|
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: &mut 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,95 @@
|
|||||||
|
mod button;
|
||||||
|
mod checkbox;
|
||||||
|
mod input;
|
||||||
|
mod number;
|
||||||
|
mod password;
|
||||||
|
mod slider;
|
||||||
|
mod textbox;
|
||||||
|
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
use dioxus_native_core::{
|
||||||
|
real_dom::RealDom,
|
||||||
|
utils::widget_watcher::{Widget, WidgetFactory, WidgetUpdater, WidgetWatcher},
|
||||||
|
};
|
||||||
|
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 });
|
||||||
|
|
||||||
|
// create the widget watcher
|
||||||
|
let mut widget_watcher = WidgetWatcher::default();
|
||||||
|
|
||||||
|
widget_watcher
|
||||||
|
.register_widget::<RinkWidgetTraitObjectFactory<input::Input>, RinkWidgetTraitObject>();
|
||||||
|
|
||||||
|
widget_watcher.attach(rdom);
|
||||||
|
}
|
||||||
|
|
||||||
|
trait RinkWidget: Sync + Send + Widget + 'static {
|
||||||
|
fn handle_event(&mut self, event: &Event, node: &mut dioxus_native_core::real_dom::NodeMut);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RinkWidgetResponder: WidgetUpdater {
|
||||||
|
fn handle_event(&mut self, event: &Event, node: &mut dioxus_native_core::real_dom::NodeMut);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: RinkWidget> RinkWidgetResponder for W {
|
||||||
|
fn handle_event(&mut self, event: &Event, node: &mut dioxus_native_core::real_dom::NodeMut) {
|
||||||
|
RinkWidget::handle_event(self, event, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RinkWidgetTraitObjectFactory<W: RinkWidget> {
|
||||||
|
_marker: std::marker::PhantomData<W>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: RinkWidget> WidgetFactory<RinkWidgetTraitObject> for RinkWidgetTraitObjectFactory<W> {
|
||||||
|
const NAME: &'static str = W::NAME;
|
||||||
|
|
||||||
|
fn create(node: &mut dioxus_native_core::real_dom::NodeMut) -> RinkWidgetTraitObject {
|
||||||
|
let myself = RinkWidgetTraitObject {
|
||||||
|
widget: Arc::new(RwLock::new(W::create(node))),
|
||||||
|
};
|
||||||
|
node.insert(myself.clone());
|
||||||
|
myself
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Component)]
|
||||||
|
pub(crate) struct RinkWidgetTraitObject {
|
||||||
|
widget: Arc<RwLock<dyn RinkWidgetResponder + Send + Sync>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetUpdater for RinkWidgetTraitObject {
|
||||||
|
fn attributes_changed(
|
||||||
|
&mut self,
|
||||||
|
root: dioxus_native_core::real_dom::NodeMut,
|
||||||
|
attributes: &dioxus_native_core::node_ref::AttributeMask,
|
||||||
|
) {
|
||||||
|
let mut widget = self.widget.write().unwrap();
|
||||||
|
widget.attributes_changed(root, attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RinkWidgetResponder for RinkWidgetTraitObject {
|
||||||
|
fn handle_event(&mut self, event: &Event, node: &mut dioxus_native_core::real_dom::NodeMut) {
|
||||||
|
let mut widget = self.widget.write().unwrap();
|
||||||
|
widget.handle_event(event, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Unique)]
|
||||||
|
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,464 @@
|
|||||||
|
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::{
|
||||||
|
node::OwnedAttributeDiscription,
|
||||||
|
node_ref::AttributeMask,
|
||||||
|
prelude::{ElementNode, NodeType},
|
||||||
|
real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut, RealDom},
|
||||||
|
utils::{
|
||||||
|
cursor::{Cursor, Pos},
|
||||||
|
widget_watcher::Widget,
|
||||||
|
},
|
||||||
|
NodeId,
|
||||||
|
};
|
||||||
|
use shipyard::UniqueView;
|
||||||
|
use taffy::geometry::Point;
|
||||||
|
|
||||||
|
use crate::{query::get_layout, Event, EventData, FormData, Query};
|
||||||
|
|
||||||
|
use super::{RinkWidget, WidgetContext};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct Number {
|
||||||
|
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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Number {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() = text_before_first_cursor.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
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() = text_highlighted.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
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() = text_after_second_cursor.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the event
|
||||||
|
{
|
||||||
|
let world = rdom.raw_world_mut();
|
||||||
|
let data = 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 increase(&mut self) {
|
||||||
|
let num = self.text.parse::<f64>().unwrap_or(0.0);
|
||||||
|
self.text = (num + 1.0).to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrease(&mut self) {
|
||||||
|
let num = self.text.parse::<f64>().unwrap_or(0.0);
|
||||||
|
self.text = (num - 1.0).to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_keydown(&mut self, root: &mut NodeMut, data: &KeyboardData) {
|
||||||
|
let key = data.key();
|
||||||
|
let is_text = 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_text {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match key {
|
||||||
|
Key::ArrowUp => {
|
||||||
|
self.increase();
|
||||||
|
}
|
||||||
|
Key::ArrowDown => {
|
||||||
|
self.decrease();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = root.id();
|
||||||
|
let rdom = root.real_dom_mut();
|
||||||
|
self.write_value(rdom, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_mousemove(&mut self, root: &mut 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);
|
||||||
|
if self.border {
|
||||||
|
new.col = new.col.saturating_sub(1);
|
||||||
|
}
|
||||||
|
// 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, root: &mut NodeMut, data: &MouseData) {
|
||||||
|
let offset = data.element_coordinates();
|
||||||
|
let mut new = Pos::new(offset.x as usize, offset.y as usize);
|
||||||
|
if self.border {
|
||||||
|
new.col = new.col.saturating_sub(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Widget for Number {
|
||||||
|
const NAME: &'static str = "input";
|
||||||
|
|
||||||
|
fn create(root: &mut 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");
|
||||||
|
|
||||||
|
root.add_child(div_wrapper_id);
|
||||||
|
|
||||||
|
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 RinkWidget for Number {
|
||||||
|
fn handle_event(
|
||||||
|
&mut self,
|
||||||
|
event: &crate::Event,
|
||||||
|
node: &mut 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_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,432 @@
|
|||||||
|
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::{
|
||||||
|
node::OwnedAttributeDiscription,
|
||||||
|
node_ref::AttributeMask,
|
||||||
|
prelude::{ElementNode, NodeType},
|
||||||
|
real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut, RealDom},
|
||||||
|
utils::{
|
||||||
|
cursor::{Cursor, Pos},
|
||||||
|
widget_watcher::Widget,
|
||||||
|
},
|
||||||
|
NodeId,
|
||||||
|
};
|
||||||
|
use shipyard::UniqueView;
|
||||||
|
use taffy::geometry::Point;
|
||||||
|
|
||||||
|
use crate::{query::get_layout, Event, EventData, FormData, Query};
|
||||||
|
|
||||||
|
use super::{RinkWidget, WidgetContext};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct Password {
|
||||||
|
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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Password {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() = ".".repeat(text_before_first_cursor.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
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() = ".".repeat(text_highlighted.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
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() = ".".repeat(text_after_second_cursor.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the event
|
||||||
|
{
|
||||||
|
let world = rdom.raw_world_mut();
|
||||||
|
let data = 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, root: &mut 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, root: &mut 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);
|
||||||
|
if self.border {
|
||||||
|
new.col = new.col.saturating_sub(1);
|
||||||
|
}
|
||||||
|
// 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, root: &mut NodeMut, data: &MouseData) {
|
||||||
|
let offset = data.element_coordinates();
|
||||||
|
let mut new = Pos::new(offset.x as usize, offset.y as usize);
|
||||||
|
if self.border {
|
||||||
|
new.col = new.col.saturating_sub(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Widget for Password {
|
||||||
|
const NAME: &'static str = "input";
|
||||||
|
|
||||||
|
fn create(root: &mut 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");
|
||||||
|
|
||||||
|
root.add_child(div_wrapper_id);
|
||||||
|
|
||||||
|
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 RinkWidget for Password {
|
||||||
|
fn handle_event(
|
||||||
|
&mut self,
|
||||||
|
event: &crate::Event,
|
||||||
|
node: &mut 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_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,458 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use dioxus_html::{input_data::keyboard_types::Key, KeyboardData, MouseData};
|
||||||
|
use dioxus_native_core::{
|
||||||
|
node::{OwnedAttributeDiscription, OwnedAttributeValue},
|
||||||
|
node_ref::AttributeMask,
|
||||||
|
prelude::{ElementNode, NodeType},
|
||||||
|
real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut, RealDom},
|
||||||
|
utils::widget_watcher::Widget,
|
||||||
|
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, root: &mut 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, root: &mut 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 Widget for Slider {
|
||||||
|
const NAME: &'static str = "input";
|
||||||
|
|
||||||
|
fn create(root: &mut 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);
|
||||||
|
|
||||||
|
div_wrapper.add_event_listener("mousemove");
|
||||||
|
div_wrapper.add_event_listener("mousedown");
|
||||||
|
root.add_event_listener("keydown");
|
||||||
|
|
||||||
|
root.add_child(div_wrapper_id);
|
||||||
|
|
||||||
|
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: &mut 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,432 @@
|
|||||||
|
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::{
|
||||||
|
node::OwnedAttributeDiscription,
|
||||||
|
node_ref::AttributeMask,
|
||||||
|
prelude::{ElementNode, NodeType},
|
||||||
|
real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut, RealDom},
|
||||||
|
utils::{
|
||||||
|
cursor::{Cursor, Pos},
|
||||||
|
widget_watcher::Widget,
|
||||||
|
},
|
||||||
|
NodeId,
|
||||||
|
};
|
||||||
|
use shipyard::UniqueView;
|
||||||
|
use taffy::geometry::Point;
|
||||||
|
|
||||||
|
use crate::{query::get_layout, Event, EventData, FormData, Query};
|
||||||
|
|
||||||
|
use super::{RinkWidget, WidgetContext};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct TextBox {
|
||||||
|
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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextBox {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() = text_before_first_cursor.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
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() = text_highlighted.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
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() = text_after_second_cursor.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the event
|
||||||
|
{
|
||||||
|
let world = rdom.raw_world_mut();
|
||||||
|
let data = 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, root: &mut 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, root: &mut 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);
|
||||||
|
if self.border {
|
||||||
|
new.col = new.col.saturating_sub(1);
|
||||||
|
}
|
||||||
|
// 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, root: &mut NodeMut, data: &MouseData) {
|
||||||
|
let offset = data.element_coordinates();
|
||||||
|
let mut new = Pos::new(offset.x as usize, offset.y as usize);
|
||||||
|
if self.border {
|
||||||
|
new.col = new.col.saturating_sub(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Widget for TextBox {
|
||||||
|
const NAME: &'static str = "input";
|
||||||
|
|
||||||
|
fn create(root: &mut 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");
|
||||||
|
|
||||||
|
root.add_child(div_wrapper_id);
|
||||||
|
|
||||||
|
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 RinkWidget for TextBox {
|
||||||
|
fn handle_event(
|
||||||
|
&mut self,
|
||||||
|
event: &crate::Event,
|
||||||
|
node: &mut 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_mousedown(node, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"mouseup" => {
|
||||||
|
self.dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
"mouseleave" => {
|
||||||
|
self.dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
"mouseenter" => {
|
||||||
|
self.dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
"focusout" => {
|
||||||
|
execute!(stdout(), MoveTo(0, 1000)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user