use std::cell::RefCell;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RenderMark {
pub kind: MarkKind,
pub label: &'static str,
pub detail: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MarkKind {
Constraint,
Style,
Widget,
Custom,
}
impl fmt::Display for MarkKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Constraint => write!(f, "constraint"),
Self::Style => write!(f, "style"),
Self::Widget => write!(f, "widget"),
Self::Custom => write!(f, "custom"),
}
}
}
impl RenderMark {
pub fn constraint(label: &'static str, detail: impl Into<String>) -> Self {
Self {
kind: MarkKind::Constraint,
label,
detail: detail.into(),
}
}
pub fn style(label: &'static str, detail: impl Into<String>) -> Self {
Self {
kind: MarkKind::Style,
label,
detail: detail.into(),
}
}
pub fn widget(label: &'static str) -> Self {
Self {
kind: MarkKind::Widget,
label,
detail: String::new(),
}
}
pub fn custom(label: &'static str, detail: impl Into<String>) -> Self {
Self {
kind: MarkKind::Custom,
label,
detail: detail.into(),
}
}
}
impl fmt::Display for RenderMark {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.detail.is_empty() {
write!(f, "[{}] {}", self.kind, self.label)
} else {
write!(f, "[{}] {}: {}", self.kind, self.label, self.detail)
}
}
}
thread_local! {
static MARK_STACK: RefCell<Vec<RenderMark>> = const { RefCell::new(Vec::new()) };
}
pub fn push_mark(mark: RenderMark) {
MARK_STACK.with(|stack| stack.borrow_mut().push(mark));
}
pub fn pop_mark() -> Option<RenderMark> {
MARK_STACK.with(|stack| stack.borrow_mut().pop())
}
pub fn current_marks() -> Vec<RenderMark> {
MARK_STACK.with(|stack| stack.borrow().clone())
}
pub fn mark_depth() -> usize {
MARK_STACK.with(|stack| stack.borrow().len())
}
pub fn clear_marks() {
MARK_STACK.with(|stack| stack.borrow_mut().clear());
}
pub struct MarkGuard {
_private: (),
}
impl MarkGuard {
pub fn constraint(label: &'static str, detail: impl Into<String>) -> Self {
push_mark(RenderMark::constraint(label, detail));
Self { _private: () }
}
pub fn style(label: &'static str, detail: impl Into<String>) -> Self {
push_mark(RenderMark::style(label, detail));
Self { _private: () }
}
pub fn widget(label: &'static str) -> Self {
push_mark(RenderMark::widget(label));
Self { _private: () }
}
pub fn custom(label: &'static str, detail: impl Into<String>) -> Self {
push_mark(RenderMark::custom(label, detail));
Self { _private: () }
}
pub fn new(mark: RenderMark) -> Self {
push_mark(mark);
Self { _private: () }
}
}
impl Drop for MarkGuard {
fn drop(&mut self) {
pop_mark();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn with_clean_stack(f: impl FnOnce()) {
clear_marks();
f();
clear_marks();
}
#[test]
fn push_pop_basic() {
with_clean_stack(|| {
push_mark(RenderMark::widget("Button"));
assert_eq!(mark_depth(), 1);
let m = pop_mark().unwrap();
assert_eq!(m.kind, MarkKind::Widget);
assert_eq!(m.label, "Button");
assert_eq!(mark_depth(), 0);
});
}
#[test]
fn pop_empty_returns_none() {
with_clean_stack(|| {
assert!(pop_mark().is_none());
});
}
#[test]
fn guard_auto_pops() {
with_clean_stack(|| {
{
let _g = MarkGuard::widget("Outer");
assert_eq!(mark_depth(), 1);
{
let _g2 = MarkGuard::constraint("Inner", "flex=1");
assert_eq!(mark_depth(), 2);
}
assert_eq!(mark_depth(), 1);
}
assert_eq!(mark_depth(), 0);
});
}
#[test]
fn current_marks_snapshot() {
with_clean_stack(|| {
push_mark(RenderMark::widget("Root"));
push_mark(RenderMark::style("Text", "theme.fg"));
let marks = current_marks();
assert_eq!(marks.len(), 2);
assert_eq!(marks[0].label, "Root");
assert_eq!(marks[1].label, "Text");
});
}
#[test]
fn clear_removes_all() {
with_clean_stack(|| {
push_mark(RenderMark::widget("A"));
push_mark(RenderMark::widget("B"));
push_mark(RenderMark::widget("C"));
assert_eq!(mark_depth(), 3);
clear_marks();
assert_eq!(mark_depth(), 0);
});
}
#[test]
fn display_formatting() {
let m = RenderMark::constraint("FlexRow", "min=20, weight=1.0");
assert_eq!(m.to_string(), "[constraint] FlexRow: min=20, weight=1.0");
let m2 = RenderMark::widget("Button");
assert_eq!(m2.to_string(), "[widget] Button");
}
#[test]
fn nested_guard_early_return() {
with_clean_stack(|| {
let f = || -> Option<()> {
let _g = MarkGuard::widget("Container");
assert_eq!(mark_depth(), 1);
if true {
return None; }
#[allow(unreachable_code)]
Some(())
};
f();
assert_eq!(mark_depth(), 0);
});
}
#[test]
fn mark_kind_display() {
assert_eq!(format!("{}", MarkKind::Constraint), "constraint");
assert_eq!(format!("{}", MarkKind::Style), "style");
assert_eq!(format!("{}", MarkKind::Widget), "widget");
assert_eq!(format!("{}", MarkKind::Custom), "custom");
}
#[test]
fn mark_equality() {
let a = RenderMark::widget("X");
let b = RenderMark::widget("X");
let c = RenderMark::widget("Y");
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn custom_mark() {
with_clean_stack(|| {
let _g = MarkGuard::custom("perf", "budget_exceeded=true");
let marks = current_marks();
assert_eq!(marks[0].kind, MarkKind::Custom);
assert_eq!(marks[0].detail, "budget_exceeded=true");
});
}
}