refactor: extract block menu overlay layout

This commit is contained in:
lix-2026
2026-05-25 02:50:31 +08:00
parent 630a7870e7
commit 3b8445ffab
6 changed files with 114 additions and 55 deletions
@@ -0,0 +1,100 @@
//! Block menu hover anchor / layout helper。
//!
//! 本模块承接 block menu 的锚点与面板布局计算,保持几何计算与 DOM 查询分离。
//! 调用方(lib.rs)保留 DOM 查询(editor_stage_element),本模块只做纯几何计算。
//!
//! 迁入自 lib.rs
//! - `hover_anchor_top`
//! - `hover_anchor_transform`
//! - `block_menu_layout`(参数化版)
use crate::editor_runtime::block_hover_state::{BlockMenuLayout, HoveredBlockState};
/// 多行块的最小高度阈值,超过此值视作多行。
const HANDLE_MULTILINE_HEIGHT: f64 = 40.0;
/// handle shell 菜单左侧最小限制。
const HANDLE_MENU_LEFT_LIMIT: f64 = -320.0;
/// 内容列水平内边距(与 lib.rs 中同名常量同值)。
const CONTENT_COLUMN_HORIZONTAL_PADDING: f64 = 48.0;
/// 内容列最大宽度(与 lib.rs 中同名常量同值)。
const CONTENT_COLUMN_MAX_WIDTH: f64 = 708.0;
/// 根据 stage 宽度计算实际内容列宽。
fn content_column_width(stage_rect_width: f64) -> f64 {
let available = (stage_rect_width - (CONTENT_COLUMN_HORIZONTAL_PADDING * 2.0)).max(0.0);
available.min(CONTENT_COLUMN_MAX_WIDTH)
}
/// 内容列左侧相对于 stage 的偏移。
fn content_column_left(stage_rect_width: f64) -> f64 {
let column_width = content_column_width(stage_rect_width);
((stage_rect_width - column_width) / 2.0).max(0.0)
}
/// 内容文本区左侧相对于 stage 的偏移(列左内边距后)。
fn content_text_left(stage_rect_width: f64) -> f64 {
content_column_left(stage_rect_width) + CONTENT_COLUMN_HORIZONTAL_PADDING
}
/// 计算 handle shell 锚点在 stage 内的垂直位置。
pub(crate) fn hover_anchor_top(block: &HoveredBlockState) -> f64 {
if block.height > HANDLE_MULTILINE_HEIGHT {
block.top + 12.0
} else {
block.top + (block.height / 2.0) - 32.0
}
}
/// 计算 handle shell 锚点 transform 值(当前固定返回 "none")。
pub(crate) fn hover_anchor_transform(_block: &HoveredBlockState) -> &'static str {
"none"
}
/// 计算 block menu 面板布局。
///
/// 参数化版本:调用方提供 stage 的 bounding client rect 尺寸和位置,
/// 本函数做纯几何计算,不查询 DOM。
pub(crate) fn block_menu_layout(
block: &HoveredBlockState,
stage_width: f64,
stage_height: f64,
stage_top: f64,
stage_left: f64,
) -> Option<BlockMenuLayout> {
let shell_top = hover_anchor_top(block);
let stage_padding = 18.0;
let space_above = (shell_top - stage_padding).max(0.0);
let space_below = (stage_height - shell_top - stage_padding).max(0.0);
let open_upward = space_below < 340.0 && space_above > space_below;
let available_height = if open_upward {
space_above
} else {
space_below
};
let max_height = if available_height >= 220.0 {
available_height
} else {
available_height.max(160.0)
};
let menu_width = 242.0;
let menu_stage_left =
(content_text_left(stage_width) - menu_width - 132.0).max(HANDLE_MENU_LEFT_LIMIT);
let viewport_shell_top = stage_top + shell_top;
let top = if open_upward {
viewport_shell_top + 64.0 - max_height
} else {
viewport_shell_top
}
.max(8.0);
let left = (stage_left + menu_stage_left).max(8.0);
Some(BlockMenuLayout {
max_height,
open_upward,
top,
left,
})
}
@@ -2,6 +2,7 @@ pub(crate) mod attachment_links;
pub(crate) mod attachment_upload;
pub(crate) mod block_hover_state;
pub(crate) mod block_menu_document;
pub(crate) mod block_menu_overlay;
pub(crate) mod block_menu_legacy_html;
pub(crate) mod command_sync;
pub(crate) mod dom_selection;