diff --git a/cimery/crates/viewer/src/lib.rs b/cimery/crates/viewer/src/lib.rs index 117229a..f53df9b 100644 --- a/cimery/crates/viewer/src/lib.rs +++ b/cimery/crates/viewer/src/lib.rs @@ -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("✓ 최신 상태"); }