use crate::core::{Bounds, HitboxId, Pixels, Point};
use crate::view::accessibility::AccessNode;
use crate::view::any_element::AnyElement;
use crate::view::context::ElementContext;
use crate::view::dispatch::DispatchNode;
use crate::view::element::{Element, ParentElement};
use crate::view::interactivity::{HandlerEntry, Interactivity, SharedScroll};
use crate::view::style::{Overflow, Style};
use crate::view::styled::Styled;
use smallvec::SmallVec;
use std::borrow::Cow;
use taffy::NodeId;
pub struct Div {
pub(crate) style: Style,
pub(crate) interactivity: Interactivity,
children: SmallVec<[AnyElement; 4]>,
node: Option<NodeId>,
hitbox: Option<HitboxId>,
scroll: Option<SharedScroll>,
}
pub fn div() -> Div {
Div {
style: Style::default(),
interactivity: Interactivity::default(),
children: SmallVec::new(),
node: None,
hitbox: None,
scroll: None,
}
}
impl Div {
fn clips_children(&self) -> bool {
matches!(self.style.overflow_x, Some(Overflow::Hidden | Overflow::Scroll))
|| matches!(self.style.overflow_y, Some(Overflow::Hidden | Overflow::Scroll))
}
fn resolved_style(&self, cx: &ElementContext) -> Cow<'_, Style> {
let mut resolved = Cow::Borrowed(&self.style);
let focused = self.interactivity.focus.is_some_and(|handle| cx.is_focused(handle.id));
if let Some(focus) = self.interactivity.focus_style.as_ref().filter(|_| focused) {
resolved.to_mut().refine(focus);
}
if let Some((_, hover)) =
self.interactivity.group_hover.as_ref().filter(|(group, _)| cx.is_group_hovered(group))
{
resolved.to_mut().refine(hover);
}
let Some(hitbox) = self.hitbox else { return resolved };
if let Some(hover) = self.interactivity.hover_style.as_ref().filter(|_| cx.is_hovered(hitbox)) {
resolved.to_mut().refine(hover);
}
if let Some(active) = self.interactivity.active_style.as_ref().filter(|_| cx.is_pressed(hitbox)) {
resolved.to_mut().refine(active);
}
if let Some(dragged) = cx.active_drag_type().filter(|_| cx.is_hovered(hitbox)) {
for (_, style) in self.interactivity.drag_over.iter().filter(|(type_id, _)| *type_id == dragged) {
resolved.to_mut().refine(style);
}
}
resolved
}
fn transform_of(style: &Style, bounds: Bounds) -> Option<crate::gpu::Affine> {
let spec = &style.extra.as_deref()?.transform;
(!spec.is_none()).then(|| spec.affine(bounds))
}
fn content_origin(&self, bounds: Bounds) -> Point {
let offset = self.scroll.as_ref().map_or(0.0, |scroll| scroll.get().offset_y);
bounds.origin - Point::new(Pixels::ZERO, Pixels(offset))
}
fn dispatch_node(&self) -> DispatchNode {
DispatchNode {
parent: None,
focus: self.interactivity.focus.map(|handle| handle.id),
tab_index: self.interactivity.tab_index,
context: self.interactivity.key_context.clone(),
actions: self.interactivity.actions.clone(),
key_down: self.interactivity.on_key_down.clone(),
text_input: self.interactivity.on_text_input.clone(),
}
}
fn visit_children(
&mut self,
cx: &mut ElementContext,
style_override: Option<&Style>,
bounds: Bounds,
visit: fn(&mut AnyElement, &mut ElementContext),
) {
let origin = self.content_origin(bounds);
let clips = self.clips_children();
let Self { style, children, .. } = self;
let active_style = style_override.unwrap_or(style);
cx.with_text_style(active_style, |cx| {
let mut body = |cx: &mut ElementContext| cx.with_origin(origin, |cx| visit_in_z_order(children, cx, visit));
if clips { cx.with_mask(bounds, body) } else { body(cx) }
});
}
}
fn visit_in_z_order(
children: &mut [AnyElement],
cx: &mut ElementContext,
visit: fn(&mut AnyElement, &mut ElementContext),
) {
if children.iter_mut().all(|child| child.z_index() == 0) {
return children.iter_mut().for_each(|child| visit(child, cx));
}
let mut order: SmallVec<[(i16, usize); 16]> =
children.iter_mut().enumerate().map(|(index, child)| (child.z_index(), index)).collect();
order.sort_by_key(|&(z, _)| z);
for (_, index) in order {
visit(&mut children[index], cx);
}
}
impl Styled for Div {
fn style(&mut self) -> &mut Style {
&mut self.style
}
}
impl ParentElement for Div {
fn extend_children(&mut self, children: impl IntoIterator<Item = AnyElement>) {
self.children.extend(children);
}
}
impl Element for Div {
fn z_index(&self) -> i16 {
self.style.extra.as_deref().and_then(|extra| extra.z).unwrap_or(0)
}
fn request_layout(&mut self, cx: &mut ElementContext) -> NodeId {
for (variant, style) in std::mem::take(&mut self.interactivity.variants) {
if variant.applies(cx) {
self.style.refine(&style);
}
}
let Self { style, children, .. } = self;
let child_nodes: SmallVec<[NodeId; 4]> =
cx.with_text_style(style, |cx| children.iter_mut().map(|child| child.request_layout(cx)).collect());
let node = cx.request_node(&self.style, &child_nodes);
self.node = Some(node);
node
}
fn prepaint(&mut self, cx: &mut ElementContext) {
let Some(node) = self.node else { return };
let bounds = cx.node_bounds(node);
let transform = Self::transform_of(&self.style, bounds);
if let Some(transform) = transform {
cx.push_hit_transform(transform);
}
self.prepaint_inside(cx, node, bounds);
if transform.is_some() {
cx.pop_hit_transform();
}
}
fn paint(&mut self, cx: &mut ElementContext) {
let Some(node) = self.node else { return };
let bounds = cx.node_bounds(node);
let resolved_owned = match self.resolved_style(cx) {
Cow::Borrowed(_) => None,
Cow::Owned(owned) => Some(owned),
};
let active_style = resolved_owned.as_ref().unwrap_or(&self.style);
let transform = Self::transform_of(active_style, bounds);
if let Some(transform) = transform {
cx.frame.scene.push_transform(transform);
}
let filter = active_style.extra.as_deref().map(|extra| extra.filter).unwrap_or_default();
if let Some(sigma) = filter.backdrop_blur {
cx.paint_blur(bounds, active_style, sigma);
}
cx.paint_box(bounds, active_style);
self.visit_children(cx, resolved_owned.as_ref(), bounds, |child, cx| child.paint(cx));
let active_style = resolved_owned.as_ref().unwrap_or(&self.style);
if let Some(scroll) = &self.scroll {
cx.scrollbar(bounds, scroll, &[]);
}
if let Some(sigma) = filter.blur {
cx.paint_blur(bounds, active_style, sigma);
}
if transform.is_some() {
cx.frame.scene.pop_transform();
}
}
}
impl Div {
fn prepaint_inside(&mut self, cx: &mut ElementContext, node: NodeId, bounds: Bounds) {
if let Some(cell) = &self.interactivity.record_bounds {
cell.set(bounds);
}
if let (true, Some(role)) = (cx.accessibility_enabled(), self.interactivity.role) {
let interactivity = &self.interactivity;
cx.register_access(AccessNode {
role,
label: interactivity.label.clone(),
value: None,
bounds,
focus: interactivity.focus.map(|handle| handle.id),
clickable: interactivity.on_click.is_some() || interactivity.on_mouse_down.is_some(),
});
}
if let Some(id) = self.interactivity.id.clone().filter(|_| self.style.is_scrollable_y()) {
let scroll = cx.scroll_state(&id);
let mut state = scroll.get();
state.viewport_y = bounds.size.height.0;
state.max_y = (cx.node_content_size(node).height.0 - state.viewport_y).max(0.0);
state.offset_y = state.offset_y.clamp(0.0, state.max_y);
scroll.set(state);
self.scroll = Some(scroll);
}
if self.interactivity.needs_hitbox(self.scroll.is_some()) {
let hitbox = cx.insert_hitbox(bounds, self.interactivity.blocks_mouse);
self.hitbox = Some(hitbox);
let interactivity = &self.interactivity;
cx.register_handlers(HandlerEntry {
hitbox,
on_click: interactivity.on_click.clone(),
on_mouse_down: interactivity.on_mouse_down.clone(),
on_right_click: interactivity.on_right_click.clone(),
on_scroll: interactivity.on_scroll.clone(),
on_drag: interactivity.on_drag.clone(),
on_mouse_move: interactivity.on_mouse_move.clone(),
scroll: self.scroll.clone(),
focus: interactivity.focus,
tooltip: interactivity.tooltip.clone(),
cursor: interactivity.cursor,
drag: interactivity.drag.clone(),
drops: interactivity.drops.clone(),
});
if let Some(group) = &self.interactivity.group {
cx.register_group(group.clone(), hitbox);
}
}
let has_dispatch = self.interactivity.needs_dispatch_node();
if has_dispatch {
cx.push_dispatch_node(self.dispatch_node());
}
self.visit_children(cx, None, bounds, |child, cx| child.prepaint(cx));
if has_dispatch {
cx.pop_dispatch_node();
}
}
}