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.");
}