use std::rc::Rc;
use geometry_core::{Rect, Transform};
use layout_core::{AvailableSpace, LayoutError, LayoutStyle, SizeDimension};
use platform_core::Event;
use renderer_core::{Color, RenderBackend, RendererError};
use renderer_hardware::HardwareRenderer;
use renderer_hardware::gpu::wgpu;
use ui_core::{
Component, ComponentList, EventResult, LayoutItem, NodeId, RenderNode, Surface, compute_layout,
mark_dirty, new_container,
};
#[derive(Debug)]
pub enum TextureUiError {
Layout(LayoutError),
Renderer(RendererError),
}
impl std::fmt::Display for TextureUiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Layout(e) => write!(f, "texture UI layout failed: {e}"),
Self::Renderer(e) => write!(f, "texture UI renderer failed: {e:?}"),
}
}
}
impl std::error::Error for TextureUiError {}
impl From<LayoutError> for TextureUiError {
fn from(e: LayoutError) -> Self {
Self::Layout(e)
}
}
impl From<RendererError> for TextureUiError {
fn from(e: RendererError) -> Self {
Self::Renderer(e)
}
}
struct Root {
content: Box<dyn LayoutItem>,
}
impl Component for Root {
fn view(&self) -> RenderNode {
self.content.view()
}
fn on_event(&mut self, event: &Event) -> EventResult {
self.content.on_event(event)
}
fn debug_name(&self) -> &'static str {
"TextureUiRoot"
}
}
pub struct TextureUi {
renderer: HardwareRenderer<platform_headless::HeadlessWindow>,
tree: ComponentList,
root: NodeId,
width: u32,
height: u32,
scale: f32,
clear_color: Color,
placement: Transform,
surface: Rc<Surface>,
}
impl TextureUi {
pub fn new(
target: wgpu::Texture,
scale: f32,
build: impl FnOnce() -> Result<Box<dyn LayoutItem>, LayoutError>,
) -> Result<Self, TextureUiError> {
Self::with_fonts(target, scale, Vec::new(), Vec::new(), build)
}
pub fn with_fonts(
target: wgpu::Texture,
scale: f32,
font_paths: Vec<std::path::PathBuf>,
font_data: Vec<Vec<u8>>,
build: impl FnOnce() -> Result<Box<dyn LayoutItem>, LayoutError>,
) -> Result<Self, TextureUiError> {
crate::install_default_text_metrics();
let (width, height) = (target.width(), target.height());
let renderer = HardwareRenderer::for_texture(
target,
None,
false,
crate::runner::offscreen_hardware_font_config(font_paths, font_data),
)?;
let surface = Surface::new();
let (tree, root) = {
let _g = surface.enter();
let content = build()?;
let root = new_container(
LayoutStyle::new()
.width(SizeDimension::Percent(1.0))
.height(SizeDimension::Percent(1.0)),
&[content.layout_node()],
)?;
(ComponentList::new(Root { content }), root)
};
let mut ui = Self {
renderer,
tree,
root,
width,
height,
scale: scale.max(f32::MIN_POSITIVE),
clear_color: Color::TRANSPARENT,
placement: Transform::IDENTITY,
surface,
};
ui.lay_out();
Ok(ui)
}
pub fn set_clear_color(&mut self, clear_color: Color) {
self.clear_color = clear_color;
}
pub fn resize(&mut self, target: wgpu::Texture, scale: f32) {
self.width = target.width();
self.height = target.height();
self.scale = scale.max(f32::MIN_POSITIVE);
self.renderer.compose_into(target);
self.lay_out();
}
pub fn place_in(&mut self, dest: Rect) {
let (w, h) = self.logical_size();
self.placement = Transform {
a: dest.width / w.max(f32::MIN_POSITIVE),
b: 0.0,
c: 0.0,
d: dest.height / h.max(f32::MIN_POSITIVE),
e: dest.x,
f: dest.y,
};
}
pub fn set_placement(&mut self, placement: Transform) {
self.placement = placement;
}
#[must_use = "the UI's world is only active while this guard is alive"]
pub fn enter(&self) -> ui_core::SurfaceGuard {
self.surface.enter()
}
pub fn logical_size(&self) -> (f32, f32) {
(
self.width as f32 / self.scale,
self.height as f32 / self.scale,
)
}
pub fn is_dirty(&self) -> bool {
self.tree.is_dirty()
}
pub fn on_event(&mut self, event: &Event) -> bool {
let _g = self.surface.enter();
let mapped = ui_core::transform_pointer(event, self.placement.to_array());
let event = mapped.as_ref().unwrap_or(event);
let consumed =
reactive_core::batch(|| ui_core::dispatch_overlays(event) == EventResult::Handled);
consumed || self.tree.on_event(event) == EventResult::Handled
}
pub fn render(&mut self) -> Result<(), RendererError> {
let _g = self.surface.enter();
ui_core::relayout_if_dirty();
let generation = self.tree.generation();
self.renderer
.begin_frame(self.width, self.height, self.scale, generation)?;
let commands = self.tree.commands();
self.renderer
.render_frame(&commands, Some(self.clear_color))
}
fn lay_out(&mut self) {
let _g = self.surface.enter();
let (w, h) = (
self.width as f32 / self.scale,
self.height as f32 / self.scale,
);
let _ = mark_dirty(self.root);
let _ = compute_layout(
self.root,
AvailableSpace::Definite(w),
AvailableSpace::Definite(h),
);
self.tree.on_event(&Event::WindowResized {
width: w.round().max(0.0) as u32,
height: h.round().max(0.0) as u32,
});
}
}
impl Drop for TextureUi {
fn drop(&mut self) {
reactive_core::cancel_tasks_for(self.surface.handle());
}
}