Merge branch 'upstream' into simplify-native-core
This commit is contained in:
commit
fb0c6c1b62
26 files changed
+608
-252
No files matched your search
@@ -0,0 +1,37 @@
|
|||||||
|
name: docs stable
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: docs
|
||||||
|
steps:
|
||||||
|
|
||||||
|
# NOTE: Comment out when https://github.com/rust-lang/mdBook/pull/1306 is merged and released
|
||||||
|
# - name: Setup mdBook
|
||||||
|
# uses: peaceiris/actions-mdbook@v1
|
||||||
|
# with:
|
||||||
|
# mdbook-version: "0.4.10"
|
||||||
|
|
||||||
|
# NOTE: Delete when the previous one is enabled
|
||||||
|
- name: Setup mdBook
|
||||||
|
run: |
|
||||||
|
cargo install mdbook --git https://github.com/Demonthos/mdBook.git --branch master
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cd docs &&
|
||||||
|
cd guide && mdbook build -d ../nightly/guide && cd .. &&
|
||||||
|
cd router && mdbook build -d ../nightly/router && cd ..
|
||||||
|
|
||||||
|
- name: Deploy 🚀
|
||||||
|
uses: JamesIves/github-pages-deploy-action@v4.4.1
|
||||||
|
with:
|
||||||
|
branch: gh-pages # The branch the action should deploy to.
|
||||||
|
folder: docs/nightly # The folder the action should deploy.
|
||||||
|
target-folder: docs
|
||||||
|
repository-name: dioxuslabs/docsite
|
||||||
|
clean: false
|
||||||
|
token: ${{ secrets.DEPLOY_KEY }} # let's pretend I don't need it for now
|
||||||
@@ -18,8 +18,6 @@ cargo run --example hello_world
|
|||||||
|
|
||||||
[custom_assets](./custom_assets.rs) - Include images
|
[custom_assets](./custom_assets.rs) - Include images
|
||||||
|
|
||||||
[custom_element](./custom_element.rs) - Render webcomponents
|
|
||||||
|
|
||||||
[custom_html](./custom_html.rs) - Customize wrapper HTML
|
[custom_html](./custom_html.rs) - Customize wrapper HTML
|
||||||
|
|
||||||
[eval](./eval.rs) - Evaluate JS expressions
|
[eval](./eval.rs) - Evaluate JS expressions
|
||||||
@@ -132,36 +130,6 @@ cargo run --example hello_world
|
|||||||
|
|
||||||
[todomvc](./todomvc.rs) - Todo task list example
|
[todomvc](./todomvc.rs) - Todo task list example
|
||||||
|
|
||||||
## Terminal UI
|
|
||||||
|
|
||||||
[tui_all_events](../packages/tui/examples/tui_all_events.rs) - All of the terminal events
|
|
||||||
|
|
||||||
[tui_border](../packages/tui/examples/tui_border.rs) - Different styles of borders and corners
|
|
||||||
|
|
||||||
[tui_buttons](../packages/tui/examples/tui_buttons.rs) - A grid of buttons that work demonstrate the focus system
|
|
||||||
|
|
||||||
[tui_color_test](../packages/tui/examples/tui_color_test.rs) - Grid of colors to demonstrate compatablility with different terminal color rendering modes
|
|
||||||
|
|
||||||
[tui_colorpicker](../packages/tui/examples/tui_colorpicker.rs) - A colorpicker
|
|
||||||
|
|
||||||
[tui_components](../packages/tui/examples/tui_components.rs) - Simple component example
|
|
||||||
|
|
||||||
[tui_flex](../packages/tui/examples/tui_flex.rs) - Flexbox support
|
|
||||||
|
|
||||||
[tui_hover](../packages/tui/examples/tui_hover.rs) - Detect hover and mouse events
|
|
||||||
|
|
||||||
[tui_list](../packages/tui/examples/tui_list.rs) - Renders a list of items
|
|
||||||
|
|
||||||
[tui_margin](../packages/tui/examples/tui_margin.rs) - Margins around flexboxes
|
|
||||||
|
|
||||||
[tui_quadrants](../packages/tui/examples/tui_quadrants.rs) - Four quadrants
|
|
||||||
|
|
||||||
[tui_readme](../packages/tui/examples/tui_readme.rs) - The readme example
|
|
||||||
|
|
||||||
[tui_task](../packages/tui/examples/tui_task.rs) - Background tasks
|
|
||||||
|
|
||||||
[tui_text](../packages/tui/examples/tui_text.rs) - Simple text example
|
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
Missing Features
|
Missing Features
|
||||||
- Fine-grained reactivity
|
- Fine-grained reactivity
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_desktop::tao::event::WindowEvent;
|
||||||
|
use dioxus_desktop::use_wry_event_handler;
|
||||||
|
use dioxus_desktop::wry::application::event::Event as WryEvent;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus_desktop::launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let focused = use_state(cx, || false);
|
||||||
|
|
||||||
|
use_wry_event_handler(cx, {
|
||||||
|
to_owned![focused];
|
||||||
|
move |event, _| {
|
||||||
|
if let WryEvent::WindowEvent {
|
||||||
|
event: WindowEvent::Focused(new_focused),
|
||||||
|
..
|
||||||
|
} = event
|
||||||
|
{
|
||||||
|
focused.set(*new_focused);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flex_direction: "column",
|
||||||
|
align_items: "center",
|
||||||
|
{
|
||||||
|
if *focused.get() {
|
||||||
|
"This window is focused!"
|
||||||
|
} else {
|
||||||
|
"This window is not focused!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -328,10 +328,8 @@ impl Writer<'_> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[BodyNode::RawExpr(ref expr)] => {
|
// TODO: let rawexprs to be inlined
|
||||||
// TODO: let rawexprs to be inlined
|
[BodyNode::RawExpr(ref expr)] => get_expr_length(expr),
|
||||||
get_expr_length(expr)
|
|
||||||
}
|
|
||||||
[BodyNode::Element(ref el)] => {
|
[BodyNode::Element(ref el)] => {
|
||||||
let attr_len = self.is_short_attrs(&el.attributes);
|
let attr_len = self.is_short_attrs(&el.attributes);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::fmt::{Result, Write};
|
|||||||
|
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
|
|
||||||
use crate::Writer;
|
use crate::{collect_macros::byte_offset, Writer};
|
||||||
|
|
||||||
impl Writer<'_> {
|
impl Writer<'_> {
|
||||||
pub fn write_raw_expr(&mut self, placement: Span) -> Result {
|
pub fn write_raw_expr(&mut self, placement: Span) -> Result {
|
||||||
@@ -16,11 +16,11 @@ impl Writer<'_> {
|
|||||||
|
|
||||||
// if the expr is on one line, just write it directly
|
// if the expr is on one line, just write it directly
|
||||||
if start.line == end.line {
|
if start.line == end.line {
|
||||||
write!(
|
// split counting utf8 chars
|
||||||
self.out,
|
let start = byte_offset(self.raw_src, start);
|
||||||
"{}",
|
let end = byte_offset(self.raw_src, end);
|
||||||
&self.src[start.line - 1][start.column - 1..end.column].trim()
|
let row = self.raw_src[start..end].trim();
|
||||||
)?;
|
write!(self.out, "{}", row)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+31
-42
@@ -1,6 +1,6 @@
|
|||||||
use crate::writer::*;
|
use crate::writer::*;
|
||||||
use collect_macros::byte_offset;
|
use collect_macros::byte_offset;
|
||||||
use dioxus_rsx::CallBody;
|
use dioxus_rsx::{BodyNode, CallBody};
|
||||||
use proc_macro2::LineColumn;
|
use proc_macro2::LineColumn;
|
||||||
use syn::{ExprMacro, MacroDelimiter};
|
use syn::{ExprMacro, MacroDelimiter};
|
||||||
|
|
||||||
@@ -53,10 +53,7 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
|||||||
return formatted_blocks;
|
return formatted_blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut writer = Writer {
|
let mut writer = Writer::new(contents);
|
||||||
src: contents.lines().collect::<Vec<_>>(),
|
|
||||||
..Writer::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Dont parse nested macros
|
// Dont parse nested macros
|
||||||
let mut end_span = LineColumn { column: 0, line: 0 };
|
let mut end_span = LineColumn { column: 0, line: 0 };
|
||||||
@@ -79,12 +76,7 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
|||||||
.count()
|
.count()
|
||||||
/ 4;
|
/ 4;
|
||||||
|
|
||||||
// Oneliner optimization
|
write_body(&mut writer, &body);
|
||||||
if writer.is_short_children(&body.roots).is_some() {
|
|
||||||
writer.write_ident(&body.roots[0]).unwrap();
|
|
||||||
} else {
|
|
||||||
writer.write_body_indented(&body.roots).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// writing idents leaves the final line ended at the end of the last ident
|
// writing idents leaves the final line ended at the end of the last ident
|
||||||
if writer.out.buf.contains('\n') {
|
if writer.out.buf.contains('\n') {
|
||||||
@@ -105,7 +97,11 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
|||||||
let start = byte_offset(contents, span.start()) + 1;
|
let start = byte_offset(contents, span.start()) + 1;
|
||||||
let end = byte_offset(contents, span.end()) - 1;
|
let end = byte_offset(contents, span.end()) - 1;
|
||||||
|
|
||||||
if formatted.len() <= 80 && !formatted.contains('\n') {
|
// Rustfmt will remove the space between the macro and the opening paren if the macro is a single expression
|
||||||
|
let body_is_solo_expr = body.roots.len() == 1
|
||||||
|
&& matches!(body.roots[0], BodyNode::RawExpr(_) | BodyNode::Text(_));
|
||||||
|
|
||||||
|
if formatted.len() <= 80 && !formatted.contains('\n') && !body_is_solo_expr {
|
||||||
formatted = format!(" {} ", formatted);
|
formatted = format!(" {} ", formatted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,35 +122,38 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_block_out(body: CallBody) -> Option<String> {
|
pub fn write_block_out(body: CallBody) -> Option<String> {
|
||||||
let mut buf = Writer {
|
let mut buf = Writer::new("");
|
||||||
src: vec![""],
|
|
||||||
..Writer::default()
|
write_body(&mut buf, &body);
|
||||||
};
|
|
||||||
|
buf.consume()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_body(buf: &mut Writer, body: &CallBody) {
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
// Oneliner optimization
|
|
||||||
if buf.is_short_children(&body.roots).is_some() {
|
if buf.is_short_children(&body.roots).is_some() {
|
||||||
buf.write_ident(&body.roots[0]).unwrap();
|
// write all the indents with spaces and commas between
|
||||||
|
for idx in 0..body.roots.len() - 1 {
|
||||||
|
let ident = &body.roots[idx];
|
||||||
|
buf.write_ident(ident).unwrap();
|
||||||
|
write!(&mut buf.out.buf, ", ").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the last ident without a comma
|
||||||
|
let ident = &body.roots[body.roots.len() - 1];
|
||||||
|
buf.write_ident(ident).unwrap();
|
||||||
} else {
|
} else {
|
||||||
buf.write_body_indented(&body.roots).unwrap();
|
buf.write_body_indented(&body.roots).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.consume()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
|
pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
|
||||||
let body = syn::parse2::<CallBody>(expr.mac.tokens).unwrap();
|
let body = syn::parse2::<CallBody>(expr.mac.tokens).unwrap();
|
||||||
|
|
||||||
let mut buf = Writer {
|
let mut buf = Writer::new(raw);
|
||||||
src: raw.lines().collect(),
|
|
||||||
..Writer::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Oneliner optimization
|
write_body(&mut buf, &body);
|
||||||
if buf.is_short_children(&body.roots).is_some() {
|
|
||||||
buf.write_ident(&body.roots[0]).unwrap();
|
|
||||||
} else {
|
|
||||||
buf.write_body_indented(&body.roots).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.consume()
|
buf.consume()
|
||||||
}
|
}
|
||||||
@@ -162,19 +161,11 @@ pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
|
|||||||
pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
|
pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
|
||||||
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
|
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
|
||||||
|
|
||||||
let mut buf = Writer {
|
let mut buf = Writer::new(block);
|
||||||
src: block.lines().collect(),
|
|
||||||
..Writer::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
buf.out.indent = indent_level;
|
buf.out.indent = indent_level;
|
||||||
|
|
||||||
// Oneliner optimization
|
write_body(&mut buf, &body);
|
||||||
if buf.is_short_children(&body.roots).is_some() {
|
|
||||||
buf.write_ident(&body.roots[0]).unwrap();
|
|
||||||
} else {
|
|
||||||
buf.write_body_indented(&body.roots).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// writing idents leaves the final line ended at the end of the last ident
|
// writing idents leaves the final line ended at the end of the last ident
|
||||||
if buf.out.buf.contains('\n') {
|
if buf.out.buf.contains('\n') {
|
||||||
@@ -191,8 +182,6 @@ pub fn apply_format(input: &str, block: FormattedBlock) -> String {
|
|||||||
let (left, _) = input.split_at(start);
|
let (left, _) = input.split_at(start);
|
||||||
let (_, right) = input.split_at(end);
|
let (_, right) = input.split_at(end);
|
||||||
|
|
||||||
// dbg!(&block.formatted);
|
|
||||||
|
|
||||||
format!("{}{}{}", left, block.formatted, right)
|
format!("{}{}{}", left, block.formatted, right)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ use syn::{spanned::Spanned, Expr, ExprIf};
|
|||||||
|
|
||||||
use crate::buffer::Buffer;
|
use crate::buffer::Buffer;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct Writer<'a> {
|
pub struct Writer<'a> {
|
||||||
|
pub raw_src: &'a str,
|
||||||
pub src: Vec<&'a str>,
|
pub src: Vec<&'a str>,
|
||||||
pub cached_formats: HashMap<Location, String>,
|
pub cached_formats: HashMap<Location, String>,
|
||||||
pub comments: VecDeque<usize>,
|
pub comments: VecDeque<usize>,
|
||||||
@@ -31,7 +32,18 @@ impl Location {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Writer<'_> {
|
impl<'a> Writer<'a> {
|
||||||
|
pub fn new(raw_src: &'a str) -> Self {
|
||||||
|
let src = raw_src.lines().collect();
|
||||||
|
Self {
|
||||||
|
raw_src,
|
||||||
|
src,
|
||||||
|
cached_formats: HashMap::new(),
|
||||||
|
comments: VecDeque::new(),
|
||||||
|
out: Buffer::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Expects to be written directly into place
|
// Expects to be written directly into place
|
||||||
pub fn write_ident(&mut self, node: &BodyNode) -> Result {
|
pub fn write_ident(&mut self, node: &BodyNode) -> Result {
|
||||||
match node {
|
match node {
|
||||||
|
|||||||
@@ -37,5 +37,8 @@ twoway![
|
|||||||
long_exprs,
|
long_exprs,
|
||||||
ifchain_forloop,
|
ifchain_forloop,
|
||||||
t2,
|
t2,
|
||||||
reallylong
|
reallylong,
|
||||||
|
immediate_expr,
|
||||||
|
collapse_expr,
|
||||||
|
trailing_expr
|
||||||
];
|
];
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fn itworks() {
|
||||||
|
rsx!( "{name}", "{name}", "{name}" )
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fn it_works() {
|
||||||
|
cx.render(rsx!(()))
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fn it_works() {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
span { "Description: ", package.description.as_deref().unwrap_or("❌❌❌❌ missing") }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -158,7 +158,7 @@ impl ToTokens for InlinePropsBody {
|
|||||||
#maybe_async #vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics>) #output
|
#maybe_async #vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics>) #output
|
||||||
#where_clause
|
#where_clause
|
||||||
{
|
{
|
||||||
let #struct_name { #(#field_names),* } = &cx.props;
|
let #struct_name { #(#field_names),* } = &#cx_token.props;
|
||||||
#block
|
#block
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+13
-29
@@ -88,7 +88,9 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Drop a scope and all its children
|
// Drop a scope and all its children
|
||||||
pub(crate) fn drop_scope(&mut self, id: ScopeId) {
|
//
|
||||||
|
// Note: This will not remove any ids from the arena
|
||||||
|
pub(crate) fn drop_scope(&mut self, id: ScopeId, recursive: bool) {
|
||||||
self.dirty_scopes.remove(&DirtyScope {
|
self.dirty_scopes.remove(&DirtyScope {
|
||||||
height: self.scopes[id.0].height,
|
height: self.scopes[id.0].height,
|
||||||
id,
|
id,
|
||||||
@@ -96,18 +98,13 @@ impl VirtualDom {
|
|||||||
|
|
||||||
self.ensure_drop_safety(id);
|
self.ensure_drop_safety(id);
|
||||||
|
|
||||||
if let Some(root) = self.scopes[id.0].as_ref().try_root_node() {
|
if recursive {
|
||||||
if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } {
|
if let Some(root) = self.scopes[id.0].as_ref().try_root_node() {
|
||||||
self.drop_scope_inner(node)
|
if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } {
|
||||||
|
self.drop_scope_inner(node)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(root) = unsafe { self.scopes[id.0].as_ref().previous_frame().try_load_node() } {
|
|
||||||
if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } {
|
|
||||||
self.drop_scope_inner(node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scopes[id.0].props.take();
|
|
||||||
|
|
||||||
let scope = &mut self.scopes[id.0];
|
let scope = &mut self.scopes[id.0];
|
||||||
|
|
||||||
@@ -121,37 +118,24 @@ impl VirtualDom {
|
|||||||
for task_id in scope.spawned_tasks.borrow_mut().drain() {
|
for task_id in scope.spawned_tasks.borrow_mut().drain() {
|
||||||
scope.tasks.remove(task_id);
|
scope.tasks.remove(task_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.scopes.remove(id.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop_scope_inner(&mut self, node: &VNode) {
|
fn drop_scope_inner(&mut self, node: &VNode) {
|
||||||
node.clear_listeners();
|
|
||||||
node.dynamic_nodes.iter().for_each(|node| match node {
|
node.dynamic_nodes.iter().for_each(|node| match node {
|
||||||
DynamicNode::Component(c) => {
|
DynamicNode::Component(c) => {
|
||||||
if let Some(f) = c.scope.get() {
|
if let Some(f) = c.scope.get() {
|
||||||
self.drop_scope(f);
|
self.drop_scope(f, true);
|
||||||
}
|
}
|
||||||
c.props.take();
|
c.props.take();
|
||||||
}
|
}
|
||||||
DynamicNode::Fragment(nodes) => {
|
DynamicNode::Fragment(nodes) => {
|
||||||
nodes.iter().for_each(|node| self.drop_scope_inner(node))
|
nodes.iter().for_each(|node| self.drop_scope_inner(node))
|
||||||
}
|
}
|
||||||
DynamicNode::Placeholder(t) => {
|
DynamicNode::Placeholder(_) => {}
|
||||||
if let Some(id) = t.id.get() {
|
DynamicNode::Text(_) => {}
|
||||||
self.try_reclaim(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DynamicNode::Text(t) => {
|
|
||||||
if let Some(id) = t.id.get() {
|
|
||||||
self.try_reclaim(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for id in &node.root_ids {
|
|
||||||
if id.0 != 0 {
|
|
||||||
self.try_reclaim(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Descend through the tree, removing any borrowed props and listeners
|
/// Descend through the tree, removing any borrowed props and listeners
|
||||||
|
|||||||
@@ -934,7 +934,7 @@ impl<'b> VirtualDom {
|
|||||||
*comp.props.borrow_mut() = unsafe { std::mem::transmute(props) };
|
*comp.props.borrow_mut() = unsafe { std::mem::transmute(props) };
|
||||||
|
|
||||||
// Now drop all the resouces
|
// Now drop all the resouces
|
||||||
self.drop_scope(scope);
|
self.drop_scope(scope, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {
|
fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {
|
||||||
|
|||||||
@@ -195,14 +195,6 @@ impl<'a> VNode<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn clear_listeners(&self) {
|
|
||||||
for attr in self.dynamic_attrs {
|
|
||||||
if let AttributeValue::Listener(l) = &attr.value {
|
|
||||||
l.borrow_mut().take();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A static layout of a UI tree that describes a set of dynamic and static nodes.
|
/// A static layout of a UI tree that describes a set of dynamic and static nodes.
|
||||||
|
|||||||
@@ -682,6 +682,6 @@ impl VirtualDom {
|
|||||||
impl Drop for VirtualDom {
|
impl Drop for VirtualDom {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// Simply drop this scope which drops all of its children
|
// Simply drop this scope which drops all of its children
|
||||||
self.drop_scope(ScopeId(0));
|
self.drop_scope(ScopeId(0), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,7 @@ tokio = { version = "1.16.1", features = [
|
|||||||
webbrowser = "0.8.0"
|
webbrowser = "0.8.0"
|
||||||
infer = "0.11.0"
|
infer = "0.11.0"
|
||||||
dunce = "1.0.2"
|
dunce = "1.0.2"
|
||||||
|
slab = "0.4"
|
||||||
|
|
||||||
interprocess = { version = "1.1.1", optional = true }
|
interprocess = { version = "1.1.1", optional = true }
|
||||||
futures-util = "0.3.25"
|
futures-util = "0.3.25"
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ pub struct Config {
|
|||||||
pub(crate) file_drop_handler: Option<DropHandler>,
|
pub(crate) file_drop_handler: Option<DropHandler>,
|
||||||
pub(crate) protocols: Vec<WryProtocol>,
|
pub(crate) protocols: Vec<WryProtocol>,
|
||||||
pub(crate) pre_rendered: Option<String>,
|
pub(crate) pre_rendered: Option<String>,
|
||||||
// pub(crate) event_handler: Option<Box<DynEventHandlerFn>>,
|
|
||||||
pub(crate) disable_context_menu: bool,
|
pub(crate) disable_context_menu: bool,
|
||||||
pub(crate) resource_dir: Option<PathBuf>,
|
pub(crate) resource_dir: Option<PathBuf>,
|
||||||
pub(crate) custom_head: Option<String>,
|
pub(crate) custom_head: Option<String>,
|
||||||
@@ -77,15 +76,6 @@ impl Config {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// Set a custom event handler
|
|
||||||
// pub fn with_event_handler(
|
|
||||||
// mut self,
|
|
||||||
// handler: impl Fn(&mut EventLoop<()>, &mut WebView) + 'static,
|
|
||||||
// ) -> Self {
|
|
||||||
// self.event_handler = Some(Box::new(handler));
|
|
||||||
// self
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Set a file drop handler
|
/// Set a file drop handler
|
||||||
pub fn with_file_drop_handler(
|
pub fn with_file_drop_handler(
|
||||||
mut self,
|
mut self,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ use crate::WebviewHandler;
|
|||||||
use dioxus_core::ScopeState;
|
use dioxus_core::ScopeState;
|
||||||
use dioxus_core::VirtualDom;
|
use dioxus_core::VirtualDom;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use slab::Slab;
|
||||||
|
use wry::application::event::Event;
|
||||||
use wry::application::event_loop::EventLoopProxy;
|
use wry::application::event_loop::EventLoopProxy;
|
||||||
use wry::application::event_loop::EventLoopWindowTarget;
|
use wry::application::event_loop::EventLoopWindowTarget;
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
@@ -57,6 +59,8 @@ pub struct DesktopContext {
|
|||||||
|
|
||||||
pub(crate) event_loop: EventLoopWindowTarget<UserWindowEvent>,
|
pub(crate) event_loop: EventLoopWindowTarget<UserWindowEvent>,
|
||||||
|
|
||||||
|
pub(crate) event_handlers: WindowEventHandlers,
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
|
pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
|
||||||
}
|
}
|
||||||
@@ -76,6 +80,7 @@ impl DesktopContext {
|
|||||||
proxy: ProxyType,
|
proxy: ProxyType,
|
||||||
event_loop: EventLoopWindowTarget<UserWindowEvent>,
|
event_loop: EventLoopWindowTarget<UserWindowEvent>,
|
||||||
webviews: WebviewQueue,
|
webviews: WebviewQueue,
|
||||||
|
event_handlers: WindowEventHandlers,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
webview,
|
webview,
|
||||||
@@ -83,6 +88,7 @@ impl DesktopContext {
|
|||||||
event_loop,
|
event_loop,
|
||||||
eval: tokio::sync::broadcast::channel(8).0,
|
eval: tokio::sync::broadcast::channel(8).0,
|
||||||
pending_windows: webviews,
|
pending_windows: webviews,
|
||||||
|
event_handlers,
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
views: Default::default(),
|
views: Default::default(),
|
||||||
}
|
}
|
||||||
@@ -102,6 +108,7 @@ impl DesktopContext {
|
|||||||
&self.proxy,
|
&self.proxy,
|
||||||
dom,
|
dom,
|
||||||
&self.pending_windows,
|
&self.pending_windows,
|
||||||
|
&self.event_handlers,
|
||||||
);
|
);
|
||||||
|
|
||||||
let id = window.webview.window().id();
|
let id = window.webview.window().id();
|
||||||
@@ -216,6 +223,22 @@ impl DesktopContext {
|
|||||||
EvalResult::new(self.eval.clone())
|
EvalResult::new(self.eval.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a wry event handler that listens for wry events.
|
||||||
|
/// This event handler is scoped to the currently active window and will only recieve events that are either global or related to the current window.
|
||||||
|
///
|
||||||
|
/// The id this function returns can be used to remove the event handler with [`DesktopContext::remove_wry_event_handler`]
|
||||||
|
pub fn create_wry_event_handler(
|
||||||
|
&self,
|
||||||
|
handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
|
||||||
|
) -> WryEventHandlerId {
|
||||||
|
self.event_handlers.add(self.id(), handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a wry event handler created with [`DesktopContext::create_wry_event_handler`]
|
||||||
|
pub fn remove_wry_event_handler(&self, id: WryEventHandlerId) {
|
||||||
|
self.event_handlers.remove(id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Push an objc view to the window
|
/// Push an objc view to the window
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
|
pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
|
||||||
@@ -276,3 +299,114 @@ fn is_main_thread() -> bool {
|
|||||||
let result: BOOL = unsafe { msg_send![cls, isMainThread] };
|
let result: BOOL = unsafe { msg_send![cls, isMainThread] };
|
||||||
result != NO
|
result != NO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The unique identifier of a window event handler. This can be used to later remove the handler.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct WryEventHandlerId(usize);
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub(crate) struct WindowEventHandlers {
|
||||||
|
handlers: Rc<RefCell<Slab<WryWindowEventHandlerInner>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowEventHandlers {
|
||||||
|
pub(crate) fn add(
|
||||||
|
&self,
|
||||||
|
window_id: WindowId,
|
||||||
|
handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
|
||||||
|
) -> WryEventHandlerId {
|
||||||
|
WryEventHandlerId(
|
||||||
|
self.handlers
|
||||||
|
.borrow_mut()
|
||||||
|
.insert(WryWindowEventHandlerInner {
|
||||||
|
window_id,
|
||||||
|
handler: Box::new(handler),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn remove(&self, id: WryEventHandlerId) {
|
||||||
|
self.handlers.borrow_mut().try_remove(id.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn apply_event(
|
||||||
|
&self,
|
||||||
|
event: &Event<UserWindowEvent>,
|
||||||
|
target: &EventLoopWindowTarget<UserWindowEvent>,
|
||||||
|
) {
|
||||||
|
for (_, handler) in self.handlers.borrow_mut().iter_mut() {
|
||||||
|
handler.apply_event(event, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WryWindowEventHandlerInner {
|
||||||
|
window_id: WindowId,
|
||||||
|
handler: WryEventHandlerCallback,
|
||||||
|
}
|
||||||
|
|
||||||
|
type WryEventHandlerCallback =
|
||||||
|
Box<dyn FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static>;
|
||||||
|
|
||||||
|
impl WryWindowEventHandlerInner {
|
||||||
|
fn apply_event(
|
||||||
|
&mut self,
|
||||||
|
event: &Event<UserWindowEvent>,
|
||||||
|
target: &EventLoopWindowTarget<UserWindowEvent>,
|
||||||
|
) {
|
||||||
|
// if this event does not apply to the window this listener cares about, return
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent { window_id, .. }
|
||||||
|
| Event::MenuEvent {
|
||||||
|
window_id: Some(window_id),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if *window_id != self.window_id {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
(self.handler)(event, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a closure that executes any JavaScript in the WebView context.
|
||||||
|
pub fn use_wry_event_handler(
|
||||||
|
cx: &ScopeState,
|
||||||
|
handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
|
||||||
|
) -> &WryEventHandler {
|
||||||
|
let desktop = use_window(cx);
|
||||||
|
cx.use_hook(move || {
|
||||||
|
let desktop = desktop.clone();
|
||||||
|
|
||||||
|
let id = desktop.create_wry_event_handler(handler);
|
||||||
|
|
||||||
|
WryEventHandler {
|
||||||
|
handlers: desktop.event_handlers,
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A wry event handler that is scoped to the current component and window. The event handler will only receive events for the window it was created for and global events.
|
||||||
|
///
|
||||||
|
/// This will automatically be removed when the component is unmounted.
|
||||||
|
pub struct WryEventHandler {
|
||||||
|
handlers: WindowEventHandlers,
|
||||||
|
/// The unique identifier of the event handler.
|
||||||
|
pub id: WryEventHandlerId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WryEventHandler {
|
||||||
|
/// Remove the event handler.
|
||||||
|
pub fn remove(&self) {
|
||||||
|
self.handlers.remove(self.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for WryEventHandler {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.handlers.remove(self.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,10 @@ mod webview;
|
|||||||
mod hot_reload;
|
mod hot_reload;
|
||||||
|
|
||||||
pub use cfg::Config;
|
pub use cfg::Config;
|
||||||
pub use desktop_context::{use_window, DesktopContext};
|
pub use desktop_context::{
|
||||||
use desktop_context::{EventData, UserWindowEvent, WebviewQueue};
|
use_window, use_wry_event_handler, DesktopContext, WryEventHandler, WryEventHandlerId,
|
||||||
|
};
|
||||||
|
use desktop_context::{EventData, UserWindowEvent, WebviewQueue, WindowEventHandlers};
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
use dioxus_html::HtmlEvent;
|
use dioxus_html::HtmlEvent;
|
||||||
pub use eval::{use_eval, EvalResult};
|
pub use eval::{use_eval, EvalResult};
|
||||||
@@ -123,6 +125,9 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
|
|||||||
// Store them in a hashmap so we can remove them when they're closed
|
// Store them in a hashmap so we can remove them when they're closed
|
||||||
let mut webviews = HashMap::<WindowId, WebviewHandler>::new();
|
let mut webviews = HashMap::<WindowId, WebviewHandler>::new();
|
||||||
|
|
||||||
|
// We use this to allow dynamically adding and removing window event handlers
|
||||||
|
let event_handlers = WindowEventHandlers::default();
|
||||||
|
|
||||||
let queue = WebviewQueue::default();
|
let queue = WebviewQueue::default();
|
||||||
|
|
||||||
// By default, we'll create a new window when the app starts
|
// By default, we'll create a new window when the app starts
|
||||||
@@ -132,11 +137,14 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
|
|||||||
&proxy,
|
&proxy,
|
||||||
VirtualDom::new_with_props(root, props),
|
VirtualDom::new_with_props(root, props),
|
||||||
&queue,
|
&queue,
|
||||||
|
&event_handlers,
|
||||||
));
|
));
|
||||||
|
|
||||||
event_loop.run(move |window_event, _event_loop, control_flow| {
|
event_loop.run(move |window_event, event_loop, control_flow| {
|
||||||
*control_flow = ControlFlow::Wait;
|
*control_flow = ControlFlow::Wait;
|
||||||
|
|
||||||
|
event_handlers.apply_event(&window_event, event_loop);
|
||||||
|
|
||||||
match window_event {
|
match window_event {
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
event, window_id, ..
|
event, window_id, ..
|
||||||
@@ -240,6 +248,7 @@ fn create_new_window(
|
|||||||
proxy: &EventLoopProxy<UserWindowEvent>,
|
proxy: &EventLoopProxy<UserWindowEvent>,
|
||||||
dom: VirtualDom,
|
dom: VirtualDom,
|
||||||
queue: &WebviewQueue,
|
queue: &WebviewQueue,
|
||||||
|
event_handlers: &WindowEventHandlers,
|
||||||
) -> WebviewHandler {
|
) -> WebviewHandler {
|
||||||
let webview = webview::build(&mut cfg, event_loop, proxy.clone());
|
let webview = webview::build(&mut cfg, event_loop, proxy.clone());
|
||||||
|
|
||||||
@@ -248,6 +257,7 @@ fn create_new_window(
|
|||||||
proxy.clone(),
|
proxy.clone(),
|
||||||
event_loop.clone(),
|
event_loop.clone(),
|
||||||
queue.clone(),
|
queue.clone(),
|
||||||
|
event_handlers.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let id = webview.window().id();
|
let id = webview.window().id();
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
- [ ] pub aspect_ratio: Number, ----> parsing is done, but taffy doesnt support it
|
- [ ] pub aspect_ratio: Number, ----> parsing is done, but taffy doesnt support it
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use lightningcss::properties::border::LineStyle;
|
||||||
use lightningcss::properties::{align, display, flex, position, size};
|
use lightningcss::properties::{align, display, flex, position, size};
|
||||||
use lightningcss::{
|
use lightningcss::{
|
||||||
properties::{align::GapValue, border::BorderSideWidth, Property, PropertyId},
|
properties::{align::GapValue, border::BorderSideWidth, Property, PropertyId},
|
||||||
@@ -45,8 +46,43 @@ use taffy::{
|
|||||||
style::{FlexDirection, PositionType},
|
style::{FlexDirection, PositionType},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct LayoutConfigeration {
|
||||||
|
/// the default border widths to use
|
||||||
|
pub border_widths: BorderWidths,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BorderWidths {
|
||||||
|
/// the default border width to use for thin borders
|
||||||
|
pub thin: f32,
|
||||||
|
/// the default border width to use for medium borders
|
||||||
|
pub medium: f32,
|
||||||
|
/// the default border width to use for thick borders
|
||||||
|
pub thick: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BorderWidths {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
thin: 1.0,
|
||||||
|
medium: 3.0,
|
||||||
|
thick: 5.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// applies the entire html namespace defined in dioxus-html
|
/// applies the entire html namespace defined in dioxus-html
|
||||||
pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) {
|
pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) {
|
||||||
|
apply_layout_attributes_cfg(name, value, style, &LayoutConfigeration::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// applies the entire html namespace defined in dioxus-html with the specified configeration
|
||||||
|
pub fn apply_layout_attributes_cfg(
|
||||||
|
name: &str,
|
||||||
|
value: &str,
|
||||||
|
style: &mut Style,
|
||||||
|
config: &LayoutConfigeration,
|
||||||
|
) {
|
||||||
if let Ok(property) =
|
if let Ok(property) =
|
||||||
Property::parse_string(PropertyId::from(name), value, ParserOptions::default())
|
Property::parse_string(PropertyId::from(name), value, ParserOptions::default())
|
||||||
{
|
{
|
||||||
@@ -84,41 +120,85 @@ pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) {
|
|||||||
style.position.right = convert_length_percentage_or_auto(inset.right);
|
style.position.right = convert_length_percentage_or_auto(inset.right);
|
||||||
}
|
}
|
||||||
Property::BorderTopWidth(width) => {
|
Property::BorderTopWidth(width) => {
|
||||||
style.border.top = convert_border_side_width(width);
|
style.border.top = convert_border_side_width(width, &config.border_widths);
|
||||||
}
|
}
|
||||||
Property::BorderBottomWidth(width) => {
|
Property::BorderBottomWidth(width) => {
|
||||||
style.border.bottom = convert_border_side_width(width);
|
style.border.bottom = convert_border_side_width(width, &config.border_widths);
|
||||||
}
|
}
|
||||||
Property::BorderLeftWidth(width) => {
|
Property::BorderLeftWidth(width) => {
|
||||||
style.border.left = convert_border_side_width(width);
|
style.border.left = convert_border_side_width(width, &config.border_widths);
|
||||||
}
|
}
|
||||||
Property::BorderRightWidth(width) => {
|
Property::BorderRightWidth(width) => {
|
||||||
style.border.right = convert_border_side_width(width);
|
style.border.right = convert_border_side_width(width, &config.border_widths);
|
||||||
}
|
}
|
||||||
Property::BorderWidth(width) => {
|
Property::BorderWidth(width) => {
|
||||||
style.border.top = convert_border_side_width(width.top);
|
style.border.top = convert_border_side_width(width.top, &config.border_widths);
|
||||||
style.border.bottom = convert_border_side_width(width.bottom);
|
style.border.bottom =
|
||||||
style.border.left = convert_border_side_width(width.left);
|
convert_border_side_width(width.bottom, &config.border_widths);
|
||||||
style.border.right = convert_border_side_width(width.right);
|
style.border.left = convert_border_side_width(width.left, &config.border_widths);
|
||||||
|
style.border.right = convert_border_side_width(width.right, &config.border_widths);
|
||||||
}
|
}
|
||||||
Property::Border(border) => {
|
Property::Border(border) => {
|
||||||
let width = convert_border_side_width(border.width);
|
let width = convert_border_side_width(border.width, &config.border_widths);
|
||||||
style.border.top = width;
|
style.border.top = width;
|
||||||
style.border.bottom = width;
|
style.border.bottom = width;
|
||||||
style.border.left = width;
|
style.border.left = width;
|
||||||
style.border.right = width;
|
style.border.right = width;
|
||||||
}
|
}
|
||||||
Property::BorderTop(top) => {
|
Property::BorderTop(top) => {
|
||||||
style.border.top = convert_border_side_width(top.width);
|
style.border.top = convert_border_side_width(top.width, &config.border_widths);
|
||||||
}
|
}
|
||||||
Property::BorderBottom(bottom) => {
|
Property::BorderBottom(bottom) => {
|
||||||
style.border.bottom = convert_border_side_width(bottom.width);
|
style.border.bottom =
|
||||||
|
convert_border_side_width(bottom.width, &config.border_widths);
|
||||||
}
|
}
|
||||||
Property::BorderLeft(left) => {
|
Property::BorderLeft(left) => {
|
||||||
style.border.left = convert_border_side_width(left.width);
|
style.border.left = convert_border_side_width(left.width, &config.border_widths);
|
||||||
}
|
}
|
||||||
Property::BorderRight(right) => {
|
Property::BorderRight(right) => {
|
||||||
style.border.right = convert_border_side_width(right.width);
|
style.border.right = convert_border_side_width(right.width, &config.border_widths);
|
||||||
|
}
|
||||||
|
Property::BorderTopStyle(line_style) => {
|
||||||
|
if line_style != LineStyle::None {
|
||||||
|
style.border.top =
|
||||||
|
convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Property::BorderBottomStyle(line_style) => {
|
||||||
|
if line_style != LineStyle::None {
|
||||||
|
style.border.bottom =
|
||||||
|
convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Property::BorderLeftStyle(line_style) => {
|
||||||
|
if line_style != LineStyle::None {
|
||||||
|
style.border.left =
|
||||||
|
convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Property::BorderRightStyle(line_style) => {
|
||||||
|
if line_style != LineStyle::None {
|
||||||
|
style.border.right =
|
||||||
|
convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Property::BorderStyle(styles) => {
|
||||||
|
if styles.top != LineStyle::None {
|
||||||
|
style.border.top =
|
||||||
|
convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
|
||||||
|
}
|
||||||
|
if styles.bottom != LineStyle::None {
|
||||||
|
style.border.bottom =
|
||||||
|
convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
|
||||||
|
}
|
||||||
|
if styles.left != LineStyle::None {
|
||||||
|
style.border.left =
|
||||||
|
convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
|
||||||
|
}
|
||||||
|
if styles.right != LineStyle::None {
|
||||||
|
style.border.right =
|
||||||
|
convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Property::FlexDirection(flex_direction, _) => {
|
Property::FlexDirection(flex_direction, _) => {
|
||||||
use FlexDirection::*;
|
use FlexDirection::*;
|
||||||
@@ -330,12 +410,15 @@ fn convert_length_percentage_or_auto(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_border_side_width(border_side_width: BorderSideWidth) -> Dimension {
|
fn convert_border_side_width(
|
||||||
|
border_side_width: BorderSideWidth,
|
||||||
|
border_width_config: &BorderWidths,
|
||||||
|
) -> Dimension {
|
||||||
match border_side_width {
|
match border_side_width {
|
||||||
BorderSideWidth::Length(Length::Value(value)) => convert_length_value(value),
|
BorderSideWidth::Length(Length::Value(value)) => convert_length_value(value),
|
||||||
BorderSideWidth::Thick => Dimension::Points(5.0),
|
BorderSideWidth::Thick => Dimension::Points(border_width_config.thick),
|
||||||
BorderSideWidth::Medium => Dimension::Points(3.0),
|
BorderSideWidth::Medium => Dimension::Points(border_width_config.medium),
|
||||||
BorderSideWidth::Thin => Dimension::Points(1.0),
|
BorderSideWidth::Thin => Dimension::Points(border_width_config.thin),
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +1,160 @@
|
|||||||
use std::cmp::Ordering;
|
use std::{cmp::Ordering, ops::Range};
|
||||||
|
|
||||||
use dioxus_html::input_data::keyboard_types::{Code, Key, Modifiers};
|
use dioxus_html::input_data::keyboard_types::{Code, Key, Modifiers};
|
||||||
|
|
||||||
|
/// This contains the information about the text that is used by the cursor to handle navigation.
|
||||||
|
pub trait Text {
|
||||||
|
/// Returns the line at the given index.
|
||||||
|
fn line(&self, number: usize) -> Option<&Self>;
|
||||||
|
/// Returns the length of the text in characters.
|
||||||
|
fn length(&self) -> usize;
|
||||||
|
/// Returns the number of lines in the text.
|
||||||
|
fn line_count(&self) -> usize;
|
||||||
|
/// Returns the character at the given character index.
|
||||||
|
fn character(&self, idx: usize) -> Option<char>;
|
||||||
|
/// Returns the length of the text before the given line in characters.
|
||||||
|
fn len_before_line(&self, line: usize) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Text for str {
|
||||||
|
fn line(&self, number: usize) -> Option<&str> {
|
||||||
|
self.lines().nth(number)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn length(&self) -> usize {
|
||||||
|
self.chars().count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_count(&self) -> usize {
|
||||||
|
self.lines().count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn character(&self, idx: usize) -> Option<char> {
|
||||||
|
self.chars().nth(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len_before_line(&self, line: usize) -> usize {
|
||||||
|
self.lines()
|
||||||
|
.take(line)
|
||||||
|
.map(|l| l.chars().count())
|
||||||
|
.sum::<usize>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This contains the information about the text that is used by the cursor to handle editing text.
|
||||||
|
pub trait TextEditable<T: Text + ?Sized>: AsRef<T> {
|
||||||
|
/// Inserts a character at the given character index.
|
||||||
|
fn insert_character(&mut self, idx: usize, text: char);
|
||||||
|
/// Deletes the given character range.
|
||||||
|
fn delete_range(&mut self, range: Range<usize>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextEditable<str> for String {
|
||||||
|
fn insert_character(&mut self, idx: usize, text: char) {
|
||||||
|
self.insert(idx, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_range(&mut self, range: Range<usize>) {
|
||||||
|
self.replace_range(range, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A cursor position
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Pos {
|
pub struct Pos {
|
||||||
|
/// The virtual column of the cursor. This can be more than the line length. To get the realized column, use [`Pos::col()`].
|
||||||
pub col: usize,
|
pub col: usize,
|
||||||
|
/// The row of the cursor.
|
||||||
pub row: usize,
|
pub row: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pos {
|
impl Pos {
|
||||||
|
/// Creates a new cursor position.
|
||||||
pub fn new(col: usize, row: usize) -> Self {
|
pub fn new(col: usize, row: usize) -> Self {
|
||||||
Self { row, col }
|
Self { row, col }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn up(&mut self, rope: &str) {
|
/// Moves the position up by one line.
|
||||||
self.move_row(-1, rope);
|
pub fn up(&mut self, text: &(impl Text + ?Sized)) {
|
||||||
|
self.move_row(-1, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn down(&mut self, rope: &str) {
|
/// Moves the position down by one line.
|
||||||
self.move_row(1, rope);
|
pub fn down(&mut self, text: &(impl Text + ?Sized)) {
|
||||||
|
self.move_row(1, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn right(&mut self, rope: &str) {
|
/// Moves the position right by one character.
|
||||||
self.move_col(1, rope);
|
pub fn right(&mut self, text: &(impl Text + ?Sized)) {
|
||||||
|
self.move_col(1, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn left(&mut self, rope: &str) {
|
/// Moves the position left by one character.
|
||||||
self.move_col(-1, rope);
|
pub fn left(&mut self, text: &(impl Text + ?Sized)) {
|
||||||
|
self.move_col(-1, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_row(&mut self, change: i32, rope: &str) {
|
/// Move the position's row by the given amount. (positive is down, negative is up)
|
||||||
|
pub fn move_row(&mut self, change: i32, text: &(impl Text + ?Sized)) {
|
||||||
let new = self.row as i32 + change;
|
let new = self.row as i32 + change;
|
||||||
if new >= 0 && new < rope.lines().count() as i32 {
|
if new >= 0 && new < text.line_count() as i32 {
|
||||||
self.row = new as usize;
|
self.row = new as usize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_col(&mut self, change: i32, rope: &str) {
|
/// Move the position's column by the given amount. (positive is right, negative is left)
|
||||||
self.realize_col(rope);
|
pub fn move_col(&mut self, change: i32, text: &(impl Text + ?Sized)) {
|
||||||
let idx = self.idx(rope) as i32;
|
self.realize_col(text);
|
||||||
if idx + change >= 0 && idx + change <= rope.len() as i32 {
|
let idx = self.idx(text) as i32;
|
||||||
let len_line = self.len_line(rope) as i32;
|
if idx + change >= 0 && idx + change <= text.length() as i32 {
|
||||||
|
let len_line = self.len_line(text) as i32;
|
||||||
let new_col = self.col as i32 + change;
|
let new_col = self.col as i32 + change;
|
||||||
let diff = new_col - len_line;
|
let diff = new_col - len_line;
|
||||||
if diff > 0 {
|
if diff > 0 {
|
||||||
self.down(rope);
|
self.down(text);
|
||||||
self.col = 0;
|
self.col = 0;
|
||||||
self.move_col(diff - 1, rope);
|
self.move_col(diff - 1, text);
|
||||||
} else if new_col < 0 {
|
} else if new_col < 0 {
|
||||||
self.up(rope);
|
self.up(text);
|
||||||
self.col = self.len_line(rope);
|
self.col = self.len_line(text);
|
||||||
self.move_col(new_col + 1, rope);
|
self.move_col(new_col + 1, text);
|
||||||
} else {
|
} else {
|
||||||
self.col = new_col as usize;
|
self.col = new_col as usize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn col(&self, rope: &str) -> usize {
|
/// Get the realized column of the position. This is the column, but capped at the line length.
|
||||||
self.col.min(self.len_line(rope))
|
pub fn col(&self, text: &(impl Text + ?Sized)) -> usize {
|
||||||
|
self.col.min(self.len_line(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the row of the position.
|
||||||
pub fn row(&self) -> usize {
|
pub fn row(&self) -> usize {
|
||||||
self.row
|
self.row
|
||||||
}
|
}
|
||||||
|
|
||||||
fn len_line(&self, rope: &str) -> usize {
|
fn len_line(&self, text: &(impl Text + ?Sized)) -> usize {
|
||||||
let line = rope.lines().nth(self.row).unwrap_or_default();
|
if let Some(line) = text.line(self.row) {
|
||||||
let len = line.len();
|
let len = line.length();
|
||||||
if len > 0 && line.chars().nth(len - 1) == Some('\n') {
|
if len > 0 && line.character(len - 1) == Some('\n') {
|
||||||
len - 1
|
len - 1
|
||||||
|
} else {
|
||||||
|
len
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
len
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn idx(&self, rope: &str) -> usize {
|
/// Get the character index of the position.
|
||||||
rope.lines().take(self.row).map(|l| l.len()).sum::<usize>() + self.col(rope)
|
pub fn idx(&self, text: &(impl Text + ?Sized)) -> usize {
|
||||||
|
text.len_before_line(self.row) + self.col(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the column can be more than the line length, cap it
|
/// If the column is more than the line length, cap it to the line length.
|
||||||
pub fn realize_col(&mut self, rope: &str) {
|
pub fn realize_col(&mut self, text: &(impl Text + ?Sized)) {
|
||||||
self.col = self.col(rope);
|
self.col = self.col(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,13 +170,17 @@ impl PartialOrd for Pos {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A cursor is a selection of text. It has a start and end position of the selection.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Cursor {
|
pub struct Cursor {
|
||||||
|
/// The start position of the selection. The start position is the origin of the selection, not necessarily the first position.
|
||||||
pub start: Pos,
|
pub start: Pos,
|
||||||
|
/// The end position of the selection. If the end position is None, the cursor is a caret.
|
||||||
pub end: Option<Pos>,
|
pub end: Option<Pos>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cursor {
|
impl Cursor {
|
||||||
|
/// Create a new cursor with the given start position.
|
||||||
pub fn from_start(pos: Pos) -> Self {
|
pub fn from_start(pos: Pos) -> Self {
|
||||||
Self {
|
Self {
|
||||||
start: pos,
|
start: pos,
|
||||||
@@ -111,6 +188,7 @@ impl Cursor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new cursor with the given start and end position.
|
||||||
pub fn new(start: Pos, end: Pos) -> Self {
|
pub fn new(start: Pos, end: Pos) -> Self {
|
||||||
Self {
|
Self {
|
||||||
start,
|
start,
|
||||||
@@ -118,7 +196,8 @@ impl Cursor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_cursor(&mut self, f: impl FnOnce(&mut Pos), shift: bool) {
|
/// Move the cursor position. If shift is true, the end position will be moved instead of the start position.
|
||||||
|
pub fn move_cursor(&mut self, f: impl FnOnce(&mut Pos), shift: bool) {
|
||||||
if shift {
|
if shift {
|
||||||
self.with_end(f);
|
self.with_end(f);
|
||||||
} else {
|
} else {
|
||||||
@@ -127,38 +206,36 @@ impl Cursor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_selection(&mut self, text: &mut String) -> [i32; 2] {
|
/// Delete the currently selected text and update the cursor position.
|
||||||
|
pub fn delete_selection<T: Text + ?Sized>(&mut self, text: &mut impl TextEditable<T>) {
|
||||||
let first = self.first();
|
let first = self.first();
|
||||||
let last = self.last();
|
let last = self.last();
|
||||||
let dr = first.row as i32 - last.row as i32;
|
text.delete_range(first.idx(text.as_ref())..last.idx(text.as_ref()));
|
||||||
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 let Some(end) = self.end.take() {
|
||||||
if self.start > end {
|
if self.start > end {
|
||||||
self.start = end;
|
self.start = end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[dc, dr]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_input(
|
/// Handle moving the cursor with the given key.
|
||||||
|
pub fn handle_input<T: Text + ?Sized>(
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &dioxus_html::KeyboardData,
|
data: &dioxus_html::KeyboardData,
|
||||||
text: &mut String,
|
text: &mut impl TextEditable<T>,
|
||||||
max_width: usize,
|
max_text_length: usize,
|
||||||
) {
|
) {
|
||||||
use Code::*;
|
use Code::*;
|
||||||
match data.code() {
|
match data.code() {
|
||||||
ArrowUp => {
|
ArrowUp => {
|
||||||
self.move_cursor(|c| c.up(text), data.modifiers().contains(Modifiers::SHIFT));
|
self.move_cursor(
|
||||||
|
|c| c.up(text.as_ref()),
|
||||||
|
data.modifiers().contains(Modifiers::SHIFT),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ArrowDown => {
|
ArrowDown => {
|
||||||
self.move_cursor(
|
self.move_cursor(
|
||||||
|c| c.down(text),
|
|c| c.down(text.as_ref()),
|
||||||
data.modifiers().contains(Modifiers::SHIFT),
|
data.modifiers().contains(Modifiers::SHIFT),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -167,22 +244,22 @@ impl Cursor {
|
|||||||
self.move_cursor(
|
self.move_cursor(
|
||||||
|c| {
|
|c| {
|
||||||
let mut change = 1;
|
let mut change = 1;
|
||||||
let idx = c.idx(text);
|
let idx = c.idx(text.as_ref());
|
||||||
let length = text.len();
|
let length = text.as_ref().length();
|
||||||
while idx + change < length {
|
while idx + change < length {
|
||||||
let chr = text.chars().nth(idx + change).unwrap();
|
let chr = text.as_ref().character(idx + change).unwrap();
|
||||||
if chr.is_whitespace() {
|
if chr.is_whitespace() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
change += 1;
|
change += 1;
|
||||||
}
|
}
|
||||||
c.move_col(change as i32, text);
|
c.move_col(change as i32, text.as_ref());
|
||||||
},
|
},
|
||||||
data.modifiers().contains(Modifiers::SHIFT),
|
data.modifiers().contains(Modifiers::SHIFT),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
self.move_cursor(
|
self.move_cursor(
|
||||||
|c| c.right(text),
|
|c| c.right(text.as_ref()),
|
||||||
data.modifiers().contains(Modifiers::SHIFT),
|
data.modifiers().contains(Modifiers::SHIFT),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -192,28 +269,28 @@ impl Cursor {
|
|||||||
self.move_cursor(
|
self.move_cursor(
|
||||||
|c| {
|
|c| {
|
||||||
let mut change = -1;
|
let mut change = -1;
|
||||||
let idx = c.idx(text) as i32;
|
let idx = c.idx(text.as_ref()) as i32;
|
||||||
while idx + change > 0 {
|
while idx + change > 0 {
|
||||||
let chr = text.chars().nth((idx + change) as usize).unwrap();
|
let chr = text.as_ref().character((idx + change) as usize).unwrap();
|
||||||
if chr == ' ' {
|
if chr == ' ' {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
change -= 1;
|
change -= 1;
|
||||||
}
|
}
|
||||||
c.move_col(change, text);
|
c.move_col(change, text.as_ref());
|
||||||
},
|
},
|
||||||
data.modifiers().contains(Modifiers::SHIFT),
|
data.modifiers().contains(Modifiers::SHIFT),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
self.move_cursor(
|
self.move_cursor(
|
||||||
|c| c.left(text),
|
|c| c.left(text.as_ref()),
|
||||||
data.modifiers().contains(Modifiers::SHIFT),
|
data.modifiers().contains(Modifiers::SHIFT),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
End => {
|
End => {
|
||||||
self.move_cursor(
|
self.move_cursor(
|
||||||
|c| c.col = c.len_line(text),
|
|c| c.col = c.len_line(text.as_ref()),
|
||||||
data.modifiers().contains(Modifiers::SHIFT),
|
data.modifiers().contains(Modifiers::SHIFT),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -221,64 +298,70 @@ impl Cursor {
|
|||||||
self.move_cursor(|c| c.col = 0, data.modifiers().contains(Modifiers::SHIFT));
|
self.move_cursor(|c| c.col = 0, data.modifiers().contains(Modifiers::SHIFT));
|
||||||
}
|
}
|
||||||
Backspace => {
|
Backspace => {
|
||||||
self.start.realize_col(text);
|
self.start.realize_col(text.as_ref());
|
||||||
let mut start_idx = self.start.idx(text);
|
let mut start_idx = self.start.idx(text.as_ref());
|
||||||
if self.end.is_some() {
|
if self.end.is_some() {
|
||||||
self.delete_selection(text);
|
self.delete_selection(text);
|
||||||
} else if start_idx > 0 {
|
} else if start_idx > 0 {
|
||||||
self.start.left(text);
|
self.start.left(text.as_ref());
|
||||||
text.replace_range(start_idx - 1..start_idx, "");
|
text.delete_range(start_idx - 1..start_idx);
|
||||||
if data.modifiers().contains(Modifiers::CONTROL) {
|
if data.modifiers().contains(Modifiers::CONTROL) {
|
||||||
start_idx = self.start.idx(text);
|
start_idx = self.start.idx(text.as_ref());
|
||||||
while start_idx > 0
|
while start_idx > 0
|
||||||
&& text
|
&& text
|
||||||
.chars()
|
.as_ref()
|
||||||
.nth(start_idx - 1)
|
.character(start_idx - 1)
|
||||||
.filter(|c| *c != ' ')
|
.filter(|c| *c != ' ')
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
self.start.left(text);
|
self.start.left(text.as_ref());
|
||||||
text.replace_range(start_idx - 1..start_idx, "");
|
text.delete_range(start_idx - 1..start_idx);
|
||||||
start_idx = self.start.idx(text);
|
start_idx = self.start.idx(text.as_ref());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Enter => {
|
Enter => {
|
||||||
if text.len() + 1 - self.selection_len(text) <= max_width {
|
if text.as_ref().length() + 1 - self.selection_len(text.as_ref()) <= max_text_length
|
||||||
text.insert(self.start.idx(text), '\n');
|
{
|
||||||
|
text.insert_character(self.start.idx(text.as_ref()), '\n');
|
||||||
self.start.col = 0;
|
self.start.col = 0;
|
||||||
self.start.down(text);
|
self.start.down(text.as_ref());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Tab => {
|
Tab => {
|
||||||
if text.len() + 1 - self.selection_len(text) <= max_width {
|
if text.as_ref().length() + 1 - self.selection_len(text.as_ref()) <= max_text_length
|
||||||
self.start.realize_col(text);
|
{
|
||||||
|
self.start.realize_col(text.as_ref());
|
||||||
self.delete_selection(text);
|
self.delete_selection(text);
|
||||||
text.insert(self.start.idx(text), '\t');
|
text.insert_character(self.start.idx(text.as_ref()), '\t');
|
||||||
self.start.right(text);
|
self.start.right(text.as_ref());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.start.realize_col(text);
|
self.start.realize_col(text.as_ref());
|
||||||
if let Key::Character(character) = data.key() {
|
if let Key::Character(character) = data.key() {
|
||||||
if text.len() + 1 - self.selection_len(text) <= max_width {
|
if text.as_ref().length() + 1 - self.selection_len(text.as_ref())
|
||||||
|
<= max_text_length
|
||||||
|
{
|
||||||
self.delete_selection(text);
|
self.delete_selection(text);
|
||||||
let character = character.chars().next().unwrap();
|
let character = character.chars().next().unwrap();
|
||||||
text.insert(self.start.idx(text), character);
|
text.insert_character(self.start.idx(text.as_ref()), character);
|
||||||
self.start.right(text);
|
self.start.right(text.as_ref());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Modify the end selection position
|
||||||
pub fn with_end(&mut self, f: impl FnOnce(&mut Pos)) {
|
pub fn with_end(&mut self, f: impl FnOnce(&mut Pos)) {
|
||||||
let mut new = self.end.take().unwrap_or_else(|| self.start.clone());
|
let mut new = self.end.take().unwrap_or_else(|| self.start.clone());
|
||||||
f(&mut new);
|
f(&mut new);
|
||||||
self.end.replace(new);
|
self.end.replace(new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns first position of the selection (this could be the start or the end depending on the position)
|
||||||
pub fn first(&self) -> &Pos {
|
pub fn first(&self) -> &Pos {
|
||||||
if let Some(e) = &self.end {
|
if let Some(e) = &self.end {
|
||||||
e.min(&self.start)
|
e.min(&self.start)
|
||||||
@@ -287,6 +370,7 @@ impl Cursor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns last position of the selection (this could be the start or the end depending on the position)
|
||||||
pub fn last(&self) -> &Pos {
|
pub fn last(&self) -> &Pos {
|
||||||
if let Some(e) = &self.end {
|
if let Some(e) = &self.end {
|
||||||
e.max(&self.start)
|
e.max(&self.start)
|
||||||
@@ -295,7 +379,8 @@ impl Cursor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selection_len(&self, text: &str) -> usize {
|
/// Returns the length of the selection
|
||||||
|
pub fn selection_len(&self, text: &(impl Text + ?Sized)) -> usize {
|
||||||
self.last().idx(text) - self.first().idx(text)
|
self.last().idx(text) - self.first().idx(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use dioxus_native_core::layout_attributes::apply_layout_attributes;
|
use dioxus_native_core::layout_attributes::{
|
||||||
|
apply_layout_attributes_cfg, BorderWidths, LayoutConfigeration,
|
||||||
|
};
|
||||||
use dioxus_native_core::node::OwnedAttributeView;
|
use dioxus_native_core::node::OwnedAttributeView;
|
||||||
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
|
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
|
||||||
use dioxus_native_core::{Pass, SendAnyMap};
|
use dioxus_native_core::{Pass, SendAnyMap};
|
||||||
@@ -101,7 +103,18 @@ impl Pass for TaffyLayout {
|
|||||||
.binary_search(&attribute.name.as_ref())
|
.binary_search(&attribute.name.as_ref())
|
||||||
.is_ok());
|
.is_ok());
|
||||||
if let Some(text) = value.as_text() {
|
if let Some(text) = value.as_text() {
|
||||||
apply_layout_attributes(&attribute.name, text, &mut style);
|
apply_layout_attributes_cfg(
|
||||||
|
&attribute.name,
|
||||||
|
text,
|
||||||
|
&mut style,
|
||||||
|
&LayoutConfigeration {
|
||||||
|
border_widths: BorderWidths {
|
||||||
|
thin: 1.0,
|
||||||
|
medium: 1.0,
|
||||||
|
thick: 1.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a
|
|||||||
let dragging = use_state(cx, || false);
|
let dragging = use_state(cx, || false);
|
||||||
|
|
||||||
let text = text_ref.read().clone();
|
let text = text_ref.read().clone();
|
||||||
let start_highlight = cursor.read().first().idx(&text);
|
let start_highlight = cursor.read().first().idx(&*text);
|
||||||
let end_highlight = cursor.read().last().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_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight);
|
||||||
let (text_highlighted, text_after_second_cursor) =
|
let (text_highlighted, text_after_second_cursor) =
|
||||||
text_after_first_cursor.split_at(end_highlight - start_highlight);
|
text_after_first_cursor.split_at(end_highlight - start_highlight);
|
||||||
@@ -113,7 +113,7 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a
|
|||||||
};
|
};
|
||||||
if is_text{
|
if is_text{
|
||||||
let mut text = text_ref.write();
|
let mut text = text_ref.write();
|
||||||
cursor.write().handle_input(&k, &mut text, max_len);
|
cursor.write().handle_input(&k, &mut *text, max_len);
|
||||||
update(text.clone());
|
update(text.clone());
|
||||||
|
|
||||||
let node = tui_query.get(get_root_id(cx).unwrap());
|
let node = tui_query.get(get_root_id(cx).unwrap());
|
||||||
@@ -165,7 +165,7 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a
|
|||||||
}
|
}
|
||||||
new.row = 0;
|
new.row = 0;
|
||||||
|
|
||||||
new.realize_col(&text_ref.read());
|
new.realize_col(text_ref.read().as_str());
|
||||||
cursor.set(Cursor::from_start(new));
|
cursor.set(Cursor::from_start(new));
|
||||||
dragging.set(true);
|
dragging.set(true);
|
||||||
let node = tui_query_clone.get(get_root_id(cx).unwrap());
|
let node = tui_query_clone.get(get_root_id(cx).unwrap());
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
|
|||||||
let dragging = use_state(cx, || false);
|
let dragging = use_state(cx, || false);
|
||||||
|
|
||||||
let text = text_ref.read().clone();
|
let text = text_ref.read().clone();
|
||||||
let start_highlight = cursor.read().first().idx(&text);
|
let start_highlight = cursor.read().first().idx(&*text);
|
||||||
let end_highlight = cursor.read().last().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_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight);
|
||||||
let (text_highlighted, text_after_second_cursor) =
|
let (text_highlighted, text_after_second_cursor) =
|
||||||
text_after_first_cursor.split_at(end_highlight - start_highlight);
|
text_after_first_cursor.split_at(end_highlight - start_highlight);
|
||||||
@@ -88,7 +88,7 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut text = text_ref.write();
|
let mut text = text_ref.write();
|
||||||
cursor.write().handle_input(&k, &mut text, max_len);
|
cursor.write().handle_input(&k, &mut *text, max_len);
|
||||||
if let Some(input_handler) = &cx.props.raw_oninput {
|
if let Some(input_handler) = &cx.props.raw_oninput {
|
||||||
input_handler.call(FormData {
|
input_handler.call(FormData {
|
||||||
value: text.clone(),
|
value: text.clone(),
|
||||||
@@ -147,7 +147,7 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
|
|||||||
// textboxs are only one line tall
|
// textboxs are only one line tall
|
||||||
new.row = 0;
|
new.row = 0;
|
||||||
|
|
||||||
new.realize_col(&text_ref.read());
|
new.realize_col(text_ref.read().as_str());
|
||||||
cursor.set(Cursor::from_start(new));
|
cursor.set(Cursor::from_start(new));
|
||||||
dragging.set(true);
|
dragging.set(true);
|
||||||
let node = tui_query_clone.get(get_root_id(cx).unwrap());
|
let node = tui_query_clone.get(get_root_id(cx).unwrap());
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
|
|||||||
let dragging = use_state(cx, || false);
|
let dragging = use_state(cx, || false);
|
||||||
|
|
||||||
let text = text_ref.read().clone();
|
let text = text_ref.read().clone();
|
||||||
let start_highlight = cursor.read().first().idx(&text);
|
let start_highlight = cursor.read().first().idx(&*text);
|
||||||
let end_highlight = cursor.read().last().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_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight);
|
||||||
let (text_highlighted, text_after_second_cursor) =
|
let (text_highlighted, text_after_second_cursor) =
|
||||||
text_after_first_cursor.split_at(end_highlight - start_highlight);
|
text_after_first_cursor.split_at(end_highlight - start_highlight);
|
||||||
@@ -90,7 +90,7 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut text = text_ref.write();
|
let mut text = text_ref.write();
|
||||||
cursor.write().handle_input(&k, &mut text, max_len);
|
cursor.write().handle_input(&k, &mut *text, max_len);
|
||||||
if let Some(input_handler) = &cx.props.raw_oninput{
|
if let Some(input_handler) = &cx.props.raw_oninput{
|
||||||
input_handler.call(FormData{
|
input_handler.call(FormData{
|
||||||
value: text.clone(),
|
value: text.clone(),
|
||||||
@@ -138,7 +138,7 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
|
|||||||
// textboxs are only one line tall
|
// textboxs are only one line tall
|
||||||
new.row = 0;
|
new.row = 0;
|
||||||
|
|
||||||
new.realize_col(&text_ref.read());
|
new.realize_col(text_ref.read().as_str());
|
||||||
cursor.set(Cursor::from_start(new));
|
cursor.set(Cursor::from_start(new));
|
||||||
dragging.set(true);
|
dragging.set(true);
|
||||||
let node = tui_query_clone.get(get_root_id(cx).unwrap());
|
let node = tui_query_clone.get(get_root_id(cx).unwrap());
|
||||||
|
|||||||
Reference in new issue
Block a user