Merge remote-tracking branch 'upstream/master' into tui_focus

This commit is contained in:
Evan Almloff 2022-06-13 17:55:18 -05:00
commit 5d323cae78
29 changed files with 387 additions and 345 deletions

View file

@ -71,7 +71,7 @@ Desktop APIs will likely be in flux as we figure out better patterns than our El
[Jump to the getting started guide for Desktop.](/reference/platforms/desktop)
Examples:
- [File explorer](https://github.com/dioxusLabs/file-explorer/)
- [File explorer](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer)
- [WiFi scanner](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner)
[![File ExplorerExample](https://raw.githubusercontent.com/DioxusLabs/example-projects/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/tree/master/file-explorer)

View file

@ -64,7 +64,7 @@ The most common hook you'll use for storing state is `use_state`. `use_state` pr
```rust
fn App(cx: Scope)-> Element {
let (post, set_post) = use_state(&cx, || {
let post = use_state(&cx, || {
PostData {
id: Uuid::new_v4(),
score: 10,
@ -112,11 +112,11 @@ For example, let's say we provide a button to generate a new post. Whenever the
```rust
fn App(cx: Scope)-> Element {
let (post, set_post) = use_state(&cx, || PostData::new());
let post = use_state(&cx, || PostData::new());
cx.render(rsx!{
button {
onclick: move |_| set_post(PostData::random())
onclick: move |_| post.set(PostData::random())
"Generate a random post"
}
Post { props: &post }
@ -141,19 +141,19 @@ We can use tasks in our components to build a tiny stopwatch that ticks every se
```rust
fn App(cx: Scope)-> Element {
let (elapsed, set_elapsed) = use_state(&cx, || 0);
use_future(&cx, || {
to_owned![set_elapsed]; // explicitly capture this hook for use in async
let elapsed = use_state(&cx, || 0);
use_future(&cx, (), |()| {
to_owned![elapsed]; // explicitly capture this hook for use in async
async move {
loop {
TimeoutFuture::from_ms(1000).await;
set_elapsed.modify(|i| i + 1)
gloo_timers::future::TimeoutFuture::new(1_000).await;
elapsed.modify(|i| i + 1)
}
}
});
rsx!(cx, div { "Current stopwatch time: {sec_elapsed}" })
rsx!(cx, div { "Current stopwatch time: {elapsed}" })
}
```

View file

@ -59,7 +59,7 @@ When using Debian/bullseye `libappindicator3-dev` is no longer available but rep
sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
```
If you run into issues, make sure you have all the basics installed, as outlined in the [Tauri docs](https://tauri.studio/en/docs/get-started/setup-linux).
If you run into issues, make sure you have all the basics installed, as outlined in the [Tauri docs](https://tauri.studio/v1/guides/getting-started/prerequisites#setting-up-linux).
### macOS

View file

@ -11,7 +11,7 @@ Getting Set up with Dioxus-Desktop is quite easy. Make sure you have Rust and Ca
```shell
$ cargo new --bin demo
$ cd app
$ cd demo
```
Add Dioxus with the `desktop` feature:

View file

@ -46,6 +46,9 @@ fn main() {
/// This type alias specifies the type for you so you don't need to write "None as Option<()>"
const NONE_ELEMENT: Option<()> = None;
use core::{fmt, str::FromStr};
use std::fmt::Display;
use baller::Baller;
use dioxus::prelude::*;
@ -187,6 +190,15 @@ fn app(cx: Scope) -> Element {
text: "using functionc all syntax"
)
// Components can be geneirc too
// This component takes i32 type to give you typed input
TypedInput::<TypedInputProps<i32>> {}
// Type inference can be used too
TypedInput { initial: 10.0 }
// geneircs with the `inline_props` macro
label(text: "hello geneirc world!")
label(text: 99.9)
// helper functions
// Single values must be wrapped in braces or `Some` to satisfy `IntoIterator`
[helper(&cx, "hello world!")]
@ -227,9 +239,35 @@ pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element {
})
}
#[derive(Props, PartialEq)]
pub struct TypedInputProps<T> {
#[props(optional, default)]
initial: Option<T>,
}
#[allow(non_snake_case)]
pub fn TypedInput<T>(_: Scope<TypedInputProps<T>>) -> Element
where
T: FromStr + fmt::Display,
<T as FromStr>::Err: std::fmt::Display,
{
todo!()
}
#[inline_props]
fn with_inline<'a>(cx: Scope<'a>, text: &'a str) -> Element {
cx.render(rsx! {
p { "{text}" }
})
}
// generic component with inline_props too
#[inline_props]
fn label<T>(cx: Scope, text: T) -> Element
where
T: Display,
{
cx.render(rsx! {
p { "{text}" }
})
}

75
examples/svg_basic.rs Normal file
View file

@ -0,0 +1,75 @@
use dioxus::prelude::*;
fn app(cx: Scope) -> Element {
cx.render(rsx!( svg {
width: "200",
height: "250",
xmlns: "http://www.w3.org/2000/svg",
version: "1.1",
rect {
x: "10",
y: "10",
width: "30",
height: "30",
stroke: "black",
fill: "transparent",
stroke_width: "5",
}
rect {
x: "60",
y: "10",
width: "30",
height: "30",
stroke: "black",
fill: "transparent",
stroke_width: "5",
}
circle {
cx: "25",
cy: "75",
r: "20",
stroke: "red",
fill: "transparent",
stroke_width: "5",
}
ellipse {
cx: "75",
cy: "75",
rx: "20",
ry: "5",
stroke: "red",
fill: "transparent",
stroke_width: "5",
}
line {
x1: "10",
x2: "50",
y1: "110",
y2: "150",
stroke: "orange",
stroke_width: "5",
}
polyline {
points: "60 110 65 120 70 115 75 130 80 125 85 140 90 135 95 150 100 145",
stroke: "orange",
fill: "transparent",
stroke_width: "5",
}
polygon {
points: "50 160 55 180 70 180 60 190 65 205 50 195 35 205 40 190 30 180 45 180",
stroke: "green",
fill: "transparent",
stroke_width: "5",
}
path {
d: "M20,230 Q40,205 50,230 T90,230",
fill: "none",
stroke: "blue",
stroke_width: "5",
}
}))
}
fn main() {
dioxus::desktop::launch(app);
}

View file

@ -17,6 +17,7 @@ pub struct InlinePropsBody {
pub inputs: Punctuated<FnArg, Token![,]>,
// pub fields: FieldsNamed,
pub output: ReturnType,
pub where_clause: Option<WhereClause>,
pub block: Box<Block>,
}
@ -28,7 +29,7 @@ impl Parse for InlinePropsBody {
let fn_token = input.parse()?;
let ident = input.parse()?;
let generics = input.parse()?;
let generics: Generics = input.parse()?;
let content;
let paren_token = syn::parenthesized!(content in input);
@ -47,6 +48,11 @@ impl Parse for InlinePropsBody {
let output = input.parse()?;
let where_clause = input
.peek(syn::token::Where)
.then(|| input.parse())
.transpose()?;
let block = input.parse()?;
Ok(Self {
@ -57,6 +63,7 @@ impl Parse for InlinePropsBody {
paren_token,
inputs,
output,
where_clause,
block,
cx_token,
attrs,
@ -73,6 +80,7 @@ impl ToTokens for InlinePropsBody {
generics,
inputs,
output,
where_clause,
block,
cx_token,
attrs,
@ -136,12 +144,16 @@ impl ToTokens for InlinePropsBody {
out_tokens.append_all(quote! {
#modifiers
#[allow(non_camel_case_types)]
#vis struct #struct_name #struct_generics {
#vis struct #struct_name #struct_generics
#where_clause
{
#(#fields),*
}
#(#attrs)*
#vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics>) #output {
#vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics>) #output
#where_clause
{
let #struct_name { #(#field_names),* } = &cx.props;
#block
}

View file

@ -29,152 +29,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
///
/// ## Complete Reference Guide:
/// ```
/// const Example: Component = |cx| {
/// let formatting = "formatting!";
/// let formatting_tuple = ("a", "b");
/// let lazy_fmt = format_args!("lazily formatted text");
/// cx.render(rsx! {
/// div {
/// // Elements
/// div {}
/// h1 {"Some text"}
/// h1 {"Some text with {formatting}"}
/// h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"}
/// h2 {
/// "Multiple"
/// "Text"
/// "Blocks"
/// "Use comments as separators in html"
/// }
/// div {
/// h1 {"multiple"}
/// h2 {"nested"}
/// h3 {"elements"}
/// }
/// div {
/// class: "my special div"
/// h1 {"Headers and attributes!"}
/// }
/// div {
/// // pass simple rust expressions in
/// class: lazy_fmt,
/// id: format_args!("attributes can be passed lazily with std::fmt::Arguments"),
/// div {
/// class: {
/// const WORD: &str = "expressions";
/// format_args!("Arguments can be passed in through curly braces for complex {}", WORD)
/// }
/// }
/// }
///
/// // Expressions can be used in element position too:
/// {rsx!(p { "More templating!" })}
/// {html!(<p>"Even HTML templating!!"</p>)}
///
/// // Iterators
/// {(0..10).map(|i| rsx!(li { "{i}" }))}
/// {{
/// let data = std::collections::HashMap::<&'static str, &'static str>::new();
/// // Iterators *should* have keys when you can provide them.
/// // Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
/// // Using an "ID" associated with your data is a good idea.
/// data.into_iter().map(|(k, v)| rsx!(li { key: "{k}" "{v}" }))
/// }}
///
/// // Matching
/// {match true {
/// true => rsx!(h1 {"Top text"}),
/// false => rsx!(h1 {"Bottom text"})
/// }}
///
/// // Conditional rendering
/// // Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
/// // You can convert a bool condition to rsx! with .then and .or
/// {true.then(|| rsx!(div {}))}
///
/// // True conditions
/// {if true {
/// rsx!(h1 {"Top text"})
/// } else {
/// rsx!(h1 {"Bottom text"})
/// }}
///
/// // returning "None" is a bit noisy... but rare in practice
/// {None as Option<()>}
///
/// // Use the Dioxus type-alias for less noise
/// {NONE_ELEMENT}
///
/// // can also just use empty fragments
/// Fragment {}
///
/// // Fragments let you insert groups of nodes without a parent.
/// // This lets you make components that insert elements as siblings without a container.
/// div {"A"}
/// Fragment {
/// div {"B"}
/// div {"C"}
/// Fragment {
/// "D"
/// Fragment {
/// "heavily nested fragments is an antipattern"
/// "they cause Dioxus to do unnecessary work"
/// "don't use them carelessly if you can help it"
/// }
/// }
/// }
///
/// // Components
/// // Can accept any paths
/// // Notice how you still get syntax highlighting and IDE support :)
/// Baller {}
/// baller::Baller { }
/// crate::baller::Baller {}
///
/// // Can take properties
/// Taller { a: "asd" }
///
/// // Can take optional properties
/// Taller { a: "asd" }
///
/// // Can pass in props directly as an expression
/// {{
/// let props = TallerProps {a: "hello"};
/// rsx!(Taller { ..props })
/// }}
///
/// // Spreading can also be overridden manually
/// Taller {
/// ..TallerProps { a: "ballin!" }
/// a: "not ballin!"
/// }
///
/// // Can take children too!
/// Taller { a: "asd", div {"hello world!"} }
/// }
/// })
/// };
///
/// mod baller {
/// use super::*;
/// pub struct BallerProps {}
///
/// /// This component totally balls
/// pub fn Baller(cx: Scope) -> DomTree {
/// todo!()
/// }
/// }
///
/// #[derive(Debug, PartialEq, Props)]
/// pub struct TallerProps {
/// a: &'static str,
/// }
///
/// /// This component is taller than most :)
/// pub fn Taller(cx: Scope<TallerProps>) -> DomTree {
/// let b = true;
/// todo!()
/// }
#[doc = include_str!("../../../examples/rsx_usage.rs")]
/// ```
#[proc_macro_error::proc_macro_error]
#[proc_macro]

View file

@ -653,7 +653,9 @@ Finally, call `.build()` to create the instance of `{name}`.
}
}
impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
impl #impl_generics dioxus::prelude::Properties for #name #ty_generics
#b_generics_where_extras_predicates
{
type Builder = #builder_name #generics_with_empty;
const IS_STATIC: bool = #is_static;
fn builder() -> Self::Builder {

View file

@ -18,19 +18,56 @@ use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
ext::IdentExt,
parse::{Parse, ParseBuffer, ParseStream},
token, Expr, Ident, LitStr, Result, Token,
token, AngleBracketedGenericArguments, Expr, Ident, LitStr, PathArguments, Result, Token,
};
pub struct Component {
pub name: syn::Path,
pub prop_gen_args: Option<AngleBracketedGenericArguments>,
pub body: Vec<ComponentField>,
pub children: Vec<BodyNode>,
pub manual_props: Option<Expr>,
}
impl Component {
pub fn validate_component_path(path: &syn::Path) -> Result<()> {
// ensure path segments doesn't have PathArguments, only the last
// segment is allowed to have one.
if path
.segments
.iter()
.take(path.segments.len() - 1)
.any(|seg| seg.arguments != PathArguments::None)
{
component_path_cannot_have_arguments!(path);
}
// ensure last segment only have value of None or AngleBracketed
if !matches!(
path.segments.last().unwrap().arguments,
PathArguments::None | PathArguments::AngleBracketed(_)
) {
invalid_component_path!(path);
}
Ok(())
}
}
impl Parse for Component {
fn parse(stream: ParseStream) -> Result<Self> {
let name = syn::Path::parse_mod_style(stream)?;
let mut name = stream.parse::<syn::Path>()?;
Component::validate_component_path(&name)?;
// extract the path arguments from the path into prop_gen_args
let prop_gen_args = name.segments.last_mut().and_then(|seg| {
if let PathArguments::AngleBracketed(args) = seg.arguments.clone() {
seg.arguments = PathArguments::None;
Some(args)
} else {
None
}
});
let content: ParseBuffer;
@ -64,6 +101,7 @@ impl Parse for Component {
Ok(Self {
name,
prop_gen_args,
body,
children,
manual_props,
@ -74,6 +112,7 @@ impl Parse for Component {
impl ToTokens for Component {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
let prop_gen_args = &self.prop_gen_args;
let mut has_key = None;
@ -101,7 +140,10 @@ impl ToTokens for Component {
}}
}
None => {
let mut toks = quote! { fc_to_builder(#name) };
let mut toks = match prop_gen_args {
Some(gen_args) => quote! { fc_to_builder #gen_args(#name) },
None => quote! { fc_to_builder(#name) },
};
for field in &self.body {
match field.name.to_string().as_str() {
"key" => {

View file

@ -13,3 +13,19 @@ macro_rules! attr_after_element {
)
};
}
macro_rules! component_path_cannot_have_arguments {
($span:expr) => {
proc_macro_error::abort!(
$span,
"expected a path without arguments";
help = "try remove the path arguments"
)
};
}
macro_rules! invalid_component_path {
($span:expr) => {
proc_macro_error::abort!($span, "Invalid component path syntax")
};
}

View file

@ -4,7 +4,7 @@ use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream},
token, Expr, LitStr, Result, Token,
token, Expr, LitStr, Result,
};
/*
@ -28,39 +28,49 @@ impl Parse for BodyNode {
return Ok(BodyNode::Text(stream.parse()?));
}
// div {} -> el
// Div {} -> comp
if stream.peek(syn::Ident) && stream.peek2(token::Brace) {
if stream
.fork()
.parse::<Ident>()?
.to_string()
.chars()
.next()
.unwrap()
.is_ascii_uppercase()
{
return Ok(BodyNode::Component(stream.parse()?));
} else {
return Ok(BodyNode::Element(stream.parse::<Element>()?));
let body_stream = stream.fork();
if let Ok(path) = body_stream.parse::<syn::Path>() {
// this is an Element if path match of:
// - one ident
// - followed by `{`
// - 1st char is lowercase
//
// example:
// div {}
if let Some(ident) = path.get_ident() {
if body_stream.peek(token::Brace)
&& ident
.to_string()
.chars()
.next()
.unwrap()
.is_ascii_lowercase()
{
return Ok(BodyNode::Element(stream.parse::<Element>()?));
}
}
}
// component() -> comp
// ::component {} -> comp
// ::component () -> comp
if (stream.peek(syn::Ident) && stream.peek2(token::Paren))
|| (stream.peek(Token![::]))
|| (stream.peek(Token![:]) && stream.peek2(Token![:]))
{
return Ok(BodyNode::Component(stream.parse::<Component>()?));
}
// Otherwise this should be Component, allowed syntax:
// - syn::Path
// - PathArguments can only apper in last segment
// - followed by `{` or `(`, note `(` cannot be used with one ident
//
// example
// Div {}
// Div ()
// ::Div {}
// crate::Div {}
// component()
// ::component {}
// ::component ()
// crate::component{}
// crate::component()
// Input::<InputProps<'_, i32> {}
// crate::Input::<InputProps<'_, i32> {}
if body_stream.peek(token::Brace) || body_stream.peek(token::Paren) {
Component::validate_component_path(&path)?;
// crate::component{} -> comp
// crate::component() -> comp
if let Ok(pat) = stream.fork().parse::<syn::Path>() {
if pat.segments.len() > 1 {
return Ok(BodyNode::Component(stream.parse::<Component>()?));
return Ok(BodyNode::Component(stream.parse()?));
}
}

View file

@ -33,7 +33,7 @@ impl<'a, const A: bool> FragmentBuilder<'a, A> {
/// fn App(cx: Scope) -> Element {
/// cx.render(rsx!{
/// CustomCard {
/// h1 {}2
/// h1 {}
/// p {}
/// }
/// })

View file

@ -609,7 +609,7 @@ impl ScopeState {
/// Create a subscription that schedules a future render for the reference component
///
/// ## Notice: you should prefer using prepare_update and get_scope_id
/// ## Notice: you should prefer using schedule_update_any and scope_id
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
Arc::new(move || {
@ -1000,12 +1000,11 @@ impl TaskQueue {
fn remove(&self, id: TaskId) {
if let Ok(mut tasks) = self.tasks.try_borrow_mut() {
let _ = tasks.remove(&id);
if let Some(task_map) = self.task_map.borrow_mut().get_mut(&id.scope) {
task_map.remove(&id);
}
}
// the task map is still around, but it'll be removed when the scope is unmounted
if let Some(task_map) = self.task_map.borrow_mut().get_mut(&id.scope) {
task_map.remove(&id);
}
}
pub(crate) fn has_tasks(&self) -> bool {

View file

@ -337,29 +337,12 @@ impl VirtualDom {
let scopes = &mut self.scopes;
let task_poll = poll_fn(|cx| {
//
let mut any_pending = false;
let mut tasks = scopes.tasks.tasks.borrow_mut();
let mut to_remove = vec![];
tasks.retain(|_, task| task.as_mut().poll(cx).is_pending());
// this would be better served by retain
for (id, task) in tasks.iter_mut() {
if task.as_mut().poll(cx).is_ready() {
to_remove.push(*id);
} else {
any_pending = true;
}
}
for id in to_remove {
tasks.remove(&id);
}
// Resolve the future if any singular task is ready
match any_pending {
true => Poll::Pending,
false => Poll::Ready(()),
match tasks.is_empty() {
true => Poll::Ready(()),
false => Poll::Pending,
}
});
@ -583,7 +566,7 @@ impl VirtualDom {
///
/// *value.borrow_mut() = "goodbye";
///
/// let edits = dom.diff();
/// let edits = dom.hard_diff(ScopeId(0));
/// ```
pub fn hard_diff(&mut self, scope_id: ScopeId) -> Mutations {
let mut diff_machine = DiffState::new(&self.scopes);

View file

@ -212,7 +212,12 @@ pub(super) fn handler(
log::warn!("Open print modal failed: {e}");
}
}
DevTool => webview.open_devtools(),
DevTool => {
#[cfg(debug_assertions)]
webview.open_devtools();
#[cfg(not(debug_assertions))]
log::warn!("Devtools are disabled in release builds");
}
Eval(code) => {
if let Err(e) = webview.evaluate_script(code.as_str()) {

View file

@ -74,6 +74,13 @@ impl AtomRoot {
}
} else {
log::trace!("no atoms found for {:?}", ptr);
atoms.insert(
ptr,
Slot {
value: Rc::new(value),
subscribers: HashSet::new(),
},
);
}
}

View file

@ -140,7 +140,9 @@ export class Interpreter {
RemoveAttribute(root, field, ns) {
const name = field;
const node = this.nodes[root];
if (ns !== null || ns !== undefined) {
if (ns == "style") {
node.style.removeProperty(name);
} else if (ns !== null || ns !== undefined) {
node.removeAttributeNS(ns, name);
} else if (name === "value") {
node.value = "";

View file

@ -15,7 +15,7 @@ dioxus-core = { path = "../core", version = "^0.2.1" }
dioxus-html = { path = "../html", version = "^0.2.1" }
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
stretch2 = "0.4.2"
taffy = "0.1.0"
smallvec = "1.6"
fxhash = "0.2"
anymap = "0.12.1"

View file

@ -1,6 +1,6 @@
/*
- [ ] pub display: Display,
- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything
- [x] pub position_type: PositionType, --> kinda, taffy doesnt support everything
- [ ] pub direction: Direction,
- [x] pub flex_direction: FlexDirection,
@ -9,7 +9,7 @@
- [x] pub flex_shrink: f32,
- [x] pub flex_basis: Dimension,
- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow
- [x] pub overflow: Overflow, ---> kinda implemented... taffy doesnt have support for directional overflow
- [x] pub align_items: AlignItems,
- [x] pub align_self: AlignSelf,
@ -29,7 +29,10 @@
- [ ] pub aspect_ratio: Number,
*/
use stretch2::{prelude::*, style::PositionType};
use taffy::{
prelude::*,
style::{FlexDirection, PositionType},
};
/// applies the entire html namespace defined in dioxus-html
pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) {
@ -109,13 +112,7 @@ pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) {
"counter-reset" => {}
"cursor" => {}
"direction" => {
match value {
"ltr" => style.direction = Direction::LTR,
"rtl" => style.direction = Direction::RTL,
_ => {}
}
}
"direction" => {}
"display" => apply_display(name, value, style),
@ -283,20 +280,8 @@ pub fn parse_value(value: &str) -> Option<UnitSystem> {
}
}
fn apply_overflow(name: &str, value: &str, style: &mut Style) {
match name {
// todo: add more overflow support to stretch2
"overflow" | "overflow-x" | "overflow-y" => {
style.overflow = match value {
"auto" => Overflow::Visible,
"hidden" => Overflow::Hidden,
"scroll" => Overflow::Scroll,
"visible" => Overflow::Visible,
_ => Overflow::Visible,
};
}
_ => {}
}
fn apply_overflow(_name: &str, _value: &str, _style: &mut Style) {
// todo: add overflow support to taffy
}
fn apply_display(_name: &str, value: &str, style: &mut Style) {
@ -307,7 +292,7 @@ fn apply_display(_name: &str, value: &str, style: &mut Style) {
}
// TODO: there are way more variants
// stretch needs to be updated to handle them
// taffy needs to be updated to handle them
//
// "block" => Display::Block,
// "inline" => Display::Inline,

View file

@ -23,7 +23,7 @@ crossterm = "0.23.0"
anyhow = "1.0.42"
tokio = { version = "1.15.0", features = ["full"] }
futures = "0.3.19"
stretch2 = "0.4.2"
taffy = "0.1.0"
smallvec = "1.6"
fxhash = "0.2"
anymap = "0.12.1"

View file

@ -17,8 +17,8 @@ use std::{
sync::Arc,
time::{Duration, Instant},
};
use stretch2::geometry::{Point, Size};
use stretch2::{prelude::Layout, Stretch};
use taffy::geometry::{Point, Size};
use taffy::{prelude::Layout, Taffy};
use crate::FocusState;
use crate::{Dom, Node};
@ -156,7 +156,7 @@ impl InnerInputState {
&mut self,
evts: &mut Vec<EventCore>,
resolved_events: &mut Vec<UserEvent>,
layout: &Stretch,
layout: &Taffy,
dom: &mut Dom,
) {
let previous_mouse = self.mouse.clone();
@ -216,7 +216,7 @@ impl InnerInputState {
&mut self,
previous_mouse: Option<MouseData>,
resolved_events: &mut Vec<UserEvent>,
layout: &Stretch,
layout: &Taffy,
dom: &mut Dom,
) {
fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool {
@ -584,8 +584,7 @@ impl RinkInputHandler {
self.state.borrow_mut().focus_state.prune(mutations, rdom);
}
// returns a list of events and if a event will force a rerender
pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec<UserEvent> {
pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut Dom) -> Vec<UserEvent> {
let mut resolved_events = Vec::new();
(*self.state).borrow_mut().update(

View file

@ -6,7 +6,7 @@ use dioxus_native_core::layout_attributes::apply_layout_attributes;
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
use dioxus_native_core::state::ChildDepState;
use dioxus_native_core_macro::sorted_str_slice;
use stretch2::prelude::*;
use taffy::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum PossiblyUninitalized<T> {
@ -28,13 +28,13 @@ impl<T> Default for PossiblyUninitalized<T> {
}
#[derive(Clone, PartialEq, Default, Debug)]
pub(crate) struct StretchLayout {
pub(crate) struct TaffyLayout {
pub style: Style,
pub node: PossiblyUninitalized<Node>,
}
impl ChildDepState for StretchLayout {
type Ctx = Rc<RefCell<Stretch>>;
impl ChildDepState for TaffyLayout {
type Ctx = Rc<RefCell<Taffy>>;
type DepState = Self;
// use tag to force this to be called when a node is built
const NODE_MASK: NodeMask =
@ -53,7 +53,7 @@ impl ChildDepState for StretchLayout {
Self::DepState: 'a,
{
let mut changed = false;
let mut stretch = ctx.borrow_mut();
let mut taffy = ctx.borrow_mut();
let mut style = Style::default();
if let Some(text) = node.text() {
let char_len = text.chars().count();
@ -70,11 +70,10 @@ impl ChildDepState for StretchLayout {
};
if let PossiblyUninitalized::Initialized(n) = self.node {
if self.style != style {
stretch.set_style(n, style).unwrap();
taffy.set_style(n, style).unwrap();
}
} else {
self.node =
PossiblyUninitalized::Initialized(stretch.new_node(style, &[]).unwrap());
self.node = PossiblyUninitalized::Initialized(taffy.new_node(style, &[]).unwrap());
changed = true;
}
} else {
@ -100,14 +99,14 @@ impl ChildDepState for StretchLayout {
if let PossiblyUninitalized::Initialized(n) = self.node {
if self.style != style {
stretch.set_style(n, style).unwrap();
taffy.set_style(n, style).unwrap();
}
if stretch.children(n).unwrap() != child_layout {
stretch.set_children(n, &child_layout).unwrap();
if taffy.children(n).unwrap() != child_layout {
taffy.set_children(n, &child_layout).unwrap();
}
} else {
self.node = PossiblyUninitalized::Initialized(
stretch.new_node(style, &child_layout).unwrap(),
taffy.new_node(style, &child_layout).unwrap(),
);
changed = true;
}

View file

@ -16,7 +16,7 @@ use futures::{
use std::cell::RefCell;
use std::rc::Rc;
use std::{io, time::Duration};
use stretch2::{prelude::Size, Stretch};
use taffy::{geometry::Point, prelude::Size, Taffy};
use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
mod config;
@ -77,9 +77,9 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
let mut rdom: Dom = RealDom::new();
let mutations = dom.rebuild();
let to_update = rdom.apply_mutations(vec![mutations]);
let stretch = Rc::new(RefCell::new(Stretch::new()));
let taffy = Rc::new(RefCell::new(Taffy::new()));
let mut any_map = AnyMap::new();
any_map.insert(stretch.clone());
any_map.insert(taffy.clone());
let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap();
render_vdom(
@ -88,7 +88,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
handler,
cfg,
rdom,
stretch,
taffy,
register_event,
)
.unwrap();
@ -100,7 +100,7 @@ fn render_vdom(
handler: RinkInputHandler,
cfg: Config,
mut rdom: Dom,
stretch: Rc<RefCell<Stretch>>,
taffy: Rc<RefCell<Taffy>>,
mut register_event: impl FnMut(crossterm::event::Event),
) -> Result<()> {
tokio::runtime::Builder::new_current_thread()
@ -135,17 +135,17 @@ fn render_vdom(
if !to_rerender.is_empty() || updated {
updated = false;
fn resize(dims: Rect, stretch: &mut Stretch, rdom: &Dom) {
fn resize(dims: Rect, taffy: &mut Taffy, rdom: &Dom) {
let width = dims.width;
let height = dims.height;
let root_node = rdom[0].state.layout.node.unwrap();
stretch
taffy
.compute_layout(
root_node,
Size {
width: stretch2::prelude::Number::Defined((width - 1) as f32),
height: stretch2::prelude::Number::Defined((height - 1) as f32),
width: taffy::prelude::Number::Defined((width - 1) as f32),
height: taffy::prelude::Number::Defined((height - 1) as f32),
},
)
.unwrap();
@ -153,9 +153,16 @@ fn render_vdom(
if let Some(terminal) = &mut terminal {
terminal.draw(|frame| {
// size is guaranteed to not change when rendering
resize(frame.size(), &mut stretch.borrow_mut(), &rdom);
resize(frame.size(), &mut taffy.borrow_mut(), &rdom);
let root = &rdom[0];
render::render_vnode(frame, &stretch.borrow(), &rdom, root, cfg);
render::render_vnode(
frame,
&taffy.borrow(),
&rdom,
root,
cfg,
Point::zero(),
);
})?;
} else {
resize(
@ -165,7 +172,7 @@ fn render_vdom(
width: 300,
height: 300,
},
&mut stretch.borrow_mut(),
&mut taffy.borrow_mut(),
&rdom,
);
}
@ -205,7 +212,7 @@ fn render_vdom(
}
{
let evts = handler.get_events(&stretch.borrow(), &mut rdom);
let evts = handler.get_events(&taffy.borrow(), &mut rdom);
{
updated |= handler.state().focus_state.clean();
}
@ -220,7 +227,7 @@ fn render_vdom(
let to_update = rdom.apply_mutations(mutations);
// update the style and layout
let mut any_map = AnyMap::new();
any_map.insert(stretch.clone());
any_map.insert(taffy.clone());
to_rerender = rdom.update_state(vdom, to_update, any_map).unwrap();
}
}

View file

@ -1,5 +1,5 @@
use crate::focus::Focus;
use crate::layout::StretchLayout;
use crate::layout::TaffyLayout;
use crate::style_attributes::StyleModifier;
use dioxus_native_core::{real_dom::RealDom, state::*};
use dioxus_native_core_macro::{sorted_str_slice, State};
@ -10,7 +10,7 @@ pub(crate) type Node = dioxus_native_core::real_dom::Node<NodeState>;
#[derive(Debug, Clone, State, Default)]
pub(crate) struct NodeState {
#[child_dep_state(layout, RefCell<Stretch>)]
pub layout: StretchLayout,
pub layout: TaffyLayout,
// depends on attributes, the C component of it's parent and a u8 context
#[parent_dep_state(style)]
pub style: StyleModifier,

View file

@ -1,9 +1,9 @@
use dioxus_native_core::layout_attributes::UnitSystem;
use std::io::Stdout;
use stretch2::{
use taffy::{
geometry::Point,
prelude::{Layout, Size},
Stretch,
Taffy,
};
use tui::{backend::CrosstermBackend, layout::Rect, style::Color};
@ -18,10 +18,11 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
pub(crate) fn render_vnode(
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
layout: &Stretch,
layout: &Taffy,
rdom: &Dom,
node: &Node,
cfg: Config,
parent_location: Point<f32>,
) {
use dioxus_native_core::real_dom::NodeType;
@ -29,7 +30,11 @@ pub(crate) fn render_vnode(
return;
}
let Layout { location, size, .. } = layout.layout(node.state.layout.node.unwrap()).unwrap();
let Layout {
mut location, size, ..
} = layout.layout(node.state.layout.node.unwrap()).unwrap();
location.x += parent_location.x;
location.y += parent_location.y;
let Point { x, y } = location;
let Size { width, height } = size;
@ -57,7 +62,7 @@ pub(crate) fn render_vnode(
text,
style: node.state.style.core,
};
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
let area = Rect::new(x as u16, y as u16, *width as u16, *height as u16);
// the renderer will panic if a node is rendered out of range even if the size is zero
if area.width > 0 && area.height > 0 {
@ -65,7 +70,7 @@ pub(crate) fn render_vnode(
}
}
NodeType::Element { children, .. } => {
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
let area = Rect::new(x as u16, y as u16, *width as u16, *height as u16);
// the renderer will panic if a node is rendered out of range even if the size is zero
if area.width > 0 && area.height > 0 {
@ -73,7 +78,7 @@ pub(crate) fn render_vnode(
}
for c in children {
render_vnode(frame, layout, rdom, &rdom[c.0], cfg);
render_vnode(frame, layout, rdom, &rdom[c.0], cfg, location);
}
}
NodeType::Placeholder => unreachable!(),

View file

@ -1,6 +1,6 @@
/*
- [ ] pub display: Display,
- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything
- [x] pub position_type: PositionType, --> kinda, taffy doesnt support everything
- [ ] pub direction: Direction,
- [x] pub flex_direction: FlexDirection,
@ -9,7 +9,7 @@
- [x] pub flex_shrink: f32,
- [x] pub flex_basis: Dimension,
- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow
- [x] pub overflow: Overflow, ---> kinda implemented... taffy doesnt have support for directional overflow
- [x] pub align_items: AlignItems,
- [x] pub align_self: AlignSelf,

View file

@ -1,15 +1,13 @@
use stretch2 as stretch;
#[test]
fn margin_and_flex_row() {
let mut stretch = stretch::Stretch::new();
let node0 = stretch
let mut taffy = taffy::Taffy::new();
let node0 = taffy
.new_node(
stretch::style::Style {
taffy::style::Style {
flex_grow: 1f32,
margin: stretch::geometry::Rect {
start: stretch::style::Dimension::Points(10f32),
end: stretch::style::Dimension::Points(10f32),
margin: taffy::geometry::Rect {
start: taffy::style::Dimension::Points(10f32),
end: taffy::style::Dimension::Points(10f32),
..Default::default()
},
..Default::default()
@ -17,50 +15,50 @@ fn margin_and_flex_row() {
&[],
)
.unwrap();
let node = stretch
let node = taffy
.new_node(
stretch::style::Style {
size: stretch::geometry::Size {
width: stretch::style::Dimension::Points(100f32),
height: stretch::style::Dimension::Points(100f32),
taffy::style::Style {
size: taffy::geometry::Size {
width: taffy::style::Dimension::Points(100f32),
height: taffy::style::Dimension::Points(100f32),
},
..Default::default()
},
&[node0],
)
.unwrap();
stretch
.compute_layout(node, stretch::geometry::Size::undefined())
taffy
.compute_layout(node, taffy::geometry::Size::undefined())
.unwrap();
assert_eq!(stretch.layout(node).unwrap().size.width, 100f32);
assert_eq!(stretch.layout(node).unwrap().size.height, 100f32);
assert_eq!(stretch.layout(node).unwrap().location.x, 0f32);
assert_eq!(stretch.layout(node).unwrap().location.y, 0f32);
assert_eq!(stretch.layout(node0).unwrap().size.width, 80f32);
assert_eq!(stretch.layout(node0).unwrap().size.height, 100f32);
assert_eq!(stretch.layout(node0).unwrap().location.x, 10f32);
assert_eq!(stretch.layout(node0).unwrap().location.y, 0f32);
assert_eq!(taffy.layout(node).unwrap().size.width, 100f32);
assert_eq!(taffy.layout(node).unwrap().size.height, 100f32);
assert_eq!(taffy.layout(node).unwrap().location.x, 0f32);
assert_eq!(taffy.layout(node).unwrap().location.y, 0f32);
assert_eq!(taffy.layout(node0).unwrap().size.width, 80f32);
assert_eq!(taffy.layout(node0).unwrap().size.height, 100f32);
assert_eq!(taffy.layout(node0).unwrap().location.x, 10f32);
assert_eq!(taffy.layout(node0).unwrap().location.y, 0f32);
}
#[test]
fn margin_and_flex_row2() {
let mut stretch = stretch::Stretch::new();
let node0 = stretch
let mut taffy = taffy::Taffy::new();
let node0 = taffy
.new_node(
stretch::style::Style {
taffy::style::Style {
flex_grow: 1f32,
margin: stretch::geometry::Rect {
margin: taffy::geometry::Rect {
// left
start: stretch::style::Dimension::Points(10f32),
start: taffy::style::Dimension::Points(10f32),
// right?
end: stretch::style::Dimension::Points(10f32),
end: taffy::style::Dimension::Points(10f32),
// top?
// top: stretch::style::Dimension::Points(10f32),
// top: taffy::style::Dimension::Points(10f32),
// bottom?
// bottom: stretch::style::Dimension::Points(10f32),
// bottom: taffy::style::Dimension::Points(10f32),
..Default::default()
},
..Default::default()
@ -69,12 +67,12 @@ fn margin_and_flex_row2() {
)
.unwrap();
let node = stretch
let node = taffy
.new_node(
stretch::style::Style {
size: stretch::geometry::Size {
width: stretch::style::Dimension::Points(100f32),
height: stretch::style::Dimension::Points(100f32),
taffy::style::Style {
size: taffy::geometry::Size {
width: taffy::style::Dimension::Points(100f32),
height: taffy::style::Dimension::Points(100f32),
},
..Default::default()
},
@ -82,12 +80,12 @@ fn margin_and_flex_row2() {
)
.unwrap();
stretch
.compute_layout(node, stretch::geometry::Size::undefined())
taffy
.compute_layout(node, taffy::geometry::Size::undefined())
.unwrap();
assert_eq!(stretch.layout(node).unwrap().size.width, 100f32);
assert_eq!(stretch.layout(node).unwrap().size.height, 100f32);
assert_eq!(stretch.layout(node).unwrap().location.x, 0f32);
assert_eq!(stretch.layout(node).unwrap().location.y, 0f32);
assert_eq!(taffy.layout(node).unwrap().size.width, 100f32);
assert_eq!(taffy.layout(node).unwrap().size.height, 100f32);
assert_eq!(taffy.layout(node).unwrap().location.x, 0f32);
assert_eq!(taffy.layout(node).unwrap().location.y, 0f32);
}

View file

@ -178,7 +178,7 @@ fn virtual_event_from_websys_event(
})
}
"keydown" | "keypress" | "keyup" => Arc::new(KeyboardData::from(event)),
"focus" | "blur" => Arc::new(FocusData {}),
"focus" | "blur" | "focusout" | "focusin" => Arc::new(FocusData {}),
// todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
// don't have a good solution with the serialized event problem
@ -258,9 +258,9 @@ fn virtual_event_from_websys_event(
Arc::new(FormData { value, values })
}
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
"click" | "contextmenu" | "dblclick" | "doubleclick" | "drag" | "dragend" | "dragenter"
| "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown"
| "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
Arc::new(MouseData::from(event))
}
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
@ -305,6 +305,8 @@ fn event_name_from_typ(typ: &str) -> &'static str {
"keypress" => "keypress",
"keyup" => "keyup",
"focus" => "focus",
"focusout" => "focusout",
"focusin" => "focusin",
"blur" => "blur",
"change" => "change",
"input" => "input",
@ -314,6 +316,7 @@ fn event_name_from_typ(typ: &str) -> &'static str {
"click" => "click",
"contextmenu" => "contextmenu",
"doubleclick" => "doubleclick",
"dblclick" => "dblclick",
"drag" => "drag",
"dragend" => "dragend",
"dragenter" => "dragenter",
@ -374,8 +377,8 @@ fn event_name_from_typ(typ: &str) -> &'static str {
"volumechange" => "volumechange",
"waiting" => "waiting",
"toggle" => "toggle",
_ => {
panic!("unsupported event type")
a => {
panic!("unsupported event type {:?}", a);
}
}
}