diff --git a/.github/overview.png b/.github/overview.png new file mode 100644 index 000000000..34ecc9cf7 Binary files /dev/null and b/.github/overview.png differ diff --git a/.gitignore b/.gitignore index 4c4071f5b..79f56b260 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ Cargo.lock .idea snapshot_before.png snapshot_after.png -documents_example \ No newline at end of file +documents_example +bacon.toml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b8831994e..6f35be597 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,9 +24,9 @@ cargo +nightly fmt --all -- --error-on-unformatted --unstable-features Freya is split in various crates, each with it's own meaning and purpose, here is the list sorted by their importance: - `freya`: Entrypoint to the library used by end users, mainly reexports the other crates and contains the launch methods. -- `renderer`: GUI Renderer using Winit and a Skia Canvas to render the app. -- `core`: Core logic for events, DOM processing, accessibility integration and text layout measurement is located here. -- `native-core`: DOM tree-like data structure to hold all the nodes with their attribute values and registered event handlers. +- `renderer`: Provides a winit event loop based execution for the app. +- `core`: Core logic for events, DOM processing, accessibility integration, element rendering and text layout measurement is located here. +- `native-core`: DOM data structure to hold all the nodes with their attribute values and registered event handlers. - `torin`: UI layout library specifically made for Freya, although it's agnostic. - `hooks`: Various Dioxus hooks to be used in Freya apps (text editing, animation, theming, etc) - `components`: Collection of built-in Dioxus components to be used out of the box with in Freya apps (Button, Switch, Slider, Table, ScrollView, etc) @@ -38,6 +38,8 @@ Freya is split in various crates, each with it's own meaning and purpose, here i - `native-core-macro`: Just some internal macros to be used in `states` so it can be integrated with `native-core`. - `common`: Some simple utilities used across the different Freya crates. +![Overview](./.github/overview.png) + ## Examples All important examples are located in the `./examples` folder although you might also find some in the form of docs comments in the code itself. diff --git a/README.md b/README.md index 1cb7d9d80..93233e2ba 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,12 @@ [Website](https://freyaui.dev) | [Nightly Docs](https://docs.freyaui.dev/freya) | [Stable Docs](https://docs.rs/freya/latest/freya) | [Book](https://book.freyaui.dev) | [Discord](https://discord.gg/sYejxCdewG) -**Freya** is a cross-paltform GUI library for Rust powered by 🧬 [Dioxus](https://dioxuslabs.com) and 🎨 [Skia](https://skia.org/). +**Freya** is a cross-platform GUI library for Rust powered by 🧬 [Dioxus](https://dioxuslabs.com) and 🎨 [Skia](https://skia.org/). **It does not use any web tech**, check the [Differences with Dioxus](https://book.freyaui.dev/differences_with_dioxus.html). ⚠️ It's currently work in progress, but you can already play with it! You can join the [Discord](https://discord.gg/sYejxCdewG) server if you have any question or issue. -

@@ -66,12 +65,6 @@ fn app() -> Element {
-### Sponsors πŸ€— - -Thanks to my sponsors for supporting this project! πŸ˜„ - -ι«˜εΊ†δΈ° - ### Want to try it? πŸ€” πŸ‘‹ Make sure to check the [Setup guide](https://book.freyaui.dev/setup.html) first. @@ -93,19 +86,21 @@ Add Freya and Dioxus as dependencies: freya = "0.2" dioxus = { version = "0.5", features = ["macro", "hooks"], default-features = false } ``` +### Contributing πŸ§™β€β™‚οΈ + +If you are interested in contributing please make sure to have read the [Contributing](CONTRIBUTING.md) guide first! ### Features ✨ - ⛏️ Built-in **components** (button, scroll views, switch and more) -- πŸš‡ Built-in **hooks** library (animations, text editing and more) -- πŸ” Built-in **devtools** panel +- πŸš‡ Built-in **hooks** (animations, text editing and more) +- πŸ” Built-in **developer tools** (tree inspection, fps overlay) - 🧰 Built-in **headless runner** to test UI -- 🎨 **Theming** support (not extensible yet ⚠️) +- 🎨 **Theming** support - πŸ›©οΈ **Cross-platform** (Windows, Linux, MacOS) - πŸ–ΌοΈ SKSL **Shaders** support -- πŸ”„οΈ Dioxus **Hot-reload** support - πŸ“’ Multi-line **text editing** -- 🦾 Basic **Accessibility** Support (experimental ⚠️) -- 🧩Compatible with dioxus-sdk and other Dioxus renderer-agnostic libraries +- 🦾 **Accessibility** support +- 🧩 Compatible with dioxus-sdk and other Dioxus renderer-agnostic libraries ### Goals 😁 - Performant and low memory usage @@ -115,6 +110,22 @@ dioxus = { version = "0.5", features = ["macro", "hooks"], default-features = fa - Useful testing APIs - Useful and extensible built-in components and hooks +### Support πŸ€— + +If you are interested in supporting the development of this project feel free to donate to my [Github Sponsor](https://github.com/sponsors/marc2332/) page. + +Thanks to my sponsors for supporting this project! πŸ˜„ + +ι«˜εΊ†δΈ° + +### Special thanks πŸ’ͺ + +- [Jonathan Kelley](https://github.com/jkelleyrtp) and [Evan Almloff](https://github.com/ealmloff) for making [Dioxus](https://dioxuslabs.com/) and all their help, specially when I was still creating Freya. +- [Armin](https://github.com/pragmatrix) for making [rust-skia](https://github.com/rust-skia/rust-skia/) and all his help and making the favor of hosting prebuilt binaries of skia for the combo of features use by Freya. +- [geom3trik](https://github.com/geom3trik) for helping me figure out how to add incremental rendering. +- [Tropical](https://github.com/Tropix126) for this contributions to improving accessibility and rendering. +- And to the rest of contributors and anybody who gave me any kind of feedback! + ### 🀠 Projects [Valin](https://github.com/marc2332/valin) βš’οΈ is a Work-In-Progress cross-platform code editor, made with Freya πŸ¦€ and Rust, by me. diff --git a/crates/components/src/activable_route.rs b/crates/components/src/activable_route.rs index e07fbe36a..362adc2b6 100644 --- a/crates/components/src/activable_route.rs +++ b/crates/components/src/activable_route.rs @@ -5,7 +5,29 @@ use dioxus_router::{ }; use freya_hooks::ActivableRouteContext; -/// Provide a context to the inner components so they can know whether the passed route is the current router in the Router or not. +/// Sometimes you might want to know if a route is selected so you can style a specific UI element in a different way, +/// like a button with a different color. +/// To avoid cluttering your components with router-specific code you might instead want to wrap your component in an `ActivableRoute` +/// and inside your component call `use_activable_route`. +/// +/// This way, your component and all its desdendants will just know whether a route is activated or not, but not which one. +/// +/// ```rs +/// Link { +/// to: Route::Home, // Direction route +/// ActivableRoute { +/// route: Route::Home, // Activation route +/// SidebarItem { +/// // `SidebarItem` will now appear "activated" when the route is `Route::Home` +/// // `ActivableRoute` is letting it know whether `Route::Home` is enabled +/// // or not, without the need to add router-specific logic in `SidebarItem`. +/// label { +/// "Go to Hey ! πŸ‘‹" +/// } +/// }, +/// } +/// } +/// ``` #[allow(non_snake_case)] #[component] pub fn ActivableRoute( diff --git a/crates/components/src/button.rs b/crates/components/src/button.rs index 9f4e97a23..9ab309b82 100644 --- a/crates/components/src/button.rs +++ b/crates/components/src/button.rs @@ -274,6 +274,7 @@ pub fn ButtonBase( corner_radius: "{corner_radius}", background: "{background}", text_align: "center", + text_height: "disable-least-ascent", main_align: "center", cross_align: "center", {&children} diff --git a/crates/components/src/progress_bar.rs b/crates/components/src/progress_bar.rs index df797f144..c26adc492 100644 --- a/crates/components/src/progress_bar.rs +++ b/crates/components/src/progress_bar.rs @@ -76,6 +76,7 @@ pub fn ProgressBar( width: "100%", color: "{color}", max_lines: "1", + text_height: "disable-least-ascent", "{progress.floor()}%" } } diff --git a/crates/components/src/scroll_views/scroll_view.rs b/crates/components/src/scroll_views/scroll_view.rs index 126eb5da0..671c1a06e 100644 --- a/crates/components/src/scroll_views/scroll_view.rs +++ b/crates/components/src/scroll_views/scroll_view.rs @@ -159,10 +159,16 @@ pub fn ScrollView( let direction_is_vertical = direction == "vertical"; - let vertical_scrollbar_is_visible = - is_scrollbar_visible(show_scrollbar, size.inner.height, size.area.height()); - let horizontal_scrollbar_is_visible = - is_scrollbar_visible(show_scrollbar, size.inner.width, size.area.width()); + let vertical_scrollbar_is_visible = is_scrollbar_visible( + show_scrollbar, + size.inner.height.floor(), + size.area.height().floor(), + ); + let horizontal_scrollbar_is_visible = is_scrollbar_visible( + show_scrollbar, + size.inner.width.floor(), + size.area.width().floor(), + ); let (container_width, content_width) = get_container_size( &width, diff --git a/crates/components/src/tile.rs b/crates/components/src/tile.rs index ed0222806..c4563ad0d 100644 --- a/crates/components/src/tile.rs +++ b/crates/components/src/tile.rs @@ -43,8 +43,6 @@ pub fn Tile( onselect: Option>, /// Theme override. theme: Option, - - a11y_name: Option, ) -> Element { let mut status = use_signal(TileStatus::default); let platform = use_platform(); diff --git a/crates/core/src/layout.rs b/crates/core/src/layout.rs index 63f5f6a18..b81ff25b9 100644 --- a/crates/core/src/layout.rs +++ b/crates/core/src/layout.rs @@ -35,22 +35,23 @@ pub fn process_layout( let mut compositor_dirty_area = fdom.compositor_dirty_area(); let mut buffer = layout.dirty.iter().copied().collect_vec(); while let Some(node_id) = buffer.pop() { - if let Some(area) = Compositor::get_drawing_area(node_id, &layout, rdom, scale_factor) { - // Unite the invalidated area with the dirty area - compositor_dirty_area.unite_or_insert(&area); + if let Some(node) = rdom.get(node_id) { + if let Some(area) = + Compositor::get_drawing_area(node_id, &layout, rdom, scale_factor) + { + // Unite the invalidated area with the dirty area + compositor_dirty_area.unite_or_insert(&area); - // Mark these elements as dirty for the compositor - compositor_dirty_nodes.insert(node_id); + // Mark these elements as dirty for the compositor + compositor_dirty_nodes.insert(node_id); - // Continue iterating in the children of this node - if let Some(node) = rdom.get(node_id) { // Mark as invalidated this node as its layout has changed if node.get_accessibility_id().is_some() { dirty_accessibility_tree.add_or_update(node_id); } - - buffer.extend(node.child_ids()); } + // Continue iterating in the children of this node + buffer.extend(node.child_ids()); } } let root_id = fdom.rdom().root_id(); diff --git a/crates/core/src/render/compositor.rs b/crates/core/src/render/compositor.rs index e7a1e503c..f133b88ee 100644 --- a/crates/core/src/render/compositor.rs +++ b/crates/core/src/render/compositor.rs @@ -257,7 +257,7 @@ mod test { use itertools::sorted; fn run_compositor( - utils: &TestingHandler, + utils: &TestingHandler<()>, compositor: &mut Compositor, ) -> (Layers, Layers, usize) { let sdom = utils.sdom(); diff --git a/crates/core/src/render/utils/label.rs b/crates/core/src/render/utils/label.rs index e1d1619b2..1ed471caa 100644 --- a/crates/core/src/render/utils/label.rs +++ b/crates/core/src/render/utils/label.rs @@ -21,12 +21,14 @@ pub fn create_label( paragraph_style.set_text_align(font_style.text_align); paragraph_style.set_max_lines(font_style.max_lines); paragraph_style.set_replace_tab_characters(true); + paragraph_style.set_text_height_behavior(font_style.text_height); if let Some(ellipsis) = font_style.text_overflow.get_ellipsis() { paragraph_style.set_ellipsis(ellipsis); } - let text_style = font_style.text_style(default_font_family, scale_factor); + let text_style = + font_style.text_style(default_font_family, scale_factor, font_style.text_height); paragraph_style.set_text_style(&text_style); let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection); diff --git a/crates/core/src/render/utils/paragraph.rs b/crates/core/src/render/utils/paragraph.rs index 6e8754a28..c4b84dabf 100644 --- a/crates/core/src/render/utils/paragraph.rs +++ b/crates/core/src/render/utils/paragraph.rs @@ -34,6 +34,7 @@ pub fn create_paragraph( paragraph_style.set_text_align(font_style.text_align); paragraph_style.set_max_lines(font_style.max_lines); paragraph_style.set_replace_tab_characters(true); + paragraph_style.set_text_height_behavior(font_style.text_height); if let Some(ellipsis) = font_style.text_overflow.get_ellipsis() { paragraph_style.set_ellipsis(ellipsis); @@ -41,7 +42,8 @@ pub fn create_paragraph( let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection); - let text_style = font_style.text_style(default_font_family, scale_factor); + let text_style = + font_style.text_style(default_font_family, scale_factor, font_style.text_height); paragraph_builder.push_style(&text_style); for text_span in node.children() { @@ -52,8 +54,12 @@ pub fn create_paragraph( let text_nodes = text_span.children(); let text_node = *text_nodes.first().unwrap(); let text_node_type = &*text_node.node_type(); - let font_style = text_span.get::().unwrap(); - let text_style = font_style.text_style(default_font_family, scale_factor); + let text_font_style = text_span.get::().unwrap(); + let text_style = text_font_style.text_style( + default_font_family, + scale_factor, + font_style.text_height, + ); paragraph_builder.push_style(&text_style); if let NodeType::Text(text) = text_node_type { diff --git a/crates/elements/src/_docs/attributes/text_height.md b/crates/elements/src/_docs/attributes/text_height.md new file mode 100644 index 000000000..270d7450a --- /dev/null +++ b/crates/elements/src/_docs/attributes/text_height.md @@ -0,0 +1,22 @@ +Specify the text height behavior. + +Accepted values: + +- `disable-all` (default) +- `all` +- `disable-first-ascent` +- `disable-least-ascent` + +### Example + +```rust, no_run +# use freya::prelude::*; +fn app() -> Element { + rsx!( + label { + text_height: "disable-all", + "Hello, World!" + } + ) +} +``` \ No newline at end of file diff --git a/crates/elements/src/definitions.rs b/crates/elements/src/definitions.rs index abeb5c600..4616f1ae4 100644 --- a/crates/elements/src/definitions.rs +++ b/crates/elements/src/definitions.rs @@ -172,8 +172,7 @@ builder_constructors! { /// } /// ``` rect { - #[doc = include_str!("_docs/attributes/padding.md")] - padding: String, + // Layout #[doc = include_str!("_docs/attributes/width_height.md")] height: String, width: String, @@ -183,17 +182,45 @@ builder_constructors! { #[doc = include_str!("_docs/attributes/max_width_max_height.md")] max_height: String, max_width: String, + #[doc = include_str!("_docs/attributes/margin.md")] + margin: String, + #[doc = include_str!("_docs/attributes/padding.md")] + padding: String, + #[doc = include_str!("_docs/attributes/position.md")] + position: String, + position_top: String, + position_right: String, + position_bottom: String, + position_left: String, + layer: String, + + // Children layout + #[doc = include_str!("_docs/attributes/direction.md")] + direction: String, + #[doc = include_str!("_docs/attributes/content.md")] + content: String, + #[doc = include_str!("_docs/attributes/main_align_cross_align.md")] + main_align: String, + cross_align: String, + #[doc = include_str!("_docs/attributes/spacing.md")] + spacing: String, + #[doc = include_str!("_docs/attributes/overflow.md")] + overflow: String, + offset_x: String, + offset_y: String, + + // Style #[doc = include_str!("_docs/attributes/background.md")] background: String, #[doc = include_str!("_docs/attributes/border.md")] border: String, - #[doc = include_str!("_docs/attributes/direction.md")] - direction: String, #[doc = include_str!("_docs/attributes/shadow.md")] shadow: String, #[doc = include_str!("_docs/attributes/corner.md")] corner_radius: String, corner_smoothing: String, + + // Font style #[doc = include_str!("_docs/attributes/color.md")] color: String, #[doc = include_str!("_docs/attributes/font_size.md")] @@ -206,39 +233,41 @@ builder_constructors! { font_weight: String, #[doc = include_str!("_docs/attributes/font_width.md")] font_width: String, - #[doc = include_str!("_docs/attributes/main_align_cross_align.md")] - main_align: String, - cross_align: String, #[doc = include_str!("_docs/attributes/text_align.md")] text_align: String, + #[doc = include_str!("_docs/attributes/line_height.md")] + line_height: String, + #[doc = include_str!("_docs/attributes/text_shadow.md")] + text_shadow: String, + #[doc = include_str!("_docs/attributes/max_lines.md")] + max_lines: String, + #[doc = include_str!("_docs/attributes/decoration.md")] + decoration: String, + #[doc = include_str!("_docs/attributes/decoration_style.md")] + decoration_style: String, + #[doc = include_str!("_docs/attributes/decoration_color.md")] + decoration_color: String, + #[doc = include_str!("_docs/attributes/text_overflow.md")] + text_overflow: String, + #[doc = include_str!("_docs/attributes/letter_spacing.md")] + letter_spacing: String, + #[doc = include_str!("_docs/attributes/word_spacing.md")] + word_spacing: String, + #[doc = include_str!("_docs/attributes/text_height.md")] + text_height: String, + + // Transform #[doc = include_str!("_docs/attributes/rotate.md")] rotate: String, - #[doc = include_str!("_docs/attributes/overflow.md")] - overflow: String, - #[doc = include_str!("_docs/attributes/margin.md")] - margin: String, - #[doc = include_str!("_docs/attributes/position.md")] - position: String, - position_top: String, - position_right: String, - position_bottom: String, - position_left: String, #[doc = include_str!("_docs/attributes/opacity.md")] opacity: String, - #[doc = include_str!("_docs/attributes/content.md")] - content: String, - #[doc = include_str!("_docs/attributes/line_height.md")] - line_height: String, - #[doc = include_str!("_docs/attributes/spacing.md")] - spacing: String, + // Reference canvas_reference: String, - layer: String, - offset_y: String, - offset_x: String, reference: Reference, cursor_reference: CursorReference, + // Accessibility a11y_id: String, a11y_focusable: String, a11y_auto_focus: String, @@ -323,15 +352,33 @@ builder_constructors! { /// } /// ``` label { - #[doc = include_str!("_docs/attributes/color.md")] - color: String, - #[doc = include_str!("_docs/attributes/text_shadow.md")] - text_shadow: String, + // Layout #[doc = include_str!("_docs/attributes/width_height.md")] height: String, width: String, + #[doc = include_str!("_docs/attributes/min_width_min_height.md")] + min_height: String, + min_width: String, + #[doc = include_str!("_docs/attributes/max_width_max_height.md")] + max_height: String, + max_width: String, + #[doc = include_str!("_docs/attributes/margin.md")] + margin: String, + #[doc = include_str!("_docs/attributes/position.md")] + position: String, + position_top: String, + position_right: String, + position_bottom: String, + position_left: String, + layer: String, + + // Children layout #[doc = include_str!("_docs/attributes/main_align_cross_align.md")] main_align: String, + + // Font style + #[doc = include_str!("_docs/attributes/color.md")] + color: String, #[doc = include_str!("_docs/attributes/font_size.md")] font_size: String, #[doc = include_str!("_docs/attributes/font_family.md")] @@ -344,16 +391,12 @@ builder_constructors! { font_width: String, #[doc = include_str!("_docs/attributes/text_align.md")] text_align: String, - #[doc = include_str!("_docs/attributes/max_lines.md")] - max_lines: String, - #[doc = include_str!("_docs/attributes/rotate.md")] - rotate: String, #[doc = include_str!("_docs/attributes/line_height.md")] line_height: String, - #[doc = include_str!("_docs/attributes/letter_spacing.md")] - letter_spacing: String, - #[doc = include_str!("_docs/attributes/word_spacing.md")] - word_spacing: String, + #[doc = include_str!("_docs/attributes/text_shadow.md")] + text_shadow: String, + #[doc = include_str!("_docs/attributes/max_lines.md")] + max_lines: String, #[doc = include_str!("_docs/attributes/decoration.md")] decoration: String, #[doc = include_str!("_docs/attributes/decoration_style.md")] @@ -362,13 +405,20 @@ builder_constructors! { decoration_color: String, #[doc = include_str!("_docs/attributes/text_overflow.md")] text_overflow: String, - #[doc = include_str!("_docs/attributes/margin.md")] - margin: String, + #[doc = include_str!("_docs/attributes/letter_spacing.md")] + letter_spacing: String, + #[doc = include_str!("_docs/attributes/word_spacing.md")] + word_spacing: String, + #[doc = include_str!("_docs/attributes/text_height.md")] + text_height: String, + + // Transform + #[doc = include_str!("_docs/attributes/rotate.md")] + rotate: String, #[doc = include_str!("_docs/attributes/opacity.md")] opacity: String, - layer: String, - + // Accessibility a11y_id: String, a11y_auto_focus: String, a11y_focusable: String, @@ -460,6 +510,7 @@ builder_constructors! { /// } /// ``` paragraph { + // Layout #[doc = include_str!("_docs/attributes/width_height.md")] height: String, width: String, @@ -469,12 +520,23 @@ builder_constructors! { #[doc = include_str!("_docs/attributes/max_width_max_height.md")] max_height: String, max_width: String, + #[doc = include_str!("_docs/attributes/margin.md")] + margin: String, + #[doc = include_str!("_docs/attributes/position.md")] + position: String, + position_top: String, + position_right: String, + position_bottom: String, + position_left: String, + layer: String, + + // Children layout #[doc = include_str!("_docs/attributes/main_align_cross_align.md")] main_align: String, - #[doc = include_str!("_docs/attributes/text_align.md")] - text_align: String, - #[doc = include_str!("_docs/attributes/rotate.md")] - rotate: String, + + // Font style + #[doc = include_str!("_docs/attributes/color.md")] + color: String, #[doc = include_str!("_docs/attributes/font_size.md")] font_size: String, #[doc = include_str!("_docs/attributes/font_family.md")] @@ -485,36 +547,45 @@ builder_constructors! { font_weight: String, #[doc = include_str!("_docs/attributes/font_width.md")] font_width: String, + #[doc = include_str!("_docs/attributes/text_align.md")] + text_align: String, #[doc = include_str!("_docs/attributes/line_height.md")] line_height: String, - #[doc = include_str!("_docs/attributes/letter_spacing.md")] - letter_spacing: String, - #[doc = include_str!("_docs/attributes/word_spacing.md")] - word_spacing: String, + #[doc = include_str!("_docs/attributes/text_shadow.md")] + text_shadow: String, + #[doc = include_str!("_docs/attributes/max_lines.md")] + max_lines: String, #[doc = include_str!("_docs/attributes/decoration.md")] decoration: String, #[doc = include_str!("_docs/attributes/decoration_style.md")] decoration_style: String, #[doc = include_str!("_docs/attributes/decoration_color.md")] + decoration_color: String, + #[doc = include_str!("_docs/attributes/text_overflow.md")] text_overflow: String, - #[doc = include_str!("_docs/attributes/overflow.md")] - overflow: String, - #[doc = include_str!("_docs/attributes/margin.md")] - margin: String, + #[doc = include_str!("_docs/attributes/letter_spacing.md")] + letter_spacing: String, + #[doc = include_str!("_docs/attributes/word_spacing.md")] + word_spacing: String, + #[doc = include_str!("_docs/attributes/text_height.md")] + text_height: String, + + // Transform + #[doc = include_str!("_docs/attributes/rotate.md")] + rotate: String, #[doc = include_str!("_docs/attributes/opacity.md")] opacity: String, - layer: String, + // Text Editing cursor_index: String, - max_lines: String, cursor_color: String, cursor_mode: String, cursor_id: String, - highlights: String, highlight_color: String, highlight_mode: String, + // Accessibility a11y_id: String, a11y_focusable: String, a11y_auto_focus: String, @@ -586,11 +657,10 @@ builder_constructors! { }; /// `text` element is simply a text span used for the `paragraph` element. text { + // Font style #[doc = include_str!("_docs/attributes/color.md")] color: String, #[doc = include_str!("_docs/attributes/font_size.md")] - text_shadow: String, - #[doc = include_str!("_docs/attributes/font_size.md")] font_size: String, #[doc = include_str!("_docs/attributes/font_family.md")] font_family: String, @@ -600,21 +670,27 @@ builder_constructors! { font_weight: String, #[doc = include_str!("_docs/attributes/font_width.md")] font_width: String, + #[doc = include_str!("_docs/attributes/text_align.md")] + text_align: String, #[doc = include_str!("_docs/attributes/line_height.md")] line_height: String, - #[doc = include_str!("_docs/attributes/letter_spacing.md")] - letter_spacing: String, - #[doc = include_str!("_docs/attributes/word_spacing.md")] - word_spacing: String, + #[doc = include_str!("_docs/attributes/text_shadow.md")] + text_shadow: String, #[doc = include_str!("_docs/attributes/decoration.md")] decoration: String, #[doc = include_str!("_docs/attributes/decoration_style.md")] decoration_style: String, #[doc = include_str!("_docs/attributes/decoration_color.md")] decoration_color: String, + #[doc = include_str!("_docs/attributes/letter_spacing.md")] + letter_spacing: String, + #[doc = include_str!("_docs/attributes/word_spacing.md")] + word_spacing: String, }; /// `image` element let's you show an image. /// + /// For dynamic Images you may use `dynamic_bytes`. + /// /// ### Example /// /// ```rust, ignore, no_run @@ -626,27 +702,47 @@ builder_constructors! { /// rsx!( /// image { /// image_data: image_data, - /// width: "{size}", - /// height: "{size}", + /// width: "100%", // You must specify size otherwhise it will default to 0 + /// height: "100%", /// } /// ) /// } /// ``` image { - #[doc = include_str!("_docs/attributes/width_height.md")] + // Layout + #[doc = include_str!("_docs/attributes/width_height.md")] height: String, width: String, + #[doc = include_str!("_docs/attributes/min_width_min_height.md")] + min_height: String, + min_width: String, + #[doc = include_str!("_docs/attributes/max_width_max_height.md")] + max_height: String, + max_width: String, + #[doc = include_str!("_docs/attributes/margin.md")] + margin: String, + #[doc = include_str!("_docs/attributes/position.md")] + position: String, + position_top: String, + position_right: String, + position_bottom: String, + position_left: String, + layer: String, + + // Transform #[doc = include_str!("_docs/attributes/rotate.md")] rotate: String, #[doc = include_str!("_docs/attributes/opacity.md")] opacity: String, + // Image image_data: String, image_reference: String, + // Accessibility a11y_id: String, - a11y_auto_focus: String, a11y_focusable: String, + a11y_auto_focus: String, a11y_name: String, a11y_description: String, a11y_value: String, @@ -715,8 +811,7 @@ builder_constructors! { }; /// `svg` element let's you display SVG code. /// - /// You will need to use the [`dynamic_bytes`](https://docs.freyaui.dev/freya/prelude/fn.dynamic_bytes.html) - /// to transform the bytes into data the element can recognize. + /// For dynamic SVGs you may use `dynamic_bytes`. /// /// ### Example /// @@ -725,28 +820,48 @@ builder_constructors! { /// static FERRIS: &[u8] = include_bytes!("./ferris.svg"); /// /// fn app() -> Element { - /// let ferris = dynamic_bytes(FERRIS); + /// let ferris = static_bytes(FERRIS); /// rsx!( /// svg { /// svg_data: ferris, + /// width: "100%", // You must specify size otherwhise it will default to 0 + /// height: "100%", /// } /// ) /// } /// ``` svg { - #[doc = include_str!("_docs/attributes/margin.md")] - margin: String, - #[doc = include_str!("_docs/attributes/width_height.md")] + // Layout + #[doc = include_str!("_docs/attributes/width_height.md")] height: String, width: String, + #[doc = include_str!("_docs/attributes/min_width_min_height.md")] + min_height: String, + min_width: String, + #[doc = include_str!("_docs/attributes/max_width_max_height.md")] + max_height: String, + max_width: String, + #[doc = include_str!("_docs/attributes/margin.md")] + margin: String, + #[doc = include_str!("_docs/attributes/position.md")] + position: String, + position_top: String, + position_right: String, + position_bottom: String, + position_left: String, + layer: String, + + // Transform #[doc = include_str!("_docs/attributes/rotate.md")] rotate: String, #[doc = include_str!("_docs/attributes/opacity.md")] opacity: String, + // Svg svg_data: String, svg_content: String, + // Accessibility a11y_id: String, a11y_focusable: String, a11y_auto_focus: String, diff --git a/crates/engine/src/mocked.rs b/crates/engine/src/mocked.rs index cfeb00df0..3ba169335 100644 --- a/crates/engine/src/mocked.rs +++ b/crates/engine/src/mocked.rs @@ -613,6 +613,18 @@ impl TextStyle { pub fn set_placeholder(&mut self) -> &mut Self { unimplemented!("This is mocked") } + + pub fn set_height_behavior(&mut self, behavior: TextHeightBehavior) { + unimplemented!("This is mocked") + } +} + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum TextHeightBehavior { + All = 0, + DisableFirstAscent = 1, + DisableLastDescent = 2, + DisableAll = 3, } pub struct Typeface; @@ -988,8 +1000,6 @@ impl From<&FontCollection> for FontCollection { pub struct StrutStyle; -pub struct TextHeightBehavior; - #[repr(i32)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub enum TextDirection { diff --git a/crates/hooks/src/use_activable_route.rs b/crates/hooks/src/use_activable_route.rs index 35c5a60ec..024b8fea8 100644 --- a/crates/hooks/src/use_activable_route.rs +++ b/crates/hooks/src/use_activable_route.rs @@ -13,6 +13,7 @@ impl ActivableRouteContext { } } +/// Consume an activable Route, use in combination with `ActivableRoute`. pub fn use_activable_route() -> bool { let ctx = try_use_context::(); diff --git a/crates/hooks/src/use_init_native_platform.rs b/crates/hooks/src/use_init_native_platform.rs index aa267f673..ce98be2d1 100644 --- a/crates/hooks/src/use_init_native_platform.rs +++ b/crates/hooks/src/use_init_native_platform.rs @@ -118,7 +118,7 @@ mod test { let mut utils = launch_test_with_config( use_focus_app, - TestingConfig { + TestingConfig::<()> { size: (100.0, 100.0).into(), ..TestingConfig::default() }, @@ -173,7 +173,7 @@ mod test { let mut utils = launch_test_with_config( use_focus_app, - TestingConfig { + TestingConfig::<()> { size: (100.0, 100.0).into(), ..TestingConfig::default() }, @@ -233,7 +233,7 @@ mod test { let mut utils = launch_test_with_config( use_focus_app, - TestingConfig { + TestingConfig::<()> { size: (100.0, 100.0).into(), ..TestingConfig::default() }, diff --git a/crates/hooks/src/use_node.rs b/crates/hooks/src/use_node.rs index 366541eab..fb5716dab 100644 --- a/crates/hooks/src/use_node.rs +++ b/crates/hooks/src/use_node.rs @@ -90,7 +90,7 @@ mod test { let mut utils = launch_test_with_config( use_node_app, - TestingConfig { + TestingConfig::<()> { size: (500.0, 800.0).into(), ..TestingConfig::default() }, diff --git a/crates/hooks/tests/use_focus.rs b/crates/hooks/tests/use_focus.rs index d62545d7a..f6552a842 100644 --- a/crates/hooks/tests/use_focus.rs +++ b/crates/hooks/tests/use_focus.rs @@ -37,7 +37,7 @@ pub async fn track_focus() { let mut utils = launch_test_with_config( use_focus_app, - TestingConfig { + TestingConfig::<()> { size: (100.0, 100.0).into(), ..TestingConfig::default() }, @@ -117,7 +117,7 @@ pub async fn block_focus() { let mut utils = launch_test_with_config( use_focus_app, - TestingConfig { + TestingConfig::<()> { size: (100.0, 100.0).into(), ..TestingConfig::default() }, diff --git a/crates/hooks/tests/use_platform_information.rs b/crates/hooks/tests/use_platform_information.rs index c3034a63e..4bd7bbc61 100644 --- a/crates/hooks/tests/use_platform_information.rs +++ b/crates/hooks/tests/use_platform_information.rs @@ -15,7 +15,7 @@ async fn window_size() { let mut utils = launch_test_with_config( use_animation_app, - TestingConfig { + TestingConfig::<()> { size: (333.0, 190.0).into(), ..TestingConfig::default() }, diff --git a/crates/native-core/src/attributes.rs b/crates/native-core/src/attributes.rs index 1e7f2713a..59526e20c 100644 --- a/crates/native-core/src/attributes.rs +++ b/crates/native-core/src/attributes.rs @@ -33,6 +33,7 @@ pub enum AttributeName { DecorationColor, DecorationStyle, TextOverflow, + TextHeight, Rotate, Overflow, Margin, @@ -253,6 +254,7 @@ impl FromStr for AttributeName { "decoration_color" => Ok(AttributeName::DecorationColor), "decoration_style" => Ok(AttributeName::DecorationStyle), "text_overflow" => Ok(AttributeName::TextOverflow), + "text_height" => Ok(AttributeName::TextHeight), "rotate" => Ok(AttributeName::Rotate), "overflow" => Ok(AttributeName::Overflow), "margin" => Ok(AttributeName::Margin), diff --git a/crates/state/src/font_style.rs b/crates/state/src/font_style.rs index 81005d484..18c033488 100644 --- a/crates/state/src/font_style.rs +++ b/crates/state/src/font_style.rs @@ -26,6 +26,7 @@ use crate::{ ExtSplit, Parse, ParseAttribute, + TextHeight, TextOverflow, }; @@ -45,10 +46,16 @@ pub struct FontStyleState { pub text_align: TextAlign, pub max_lines: Option, pub text_overflow: TextOverflow, + pub text_height: TextHeightBehavior, } impl FontStyleState { - pub fn text_style(&self, default_font_family: &[String], scale_factor: f32) -> TextStyle { + pub fn text_style( + &self, + default_font_family: &[String], + scale_factor: f32, + paragraph_text_height: TextHeightBehavior, + ) -> TextStyle { let mut text_style = TextStyle::new(); let mut font_family = self.font_family.clone(); @@ -66,6 +73,11 @@ impl FontStyleState { .set_word_spacing(self.word_spacing) .set_letter_spacing(self.letter_spacing); + if paragraph_text_height.needs_custom_height() { + text_style.set_height_override(true); + text_style.set_half_leading(true); + } + if let Some(line_height) = self.line_height { text_style.set_height_override(true).set_height(line_height); } @@ -102,6 +114,7 @@ impl Default for FontStyleState { text_align: TextAlign::default(), max_lines: None, text_overflow: TextOverflow::default(), + text_height: TextHeightBehavior::DisableAll, } } } @@ -234,6 +247,14 @@ impl ParseAttribute for FontStyleState { } } } + AttributeName::TextHeight => { + let value = attr.value.as_text(); + if let Some(value) = value { + if let Ok(text_height) = TextHeightBehavior::parse(value) { + self.text_height = text_height; + } + } + } _ => {} } @@ -267,6 +288,7 @@ impl State for FontStyleState { AttributeName::DecorationColor, AttributeName::DecorationStyle, AttributeName::TextOverflow, + AttributeName::TextHeight, ])); fn update<'a>( diff --git a/crates/state/src/values/mod.rs b/crates/state/src/values/mod.rs index 7cabda902..83d564b48 100644 --- a/crates/state/src/values/mod.rs +++ b/crates/state/src/values/mod.rs @@ -16,6 +16,7 @@ mod overflow; mod position; mod shadow; mod size; +mod text_height; mod text_shadow; pub use border::*; @@ -30,3 +31,4 @@ pub use highlight::*; pub use overflow::*; pub use shadow::*; pub use size::*; +pub use text_height::*; diff --git a/crates/state/src/values/text_height.rs b/crates/state/src/values/text_height.rs new file mode 100644 index 000000000..4977b383d --- /dev/null +++ b/crates/state/src/values/text_height.rs @@ -0,0 +1,31 @@ +use freya_engine::prelude::*; + +use crate::{ + Parse, + ParseError, +}; + +impl Parse for TextHeightBehavior { + fn parse(value: &str) -> Result { + match value { + "all" => Ok(TextHeightBehavior::All), + "disable-first-ascent" => Ok(TextHeightBehavior::DisableFirstAscent), + "disable-least-ascent" => Ok(TextHeightBehavior::DisableLastDescent), + "disable-all" => Ok(TextHeightBehavior::DisableAll), + _ => Err(ParseError), + } + } +} + +pub trait TextHeight { + fn needs_custom_height(&self) -> bool; +} + +impl TextHeight for TextHeightBehavior { + fn needs_custom_height(&self) -> bool { + matches!( + self, + Self::All | Self::DisableFirstAscent | Self::DisableLastDescent + ) + } +} diff --git a/crates/testing/src/config.rs b/crates/testing/src/config.rs index 3b9d8d8bc..009c858a9 100644 --- a/crates/testing/src/config.rs +++ b/crates/testing/src/config.rs @@ -3,24 +3,26 @@ use std::time::Duration; use torin::geometry::Size2D; /// Configuration for [`crate::test_handler::TestingHandler`]. -#[derive(Clone, Copy)] -pub struct TestingConfig { +#[derive(Clone)] +pub struct TestingConfig { pub vdom_timeout: Duration, pub size: Size2D, pub event_loop_ticker: bool, + pub state: Option, } -impl Default for TestingConfig { +impl Default for TestingConfig { fn default() -> Self { Self { vdom_timeout: Duration::from_millis(16), size: Size2D::from((500.0, 500.0)), event_loop_ticker: true, + state: None, } } } -impl TestingConfig { +impl TestingConfig { pub fn new() -> Self { TestingConfig::default() } diff --git a/crates/testing/src/launch.rs b/crates/testing/src/launch.rs index 225dc4e64..7fad171c2 100644 --- a/crates/testing/src/launch.rs +++ b/crates/testing/src/launch.rs @@ -32,12 +32,15 @@ use crate::{ /// Run a Component in a headless testing environment. /// /// Default size is `500x500`. -pub fn launch_test(root: AppComponent) -> TestingHandler { +pub fn launch_test(root: AppComponent) -> TestingHandler<()> { launch_test_with_config(root, TestingConfig::default()) } /// Run a Component in a headless testing environment -pub fn launch_test_with_config(root: AppComponent, config: TestingConfig) -> TestingHandler { +pub fn launch_test_with_config( + root: AppComponent, + config: TestingConfig, +) -> TestingHandler { let vdom = with_accessibility(root); let fdom = FreyaDOM::default(); let sdom = SafeDOM::new(fdom); diff --git a/crates/testing/src/lib.rs b/crates/testing/src/lib.rs index 401d246b6..a92b65032 100644 --- a/crates/testing/src/lib.rs +++ b/crates/testing/src/lib.rs @@ -139,7 +139,7 @@ //! //! let mut utils = launch_test_with_config( //! our_component, -//! TestingConfig { +//! TestingConfig::<()> { //! size: (500.0, 800.0).into(), //! ..TestingConfig::default() //! }, diff --git a/crates/testing/src/test_handler.rs b/crates/testing/src/test_handler.rs index 3b4876622..dd69962d9 100644 --- a/crates/testing/src/test_handler.rs +++ b/crates/testing/src/test_handler.rs @@ -15,6 +15,7 @@ use freya_core::prelude::{ use freya_engine::prelude::{ raster_n32_premul, Color, + Data, EncodedImageFormat, FontCollection, FontMgr, @@ -53,7 +54,7 @@ use crate::{ }; /// Manages the lifecycle of your tests. -pub struct TestingHandler { +pub struct TestingHandler { pub(crate) vdom: VirtualDom, pub(crate) utils: TestUtils, pub(crate) event_emitter: EventEmitter, @@ -67,12 +68,12 @@ pub struct TestingHandler { pub(crate) font_collection: FontCollection, pub(crate) font_mgr: FontMgr, pub(crate) accessibility_tree: SharedAccessibilityTree, - pub(crate) config: TestingConfig, + pub(crate) config: TestingConfig, pub(crate) ticker_sender: broadcast::Sender<()>, pub(crate) cursor_icon: CursorIcon, } -impl TestingHandler { +impl TestingHandler { /// Init the DOM. pub(crate) fn init_dom(&mut self) { self.provide_vdom_contexts(); @@ -82,7 +83,7 @@ impl TestingHandler { } /// Get a mutable reference to the current [`TestingConfig`]. - pub fn config(&mut self) -> &mut TestingConfig { + pub fn config(&mut self) -> &mut TestingConfig { &mut self.config } @@ -101,6 +102,10 @@ impl TestingHandler { }; self.vdom .insert_any_root_context(Box::new(accessibility_generator)); + + if let Some(state) = self.config.state.clone() { + self.vdom.insert_any_root_context(Box::new(state)); + } } /// Wait and apply new changes @@ -299,8 +304,8 @@ impl TestingHandler { self.utils.sdom() } - /// Render the app into a canvas and save it into a file. - pub fn save_snapshot(&mut self, snapshot_path: impl Into) { + /// Render the app into a canvas and make a snapshot of it. + pub fn create_snapshot(&mut self) -> Data { let fdom = self.utils.sdom.get(); let (width, height) = self.config.size.to_i32().to_tuple(); @@ -341,16 +346,19 @@ impl TestingHandler { // Capture snapshot let image = surface.image_snapshot(); let mut context = surface.direct_context(); - let snapshot_data = image + image .encode(context.as_mut(), EncodedImageFormat::PNG, None) - .expect("Failed to encode the snapshot."); + .expect("Failed to encode the snapshot.") + } - // Save snapshot + /// Render the app into a canvas and save it into a file. + pub fn save_snapshot(&mut self, snapshot_path: impl Into) { let mut snapshot_file = File::create(snapshot_path.into()).expect("Failed to create the snapshot file."); - let snapshot_bytes = snapshot_data.as_bytes(); + let snapshot_data = self.create_snapshot(); + snapshot_file - .write_all(snapshot_bytes) + .write_all(&snapshot_data) .expect("Failed to save the snapshot file."); }