21 files changed
+1617
-32
No files matched your search
@@ -306,8 +306,7 @@ impl Member {
|
|||||||
+ field.ty.to_token_stream().to_string().as_str())
|
+ field.ty.to_token_stream().to_string().as_str())
|
||||||
.as_str(),
|
.as_str(),
|
||||||
Span::call_site(),
|
Span::call_site(),
|
||||||
)
|
),
|
||||||
.into(),
|
|
||||||
ident: field.ident.as_ref()?.clone(),
|
ident: field.ident.as_ref()?.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,11 +102,7 @@ impl<S: State> RealDom<S> {
|
|||||||
self.tree.add_child(node_id, child_id);
|
self.tree.add_child(node_id, child_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_template_node(
|
fn create_template_node(&mut self, node: &TemplateNode) -> RealNodeId {
|
||||||
&mut self,
|
|
||||||
node: &TemplateNode,
|
|
||||||
mutations_vec: &mut FxHashMap<RealNodeId, NodeMask>,
|
|
||||||
) -> RealNodeId {
|
|
||||||
match node {
|
match node {
|
||||||
TemplateNode::Element {
|
TemplateNode::Element {
|
||||||
tag,
|
tag,
|
||||||
@@ -139,27 +135,18 @@ impl<S: State> RealDom<S> {
|
|||||||
});
|
});
|
||||||
let node_id = self.create_node(node);
|
let node_id = self.create_node(node);
|
||||||
for child in *children {
|
for child in *children {
|
||||||
let child_id = self.create_template_node(child, mutations_vec);
|
let child_id = self.create_template_node(child);
|
||||||
self.add_child(node_id, child_id);
|
self.add_child(node_id, child_id);
|
||||||
}
|
}
|
||||||
node_id
|
node_id
|
||||||
}
|
}
|
||||||
TemplateNode::Text { text } => {
|
TemplateNode::Text { text } => self.create_node(Node::new(NodeType::Text {
|
||||||
let node_id = self.create_node(Node::new(NodeType::Text {
|
text: text.to_string(),
|
||||||
text: text.to_string(),
|
})),
|
||||||
}));
|
TemplateNode::Dynamic { .. } => self.create_node(Node::new(NodeType::Placeholder)),
|
||||||
node_id
|
TemplateNode::DynamicText { .. } => self.create_node(Node::new(NodeType::Text {
|
||||||
}
|
text: String::new(),
|
||||||
TemplateNode::Dynamic { .. } => {
|
})),
|
||||||
let node_id = self.create_node(Node::new(NodeType::Placeholder));
|
|
||||||
node_id
|
|
||||||
}
|
|
||||||
TemplateNode::DynamicText { .. } => {
|
|
||||||
let node_id = self.create_node(Node::new(NodeType::Text {
|
|
||||||
text: String::new(),
|
|
||||||
}));
|
|
||||||
node_id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +159,7 @@ impl<S: State> RealDom<S> {
|
|||||||
for template in mutations.templates {
|
for template in mutations.templates {
|
||||||
let mut template_root_ids = Vec::new();
|
let mut template_root_ids = Vec::new();
|
||||||
for root in template.roots {
|
for root in template.roots {
|
||||||
let id = self.create_template_node(root, &mut nodes_updated);
|
let id = self.create_template_node(root);
|
||||||
template_root_ids.push(id);
|
template_root_ids.push(id);
|
||||||
}
|
}
|
||||||
self.templates
|
self.templates
|
||||||
|
|||||||
@@ -0,0 +1,484 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use dioxus_html::input_data::keyboard_types::{Code, Key, Modifiers};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Pos {
|
||||||
|
pub col: usize,
|
||||||
|
pub row: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pos {
|
||||||
|
pub fn new(col: usize, row: usize) -> Self {
|
||||||
|
Self { row, col }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn up(&mut self, rope: &str) {
|
||||||
|
self.move_row(-1, rope);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn down(&mut self, rope: &str) {
|
||||||
|
self.move_row(1, rope);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn right(&mut self, rope: &str) {
|
||||||
|
self.move_col(1, rope);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn left(&mut self, rope: &str) {
|
||||||
|
self.move_col(-1, rope);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_row(&mut self, change: i32, rope: &str) {
|
||||||
|
let new = self.row as i32 + change;
|
||||||
|
if new >= 0 && new < rope.lines().count() as i32 {
|
||||||
|
self.row = new as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_col(&mut self, change: i32, rope: &str) {
|
||||||
|
self.realize_col(rope);
|
||||||
|
let idx = self.idx(rope) as i32;
|
||||||
|
if idx + change >= 0 && idx + change <= rope.len() as i32 {
|
||||||
|
let len_line = self.len_line(rope) as i32;
|
||||||
|
let new_col = self.col as i32 + change;
|
||||||
|
let diff = new_col - len_line;
|
||||||
|
if diff > 0 {
|
||||||
|
self.down(rope);
|
||||||
|
self.col = 0;
|
||||||
|
self.move_col(diff - 1, rope);
|
||||||
|
} else if new_col < 0 {
|
||||||
|
self.up(rope);
|
||||||
|
self.col = self.len_line(rope);
|
||||||
|
self.move_col(new_col + 1, rope);
|
||||||
|
} else {
|
||||||
|
self.col = new_col as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn col(&self, rope: &str) -> usize {
|
||||||
|
self.col.min(self.len_line(rope))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn row(&self) -> usize {
|
||||||
|
self.row
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len_line(&self, rope: &str) -> usize {
|
||||||
|
let line = rope.lines().nth(self.row).unwrap_or_default();
|
||||||
|
let len = line.len();
|
||||||
|
if len > 0 && line.chars().nth(len - 1) == Some('\n') {
|
||||||
|
len - 1
|
||||||
|
} else {
|
||||||
|
len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn idx(&self, rope: &str) -> usize {
|
||||||
|
rope.lines().take(self.row).map(|l| l.len()).sum::<usize>() + self.col(rope)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the column can be more than the line length, cap it
|
||||||
|
pub fn realize_col(&mut self, rope: &str) {
|
||||||
|
self.col = self.col(rope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Pos {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.row.cmp(&other.row).then(self.col.cmp(&other.col))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Pos {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Cursor {
|
||||||
|
pub start: Pos,
|
||||||
|
pub end: Option<Pos>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cursor {
|
||||||
|
pub fn from_start(pos: Pos) -> Self {
|
||||||
|
Self {
|
||||||
|
start: pos,
|
||||||
|
end: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(start: Pos, end: Pos) -> Self {
|
||||||
|
Self {
|
||||||
|
start,
|
||||||
|
end: Some(end),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_cursor(&mut self, f: impl FnOnce(&mut Pos), shift: bool) {
|
||||||
|
if shift {
|
||||||
|
self.with_end(f);
|
||||||
|
} else {
|
||||||
|
f(&mut self.start);
|
||||||
|
self.end = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_selection(&mut self, text: &mut String) -> [i32; 2] {
|
||||||
|
let first = self.first();
|
||||||
|
let last = self.last();
|
||||||
|
let dr = first.row as i32 - last.row as i32;
|
||||||
|
let dc = if dr != 0 {
|
||||||
|
-(last.col as i32)
|
||||||
|
} else {
|
||||||
|
first.col as i32 - last.col as i32
|
||||||
|
};
|
||||||
|
text.replace_range(first.idx(text)..last.idx(text), "");
|
||||||
|
if let Some(end) = self.end.take() {
|
||||||
|
if self.start > end {
|
||||||
|
self.start = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[dc, dr]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_input(
|
||||||
|
&mut self,
|
||||||
|
data: &dioxus_html::KeyboardData,
|
||||||
|
text: &mut String,
|
||||||
|
max_width: usize,
|
||||||
|
) {
|
||||||
|
use Code::*;
|
||||||
|
match data.code() {
|
||||||
|
ArrowUp => {
|
||||||
|
self.move_cursor(|c| c.up(text), data.modifiers().contains(Modifiers::SHIFT));
|
||||||
|
}
|
||||||
|
ArrowDown => {
|
||||||
|
self.move_cursor(
|
||||||
|
|c| c.down(text),
|
||||||
|
data.modifiers().contains(Modifiers::SHIFT),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ArrowRight => {
|
||||||
|
if data.modifiers().contains(Modifiers::CONTROL) {
|
||||||
|
self.move_cursor(
|
||||||
|
|c| {
|
||||||
|
let mut change = 1;
|
||||||
|
let idx = c.idx(text);
|
||||||
|
let length = text.len();
|
||||||
|
while idx + change < length {
|
||||||
|
let chr = text.chars().nth(idx + change).unwrap();
|
||||||
|
if chr.is_whitespace() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
change += 1;
|
||||||
|
}
|
||||||
|
c.move_col(change as i32, text);
|
||||||
|
},
|
||||||
|
data.modifiers().contains(Modifiers::SHIFT),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.move_cursor(
|
||||||
|
|c| c.right(text),
|
||||||
|
data.modifiers().contains(Modifiers::SHIFT),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ArrowLeft => {
|
||||||
|
if data.modifiers().contains(Modifiers::CONTROL) {
|
||||||
|
self.move_cursor(
|
||||||
|
|c| {
|
||||||
|
let mut change = -1;
|
||||||
|
let idx = c.idx(text) as i32;
|
||||||
|
while idx + change > 0 {
|
||||||
|
let chr = text.chars().nth((idx + change) as usize).unwrap();
|
||||||
|
if chr == ' ' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
change -= 1;
|
||||||
|
}
|
||||||
|
c.move_col(change as i32, text);
|
||||||
|
},
|
||||||
|
data.modifiers().contains(Modifiers::SHIFT),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.move_cursor(
|
||||||
|
|c| c.left(text),
|
||||||
|
data.modifiers().contains(Modifiers::SHIFT),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
End => {
|
||||||
|
self.move_cursor(
|
||||||
|
|c| c.col = c.len_line(text),
|
||||||
|
data.modifiers().contains(Modifiers::SHIFT),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Home => {
|
||||||
|
self.move_cursor(|c| c.col = 0, data.modifiers().contains(Modifiers::SHIFT));
|
||||||
|
}
|
||||||
|
Backspace => {
|
||||||
|
self.start.realize_col(text);
|
||||||
|
let mut start_idx = self.start.idx(text);
|
||||||
|
if self.end.is_some() {
|
||||||
|
self.delete_selection(text);
|
||||||
|
} else if start_idx > 0 {
|
||||||
|
self.start.left(text);
|
||||||
|
text.replace_range(start_idx - 1..start_idx, "");
|
||||||
|
if data.modifiers().contains(Modifiers::CONTROL) {
|
||||||
|
start_idx = self.start.idx(text);
|
||||||
|
while start_idx > 0
|
||||||
|
&& text
|
||||||
|
.chars()
|
||||||
|
.nth(start_idx - 1)
|
||||||
|
.filter(|c| *c != ' ')
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
self.start.left(text);
|
||||||
|
text.replace_range(start_idx - 1..start_idx, "");
|
||||||
|
start_idx = self.start.idx(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Enter => {
|
||||||
|
if text.len() + 1 - self.selection_len(text) <= max_width {
|
||||||
|
text.insert(self.start.idx(text), '\n');
|
||||||
|
self.start.col = 0;
|
||||||
|
self.start.down(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tab => {
|
||||||
|
if text.len() + 1 - self.selection_len(text) <= max_width {
|
||||||
|
self.start.realize_col(text);
|
||||||
|
self.delete_selection(text);
|
||||||
|
text.insert(self.start.idx(text), '\t');
|
||||||
|
self.start.right(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.start.realize_col(text);
|
||||||
|
if let Key::Character(character) = data.key() {
|
||||||
|
if text.len() + 1 - self.selection_len(text) <= max_width {
|
||||||
|
self.delete_selection(text);
|
||||||
|
let character = character.chars().next().unwrap();
|
||||||
|
text.insert(self.start.idx(text), character);
|
||||||
|
self.start.right(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_end(&mut self, f: impl FnOnce(&mut Pos)) {
|
||||||
|
let mut new = self.end.take().unwrap_or_else(|| self.start.clone());
|
||||||
|
f(&mut new);
|
||||||
|
self.end.replace(new);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn first(&self) -> &Pos {
|
||||||
|
if let Some(e) = &self.end {
|
||||||
|
e.min(&self.start)
|
||||||
|
} else {
|
||||||
|
&self.start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn last(&self) -> &Pos {
|
||||||
|
if let Some(e) = &self.end {
|
||||||
|
e.max(&self.start)
|
||||||
|
} else {
|
||||||
|
&self.start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selection_len(&self, text: &str) -> usize {
|
||||||
|
self.last().idx(text) - self.first().idx(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Cursor {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
start: Pos::new(0, 0),
|
||||||
|
end: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pos_direction_movement() {
|
||||||
|
let mut pos = Pos::new(100, 0);
|
||||||
|
let text = "hello world\nhi";
|
||||||
|
|
||||||
|
assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
|
||||||
|
pos.down(text);
|
||||||
|
assert_eq!(pos.col(text), text.lines().nth(1).unwrap_or_default().len());
|
||||||
|
pos.up(text);
|
||||||
|
assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
|
||||||
|
pos.left(text);
|
||||||
|
assert_eq!(
|
||||||
|
pos.col(text),
|
||||||
|
text.lines().next().unwrap_or_default().len() - 1
|
||||||
|
);
|
||||||
|
pos.right(text);
|
||||||
|
assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pos_col_movement() {
|
||||||
|
let mut pos = Pos::new(100, 0);
|
||||||
|
let text = "hello world\nhi";
|
||||||
|
|
||||||
|
// move inside a row
|
||||||
|
pos.move_col(-5, text);
|
||||||
|
assert_eq!(
|
||||||
|
pos.col(text),
|
||||||
|
text.lines().next().unwrap_or_default().len() - 5
|
||||||
|
);
|
||||||
|
pos.move_col(5, text);
|
||||||
|
assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
|
||||||
|
|
||||||
|
// move between rows
|
||||||
|
pos.move_col(3, text);
|
||||||
|
assert_eq!(pos.col(text), 2);
|
||||||
|
pos.move_col(-3, text);
|
||||||
|
assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
|
||||||
|
|
||||||
|
// don't panic if moving out of range
|
||||||
|
pos.move_col(-100, text);
|
||||||
|
pos.move_col(1000, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cursor_row_movement() {
|
||||||
|
let mut pos = Pos::new(100, 0);
|
||||||
|
let text = "hello world\nhi";
|
||||||
|
|
||||||
|
pos.move_row(1, text);
|
||||||
|
assert_eq!(pos.row(), 1);
|
||||||
|
pos.move_row(-1, text);
|
||||||
|
assert_eq!(pos.row(), 0);
|
||||||
|
|
||||||
|
// don't panic if moving out of range
|
||||||
|
pos.move_row(-100, text);
|
||||||
|
pos.move_row(1000, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cursor_input() {
|
||||||
|
let mut cursor = Cursor::from_start(Pos::new(0, 0));
|
||||||
|
let mut text = "hello world\nhi".to_string();
|
||||||
|
|
||||||
|
for _ in 0..5 {
|
||||||
|
cursor.handle_input(
|
||||||
|
&dioxus_html::KeyboardData::new(
|
||||||
|
dioxus_html::input_data::keyboard_types::Key::ArrowRight,
|
||||||
|
dioxus_html::input_data::keyboard_types::Code::ArrowRight,
|
||||||
|
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||||
|
false,
|
||||||
|
Modifiers::empty(),
|
||||||
|
),
|
||||||
|
&mut text,
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..5 {
|
||||||
|
cursor.handle_input(
|
||||||
|
&dioxus_html::KeyboardData::new(
|
||||||
|
dioxus_html::input_data::keyboard_types::Key::Backspace,
|
||||||
|
dioxus_html::input_data::keyboard_types::Code::Backspace,
|
||||||
|
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||||
|
false,
|
||||||
|
Modifiers::empty(),
|
||||||
|
),
|
||||||
|
&mut text,
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(text, " world\nhi");
|
||||||
|
|
||||||
|
let goal_text = "hello world\nhi";
|
||||||
|
let max_width = goal_text.len();
|
||||||
|
cursor.handle_input(
|
||||||
|
&dioxus_html::KeyboardData::new(
|
||||||
|
dioxus_html::input_data::keyboard_types::Key::Character("h".to_string()),
|
||||||
|
dioxus_html::input_data::keyboard_types::Code::KeyH,
|
||||||
|
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||||
|
false,
|
||||||
|
Modifiers::empty(),
|
||||||
|
),
|
||||||
|
&mut text,
|
||||||
|
max_width,
|
||||||
|
);
|
||||||
|
|
||||||
|
cursor.handle_input(
|
||||||
|
&dioxus_html::KeyboardData::new(
|
||||||
|
dioxus_html::input_data::keyboard_types::Key::Character("e".to_string()),
|
||||||
|
dioxus_html::input_data::keyboard_types::Code::KeyE,
|
||||||
|
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||||
|
false,
|
||||||
|
Modifiers::empty(),
|
||||||
|
),
|
||||||
|
&mut text,
|
||||||
|
max_width,
|
||||||
|
);
|
||||||
|
|
||||||
|
cursor.handle_input(
|
||||||
|
&dioxus_html::KeyboardData::new(
|
||||||
|
dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
|
||||||
|
dioxus_html::input_data::keyboard_types::Code::KeyL,
|
||||||
|
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||||
|
false,
|
||||||
|
Modifiers::empty(),
|
||||||
|
),
|
||||||
|
&mut text,
|
||||||
|
max_width,
|
||||||
|
);
|
||||||
|
|
||||||
|
cursor.handle_input(
|
||||||
|
&dioxus_html::KeyboardData::new(
|
||||||
|
dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
|
||||||
|
dioxus_html::input_data::keyboard_types::Code::KeyL,
|
||||||
|
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||||
|
false,
|
||||||
|
Modifiers::empty(),
|
||||||
|
),
|
||||||
|
&mut text,
|
||||||
|
max_width,
|
||||||
|
);
|
||||||
|
|
||||||
|
cursor.handle_input(
|
||||||
|
&dioxus_html::KeyboardData::new(
|
||||||
|
dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
|
||||||
|
dioxus_html::input_data::keyboard_types::Code::KeyO,
|
||||||
|
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||||
|
false,
|
||||||
|
Modifiers::empty(),
|
||||||
|
),
|
||||||
|
&mut text,
|
||||||
|
max_width,
|
||||||
|
);
|
||||||
|
|
||||||
|
// these should be ignored
|
||||||
|
for _ in 0..10 {
|
||||||
|
cursor.handle_input(
|
||||||
|
&dioxus_html::KeyboardData::new(
|
||||||
|
dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
|
||||||
|
dioxus_html::input_data::keyboard_types::Code::KeyO,
|
||||||
|
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||||
|
false,
|
||||||
|
Modifiers::empty(),
|
||||||
|
),
|
||||||
|
&mut text,
|
||||||
|
max_width,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(text.to_string(), goal_text);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mod persistant_iterator;
|
||||||
|
pub use persistant_iterator::*;
|
||||||
|
pub mod cursor;
|
||||||
File renamed without changes.
@@ -13,6 +13,7 @@ license = "MIT/Apache-2.0"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
dioxus = { path = "../dioxus", version = "^0.2.1" }
|
||||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||||
dioxus-html = { path = "../html", version = "^0.2.1" }
|
dioxus-html = { path = "../html", version = "^0.2.1" }
|
||||||
dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
|
dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_html::FormData;
|
||||||
|
use dioxus_tui::prelude::*;
|
||||||
|
use dioxus_tui::Config;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus_tui::launch_cfg(app, Config::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let bg_green = use_state(cx, || false);
|
||||||
|
|
||||||
|
let color = if *bg_green.get() { "green" } else { "red" };
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
background_color: "{color}",
|
||||||
|
flex_direction: "column",
|
||||||
|
align_items: "center",
|
||||||
|
justify_content: "center",
|
||||||
|
|
||||||
|
Input{
|
||||||
|
oninput: |data: FormData| if &data.value == "good"{
|
||||||
|
bg_green.set(true);
|
||||||
|
} else{
|
||||||
|
bg_green.set(false);
|
||||||
|
},
|
||||||
|
r#type: "checkbox",
|
||||||
|
value: "good",
|
||||||
|
width: "50%",
|
||||||
|
height: "10%",
|
||||||
|
checked: "true",
|
||||||
|
}
|
||||||
|
Input{
|
||||||
|
oninput: |data: FormData| if &data.value == "hello world"{
|
||||||
|
bg_green.set(true);
|
||||||
|
} else{
|
||||||
|
bg_green.set(false);
|
||||||
|
},
|
||||||
|
width: "50%",
|
||||||
|
height: "10%",
|
||||||
|
maxlength: "11",
|
||||||
|
}
|
||||||
|
Input{
|
||||||
|
oninput: |data: FormData| {
|
||||||
|
if (data.value.parse::<f32>().unwrap() - 40.0).abs() < 5.0 {
|
||||||
|
bg_green.set(true);
|
||||||
|
} else{
|
||||||
|
bg_green.set(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
r#type: "range",
|
||||||
|
width: "50%",
|
||||||
|
height: "10%",
|
||||||
|
min: "20",
|
||||||
|
max: "80",
|
||||||
|
}
|
||||||
|
Input{
|
||||||
|
oninput: |data: FormData| {
|
||||||
|
if data.value == "10"{
|
||||||
|
bg_green.set(true);
|
||||||
|
} else{
|
||||||
|
bg_green.set(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
r#type: "number",
|
||||||
|
width: "50%",
|
||||||
|
height: "10%",
|
||||||
|
maxlength: "4",
|
||||||
|
}
|
||||||
|
Input{
|
||||||
|
oninput: |data: FormData| {
|
||||||
|
if data.value == "hello world"{
|
||||||
|
bg_green.set(true);
|
||||||
|
} else{
|
||||||
|
bg_green.set(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
r#type: "password",
|
||||||
|
width: "50%",
|
||||||
|
height: "10%",
|
||||||
|
maxlength: "11",
|
||||||
|
}
|
||||||
|
Input{
|
||||||
|
onclick: |_: FormData| bg_green.set(true),
|
||||||
|
r#type: "button",
|
||||||
|
value: "green",
|
||||||
|
width: "50%",
|
||||||
|
height: "10%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -287,7 +287,10 @@ impl InnerInputState {
|
|||||||
|
|
||||||
fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData {
|
fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData {
|
||||||
let Point { x, y } = layout.location;
|
let Point { x, y } = layout.location;
|
||||||
let node_origin = ClientPoint::new(x.into(), y.into());
|
let node_origin = ClientPoint::new(
|
||||||
|
layout_to_screen_space(x).into(),
|
||||||
|
layout_to_screen_space(y).into(),
|
||||||
|
);
|
||||||
|
|
||||||
let new_client_coordinates = (mouse_data.client_coordinates() - node_origin)
|
let new_client_coordinates = (mouse_data.client_coordinates() - node_origin)
|
||||||
.to_point()
|
.to_point()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use dioxus_native_core::state::ChildDepState;
|
|||||||
use dioxus_native_core_macro::sorted_str_slice;
|
use dioxus_native_core_macro::sorted_str_slice;
|
||||||
use taffy::prelude::*;
|
use taffy::prelude::*;
|
||||||
|
|
||||||
use crate::screen_to_layout_space;
|
use crate::{screen_to_layout_space, unit_to_layout_space};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub(crate) enum PossiblyUninitalized<T> {
|
pub(crate) enum PossiblyUninitalized<T> {
|
||||||
@@ -105,6 +105,59 @@ impl ChildDepState for TaffyLayout {
|
|||||||
child_layout.push(l.node.unwrap());
|
child_layout.push(l.node.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scale_dimention(d: Dimension) -> Dimension {
|
||||||
|
match d {
|
||||||
|
Dimension::Points(p) => Dimension::Points(unit_to_layout_space(p)),
|
||||||
|
Dimension::Percent(p) => Dimension::Percent(p),
|
||||||
|
Dimension::Auto => Dimension::Auto,
|
||||||
|
Dimension::Undefined => Dimension::Undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let style = Style {
|
||||||
|
position: Rect {
|
||||||
|
left: scale_dimention(style.position.left),
|
||||||
|
right: scale_dimention(style.position.right),
|
||||||
|
top: scale_dimention(style.position.top),
|
||||||
|
bottom: scale_dimention(style.position.bottom),
|
||||||
|
},
|
||||||
|
margin: Rect {
|
||||||
|
left: scale_dimention(style.margin.left),
|
||||||
|
right: scale_dimention(style.margin.right),
|
||||||
|
top: scale_dimention(style.margin.top),
|
||||||
|
bottom: scale_dimention(style.margin.bottom),
|
||||||
|
},
|
||||||
|
padding: Rect {
|
||||||
|
left: scale_dimention(style.padding.left),
|
||||||
|
right: scale_dimention(style.padding.right),
|
||||||
|
top: scale_dimention(style.padding.top),
|
||||||
|
bottom: scale_dimention(style.padding.bottom),
|
||||||
|
},
|
||||||
|
border: Rect {
|
||||||
|
left: scale_dimention(style.border.left),
|
||||||
|
right: scale_dimention(style.border.right),
|
||||||
|
top: scale_dimention(style.border.top),
|
||||||
|
bottom: scale_dimention(style.border.bottom),
|
||||||
|
},
|
||||||
|
gap: Size {
|
||||||
|
width: scale_dimention(style.gap.width),
|
||||||
|
height: scale_dimention(style.gap.height),
|
||||||
|
},
|
||||||
|
flex_basis: scale_dimention(style.flex_basis),
|
||||||
|
size: Size {
|
||||||
|
width: scale_dimention(style.size.width),
|
||||||
|
height: scale_dimention(style.size.height),
|
||||||
|
},
|
||||||
|
min_size: Size {
|
||||||
|
width: scale_dimention(style.min_size.width),
|
||||||
|
height: scale_dimention(style.min_size.height),
|
||||||
|
},
|
||||||
|
max_size: Size {
|
||||||
|
width: scale_dimention(style.max_size.width),
|
||||||
|
height: scale_dimention(style.max_size.height),
|
||||||
|
},
|
||||||
|
..style
|
||||||
|
};
|
||||||
|
|
||||||
if let PossiblyUninitalized::Initialized(n) = self.node {
|
if let PossiblyUninitalized::Initialized(n) = self.node {
|
||||||
if self.style != style {
|
if self.style != style {
|
||||||
taffy.set_style(n, style).unwrap();
|
taffy.set_style(n, style).unwrap();
|
||||||
|
|||||||
+18
-3
@@ -1,5 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
|
cursor::{MoveTo, RestorePosition, SavePosition, Show},
|
||||||
event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
|
event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
@@ -28,11 +29,13 @@ mod focus;
|
|||||||
mod hooks;
|
mod hooks;
|
||||||
mod layout;
|
mod layout;
|
||||||
mod node;
|
mod node;
|
||||||
|
pub mod prelude;
|
||||||
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::*;
|
||||||
@@ -43,6 +46,10 @@ pub(crate) fn screen_to_layout_space(screen: u16) -> f32 {
|
|||||||
screen as f32 * 10.0
|
screen as f32 * 10.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unit_to_layout_space(screen: f32) -> f32 {
|
||||||
|
screen * 10.0
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn layout_to_screen_space(layout: f32) -> f32 {
|
pub(crate) fn layout_to_screen_space(layout: f32) -> f32 {
|
||||||
layout / 10.0
|
layout / 10.0
|
||||||
}
|
}
|
||||||
@@ -136,7 +143,13 @@ fn render_vdom(
|
|||||||
let mut terminal = (!cfg.headless).then(|| {
|
let mut terminal = (!cfg.headless).then(|| {
|
||||||
enable_raw_mode().unwrap();
|
enable_raw_mode().unwrap();
|
||||||
let mut stdout = std::io::stdout();
|
let mut stdout = std::io::stdout();
|
||||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
|
execute!(
|
||||||
|
stdout,
|
||||||
|
EnterAlternateScreen,
|
||||||
|
EnableMouseCapture,
|
||||||
|
MoveTo(0, 1000)
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let backend = CrosstermBackend::new(io::stdout());
|
let backend = CrosstermBackend::new(io::stdout());
|
||||||
Terminal::new(backend).unwrap()
|
Terminal::new(backend).unwrap()
|
||||||
});
|
});
|
||||||
@@ -181,14 +194,16 @@ fn render_vdom(
|
|||||||
taffy.compute_layout(root_node, size).unwrap();
|
taffy.compute_layout(root_node, size).unwrap();
|
||||||
}
|
}
|
||||||
if let Some(terminal) = &mut terminal {
|
if let Some(terminal) = &mut terminal {
|
||||||
|
execute!(terminal.backend_mut(), SavePosition).unwrap();
|
||||||
terminal.draw(|frame| {
|
terminal.draw(|frame| {
|
||||||
let rdom = rdom.borrow();
|
let rdom = rdom.borrow();
|
||||||
let mut taffy = taffy.lock().expect("taffy lock poisoned");
|
let mut taffy = taffy.lock().expect("taffy lock poisoned");
|
||||||
// size is guaranteed to not change when rendering
|
// size is guaranteed to not change when rendering
|
||||||
resize(frame.size(), &mut *taffy, &rdom);
|
resize(frame.size(), &mut taffy, &rdom);
|
||||||
let root = &rdom[NodeId(0)];
|
let root = &rdom[NodeId(0)];
|
||||||
render::render_vnode(frame, &*taffy, &rdom, root, cfg, Point::ZERO);
|
render::render_vnode(frame, &taffy, &rdom, root, cfg, Point::ZERO);
|
||||||
})?;
|
})?;
|
||||||
|
execute!(terminal.backend_mut(), RestorePosition, Show).unwrap();
|
||||||
} else {
|
} else {
|
||||||
let rdom = rdom.borrow();
|
let rdom = rdom.borrow();
|
||||||
resize(
|
resize(
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
pub use crate::widgets::*;
|
||||||
@@ -89,7 +89,7 @@ impl<'a> ElementRef<'a> {
|
|||||||
.ok();
|
.ok();
|
||||||
layout.map(|layout| Layout {
|
layout.map(|layout| Layout {
|
||||||
order: layout.order,
|
order: layout.order,
|
||||||
size: layout.size.map(|v| layout_to_screen_space(v)),
|
size: layout.size.map(layout_to_screen_space),
|
||||||
location: Point {
|
location: Point {
|
||||||
x: layout_to_screen_space(layout.location.x),
|
x: layout_to_screen_space(layout.location.x),
|
||||||
y: layout_to_screen_space(layout.location.y),
|
y: layout_to_screen_space(layout.location.y),
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ pub(crate) fn render_vnode(
|
|||||||
let x = layout_to_screen_space(fx).round() as u16;
|
let x = layout_to_screen_space(fx).round() as u16;
|
||||||
let y = layout_to_screen_space(fy).round() as u16;
|
let y = layout_to_screen_space(fy).round() as u16;
|
||||||
let Size { width, height } = *size;
|
let Size { width, height } = *size;
|
||||||
let width = layout_to_screen_space(fx + width).round() as u16 + x;
|
let width = layout_to_screen_space(fx + width).round() as u16 - x;
|
||||||
let height = layout_to_screen_space(fy + height).round() as u16 - y;
|
let height = layout_to_screen_space(fy + height).round() as u16 - y;
|
||||||
|
|
||||||
match &node.node_data.node_type {
|
match &node.node_data.node_type {
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
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);
|
||||||
|
};
|
||||||
|
cx.render(rsx! {
|
||||||
|
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}"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
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);
|
||||||
|
};
|
||||||
|
cx.render(rsx! {
|
||||||
|
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}"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
mod button;
|
||||||
|
mod checkbox;
|
||||||
|
mod input;
|
||||||
|
mod number;
|
||||||
|
mod password;
|
||||||
|
mod slider;
|
||||||
|
mod textbox;
|
||||||
|
|
||||||
|
use dioxus_core::{ElementId, RenderReturn, Scope};
|
||||||
|
pub use input::*;
|
||||||
|
|
||||||
|
pub(crate) fn get_root_id<T>(cx: Scope<T>) -> Option<ElementId> {
|
||||||
|
if let RenderReturn::Sync(Ok(sync)) = cx.root_node() {
|
||||||
|
sync.root_ids.get(0).map(|id| id.get())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
use crate::widgets::get_root_id;
|
||||||
|
use crate::Query;
|
||||||
|
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 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, &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 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
|
||||||
|
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());
|
||||||
|
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 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
|
||||||
|
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}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
use crate::widgets::get_root_id;
|
||||||
|
use crate::Query;
|
||||||
|
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 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"
|
||||||
|
};
|
||||||
|
|
||||||
|
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, &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 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
|
||||||
|
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());
|
||||||
|
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 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
|
||||||
|
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}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::widgets::get_root_id;
|
||||||
|
use crate::Query;
|
||||||
|
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 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 = if let Some(value) = value {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
*value_state.get()
|
||||||
|
}
|
||||||
|
.max(min)
|
||||||
|
.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).max(min).min(max));
|
||||||
|
update(value_state.current().to_string());
|
||||||
|
}
|
||||||
|
Key::ArrowRight => {
|
||||||
|
value_state.set((current_value + step).max(min).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)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
use crate::widgets::get_root_id;
|
||||||
|
use crate::Query;
|
||||||
|
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 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, &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 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
|
||||||
|
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());
|
||||||
|
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 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
|
||||||
|
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}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user