///|
/// Event handlers and hit testing for TUI components
///|
/// Click handler type
pub struct ClickHandler(() -> Unit)
///|
/// Create a new click handler
pub fn ClickHandler::new(handler : () -> Unit) -> ClickHandler {
ClickHandler(handler)
}
///|
/// Event handlers map (component ID -> handler)
pub struct EventHandlers {
click_handlers : Map[String, ClickHandler]
}
///|
pub fn EventHandlers::new() -> EventHandlers {
{ click_handlers: Map([]) }
}
///|
/// Register a click handler for a component
pub fn EventHandlers::on_click(
self : EventHandlers,
id : String,
handler : ClickHandler,
) -> Unit {
self.click_handlers.set(id, handler)
}
///|
/// Get click handler for a component
pub fn EventHandlers::get_click(
self : EventHandlers,
id : String,
) -> ClickHandler? {
self.click_handlers.get(id)
}
///|
/// Perform hit test on layout tree
/// Returns the deepest (most specific) node containing the point
pub fn hit_test(
layout : @ccore.Layout,
mouse_x : Int,
mouse_y : Int,
offset_x : Int,
offset_y : Int,
) -> HitTestResult? {
hit_test_f(
layout,
mouse_x,
mouse_y,
offset_x.to_double(),
offset_y.to_double(),
)
}
///|
fn hit_test_f(
layout : @ccore.Layout,
mouse_x : Int,
mouse_y : Int,
offset_x : Double,
offset_y : Double,
) -> HitTestResult? {
let abs_x = offset_x + layout.x
let abs_y = offset_y + layout.y
let rect = @core.normalize_grid_rect(
abs_x,
abs_y,
layout.width,
layout.height,
)
let x = rect.x
let y = rect.y
let w = rect.width
let h = rect.height
if mouse_x >= x && mouse_x < x + w && mouse_y >= y && mouse_y < y + h {
for child in layout.children {
match hit_test_f(child, mouse_x, mouse_y, abs_x, abs_y) {
Some(result) => return Some(result)
None => continue
}
}
Some({ id: layout.id, x, y, width: w, height: h, role: None })
} else {
None
}
}
///|
/// Hit test from root (convenience function)
pub fn hit_test_root(
layout : @ccore.Layout,
mouse_x : Int,
mouse_y : Int,
) -> HitTestResult? {
hit_test(layout, mouse_x, mouse_y, 0, 0)
}
///|
/// Handle mouse click event
/// Returns true if a handler was invoked
pub fn handle_click(
layout : @ccore.Layout,
handlers : EventHandlers,
mouse_x : Int,
mouse_y : Int,
) -> Bool {
match hit_test_root(layout, mouse_x, mouse_y) {
Some(result) =>
match handlers.get_click(result.id) {
Some(ClickHandler(handler)) => {
handler()
true
}
None => false
}
None => false
}
}
///|
/// Find layout by ID
/// Returns the position and size of the component with the given ID
pub fn find_layout_by_id(
layout : @ccore.Layout,
target_id : String,
offset_x : Int,
offset_y : Int,
) -> HitTestResult? {
find_layout_by_id_f(
layout,
target_id,
offset_x.to_double(),
offset_y.to_double(),
)
}
///|
fn find_layout_by_id_f(
layout : @ccore.Layout,
target_id : String,
offset_x : Double,
offset_y : Double,
) -> HitTestResult? {
let abs_x = offset_x + layout.x
let abs_y = offset_y + layout.y
let rect = @core.normalize_grid_rect(
abs_x,
abs_y,
layout.width,
layout.height,
)
let x = rect.x
let y = rect.y
let w = rect.width
let h = rect.height
if layout.id == target_id {
return Some({ id: layout.id, x, y, width: w, height: h, role: None })
}
for child in layout.children {
match find_layout_by_id_f(child, target_id, abs_x, abs_y) {
Some(result) => return Some(result)
None => continue
}
}
None
}
///|
/// Find layout by ID from root
pub fn find_layout_by_id_root(
layout : @ccore.Layout,
target_id : String,
) -> HitTestResult? {
find_layout_by_id(layout, target_id, 0, 0)
}
///|
/// Collect all layout rectangles (absolute coordinates) into a flat list.
fn collect_layout_rects(
layout : @ccore.Layout,
offset_x : Double,
offset_y : Double,
out : Array[HitTestResult],
) -> Unit {
let abs_x = offset_x + layout.x
let abs_y = offset_y + layout.y
let rect = @core.normalize_grid_rect(
abs_x,
abs_y,
layout.width,
layout.height,
)
let x = rect.x
let y = rect.y
let w = rect.width
let h = rect.height
out.push({ id: layout.id, x, y, width: w, height: h, role: None })
for child in layout.children {
collect_layout_rects(child, abs_x, abs_y, out)
}
}
///|
/// Flatten layout tree into rects (root is included).
pub fn layout_rects_root(layout : @ccore.Layout) -> Array[HitTestResult] {
let out : Array[HitTestResult] = []
collect_layout_rects(layout, 0.0, 0.0, out)
out
}
///|
/// Debug: dump layout tree structure
pub fn dump_layout(
layout : @ccore.Layout,
offset_x : Int,
offset_y : Int,
depth : Int,
) -> String {
dump_layout_f(layout, offset_x.to_double(), offset_y.to_double(), depth)
}
///|
fn dump_layout_f(
layout : @ccore.Layout,
offset_x : Double,
offset_y : Double,
depth : Int,
) -> String {
let abs_x = offset_x + layout.x
let abs_y = offset_y + layout.y
let rect = @core.normalize_grid_rect(
abs_x,
abs_y,
layout.width,
layout.height,
)
let raw_x = @core.snap_to_grid(layout.x)
let raw_y = @core.snap_to_grid(layout.y)
let x = rect.x
let y = rect.y
let w = rect.width
let h = rect.height
let indent = " ".repeat(depth)
let mut result = indent +
layout.id +
" raw=(" +
raw_x.to_string() +
"," +
raw_y.to_string() +
") abs=(" +
x.to_string() +
"," +
y.to_string() +
") " +
w.to_string() +
"x" +
h.to_string() +
" " +
layout.children.length().to_string() +
"\n"
for child in layout.children {
result = result + dump_layout_f(child, abs_x, abs_y, depth + 1)
}
result
}