Skip to content

Commit 06c359e

Browse files
committed
feat: 优化性能
1 parent 14c0f5d commit 06c359e

4 files changed

Lines changed: 88 additions & 65 deletions

File tree

crates/project_graph/src/stage.rs

Lines changed: 58 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,24 @@ pub mod context;
33
pub mod elements;
44
pub mod render_context;
55

6+
use crate::stage::{elements::ElementTrait, render_context::RenderContext};
67
use camera::Camera;
78
use context::StageContext;
8-
use egui::{Vec2, vec2};
9-
10-
use crate::stage::{
11-
elements::{Element, ElementTrait, entities::EntityTrait},
12-
render_context::RenderContext,
13-
};
149

1510
/// egui 和画布之间的桥梁
1611
/// 负责坐标系转换、事件处理等
1712
pub struct Stage {
1813
pub camera: Camera,
1914
pub context: StageContext,
20-
pub selection: Vec<String>,
15+
pub selection: std::collections::HashSet<String>,
2116
}
2217

2318
impl Stage {
2419
pub fn new() -> Self {
2520
Stage {
2621
camera: Camera::new(),
2722
context: StageContext::random(),
28-
selection: Vec::new(),
23+
selection: std::collections::HashSet::new(),
2924
}
3025
}
3126

@@ -38,61 +33,46 @@ impl Stage {
3833
let screen_center = rect.center();
3934
let mut visible_count = 0;
4035

36+
// 获取视野范围(世界坐标)
37+
let world_min = self.camera.screen_to_world(rect.min, screen_center);
38+
let world_max = self.camera.screen_to_world(rect.max, screen_center);
39+
let world_viewport = egui::Rect::from_two_pos(world_min, world_max);
40+
4141
for element in self.context.elements().values() {
42-
let entity = match element {
43-
Element::Entity(e) => e,
44-
};
45-
// 剔除
46-
if !rect.contains(entity.position()) {
42+
// 剔除:检查元素的包围盒是否与视野范围相交
43+
if !world_viewport.intersects(element.world_rect().expand(20.0)) {
4744
continue;
4845
}
4946

5047
visible_count += 1;
5148

52-
let selected = self.selection.contains(&entity.id().to_string());
53-
49+
let selected = self.selection.contains(element.id());
5450
let screen_pos = self
5551
.camera
56-
.world_to_screen(entity.position(), screen_center)
57-
- if selected {
58-
vec2(4.0, 4.0) * self.camera.zoom()
59-
} else {
60-
Vec2::ZERO
61-
};
62-
63-
egui::Area::new(ui.make_persistent_id(entity.id()))
64-
.order(egui::Order::Background)
65-
.fixed_pos(screen_pos)
66-
.show(ui.ctx(), |ui| {
67-
let response = egui::Frame::new()
68-
.inner_margin(4.0 * self.camera.zoom())
69-
.corner_radius(24.0 * self.camera.zoom())
70-
.stroke(if selected {
71-
egui::Stroke::new(
72-
4.0 * self.camera.zoom(),
73-
egui::Color32::LIGHT_BLUE,
74-
)
75-
} else {
76-
egui::Stroke::NONE
77-
})
78-
.show(ui, |ui| {
79-
entity.ui(
80-
ui,
81-
&mut RenderContext {
82-
zoom: self.camera.zoom(),
83-
selected: selected,
84-
},
85-
);
86-
})
87-
.response
88-
.interact(egui::Sense::click());
89-
90-
if response.clicked() {
91-
log::info!("Entity {} clicked", entity.id());
92-
self.selection.clear();
93-
self.selection.push(entity.id().to_string());
94-
}
95-
});
52+
.world_to_screen(element.world_rect().center(), screen_center);
53+
54+
// 在屏幕坐标系下进行局部渲染,不再使用 egui::Area
55+
let mut rc = RenderContext {
56+
zoom: self.camera.zoom(),
57+
selected,
58+
clicked: false,
59+
};
60+
61+
let element_size = element.world_rect().size() * self.camera.zoom();
62+
let element_rect = egui::Rect::from_center_size(screen_pos, element_size);
63+
64+
// 显式限制裁剪区域,避免过度渲染
65+
ui.scope_builder(egui::UiBuilder::new().max_rect(element_rect), |ui| {
66+
element.ui(ui, &mut rc);
67+
});
68+
69+
if rc.clicked {
70+
log::info!("Entity {} clicked", element.id());
71+
if !ui.input(|i| i.modifiers.shift) {
72+
self.selection.clear();
73+
}
74+
self.selection.insert(element.id().to_string());
75+
}
9676
}
9777

9878
painter.text(
@@ -104,19 +84,38 @@ impl Stage {
10484
);
10585
});
10686

107-
let scroll_delta = ui.input(|i| i.smooth_scroll_delta);
108-
if scroll_delta.y != 0.0 {
87+
// 处理手势和滚动
88+
let zoom_delta = ui.input(|i| i.zoom_delta());
89+
if zoom_delta != 1.0 {
10990
if let Some(mouse_pos) = ui.input(|i| i.pointer.hover_pos()) {
110-
let zoom_factor = (1.0 + scroll_delta.y * 0.01).clamp(0.9, 1.1);
11191
let old_zoom = self.camera.zoom();
112-
self.camera.zoom_by(zoom_factor);
92+
self.camera.zoom_by(zoom_delta);
11393

11494
let offset = mouse_pos - rect.center();
11595
let delta = offset * (self.camera.zoom() / old_zoom - 1.0);
11696
self.camera.pan_by(delta);
11797
}
11898
}
11999

100+
let scroll_delta = ui.input(|i| i.smooth_scroll_delta);
101+
if scroll_delta != egui::Vec2::ZERO {
102+
if ui.input(|i| i.modifiers.command || i.modifiers.ctrl) {
103+
// Ctrl + 滚动 = 缩放
104+
if let Some(mouse_pos) = ui.input(|i| i.pointer.hover_pos()) {
105+
let zoom_factor = (1.0 + scroll_delta.y * 0.01).clamp(0.9, 1.1);
106+
let old_zoom = self.camera.zoom();
107+
self.camera.zoom_by(zoom_factor);
108+
109+
let offset = mouse_pos - rect.center();
110+
let delta = offset * (self.camera.zoom() / old_zoom - 1.0);
111+
self.camera.pan_by(delta);
112+
}
113+
} else {
114+
// 普通滚动 = 平移
115+
self.camera.pan_by(-scroll_delta);
116+
}
117+
}
118+
120119
// 中键拖拽平移
121120
if response.dragged_by(egui::PointerButton::Middle) {
122121
let drag_delta = ui.input(|i| i.pointer.delta());

crates/project_graph/src/stage/elements.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pub trait ElementTrait {
1414
fn id(&self) -> &str;
1515
/// 渲染函数
1616
fn ui(&self, ui: &mut egui::Ui, rc: &mut RenderContext);
17+
/// 获取包围盒(世界坐标)
18+
fn world_rect(&self) -> egui::Rect;
1719
}
1820

1921
#[enum_dispatch(ElementTrait)]

crates/project_graph/src/stage/elements/entities/text.rs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,43 @@ impl ElementTrait for Text {
3232
&self.id
3333
}
3434
fn ui(&self, ui: &mut egui::Ui, rc: &mut RenderContext) {
35-
let response = egui::Frame::new()
35+
// 预计算,避免在 egui 生命周期内做重算
36+
let zoom = rc.zoom;
37+
let font_id = egui::FontId::proportional(16.0 * zoom);
38+
39+
// 使用更加紧凑的布局方式
40+
let frame = egui::Frame::new()
3641
.fill(if rc.selected {
3742
egui::Color32::from_rgba_unmultiplied(150, 150, 255, 75)
3843
} else {
3944
egui::Color32::from_rgba_unmultiplied(255, 255, 255, 25)
4045
})
41-
.corner_radius(16.0 * rc.zoom)
42-
.inner_margin(egui::Margin::symmetric(12, 10) * rc.zoom)
46+
.corner_radius(16.0 * zoom)
47+
.inner_margin(egui::Margin::symmetric(12, 10) * zoom)
48+
.stroke(if rc.selected {
49+
egui::Stroke::new(2.0 * zoom, egui::Color32::LIGHT_BLUE)
50+
} else {
51+
egui::Stroke::NONE
52+
});
53+
54+
let response = frame
4355
.show(ui, |ui| {
56+
// 极简渲染,避免 Label 的复杂逻辑
4457
ui.add(
45-
egui::Label::new(egui::RichText::new(&self.val).size(16.0 * rc.zoom))
58+
egui::Label::new(egui::RichText::new(&self.val).font(font_id))
4659
.wrap_mode(egui::TextWrapMode::Extend),
4760
);
4861
})
49-
.response
50-
.interact(egui::Sense::click());
62+
.response;
63+
64+
if response.interact(egui::Sense::click()).clicked() {
65+
rc.clicked = true;
66+
}
67+
}
68+
69+
fn world_rect(&self) -> egui::Rect {
70+
// 假设基础大小为 80x40
71+
egui::Rect::from_center_size(self.position(), egui::vec2(80.0, 40.0))
5172
}
5273
}
5374
impl EntityTrait for Text {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub struct RenderContext {
22
pub zoom: f32,
33
pub selected: bool,
4+
pub clicked: bool,
45
}
56

67
impl RenderContext {}

0 commit comments

Comments
 (0)