use crate::core::{Bounds, FocusId, Pixels, Point, Size};
use crate::gpu::text::GlyphTarget;
use crate::gpu::{Affine, ShapedText, SpriteCache, TextSystem};
use crate::view::element_state::ElementStateMap;
use crate::view::interactivity::{ScrollState, SharedScroll};
use crate::view::style::Style;
use crate::view::text_layout::{ShapeCache, TextLeaf};
use crate::view::theme::Theme;
use std::any::TypeId;
use std::rc::Rc;
use std::time::Instant;
use taffy::NodeId;
pub use crate::view::frame_state::{DEFAULT_LEADING, FrameState, InheritedText, MouseState};
pub struct FrameResources<'a> {
pub theme: &'a Theme,
pub scale: f32,
pub viewport: Size,
pub frame: &'a mut FrameState,
pub text: &'a mut TextSystem,
pub sprites: &'a mut SpriteCache,
pub shape_cache: &'a mut ShapeCache,
pub glyphs: Option<GlyphTarget<'a>>,
pub element_state: &'a mut ElementStateMap,
pub mouse: MouseState,
pub focused: Option<FocusId>,
pub active_drag: Option<TypeId>,
}
pub struct ElementContext<'a> {
pub theme: &'a Theme,
pub scale: f32,
viewport: Size,
pub(crate) hit_transforms: Vec<Affine>,
pub now: Instant,
pub(crate) frame: &'a mut FrameState,
pub(crate) text: &'a mut TextSystem,
pub(crate) sprites: &'a mut SpriteCache,
pub(crate) shape_cache: &'a mut ShapeCache,
pub(crate) glyphs: Option<GlyphTarget<'a>>,
pub(crate) element_state: &'a mut ElementStateMap,
pub(crate) mouse: MouseState,
pub(crate) focused: Option<FocusId>,
pub(crate) active_drag: Option<TypeId>,
origins: Vec<Point>,
masks: Vec<Bounds>,
text_styles: Vec<InheritedText>,
}
impl<'a> ElementContext<'a> {
pub fn new(resources: FrameResources<'a>) -> Self {
Self {
theme: resources.theme,
scale: resources.scale,
viewport: resources.viewport,
hit_transforms: Vec::new(),
now: Instant::now(),
frame: resources.frame,
text: resources.text,
sprites: resources.sprites,
shape_cache: resources.shape_cache,
glyphs: resources.glyphs,
element_state: resources.element_state,
mouse: resources.mouse,
focused: resources.focused,
active_drag: resources.active_drag,
origins: vec![Point::default()],
masks: Vec::new(),
text_styles: vec![InheritedText::default()],
}
}
pub fn request_node(&mut self, style: &Style, children: &[NodeId]) -> NodeId {
let taffy_style = crate::view::layout::to_taffy(style);
let tree = &mut self.frame.taffy;
match tree.new_with_children(taffy_style, children) {
Ok(node) => node,
Err(error) => {
eprintln!("no se pudo crear el nodo de layout: {error}");
tree.new_leaf(taffy::Style::default()).unwrap_or(NodeId::new(0))
}
}
}
pub fn request_text_node(&mut self, style: &Style, leaf: TextLeaf) -> NodeId {
let taffy_style = crate::view::layout::to_taffy(style);
match self.frame.taffy.new_leaf_with_context(taffy_style, leaf) {
Ok(node) => node,
Err(error) => {
eprintln!("no se pudo crear la hoja de texto: {error}");
self.request_node(style, &[])
}
}
}
pub fn node_bounds(&self, node: NodeId) -> Bounds {
let Ok(layout) = self.frame.taffy.layout(node) else { return Bounds::default() };
let origin = self.origin() + Point::new(Pixels(layout.location.x), Pixels(layout.location.y));
Bounds::new(origin, Size::new(Pixels(layout.size.width), Pixels(layout.size.height)))
}
pub fn node_content_size(&self, node: NodeId) -> Size {
match self.frame.taffy.layout(node) {
Ok(layout) => {
let reach = layout.scrollable_overflow_rect;
Size::new(Pixels(reach.right), Pixels(reach.bottom))
}
Err(_) => Size::default(),
}
}
pub fn origin(&self) -> Point {
self.origins.last().copied().unwrap_or_default()
}
pub fn with_origin<R>(&mut self, origin: Point, body: impl FnOnce(&mut Self) -> R) -> R {
self.origins.push(origin);
let result = body(self);
self.origins.pop();
result
}
pub fn mask(&self) -> Bounds {
self.masks
.last()
.copied()
.unwrap_or(Bounds::new(Point::new(Pixels(-1.0e6), Pixels(-1.0e6)), Size::new(Pixels(2.0e6), Pixels(2.0e6))))
}
pub fn with_mask<R>(&mut self, bounds: Bounds, body: impl FnOnce(&mut Self) -> R) -> R {
let clipped = self.mask().intersect(&bounds);
self.masks.push(clipped);
let result = body(self);
self.masks.pop();
result
}
pub(crate) fn without_mask<R>(&mut self, body: impl FnOnce(&mut Self) -> R) -> R {
let saved = std::mem::take(&mut self.masks);
let result = body(self);
self.masks = saved;
result
}
pub fn viewport(&self) -> Size {
self.viewport
}
pub fn text_style(&self) -> InheritedText {
self.text_styles.last().copied().unwrap_or_default()
}
pub fn with_text_style<R>(&mut self, style: &Style, body: impl FnOnce(&mut Self) -> R) -> R {
let mut inherited = self.text_style();
inherited.color = style.text_color.unwrap_or(inherited.color);
inherited.size = style.font_size.unwrap_or(inherited.size);
inherited.bold = style.font_bold.unwrap_or(inherited.bold);
inherited.italic = style.italic.unwrap_or(inherited.italic);
inherited.underline = style.underline.unwrap_or(inherited.underline);
inherited.wrap = style.text_wrap.unwrap_or(inherited.wrap);
if let Some(extra) = &style.extra {
inherited.refine(&extra.text);
}
self.text_styles.push(inherited);
let result = body(self);
self.text_styles.pop();
result
}
pub fn element_state<S: Default + 'static>(&mut self, id: &str) -> Rc<S> {
self.element_state.get_or_default(id)
}
pub fn scroll_state(&mut self, id: &str) -> SharedScroll {
self.element_state::<std::cell::Cell<ScrollState>>(id)
}
pub fn set_ime_area(&mut self, bounds: Bounds) {
self.frame.ime_area = Some(bounds);
}
pub fn request_animation_frame(&mut self) {
self.frame.wants_animation_frame = true;
}
pub fn shape_leaf(&mut self, leaf: &TextLeaf, max_width: Option<f32>) -> Rc<ShapedText> {
self.shape_cache.shape(self.text, leaf, max_width)
}
pub fn shape(&mut self, content: &str, size: Pixels, monospace: bool) -> Rc<ShapedText> {
self.shape_in(content, size, monospace, None)
}
pub fn shape_in(
&mut self,
content: &str,
size: Pixels,
monospace: bool,
family: Option<crate::gpu::FontFamily>,
) -> Rc<ShapedText> {
let leaf = TextLeaf {
content: Rc::from(content),
runs: Rc::from([]),
font_size: size.0,
line_height: (size.0 * DEFAULT_LEADING).round(),
monospace,
family,
wrap: false,
traits: Default::default(),
};
self.shape_leaf(&leaf, None)
}
pub fn font_family(&mut self, name: &str) -> Option<crate::gpu::FontFamily> {
self.text.font_family(name)
}
}