use std::cell::Cell;
use cranpose_core::{NodeId, PackedU64};
use crate::{
modifier::{Point, Size},
render_state::AppContext,
};
pub(crate) struct PlacementPass {
id: Cell<u64>,
unplaced: Cell<usize>,
}
impl PlacementPass {
pub(crate) const fn new() -> Self {
Self {
id: Cell::new(1),
unplaced: Cell::new(0),
}
}
pub(crate) fn begin(&self) {
self.id.set(self.id.get() + 1);
self.unplaced.set(0);
}
pub(crate) fn with_unplaced_nodes(&self) -> Option<u64> {
(self.unplaced.get() > 0).then(|| self.id.get())
}
fn clear(&self) -> u64 {
self.unplaced.set(self.unplaced.get() + 1);
self.id.get()
}
fn restore(&self, cleared_in: u64) -> bool {
let restored = cleared_in == self.id.get();
if restored {
self.unplaced.set(self.unplaced.get().saturating_sub(1));
}
restored
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct LayoutState {
size: Size,
position: Point,
is_placed: bool,
node_id: Option<NodeId>,
cleared_in_pass: PackedU64,
content_offset: Point,
semantics_stamp: PackedU64,
moved_noted_in: PackedU64,
reshaped_noted_in: PackedU64,
z_index: f32,
places_children_with_z_index: bool,
}
#[derive(Clone, Copy, Default)]
pub(crate) struct ChildPlacement {
position: Point,
z_index: f32,
}
impl ChildPlacement {
pub(crate) fn new(position: Point, z_index: f32) -> Self {
Self { position, z_index }
}
}
impl LayoutState {
pub fn size(&self) -> Size {
self.size
}
pub fn position(&self) -> Point {
self.position
}
pub fn content_offset(&self) -> Point {
self.content_offset
}
pub fn set_content_offset(&mut self, offset: Point) {
if self.content_offset != offset {
self.content_offset = offset;
self.note_semantics_change();
}
}
pub(crate) fn note_geometry_change(&mut self, node_id: NodeId, context: &AppContext) {
context.note_geometry_scene_node(node_id, &mut self.reshaped_noted_in);
}
pub fn at_origin(mut self) -> Self {
self.position = Point::default();
self
}
pub fn is_placed(&self) -> bool {
self.is_placed
}
pub(crate) fn set_node_id(&mut self, node_id: NodeId) {
self.node_id = Some(node_id);
}
pub fn set_size(&mut self, size: Size, context: &AppContext) {
if self.size != size {
if let Some(id) = self.node_id {
context.note_geometry_scene_node(id, &mut self.reshaped_noted_in);
}
self.size = size;
self.note_semantics_change();
}
}
pub fn z_index(&self) -> f32 {
self.z_index
}
pub fn places_children_with_z_index(&self) -> bool {
self.places_children_with_z_index
}
pub(crate) fn set_places_children_with_z_index(&mut self, places: bool) {
if self.places_children_with_z_index != places {
self.places_children_with_z_index = places;
count_z_index_placer(places);
}
}
pub(crate) fn place_child(
&mut self,
at: ChildPlacement,
placer: Option<NodeId>,
context: &AppContext,
) {
self.place(at.position, placer, context);
self.set_z_index(at.z_index);
}
pub(crate) fn set_z_index(&mut self, z_index: f32) {
if self.z_index.to_bits() == z_index.to_bits() {
return;
}
self.z_index = z_index;
if let Some(id) = self.node_id {
crate::schedule_z_order_change(id);
}
}
pub fn place(&mut self, position: Point, placer: Option<NodeId>, context: &AppContext) {
let newly_placed =
!self.is_placed && !context.placement_pass().restore(self.cleared_in_pass.get());
let moved = self.position != position;
if let Some(id) = self.node_id {
if newly_placed {
context.note_geometry_scene_node(id, &mut self.reshaped_noted_in);
} else if moved {
context.record_moved_scene_node(id, placer, &mut self.moved_noted_in);
}
}
self.position = position;
if moved || !self.is_placed {
self.is_placed = true;
self.note_semantics_change();
}
}
pub fn clear_placed(&mut self, context: &AppContext) {
if !self.is_placed {
return;
}
self.is_placed = false;
self.cleared_in_pass = PackedU64::new(context.placement_pass().clear());
self.note_semantics_change();
}
pub(crate) fn unplaced_in(&self, pass: u64) -> bool {
!self.is_placed && self.cleared_in_pass.get() == pass
}
#[inline]
pub(crate) fn note_semantics_change(&mut self) {
if let Some(id) = self.node_id {
crate::semantics_layout_log::record_layout_change(id, &mut self.semantics_stamp);
}
}
}
pub(super) fn count_z_index_placer(places: bool) {
crate::thread_state::cells(|cells| {
let reasons = &cells.z_order_reasons;
reasons.set(if places {
reasons.get() + 1
} else {
reasons.get().saturating_sub(1)
});
});
}