#![warn(missing_docs)]
use bevy::render::extract_resource::ExtractResourcePlugin;
use bevy::{
asset::embedded_asset, prelude::*, reflect::GetTypeRegistration, render::view::RenderLayers,
};
use bevy_trait_query::RegisterExt;
use bevy_vello::prelude::VelloFont;
use bevy_vello::render::VelloView;
use bevy_vello::{vello::AaConfig, VelloPlugin, VelloSceneBundle};
use context::{Widget, WoodpeckerContext};
use convert_render_target::ConvertRenderTargetPlugin;
use entity_mapping::WidgetMapper;
use font::FontManager;
use hook_helper::HookHelper;
use image::ImageManager;
use layout::WoodpeckerLayoutPlugin;
use metrics::WidgetMetrics;
use observer_cache::ObserverCache;
use svg::{SvgAsset, SvgLoader, SvgManager};
use widgets::WoodpeckerUIWidgetPlugin;
mod children;
mod context;
mod convert_render_target;
mod entity_mapping;
mod focus;
mod font;
mod hook_helper;
mod image;
mod keyboard_input;
mod layout;
mod metrics;
mod observer_cache;
mod on_change;
mod picking_backend;
mod render;
mod rich_text;
mod runner;
mod styles;
mod svg;
mod vello_renderer;
mod vello_svg;
mod widgets;
pub mod prelude {
pub use crate::children::{Mounted, PassedChildren, WidgetChildren};
pub use crate::context::*;
pub use crate::entity_mapping::*;
pub use crate::focus::*;
pub use crate::font::{FontManager, TextAlign};
pub use crate::hook_helper::{HookHelper, PreviousWidget};
pub use crate::keyboard_input::{WidgetKeyboardButtonEvent, WidgetKeyboardCharEvent};
pub use crate::layout::system::{WidgetLayout, WidgetPreviousLayout};
pub use crate::metrics::WidgetMetrics;
pub use crate::on_change::Change;
pub use crate::render::{WidgetRender, WidgetRenderCustom};
pub use crate::rich_text::*;
pub use crate::styles::*;
pub use crate::svg::SvgAsset;
pub use crate::widgets::*;
pub use crate::PreviousResource;
pub use crate::WoodpeckerView;
pub use crate::{
CurrentWidget, ParentWidget, RenderSettings, WidgetRegisterExt, WoodpeckerUIPlugin,
};
pub use bevy_vello::vello;
pub use bevy_vello::vello::AaConfig;
pub use woodpecker_ui_macros::*;
}
#[derive(Resource, Clone)]
pub struct RenderSettings {
pub layer: RenderLayers,
pub antialiasing: AaConfig,
pub use_cpu: bool,
}
impl Default for RenderSettings {
fn default() -> Self {
Self {
layer: Default::default(),
antialiasing: AaConfig::Area,
use_cpu: Default::default(),
}
}
}
#[derive(Resource)]
pub struct DefaultFont(pub Handle<VelloFont>);
impl FromWorld for DefaultFont {
fn from_world(world: &mut World) -> Self {
let asset_server = world.get_resource::<AssetServer>().unwrap();
DefaultFont(
asset_server.load("embedded://woodpecker_ui/embedded_assets/Poppins-Regular.ttf"),
)
}
}
#[derive(Resource)]
pub struct PreviousResource<T: Resource>(pub T);
#[derive(Resource, Debug, Clone, Copy, Deref, DerefMut, PartialEq, Eq, Hash)]
pub struct ParentWidget(pub Entity);
impl ParentWidget {
pub fn as_current(&self) -> CurrentWidget {
CurrentWidget(self.0)
}
}
#[derive(Resource, Debug, Clone, Copy, Deref, DerefMut, PartialEq, Eq, Hash)]
pub struct CurrentWidget(pub Entity);
impl CurrentWidget {
pub fn as_parent(&self) -> ParentWidget {
ParentWidget(self.0)
}
}
#[derive(Component, Clone, Copy, Debug)]
#[require(VelloView = vello_view())]
pub struct WoodpeckerView;
fn vello_view() -> VelloView {
VelloView
}
#[derive(Default)]
pub struct WoodpeckerUIPlugin {
pub render_settings: RenderSettings,
}
impl Plugin for WoodpeckerUIPlugin {
fn build(&self, app: &mut App) {
embedded_asset!(app, "embedded_assets/Poppins-Regular.ttf");
embedded_asset!(app, "embedded_assets/icons/arrow-down.svg");
embedded_asset!(app, "embedded_assets/icons/arrow-up.svg");
embedded_asset!(app, "embedded_assets/icons/checkmark.svg");
embedded_asset!(app, "embedded_assets/icons/copy-outline.svg");
app.add_plugins(WoodpeckerLayoutPlugin)
.add_plugins(VelloPlugin {
canvas_render_layers: self.render_settings.layer.clone(),
use_cpu: self.render_settings.use_cpu,
antialiasing: self.render_settings.antialiasing,
})
.add_plugins(WoodpeckerUIWidgetPlugin)
.add_plugins(ExtractResourcePlugin::<ImageManager>::default())
.add_plugins(ConvertRenderTargetPlugin)
.add_event::<focus::WidgetFocus>()
.add_event::<focus::WidgetBlur>()
.insert_resource(focus::CurrentFocus::new(Entity::PLACEHOLDER))
.init_resource::<ObserverCache>()
.init_resource::<FontManager>()
.init_resource::<HookHelper>()
.init_resource::<WoodpeckerContext>()
.init_resource::<WidgetMapper>()
.init_resource::<DefaultFont>()
.init_resource::<WidgetMetrics>()
.init_resource::<SvgManager>()
.init_resource::<ImageManager>()
.insert_resource(self.render_settings.clone())
.init_asset::<SvgAsset>()
.init_asset_loader::<SvgLoader>()
.add_systems(
Update,
(
runner::system,
focus::CurrentFocus::click_focus,
#[cfg(not(target_arch = "wasm32"))]
keyboard_input::runner,
#[cfg(target_arch = "wasm32")]
(keyboard_input::runner, keyboard_input::read_paste_events).chain(),
hook_helper::HookHelper::update_context_helper,
vello_renderer::run.after(layout::system::run),
)
.run_if(has_root()),
)
.add_systems(
Update,
(
font::load_fonts,
picking_backend::mouse_wheel_system,
picking_backend::system.after(crate::layout::system::run),
#[cfg(feature = "metrics")]
metrics::WidgetMetrics::print_metrics_x_seconds,
),
)
.add_systems(Startup, startup)
.register_type::<render::WidgetRender>()
.register_type::<crate::prelude::WidgetLayout>()
.register_type::<styles::WoodpeckerStyle>()
.register_type::<styles::Corner>()
.register_type::<styles::Edge>()
.register_type::<styles::Units>()
.register_type::<styles::WidgetAlignContent>()
.register_type::<styles::WidgetAlignItems>()
.register_type::<styles::WidgetDisplay>()
.register_type::<styles::WidgetFlexDirection>()
.register_type::<styles::WidgetFlexWrap>()
.register_type::<styles::WidgetOverflow>()
.register_type::<styles::WidgetPosition>()
.register_type::<Option<styles::WidgetAlignContent>>()
.register_type::<Option<styles::WidgetAlignItems>>()
.register_type::<Option<styles::WidgetDisplay>>()
.register_type::<Option<styles::WidgetFlexDirection>>()
.register_type::<Option<styles::WidgetFlexWrap>>()
.register_type::<Option<styles::WidgetOverflow>>()
.register_type::<Option<styles::WidgetPosition>>();
}
}
fn has_root() -> impl Condition<(), ()> {
IntoSystem::into_system(|context: Res<WoodpeckerContext>| context.root_widget.is_some())
}
fn startup(mut commands: Commands, render_settings: Res<RenderSettings>) {
commands.spawn((
Node {
position_type: PositionType::Absolute,
..default()
},
Interaction::default(),
VelloSceneBundle {
transform: Transform::from_xyz(0.0, 0.0, f32::MAX),
..Default::default()
},
render_settings.layer.clone(),
));
}
pub trait WidgetRegisterExt {
fn register_widget<T: Component + Widget + GetTypeRegistration>(&mut self) -> &mut Self;
fn add_widget_systems<Params, Params2>(
&mut self,
widget_name: String,
update: impl IntoSystem<(), bool, Params>,
render: impl IntoSystem<(), (), Params2>,
) -> &mut Self;
}
impl WidgetRegisterExt for App {
fn register_widget<T: Component + Widget + GetTypeRegistration>(&mut self) -> &mut Self {
self.register_component_as::<dyn Widget, T>();
self.register_type::<T>();
let mut context = self
.world_mut()
.get_resource_or_insert_with::<WoodpeckerContext>(WoodpeckerContext::default);
context.add_widget_systems_non_into(
T::get_name(),
Box::new(T::update()),
Box::new(T::render()),
);
self
}
fn add_widget_systems<Params, Params2>(
&mut self,
widget_name: String,
update: impl IntoSystem<(), bool, Params>,
render: impl IntoSystem<(), (), Params2>,
) -> &mut Self {
let mut context = self
.world_mut()
.get_resource_or_insert_with::<WoodpeckerContext>(WoodpeckerContext::default);
context.add_widget_system(widget_name, update, render);
self
}
}
mod test_proc_macro {
#[test]
fn test_widget_macro() {
use crate::prelude::*;
use bevy::prelude::*;
#[derive(Widget)]
#[widget_systems(update, render)]
pub struct MyStruct {}
fn update() -> bool {
false
}
#[derive(Widget, Component, PartialEq, Clone)]
#[auto_update(render)]
#[props(MyStruct2)]
pub struct MyStruct2 {}
fn render() {}
}
}