pub mod animation;
pub mod builder;
pub mod clip;
pub mod constraint;
pub mod constraint_builder;
pub mod constraint_resolver;
pub mod culling;
pub mod debug;
pub mod dirty;
pub mod draw_list;
pub mod event;
pub mod focus;
pub mod glyph_atlas;
pub mod gpu_types;
pub mod inspector;
pub mod instance_buffer;
pub mod layout;
pub mod layout_engine;
pub mod length;
pub mod menu;
pub mod metrics;
pub mod metrics_collector;
pub mod middleware;
pub mod overlay;
pub mod plugin;
pub mod renderer;
pub mod scroll_plugin;
pub mod style;
pub use style::{Overflow, PointerEvents};
pub mod theme;
pub mod tooltip;
pub mod tree;
pub mod viewport_context;
pub mod virtual_scroll;
pub mod widget_id;
pub mod widgets;
pub use animation::{
AnimatableProperty, Animation, AnimationState, AnimationSystem, EasingFunction,
WidgetAnimations, bounce, fade_in, fade_out, scale, slide_in_left, slide_in_top, translate_x,
translate_y,
};
use astrelis_core::geometry::Size;
pub use clip::{ClipRect, PhysicalClipRect};
pub use debug::DebugOverlay;
pub use dirty::{DirtyFlags, DirtyRanges, Versioned};
pub use draw_list::{DrawCommand, DrawList, ImageCommand, QuadCommand, RenderLayer, TextCommand};
pub use glyph_atlas::{
GlyphBatch, atlas_entry_uv_coords, create_glyph_batches, glyph_to_instance, glyphs_to_instances,
};
pub use gpu_types::{ImageInstance, QuadInstance, QuadVertex, TextInstance};
pub use instance_buffer::InstanceBuffer;
pub use length::{Length, LengthAuto, LengthPercentage, auto, length, percent, vh, vmax, vmin, vw};
use std::sync::Arc;
pub use astrelis_render::ImageSampling;
pub use astrelis_text::{SyncTextShaper, TextPipeline, TextShapeRequest, TextShaper};
pub use constraint::{CalcExpr, Constraint};
pub use constraint_builder::{calc, clamp, max_of, max2, min_of, min2, px};
pub use constraint_resolver::{ConstraintResolver, ResolveContext};
pub use metrics::UiMetrics;
pub use viewport_context::ViewportContext;
pub use widget_id::{WidgetId, WidgetIdRegistry};
pub use widgets::{HScrollbar, ScrollbarOrientation, ScrollbarTheme, VScrollbar};
pub use widgets::{Image, ImageFit, ImageTexture, ImageUV};
pub use widgets::{ScrollAxis, ScrollContainer, ScrollbarVisibility};
pub use builder::{
ContainerNodeBuilder,
ImageBuilder,
IntoNodeBuilder,
LeafNodeBuilder,
UiBuilder,
WidgetBuilder,
};
#[cfg(feature = "docking")]
pub use builder::{DockSplitterNodeBuilder, DockTabsNodeBuilder};
pub use event::{UiEvent, UiEventSystem};
pub use focus::{FocusDirection, FocusEvent, FocusManager, FocusPolicy, FocusScopeId};
pub use layout::LayoutCache;
pub use renderer::UiRenderer;
pub use style::Style;
pub use theme::{ColorPalette, ColorRole, Shapes, Spacing, Theme, ThemeBuilder, Typography};
pub use tree::{NodeId, UiTree};
pub use widgets::Widget;
pub use culling::{AABB, CullingStats, CullingTree};
pub use inspector::{
EditableProperty, InspectorConfig, InspectorGraphs, PropertyEditor, SearchState, TreeViewState,
UiInspector, WidgetIdRegistryExt, WidgetKind,
};
pub use layout_engine::{LayoutEngine, LayoutMode, LayoutRequest};
pub use menu::{ContextMenu, MenuBar, MenuItem, MenuStyle};
pub use metrics_collector::{
FrameTimingMetrics, MemoryMetrics, MetricsCollector, MetricsConfig, PerformanceWarning,
WidgetMetrics,
};
pub use middleware::{
InspectorMiddleware, Keybind, KeybindRegistry, MiddlewareContext, MiddlewareManager, Modifiers,
OverlayContext, OverlayDrawList, OverlayRenderer, UiMiddleware,
};
pub use overlay::{
AnchorAlignment, Overlay, OverlayConfig, OverlayId, OverlayManager, OverlayPosition, ZLayer,
};
pub use tooltip::{TooltipConfig, TooltipContent, TooltipManager, TooltipPosition};
pub use virtual_scroll::{
ItemHeight, MountedItem, VirtualScrollConfig, VirtualScrollState, VirtualScrollStats,
VirtualScrollUpdate, VirtualScrollView,
};
#[cfg(feature = "docking")]
pub use widgets::docking::{
DRAG_THRESHOLD, DockSplitter, DockTabs, DockZone, DockingStyle, DragManager, DragState,
DragType, PanelConstraints, SplitDirection, TabScrollIndicator, TabScrollbarPosition,
};
pub use plugin::{
CorePlugin, PluginHandle, PluginManager, TraversalBehavior, UiPlugin, WidgetOverflow,
WidgetRenderContext, WidgetTypeDescriptor, WidgetTypeRegistry,
};
pub use astrelis_core::math::{Vec2, Vec4};
pub use astrelis_render::Color;
pub use taffy::{
AlignContent, AlignItems, Display, FlexDirection, FlexWrap, JustifyContent, Position,
};
use astrelis_core::profiling::profile_function;
use astrelis_render::{GraphicsContext, RenderWindow, Viewport};
use astrelis_winit::event::EventBatch;
pub use renderer::{UiRendererBuilder, UiRendererDescriptor};
pub struct UiCore {
tree: UiTree,
event_system: UiEventSystem,
plugin_manager: PluginManager,
viewport_size: Size<f32>,
widget_registry: WidgetIdRegistry,
viewport: Viewport,
theme: Theme,
}
impl UiCore {
pub fn new() -> Self {
let mut plugin_manager = PluginManager::new();
plugin_manager.add_plugin(CorePlugin);
plugin_manager.add_plugin(scroll_plugin::ScrollPlugin::new());
#[cfg(feature = "docking")]
plugin_manager.add_plugin(widgets::docking::plugin::DockingPlugin::new());
Self {
tree: UiTree::new(),
event_system: UiEventSystem::new(),
plugin_manager,
viewport_size: Size::new(800.0, 600.0),
widget_registry: WidgetIdRegistry::new(),
viewport: Viewport::default(),
theme: Theme::dark(),
}
}
pub fn build<F>(&mut self, build_fn: F)
where
F: FnOnce(&mut UiBuilder),
{
self.widget_registry.clear();
let mut builder = UiBuilder::new(&mut self.tree, &mut self.widget_registry);
build_fn(&mut builder);
builder.finish();
}
pub fn set_viewport(&mut self, viewport: Viewport) {
let new_size = viewport.to_logical().into();
let size_changed = self.viewport_size != new_size;
self.viewport_size = new_size;
self.viewport = viewport;
if size_changed {
self.tree.mark_viewport_dirty();
}
}
pub fn viewport_size(&self) -> Size<f32> {
self.viewport_size
}
pub fn compute_layout(&mut self) {
#[cfg(feature = "docking")]
{
let padding = self
.plugin_manager
.get::<widgets::docking::plugin::DockingPlugin>()
.map(|p| p.docking_context.style().content_padding)
.unwrap_or(0.0);
self.tree.set_docking_content_padding(padding);
}
let widget_registry = self.plugin_manager.widget_registry();
self.tree
.compute_layout(self.viewport_size, None, widget_registry);
#[cfg(feature = "docking")]
if let Some(dp) = self
.plugin_manager
.get_mut::<widgets::docking::plugin::DockingPlugin>()
{
dp.invalidate_cache();
}
}
pub fn run_post_layout_plugins(&mut self) {
self.plugin_manager.post_layout(&mut self.tree);
}
pub fn compute_layout_instrumented(&mut self) -> UiMetrics {
let widget_registry = self.plugin_manager.widget_registry();
let metrics =
self.tree
.compute_layout_instrumented(self.viewport_size, None, widget_registry);
#[cfg(feature = "docking")]
if let Some(dp) = self
.plugin_manager
.get_mut::<widgets::docking::plugin::DockingPlugin>()
{
dp.invalidate_cache();
}
metrics
}
pub fn get_node_id(&self, widget_id: WidgetId) -> Option<NodeId> {
self.widget_registry.get_node(widget_id)
}
pub fn register_widget(&mut self, widget_id: WidgetId, node_id: NodeId) {
self.widget_registry.register(widget_id, node_id);
}
pub fn update_text(&mut self, widget_id: WidgetId, new_content: impl Into<String>) -> bool {
if let Some(node_id) = self.widget_registry.get_node(widget_id) {
self.tree.update_text_content(node_id, new_content)
} else {
false
}
}
pub fn update_button_label(
&mut self,
widget_id: WidgetId,
new_label: impl Into<String>,
) -> bool {
if let Some(node_id) = self.widget_registry.get_node(widget_id)
&& let Some(node) = self.tree.get_node_mut(node_id)
&& let Some(button) = node.widget.as_any_mut().downcast_mut::<widgets::Button>()
{
let changed = button.set_label(new_label);
if changed {
self.tree
.mark_dirty_flags(node_id, DirtyFlags::TEXT_SHAPING);
}
return changed;
}
false
}
pub fn update_text_input(&mut self, widget_id: WidgetId, new_value: impl Into<String>) -> bool {
if let Some(node_id) = self.widget_registry.get_node(widget_id)
&& let Some(node) = self.tree.get_node_mut(node_id)
&& let Some(input) = node
.widget
.as_any_mut()
.downcast_mut::<widgets::TextInput>()
{
let changed = input.set_value(new_value);
if changed {
self.tree
.mark_dirty_flags(node_id, DirtyFlags::TEXT_SHAPING);
}
return changed;
}
false
}
pub fn update_color(&mut self, widget_id: WidgetId, color: astrelis_render::Color) -> bool {
if let Some(node_id) = self.widget_registry.get_node(widget_id) {
self.tree.update_color(node_id, color)
} else {
false
}
}
pub fn update_opacity(&mut self, widget_id: WidgetId, opacity: f32) -> bool {
if let Some(node_id) = self.widget_registry.get_node(widget_id) {
self.tree.update_opacity(node_id, opacity)
} else {
false
}
}
pub fn update_translate(&mut self, widget_id: WidgetId, translate: Vec2) -> bool {
if let Some(node_id) = self.widget_registry.get_node(widget_id) {
self.tree.update_translate(node_id, translate)
} else {
false
}
}
pub fn update_translate_x(&mut self, widget_id: WidgetId, x: f32) -> bool {
if let Some(node_id) = self.widget_registry.get_node(widget_id) {
self.tree.update_translate_x(node_id, x)
} else {
false
}
}
pub fn update_translate_y(&mut self, widget_id: WidgetId, y: f32) -> bool {
if let Some(node_id) = self.widget_registry.get_node(widget_id) {
self.tree.update_translate_y(node_id, y)
} else {
false
}
}
pub fn update_scale(&mut self, widget_id: WidgetId, scale: Vec2) -> bool {
if let Some(node_id) = self.widget_registry.get_node(widget_id) {
self.tree.update_scale(node_id, scale)
} else {
false
}
}
pub fn update_scale_x(&mut self, widget_id: WidgetId, x: f32) -> bool {
if let Some(node_id) = self.widget_registry.get_node(widget_id) {
self.tree.update_scale_x(node_id, x)
} else {
false
}
}
pub fn update_scale_y(&mut self, widget_id: WidgetId, y: f32) -> bool {
if let Some(node_id) = self.widget_registry.get_node(widget_id) {
self.tree.update_scale_y(node_id, y)
} else {
false
}
}
pub fn set_visible(&mut self, widget_id: WidgetId, visible: bool) -> bool {
if let Some(node_id) = self.widget_registry.get_node(widget_id) {
self.tree.set_visible(node_id, visible)
} else {
false
}
}
pub fn toggle_visible(&mut self, widget_id: WidgetId) -> bool {
if let Some(node_id) = self.widget_registry.get_node(widget_id) {
self.tree.toggle_visible(node_id)
} else {
false
}
}
pub fn tree_mut(&mut self) -> &mut UiTree {
&mut self.tree
}
pub fn tree(&self) -> &UiTree {
&self.tree
}
pub fn events(&self) -> &UiEventSystem {
&self.event_system
}
pub fn event_system_mut(&mut self) -> &mut UiEventSystem {
&mut self.event_system
}
pub fn widget_registry(&self) -> &WidgetIdRegistry {
&self.widget_registry
}
pub fn set_theme(&mut self, theme: Theme) {
self.theme = theme;
self.tree.mark_all_dirty(DirtyFlags::COLOR);
}
pub fn theme(&self) -> &Theme {
&self.theme
}
#[cfg(feature = "docking")]
pub fn docking_style(&self) -> &widgets::docking::DockingStyle {
self.plugin_manager
.get::<widgets::docking::plugin::DockingPlugin>()
.expect("DockingPlugin is auto-added when docking feature is enabled")
.docking_context
.style()
}
#[cfg(feature = "docking")]
pub fn set_docking_style(&mut self, style: widgets::docking::DockingStyle) {
self.plugin_manager
.get_mut::<widgets::docking::plugin::DockingPlugin>()
.expect("DockingPlugin is auto-added when docking feature is enabled")
.docking_context
.set_style(style);
}
pub fn add_plugin<P: UiPlugin>(&mut self, plugin: P) -> PluginHandle<P> {
self.plugin_manager.add_plugin(plugin)
}
pub fn plugin_handle<P: UiPlugin>(&self) -> Option<PluginHandle<P>> {
self.plugin_manager.handle::<P>()
}
pub fn plugin<P: UiPlugin>(&self, _handle: &PluginHandle<P>) -> &P {
self.plugin_manager
.get::<P>()
.expect("plugin handle guarantees registration")
}
pub fn plugin_mut<P: UiPlugin>(&mut self, _handle: &PluginHandle<P>) -> &mut P {
self.plugin_manager
.get_mut::<P>()
.expect("plugin handle guarantees registration")
}
pub fn plugin_manager(&self) -> &PluginManager {
&self.plugin_manager
}
pub fn plugin_manager_mut(&mut self) -> &mut PluginManager {
&mut self.plugin_manager
}
pub fn handle_events(&mut self, events: &mut EventBatch) {
self.event_system.handle_events_with_plugins(
events,
&mut self.tree,
&mut self.plugin_manager,
);
}
}
impl Default for UiCore {
fn default() -> Self {
Self::new()
}
}
pub struct UiSystem {
core: UiCore,
renderer: UiRenderer,
}
impl UiSystem {
pub fn new(context: Arc<GraphicsContext>) -> Self {
profile_function!();
Self {
core: UiCore::new(),
renderer: UiRenderer::new(context),
}
}
pub fn from_window(context: Arc<GraphicsContext>, window: &RenderWindow) -> Self {
profile_function!();
Self {
core: UiCore::new(),
renderer: UiRenderer::from_window(context, window),
}
}
pub fn with_descriptor(
context: Arc<GraphicsContext>,
descriptor: UiRendererDescriptor,
) -> Self {
profile_function!();
Self {
core: UiCore::new(),
renderer: UiRenderer::with_descriptor(context, descriptor),
}
}
pub fn build<F>(&mut self, build_fn: F)
where
F: FnOnce(&mut UiBuilder),
{
self.renderer.clear_draw_list();
self.core.build(build_fn);
}
pub fn update(&mut self, delta_time: f32) {
#[cfg(feature = "docking")]
if let Some(dp) = self
.core
.plugin_manager
.get_mut::<widgets::docking::plugin::DockingPlugin>()
{
dp.update_animations(delta_time);
}
let _ = delta_time;
}
pub fn set_viewport(&mut self, viewport: Viewport) {
self.renderer.set_viewport(viewport);
self.core.set_viewport(viewport);
}
pub fn handle_events(&mut self, events: &mut EventBatch) {
self.core.handle_events(events);
}
pub fn compute_layout(&mut self) {
let viewport_size = self.core.viewport_size();
let font_renderer = self.renderer.font_renderer();
#[cfg(feature = "docking")]
{
let padding = self
.core
.plugin_manager
.get::<widgets::docking::plugin::DockingPlugin>()
.map(|p| p.docking_context.style().content_padding)
.unwrap_or(0.0);
self.core.tree.set_docking_content_padding(padding);
}
let widget_registry = self.core.plugin_manager.widget_registry();
self.core
.tree
.compute_layout(viewport_size, Some(font_renderer), widget_registry);
#[cfg(feature = "docking")]
if let Some(dp) = self
.core
.plugin_manager
.get_mut::<widgets::docking::plugin::DockingPlugin>()
{
dp.invalidate_cache();
}
}
pub fn get_node_id(&self, widget_id: WidgetId) -> Option<tree::NodeId> {
self.core.get_node_id(widget_id)
}
pub fn register_widget(&mut self, widget_id: WidgetId, node_id: tree::NodeId) {
self.core.register_widget(widget_id, node_id);
}
pub fn update_text(&mut self, widget_id: WidgetId, new_content: impl Into<String>) -> bool {
self.core.update_text(widget_id, new_content)
}
pub fn text_cache_stats(&self) -> String {
self.renderer.text_cache_stats()
}
pub fn text_cache_hit_rate(&self) -> f32 {
self.renderer.text_cache_hit_rate()
}
pub fn log_text_cache_stats(&self) {
self.renderer.log_text_cache_stats();
}
pub fn update_button_label(
&mut self,
widget_id: WidgetId,
new_label: impl Into<String>,
) -> bool {
self.core.update_button_label(widget_id, new_label)
}
pub fn update_text_input(&mut self, widget_id: WidgetId, new_value: impl Into<String>) -> bool {
self.core.update_text_input(widget_id, new_value)
}
pub fn update_color(&mut self, widget_id: WidgetId, color: astrelis_render::Color) -> bool {
self.core.update_color(widget_id, color)
}
pub fn update_opacity(&mut self, widget_id: WidgetId, opacity: f32) -> bool {
self.core.update_opacity(widget_id, opacity)
}
pub fn update_translate(&mut self, widget_id: WidgetId, translate: Vec2) -> bool {
self.core.update_translate(widget_id, translate)
}
pub fn update_translate_x(&mut self, widget_id: WidgetId, x: f32) -> bool {
self.core.update_translate_x(widget_id, x)
}
pub fn update_translate_y(&mut self, widget_id: WidgetId, y: f32) -> bool {
self.core.update_translate_y(widget_id, y)
}
pub fn update_scale(&mut self, widget_id: WidgetId, scale: Vec2) -> bool {
self.core.update_scale(widget_id, scale)
}
pub fn update_scale_x(&mut self, widget_id: WidgetId, x: f32) -> bool {
self.core.update_scale_x(widget_id, x)
}
pub fn update_scale_y(&mut self, widget_id: WidgetId, y: f32) -> bool {
self.core.update_scale_y(widget_id, y)
}
pub fn set_visible(&mut self, widget_id: WidgetId, visible: bool) -> bool {
self.core.set_visible(widget_id, visible)
}
pub fn toggle_visible(&mut self, widget_id: WidgetId) -> bool {
self.core.toggle_visible(widget_id)
}
pub fn render(&mut self, render_pass: &mut astrelis_render::wgpu::RenderPass) {
profile_function!();
let logical_size = self.core.viewport_size();
#[cfg(feature = "docking")]
{
let padding = self
.core
.plugin_manager
.get::<widgets::docking::plugin::DockingPlugin>()
.map(|p| p.docking_context.style().content_padding)
.unwrap_or(0.0);
self.core.tree.set_docking_content_padding(padding);
}
let font_renderer = self.renderer.font_renderer();
let widget_registry = self.core.plugin_manager.widget_registry();
self.core
.tree
.compute_layout(logical_size, Some(font_renderer), widget_registry);
#[cfg(feature = "docking")]
if let Some(dp) = self
.core
.plugin_manager
.get_mut::<widgets::docking::plugin::DockingPlugin>()
{
dp.invalidate_cache();
}
#[cfg(feature = "docking")]
{
let font_renderer = self.renderer.font_renderer();
crate::widgets::docking::compute_all_tab_widths(self.core.tree_mut(), font_renderer);
}
self.core.run_post_layout_plugins();
let removed = self.core.tree_mut().drain_removed_nodes();
if !removed.is_empty() {
self.renderer.remove_stale_nodes(&removed);
}
#[cfg(feature = "docking")]
{
let (preview, animations) = self
.core
.plugin_manager
.get::<widgets::docking::plugin::DockingPlugin>()
.map(|dp| (dp.cross_container_preview, &dp.dock_animations))
.unzip();
let widget_registry = self.core.plugin_manager.widget_registry();
self.renderer.render_instanced_with_preview(
self.core.tree(),
render_pass,
self.core.viewport,
preview.flatten().as_ref(),
animations,
widget_registry,
);
}
#[cfg(not(feature = "docking"))]
{
let widget_registry = self.core.plugin_manager.widget_registry();
self.renderer.render_instanced(
self.core.tree(),
render_pass,
self.core.viewport,
widget_registry,
);
}
self.core.tree_mut().clear_dirty_flags();
}
pub fn render_without_layout(
&mut self,
render_pass: &mut astrelis_render::wgpu::RenderPass,
clear_dirty_flags: bool,
) {
profile_function!();
self.core.run_post_layout_plugins();
let removed = self.core.tree_mut().drain_removed_nodes();
if !removed.is_empty() {
self.renderer.remove_stale_nodes(&removed);
}
let widget_registry = self.core.plugin_manager.widget_registry();
self.renderer.render_instanced(
self.core.tree(),
render_pass,
self.core.viewport,
widget_registry,
);
if clear_dirty_flags {
self.core.tree_mut().clear_dirty_flags();
}
}
pub fn core_mut(&mut self) -> &mut UiCore {
&mut self.core
}
pub fn core(&self) -> &UiCore {
&self.core
}
pub fn tree_mut(&mut self) -> &mut UiTree {
self.core.tree_mut()
}
pub fn tree(&self) -> &UiTree {
self.core.tree()
}
pub fn event_system_mut(&mut self) -> &mut UiEventSystem {
self.core.event_system_mut()
}
pub fn font_renderer(&self) -> &astrelis_text::FontRenderer {
self.renderer.font_renderer()
}
pub fn set_theme(&mut self, theme: Theme) {
self.renderer.set_theme_colors(theme.colors.clone());
self.core.set_theme(theme);
}
pub fn theme(&self) -> &Theme {
self.core.theme()
}
#[cfg(feature = "docking")]
pub fn docking_style(&self) -> &widgets::docking::DockingStyle {
self.core.docking_style()
}
#[cfg(feature = "docking")]
pub fn set_docking_style(&mut self, style: widgets::docking::DockingStyle) {
self.core.set_docking_style(style);
}
pub fn add_plugin<P: UiPlugin>(&mut self, plugin: P) -> PluginHandle<P> {
self.core.add_plugin(plugin)
}
pub fn plugin_handle<P: UiPlugin>(&self) -> Option<PluginHandle<P>> {
self.core.plugin_handle::<P>()
}
pub fn plugin<P: UiPlugin>(&self, handle: &PluginHandle<P>) -> &P {
self.core.plugin(handle)
}
pub fn plugin_mut<P: UiPlugin>(&mut self, handle: &PluginHandle<P>) -> &mut P {
self.core.plugin_mut(handle)
}
pub fn plugin_manager(&self) -> &PluginManager {
self.core.plugin_manager()
}
pub fn renderer_descriptor(&self) -> &UiRendererDescriptor {
self.renderer.descriptor()
}
pub fn reconfigure(&mut self, descriptor: UiRendererDescriptor) {
self.renderer.reconfigure(descriptor);
}
pub fn reconfigure_from_window(&mut self, window: &RenderWindow) {
self.renderer.reconfigure_from_window(window);
}
}