viewer Sprint 5 완성 — 한글 폰트 + 슬라이더 자동 재생성

- Windows Malgun Gothic 폰트 로드 (한글 지원)
- param_slider! 매크로: drag_released() / lost_focus() 시 자동 rebuild_mesh()
- "▶ 적용 (Apply)" 버튼은 manual fallback으로 유지

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
minsung
2026-04-14 20:50:37 +09:00
parent a05e8f5dea
commit 2550e13b10

View File

@@ -278,6 +278,27 @@ impl RenderState {
// ── egui ──────────────────────────────────────────────────────────────
let egui_ctx = egui::Context::default();
// Load Korean system font (Windows Malgun Gothic) for CJK support
{
let mut fonts = egui::FontDefinitions::default();
let font_path = "C:\\Windows\\Fonts\\malgun.ttf";
if let Ok(data) = std::fs::read(font_path) {
fonts.font_data.insert(
"MalgunGothic".to_owned(),
egui::FontData::from_owned(data),
);
// Insert as primary font (index 0) so Korean renders by default
for family in fonts.families.values_mut() {
family.insert(0, "MalgunGothic".to_owned());
}
egui_ctx.set_fonts(fonts);
log::info!("Korean font loaded: {}", font_path);
} else {
log::warn!("Korean font not found at {}; UI labels will show boxes", font_path);
}
}
let egui_state = egui_winit::State::new(
egui_ctx.clone(),
egui::ViewportId::ROOT,
@@ -408,35 +429,28 @@ impl RenderState {
ui.heading("교량 속성");
ui.separator();
let prev = p.span_m;
ui.label("경간 (m)");
ui.add(egui::Slider::new(&mut p.span_m, 20.0..=80.0).step_by(1.0));
if (p.span_m - prev).abs() > 0.001 { dirty = true; }
// Helper: slider that auto-applies on drag release
macro_rules! param_slider {
($label:expr, $val:expr, $range:expr, $step:expr) => {{
ui.label($label);
let r = ui.add(egui::Slider::new($val, $range).step_by($step));
if r.drag_released() || r.lost_focus() { apply = true; dirty = true; }
if r.changed() { dirty = true; }
}};
}
let prev = p.girder_count;
ui.label("거더 수");
ui.add(egui::Slider::new(&mut p.girder_count, 3..=7));
if p.girder_count != prev { dirty = true; }
let prev = p.girder_spacing;
ui.label("c/c 간격 (mm)");
ui.add(egui::Slider::new(&mut p.girder_spacing, 1_500.0..=4_000.0).step_by(100.0));
if (p.girder_spacing - prev).abs() > 1.0 { dirty = true; }
let prev = p.girder_height;
ui.label("거더 높이 (mm)");
ui.add(egui::Slider::new(&mut p.girder_height, 1_000.0..=3_000.0).step_by(100.0));
if (p.girder_height - prev).abs() > 1.0 { dirty = true; }
let prev = p.slab_thickness;
ui.label("슬래브 두께 (mm)");
ui.add(egui::Slider::new(&mut p.slab_thickness, 150.0..=400.0).step_by(10.0));
if (p.slab_thickness - prev).abs() > 1.0 { dirty = true; }
param_slider!("경간 (m)", &mut p.span_m, 20.0..=80.0, 1.0);
param_slider!("거더 수", &mut p.girder_count, 3..=7, 1.0);
param_slider!("c/c 간격 (mm)", &mut p.girder_spacing, 1_500.0..=4_000.0, 100.0);
param_slider!("거더 높이 (mm)",&mut p.girder_height, 1_000.0..=3_000.0, 100.0);
param_slider!("슬래브 두께(mm)",&mut p.slab_thickness, 150.0..=400.0, 10.0);
ui.separator();
if dirty {
apply = ui.button("▶ 적용").clicked();
} else {
if dirty && !apply {
// Manual apply button as fallback
if ui.button("▶ 적용 (Apply)").clicked() { apply = true; }
ui.small("* 슬라이더를 놓으면 자동 적용");
} else if !dirty {
ui.label("✓ 최신 상태");
}