You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
3.8 KiB
Rust
155 lines
3.8 KiB
Rust
use std::{
|
|
any::Any,
|
|
sync::atomic::{AtomicU64, Ordering},
|
|
};
|
|
|
|
use crate::backend::{Backend, Combine, Render, RenderResult, TargetBackend};
|
|
|
|
pub struct View<B: Backend = TargetBackend> {
|
|
id: Id,
|
|
view: SomeView<B>,
|
|
children: Option<Vec<Self>>,
|
|
}
|
|
|
|
pub enum SomeView<B: Backend> {
|
|
Native(Box<dyn NativeComponent<B>>),
|
|
Custom(Box<dyn CustomComponent<B>>),
|
|
}
|
|
|
|
pub trait Construct<B: Backend>: Sized {
|
|
type Params: Default;
|
|
|
|
fn construct(params: Self::Params) -> Self;
|
|
|
|
fn into_view(self) -> SomeView<B>;
|
|
}
|
|
|
|
pub trait ConstructGroup<B: Backend>: Construct<B> {
|
|
fn construct_group<'a, I>(params: Self::Params, children: I) -> Self
|
|
where
|
|
I: Iterator<Item = &'a View<B>> + 'a;
|
|
}
|
|
|
|
impl<B: Backend> View<B> {
|
|
pub fn new<T: Construct<B>>(params: T::Params) -> Self {
|
|
Self {
|
|
id: Id::new(),
|
|
view: T::construct(params).into_view(),
|
|
children: None,
|
|
}
|
|
}
|
|
|
|
pub fn new_group<T, I>(params: T::Params, children: I) -> Self
|
|
where
|
|
T: ConstructGroup<B>,
|
|
I: IntoIterator<Item = Self>,
|
|
{
|
|
let children: Vec<_> = children.into_iter().collect();
|
|
Self {
|
|
id: Id::new(),
|
|
view: T::construct_group(params, children.iter()).into_view(),
|
|
children: Some(children),
|
|
}
|
|
}
|
|
|
|
pub fn id(&self) -> Id {
|
|
self.id
|
|
}
|
|
}
|
|
|
|
impl<B: Backend> Render<B> for View<B> {
|
|
fn render(&self, context: &mut B::Context) -> RenderResult<B> {
|
|
match &self.view {
|
|
SomeView::Native(native) => native.render(context),
|
|
SomeView::Custom(wrapper) => todo!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Template<B: Backend = TargetBackend> {
|
|
content: Vec<View<B>>,
|
|
}
|
|
|
|
impl<B: Backend> FromIterator<View<B>> for Template<B> {
|
|
fn from_iter<T: IntoIterator<Item = View<B>>>(iter: T) -> Self {
|
|
Self {
|
|
content: iter.into_iter().collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<B: Backend> Render<B> for Template<B>
|
|
where
|
|
View<B>: Render<B>,
|
|
{
|
|
fn render(&self, context: &mut B::Context) -> RenderResult<B> {
|
|
let mut result: Option<B::Output> = None;
|
|
for view in &self.content {
|
|
if let Some(x) = view.render(context)? {
|
|
result = Some(match result {
|
|
Some(result) => result.combine(x),
|
|
None => x,
|
|
})
|
|
}
|
|
}
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
pub trait NativeComponent<B: Backend = TargetBackend>: Any + Send + Render<B> {}
|
|
|
|
pub trait NewNative<B: Backend = TargetBackend>: NativeComponent<B> + Sized {
|
|
type Params: Default;
|
|
|
|
fn new(params: Self::Params) -> Self;
|
|
}
|
|
|
|
pub trait NewNativeGroup<B: Backend = TargetBackend>: NewNative<B> {
|
|
fn new_group<'a, I>(params: Self::Params, children: I) -> Self
|
|
where
|
|
I: Iterator<Item = &'a View<B>>;
|
|
}
|
|
|
|
/// Globally unique ID for any instance of [`View`].
|
|
/// No ID will be issued twice for the entire lifetime of the application,
|
|
/// even IDs of views that have been destroyed. You shouldn't make any
|
|
/// assumptions about this type beyond that.
|
|
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
|
pub struct Id(u64);
|
|
|
|
static ID_COUNTER: AtomicU64 = AtomicU64::new(Id::SENTINEL);
|
|
|
|
impl Id {
|
|
const SENTINEL: u64 = 0;
|
|
|
|
#[allow(clippy::new_without_default)]
|
|
pub fn new() -> Self {
|
|
let id = ID_COUNTER
|
|
.fetch_add(1, Ordering::Relaxed)
|
|
.checked_add(1)
|
|
.expect("ID counter should never overflow");
|
|
|
|
Self(id)
|
|
}
|
|
|
|
pub(crate) const fn sentinel() -> Self {
|
|
Self(Self::SENTINEL)
|
|
}
|
|
|
|
pub(crate) const fn is_sentinel(&self) -> bool {
|
|
self.0 == Self::SENTINEL
|
|
}
|
|
}
|
|
|
|
pub mod custom;
|
|
pub use custom::*;
|
|
|
|
pub mod button;
|
|
pub use button::Button;
|
|
|
|
pub mod text;
|
|
pub use text::Text;
|
|
|
|
pub mod stack;
|
|
pub use stack::{HStack, VStack};
|