// Copyright 2026 Ling71671
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
fn handle_node_hit_test(
state : BenchmarkState,
ctx : @core.UIContext,
mouse_down : Bool,
in_canvas : Bool,
mwx : Double,
mwy : Double,
pitch : Double,
grid_dim : Int,
matrix_world_size : Double,
) -> Unit {
let can_hit_nodes = in_canvas &&
!ctx.wants_capture_mouse() &&
mwx >= 0.0 &&
mwx < matrix_world_size &&
mwy >= 0.0 &&
mwy < matrix_world_size
if can_hit_nodes {
let col = (mwx / pitch).to_int()
let row = (mwy / pitch).to_int()
if col >= 0 && col < grid_dim && row >= 0 && row < grid_dim {
let node_id = row * grid_dim + col
if mouse_down {
state.click_count = state.click_count + 1
if state.selected_id != node_id {
state.selected_id = node_id
state.selected_col = col
state.selected_row = row
state.item_drag_dx = 0.0
state.item_drag_dy = 0.0
}
// Launch dynamic water ripple from click origin
state.ripple_active = true
state.ripple_cx = col.to_double() * pitch + pitch * 0.5
state.ripple_cy = row.to_double() * pitch + pitch * 0.5
state.ripple_progress = 0.0
}
}
}
}
///|
fn update_ripple_physics(
state : BenchmarkState,
matrix_world_size : Double,
pitch : Double,
) -> Unit {
if state.ripple_active {
state.ripple_progress = state.ripple_progress + pitch * 1.6
if state.ripple_progress > matrix_world_size * 0.6 {
state.ripple_active = false
state.ripple_progress = 0.0
}
}
}
///|
fn calc_culling_bounds(
state : BenchmarkState,
cv_w : Double,
cv_h : Double,
pitch : Double,
grid_dim : Int,
) -> (Int, Int, Int, Int, Int, Double, Double, Double, Double) {
let half_vw = cv_w * 0.5 / state.zoom
let half_vh = cv_h * 0.5 / state.zoom
let min_wx = state.cam_x - half_vw
let max_wx = state.cam_x + half_vw
let min_wy = state.cam_y - half_vh
let max_wy = state.cam_y + half_vh
let mut c_start = (min_wx / pitch).to_int()
if c_start < 0 {
c_start = 0
}
let mut c_end = (max_wx / pitch).to_int() + 1
if c_end > grid_dim {
c_end = grid_dim
}
let mut r_start = (min_wy / pitch).to_int()
if r_start < 0 {
r_start = 0
}
let mut r_end = (max_wy / pitch).to_int() + 1
if r_end > grid_dim {
r_end = grid_dim
}
let total_vis = if c_end > c_start && r_end > r_start {
(c_end - c_start) * (r_end - r_start)
} else {
0
}
state.visible_cells = total_vis
(c_start, c_end, r_start, r_end, total_vis, min_wx, max_wx, min_wy, max_wy)
}
///|
fn render_matrix_nodes(
dl : @draw.DrawList,
state : BenchmarkState,
in_canvas : Bool,
mouse_x : Double,
mouse_y : Double,
cv_x : Double,
cv_y : Double,
cv_w : Double,
cv_h : Double,
sc_x : Double,
sc_y : Double,
grid_dim : Int,
pitch : Double,
cell_size : Double,
mwx : Double,
mwy : Double,
c_start : Int,
c_end : Int,
r_start : Int,
r_end : Int,
total_vis : Int,
matrix_world_size : Double,
) -> Bool {
dl.push_clip(@math.Rect::new(cv_x, cv_y, cv_w, cv_h))
let repel_radius = pitch * 4.5
let repel_radius_sq = repel_radius * repel_radius
let is_micro_vector = total_vis <= 4000
if is_micro_vector {
let on_screen_cell_w = cell_size * state.zoom
let on_screen_cell_h = cell_size * state.zoom
let rect_radius = 0.0
for r = r_start; r < r_end; r = r + 1 {
let base_wy = r.to_double() * pitch
for c = c_start; c < c_end; c = c + 1 {
let base_wx = c.to_double() * pitch
let node_id = r * grid_dim + c
let is_selected = node_id == state.selected_id
let is_dragged = is_selected &&
(
state.item_drag_dx != 0.0 ||
state.item_drag_dy != 0.0 ||
state.is_dragging_item
)
// Physics: Magnetic Repel
let mut pdx = 0.0
let mut pdy = 0.0
if state.magnetic_repel && in_canvas {
let c_center_x = base_wx + pitch * 0.5
let c_center_y = base_wy + pitch * 0.5
let dist_sq = (c_center_x - mwx) * (c_center_x - mwx) +
(c_center_y - mwy) * (c_center_y - mwy)
if dist_sq < repel_radius_sq && dist_sq > 0.1 {
let repel_factor = (repel_radius_sq - dist_sq) / repel_radius_sq
let push = repel_factor * pitch * 0.8
pdx = (c_center_x - mwx) / repel_radius * push
pdy = (c_center_y - mwy) / repel_radius * push
}
}
// Physics: Wave Pulse
if state.wave_pulse {
let wave_val = state.frame_tick * 0.07 + (c + r).to_double() * 0.25
let phase = wave_val - (wave_val / 6.283).to_int().to_double() * 6.283
let wave_dy = if phase < 3.14159 {
(phase / 3.14159 * 2.0 - 1.0) * pitch * 0.12
} else {
(1.0 - (phase - 3.14159) / 3.14159 * 2.0) * pitch * 0.12
}
pdy = pdy + wave_dy
}
// Physics: Water Ripple Wavefront
let mut ripple_bright = false
if state.ripple_active {
let rx = base_wx - state.ripple_cx
let ry = base_wy - state.ripple_cy
let r_dist = rx * rx + ry * ry
let target_dist = state.ripple_progress * state.ripple_progress
let diff = d_abs(r_dist - target_dist)
if diff < pitch * pitch * 36.0 {
ripple_bright = true
pdy = pdy - pitch * 0.3
}
}
let cur_wx = if is_dragged {
base_wx + state.item_drag_dx + pdx
} else {
base_wx + pdx
}
let cur_wy = if is_dragged {
base_wy + state.item_drag_dy + pdy
} else {
base_wy + pdy
}
let sx = sc_x + (cur_wx - state.cam_x) * state.zoom
let sy = sc_y + (cur_wy - state.cam_y) * state.zoom
// Determine node color
let is_hovered = in_canvas &&
mwx >= base_wx &&
mwx < base_wx + pitch &&
mwy >= base_wy &&
mwy < base_wy + pitch
let cell_color = if is_selected {
@color.Color::rgb(244, 63, 94) // Active Selected Rose
} else if is_hovered {
@color.Color::rgb(56, 189, 248) // Hover Cyan
} else if ripple_bright {
@color.Color::rgb(251, 146, 60) // Ripple Amber
} else {
match get_logo_color(grid_dim, c, r) {
Some(lc) => lc
None =>
if (c + r) % 2 == 0 {
@color.Color::bg_window() // Pure White
} else {
@color.Color::bg_hover() // Slate-100 Light Gray
}
}
}
dl.add_rect(
@math.Rect::new(sx, sy, on_screen_cell_w, on_screen_cell_h),
cell_color,
rect_radius,
)
if is_hovered {
dl.add_rect_stroke(
@math.Rect::new(sx, sy, on_screen_cell_w, on_screen_cell_h),
@color.Color::rgb(37, 99, 235),
1.5,
0.0,
)
}
}
}
} else {
// Macro / Texture LOD Mode:
// Render dynamic hover indicator, ripple wavefront & magnetic indicator on top of texture
if in_canvas &&
mwx >= 0.0 &&
mwx < matrix_world_size &&
mwy >= 0.0 &&
mwy < matrix_world_size {
let h_col = (mwx / pitch).to_int()
let h_row = (mwy / pitch).to_int()
if h_col >= 0 && h_col < grid_dim && h_row >= 0 && h_row < grid_dim {
let h_base_wx = h_col.to_double() * pitch
let h_base_wy = h_row.to_double() * pitch
let hs_x = sc_x + (h_base_wx - state.cam_x) * state.zoom
let hs_y = sc_y + (h_base_wy - state.cam_y) * state.zoom
let hs_w = (cell_size * state.zoom).max(4.0)
let hs_h = (cell_size * state.zoom).max(4.0)
dl.add_rect(
@math.Rect::new(hs_x, hs_y, hs_w, hs_h),
@color.Color::rgb(56, 189, 248),
0.0,
)
}
}
if state.ripple_active {
let rip_sx = sc_x + (state.ripple_cx - state.cam_x) * state.zoom
let rip_sy = sc_y + (state.ripple_cy - state.cam_y) * state.zoom
let rip_r = state.ripple_progress * state.zoom
if rip_r > 1.0 {
dl.add_circle_stroke(
@math.Vec2::new(rip_sx, rip_sy),
rip_r,
@color.Color::rgb(251, 146, 60),
2.0,
)
}
}
if state.magnetic_repel && in_canvas {
dl.add_circle_stroke(
@math.Vec2::new(mouse_x, mouse_y),
repel_radius * state.zoom,
@color.Color::rgba(249, 115, 22, 180),
1.5,
)
}
}
// End matrix cell clipping
dl.pop_clip()
is_micro_vector
}
///|
fn render_selected_transform_frame(
dl : @draw.DrawList,
state : BenchmarkState,
cv_x : Double,
cv_y : Double,
cv_w : Double,
cv_h : Double,
sc_x : Double,
sc_y : Double,
pitch : Double,
cell_size : Double,
grid_dim : Int,
) -> Unit {
if state.selected_id >= 0 {
dl.push_clip(@math.Rect::new(cv_x, cv_y, cv_w, cv_h))
let sel_id = state.selected_id
let r = sel_id / grid_dim
let c = sel_id % grid_dim
let is_dragged = state.item_drag_dx != 0.0 ||
state.item_drag_dy != 0.0 ||
state.is_dragging_item
let sel_orig_wx = c.to_double() * pitch
let sel_orig_wy = r.to_double() * pitch
let sel_wx = if is_dragged {
sel_orig_wx + state.item_drag_dx
} else {
sel_orig_wx
}
let sel_wy = if is_dragged {
sel_orig_wy + state.item_drag_dy
} else {
sel_orig_wy
}
let sel_sx = sc_x + (sel_wx - state.cam_x) * state.zoom
let sel_sy = sc_y + (sel_wy - state.cam_y) * state.zoom
let sel_w = (cell_size * state.zoom).max(8.0)
let sel_h = (cell_size * state.zoom).max(8.0)
// 1. Purple Outline
dl.add_rect_stroke(
@math.Rect::new(sel_sx - 3.0, sel_sy - 3.0, sel_w + 6.0, sel_h + 6.0),
@color.Color::rgb(139, 92, 246),
1.5,
2.0,
)
// 2. 4 Circular Corner Handles
let handle_r = 3.0
let h_purple = @color.Color::rgb(139, 92, 246)
let h_white = @color.Color::bg_window()
let tl_pos = @math.Vec2::new(sel_sx - 3.0, sel_sy - 3.0)
dl.add_circle(tl_pos, handle_r, h_white)
dl.add_circle_stroke(tl_pos, handle_r, h_purple, 1.5)
let tr_pos = @math.Vec2::new(sel_sx + sel_w + 3.0, sel_sy - 3.0)
dl.add_circle(tr_pos, handle_r, h_white)
dl.add_circle_stroke(tr_pos, handle_r, h_purple, 1.5)
let bl_pos = @math.Vec2::new(sel_sx - 3.0, sel_sy + sel_h + 3.0)
dl.add_circle(bl_pos, handle_r, h_white)
dl.add_circle_stroke(bl_pos, handle_r, h_purple, 1.5)
let br_pos = @math.Vec2::new(sel_sx + sel_w + 3.0, sel_sy + sel_h + 3.0)
dl.add_circle(br_pos, handle_r, h_white)
dl.add_circle_stroke(br_pos, handle_r, h_purple, 1.5)
// 3. Floating Inspector Tag
let tag_txt = if is_dragged {
"拖拽中 #\{sel_id} [列:\{c}, 行:\{r}]"
} else {
"#\{sel_id} [列:\{c}, 行:\{r}]"
}
let tag_w = tag_txt.length().to_double() * 7.0 + 16.0
dl.add_rect(
@math.Rect::new(
sel_sx + (sel_w - tag_w) * 0.5,
sel_sy - 24.0,
tag_w,
18.0,
),
@color.Color::bg_window(),
4.0,
)
dl.add_rect_stroke(
@math.Rect::new(
sel_sx + (sel_w - tag_w) * 0.5,
sel_sy - 24.0,
tag_w,
18.0,
),
@color.Color::border_default(),
1.0,
4.0,
)
dl.add_text(
@math.Vec2::new(sel_sx + (sel_w - tag_w) * 0.5 + 8.0, sel_sy - 22.0),
tag_txt,
10.0,
@color.Color::text_primary(),
)
dl.pop_clip()
}
}