Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ repository = "https://github.com/voxell-tech/fynix"

[workspace.dependencies]
fynix = { path = "crates/fynix" }
fynix_macros = { path = "crates/fynix_macros" }
fynix_elements = { path = "crates/fynix_elements" }

rectree = { git = "https://github.com/voxell-tech/rectree", rev = "08ddf2a" }
Expand Down
1 change: 1 addition & 0 deletions crates/fynix/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ license.workspace = true
repository.workspace = true

[dependencies]
fynix_macros.workspace = true
rectree.workspace = true
hashbrown.workspace = true
field_path.workspace = true
Expand Down
32 changes: 8 additions & 24 deletions crates/fynix/src/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,39 +103,30 @@ mod tests {

use field_path::field_accessor;
use rectree::{Constraint, NodeContext, Size, Vec2};
use typeslot::TypeSlot;

use crate::element::{ElementGroup, ElementNodes};
use crate::element::{ElementBuild, ElementNodes};

use super::*;

#[derive(Default, Clone, TypeSlot)]
#[slot(ElementGroup)]
#[derive(Element, Default, Clone)]
struct Label {
pub text: String,
}

impl Element for Label {
fn new() -> Self {
Self::default()
}

impl ElementBuild for Label {
fn build(
&self,
_id: &ElementId,
constraint: Constraint,
_nodes: &mut ElementNodes,
) -> rectree::Size
where
Self: Sized,
{
) -> Size {
constraint.min
}
}

#[derive(Default, Clone, TypeSlot)]
#[slot(ElementGroup)]
#[derive(Element, Default, Clone)]
struct Vertical {
#[children]
children: Vec<ElementId>,
}

Expand All @@ -145,20 +136,13 @@ mod tests {
}
}

impl Element for Vertical {
fn new() -> Self {
Self::default()
}

impl ElementBuild for Vertical {
fn build(
&self,
_id: &ElementId,
constraint: Constraint,
nodes: &mut ElementNodes,
) -> Size
where
Self: Sized,
{
) -> Size {
let mut size = Size::ZERO;

for child in self.children.iter() {
Expand Down
135 changes: 77 additions & 58 deletions crates/fynix/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,89 @@ use crate::element::table::ElementTable;
use crate::id::{GenId, IdGenerator};
use crate::resource::Resources;

pub use fynix_macros::{Element, ElementSlot};

pub mod meta;
pub mod table;

/// Marker type for the element slot group.
#[derive(SlotGroup)]
pub struct ElementGroup;

/// Constructs a default (unstyled) instance of an element.
///
/// Derived by `#[derive(Element)]` - calls `Default::default()` unless
/// overridden with `#[element(new = my_fn)]`.
pub trait ElementNew {
fn new() -> Self
where
Self: Sized;
}

/// Enumerates the children of an element.
///
/// Derived by `#[derive(Element)]` - iterates the field tagged `#[children]`,
/// or the fn given in `#[element(children = my_fn)]`. Defaults to no children.
pub trait ElementChildren {
fn children(&self) -> impl IntoIterator<Item = &ElementId>
where
Self: Sized,
{
[]
}
}

/// Layout and rendering protocol for element types.
///
/// Implement this manually alongside `#[derive(Element)]`.
pub trait ElementBuild {
fn constrain(&self, parent_constraint: Constraint) -> Constraint {
parent_constraint
}

fn build(
&self,
id: &ElementId,
constraint: Constraint,
nodes: &mut ElementNodes,
) -> Size;

/// Paints the element's own visual layer into `painter`.
///
/// The element's world-space position and layout size can
/// be read from `metas` using `id`. Both are set by the
/// layout pass and are safe to use for rendering
/// coordinates.
///
/// Child elements are rendered by the tree walker after
/// this method returns - do not recurse into children
/// here.
///
/// The default implementation is a no-op, suitable for
/// purely structural elements that have no visual of
/// their own.
#[expect(unused_variables)]
fn render(
&self,
id: &ElementId,
painter: &mut dyn PaintSink,
metas: &ElementMetas,
) {
}
}

/// Marker trait for element types. Use `#[derive(Element)]` to implement
/// this alongside [`ElementNew`] and [`ElementChildren`] automatically.
/// Implement [`ElementBuild`] manually.
pub trait Element:
ElementNew
+ ElementChildren
+ ElementBuild
+ TypeSlot<ElementGroup>
+ 'static
{
}

/// Type-erased storage for all element instances.
///
/// Internally holds one [`ElementTable`] column per element
Expand Down Expand Up @@ -102,7 +178,7 @@ impl Elements {
/// Renders the subtree rooted at `id` into `sink`.
///
/// Each element's own visual layer is painted via
/// [`Element::render`] before its children are visited,
/// [`ElementBuild::render`] before its children are visited,
/// so parents always draw behind their children.
///
/// Layout must be complete before calling this -
Expand Down Expand Up @@ -279,63 +355,6 @@ impl<'a> Rectree for ElementTree<'a> {
}
}

/// Trait for element types.
///
/// Implement this for any type you want to add to the
/// element tree via
/// [`FynixCtx::add`](crate::ctx::FynixCtx::add). The single
/// required method, `new`, must return a default (unstyled)
/// instance.
///
/// Styles are applied immediately after construction by the
/// build context.
pub trait Element: TypeSlot<ElementGroup> + 'static {
fn new() -> Self
where
Self: Sized;

fn children(&self) -> impl IntoIterator<Item = &ElementId>
where
Self: Sized,
{
[]
}

fn constrain(&self, parent_constraint: Constraint) -> Constraint {
parent_constraint
}

fn build(
&self,
id: &ElementId,
constraint: Constraint,
nodes: &mut ElementNodes,
) -> Size;

/// Paints the element's own visual layer into `painter`.
///
/// The element's world-space position and layout size can
/// be read from `metas` using `id`. Both are set by the
/// layout pass and are safe to use for rendering
/// coordinates.
///
/// Child elements are rendered by the tree walker after
/// this method returns - do not recurse into children
/// here.
///
/// The default implementation is a no-op, suitable for
/// purely structural elements that have no visual of
/// their own.
#[expect(unused_variables)]
fn render(
&self,
id: &ElementId,
painter: &mut dyn PaintSink,
metas: &ElementMetas,
) {
}
}

/// Generational ID for element instances.
pub type ElementId = GenId<_ElementMarker>;
pub type ElementIdGenerator = IdGenerator<_ElementMarker>;
Expand Down
6 changes: 4 additions & 2 deletions crates/fynix/src/element/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,14 @@ pub fn get_dyn_element<'a, E: Element>(

/// Visits each child of an element by calling `f` for every
/// [`ElementId`] the element yields from
/// [`Element::children`].
/// [`ElementChildren::children`].
///
/// Using a visitor avoids the need to name the concrete
/// iterator type returned by [`Element::children`], which
/// iterator type returned by [`ElementChildren::children`], which
/// differs per `E` and cannot be expressed in a
/// function-pointer signature.
///
/// [`ElementChildren::children`]: super::ElementChildren::children
pub type ChildrenElementFn = fn(
table: &ElementTable,
id: &ElementId,
Expand Down
3 changes: 2 additions & 1 deletion crates/fynix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::style::{StyleId, Styles};

pub use imaging;
pub use rectree;
pub use typeslot;

pub mod ctx;
pub mod element;
Expand All @@ -27,7 +28,7 @@ mod id;
/// Initializes the Fynix framework.
///
/// Must be called before any element is added to a [`Fynix`]
/// instance. Safe to call more than once subsequent calls
/// instance. Safe to call more than once - subsequent calls
/// are no-ops.
pub fn init() {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probs want to call this in Fynix::new() instead so users don't need to be concerned with this thing.

static INITIALIZED: AtomicBool = AtomicBool::new(false);
Expand Down
Loading
Loading