Port example 07

This commit is contained in:
2026-03-22 13:12:00 -04:00
parent cf53a9f86d
commit 28e140b302
4 changed files with 610 additions and 55 deletions

View File

@@ -3,6 +3,8 @@
//! This crate is intentionally low-level. It is the substrate that a future proc-macro-driven
//! component system can expand to, not the final ergonomic authoring API.
extern crate self as ruin_app;
use std::any::{Any, TypeId, type_name};
use std::cell::{Cell as StdCell, RefCell};
use std::collections::HashMap;
@@ -496,6 +498,19 @@ pub trait Children {
fn into_views(self) -> Vec<View>;
}
#[derive(Clone, Default)]
pub struct ChildViews(Vec<View>);
impl ChildViews {
pub fn from_children(children: impl Children) -> Self {
Self(children.into_views())
}
pub fn into_vec(self) -> Vec<View> {
self.0
}
}
impl Children for () {
fn into_views(self) -> Vec<View> {
Vec::new()
@@ -514,6 +529,12 @@ impl<T: IntoView> Children for Vec<T> {
}
}
impl Children for ChildViews {
fn into_views(self) -> Vec<View> {
self.0
}
}
macro_rules! impl_children_tuple {
($($name:ident),+ $(,)?) => {
#[allow(non_camel_case_types)]
@@ -538,6 +559,25 @@ pub trait TextChildren {
fn into_text(self) -> String;
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TextValue(String);
impl TextValue {
pub fn from_text(children: impl TextChildren) -> Self {
Self(children.into_text())
}
pub fn into_string(self) -> String {
self.0
}
}
impl TextChildren for TextValue {
fn into_text(self) -> String {
self.0
}
}
impl TextChildren for &'static str {
fn into_text(self) -> String {
self.to_string()
@@ -1911,12 +1951,12 @@ fn build_focused_ancestor_chain(
pub mod prelude {
pub use crate::{
App, BlockWidget, ButtonBuilder, Children, Component, ContainerBuilder, ContextKey,
FocusScope, FontWeight, IntoBorder, IntoEdges, IntoView, Key, Memo, Mountable, Pending,
Ready, Resource, ResourceState, Result, ScrollBoxBuilder, ScrollBoxWidget, Shortcut,
ShortcutScope, Signal, TextBuilder, TextChildren, TextRole, View, WidgetRef, Window, block,
button, colors, column, component, context_provider, provide, row, scroll_box, surfaces,
text, use_context, use_effect, use_memo, use_resource, use_shortcut,
App, BlockWidget, ButtonBuilder, ChildViews, Children, Component, ContainerBuilder,
ContextKey, FocusScope, FontWeight, IntoBorder, IntoEdges, IntoView, Key, Memo, Mountable,
Pending, Ready, Resource, ResourceState, Result, ScrollBoxBuilder, ScrollBoxWidget,
Shortcut, ShortcutScope, Signal, TextBuilder, TextChildren, TextRole, TextValue, View,
WidgetRef, Window, block, button, colors, column, component, context_provider, provide,
row, scroll_box, surfaces, text, use_context, use_effect, use_memo, use_resource, use_shortcut,
use_shortcut_with_context, use_signal, use_widget_ref, use_window_title, view,
};
pub use ruin_ui::{
@@ -1949,6 +1989,15 @@ mod tests {
type Value = NamedValue;
}
#[component]
fn ContractProbe(label: TextValue, actions: ChildViews, children: ChildViews) -> impl IntoView {
column().children((
text().children(label),
row().children(actions),
block().children(children),
))
}
#[test]
fn use_context_distinguishes_marker_types_for_same_value_type() {
let seen_outer = Rc::new(RefCell::new(None::<NamedValue>));
@@ -1991,6 +2040,29 @@ mod tests {
assert_eq!(*seen_value.borrow(), Some(NamedValue("inner")));
}
#[test]
fn components_accept_child_contracts_and_child_like_slot_props() {
let render = render_with_context(Rc::new(RenderState::default()), || {
IntoView::into_view(view! {
ContractProbe(
label = "slot label",
actions = (
text().children("action a"),
text().children("action b"),
)
) {
text() { "body child" }
}
})
});
let debug = format!("{:?}", render.view.element());
assert!(debug.contains("slot label"), "{debug}");
assert!(debug.contains("action a"), "{debug}");
assert!(debug.contains("action b"), "{debug}");
assert!(debug.contains("body child"), "{debug}");
}
#[test]
fn key_dispatch_prefers_the_nearest_focused_ancestor_handler() {
let outer_id = ElementId::new(41);