use crate::{
LibraryModel, MainWindow, SettingsModel, ViewportModel,
bridge::{
preview_render::material_ri_at_sodium_d,
render_thread::{RenderContext, resolve_material},
},
gui::{
is_c_axis_override_available, local_compute_target_from_index,
local_preview_scale_from_index,
optics::c_axis::{angles_to_c_axis, c_axis_to_angles},
render::sample_scale::exponent_to_count,
show_toast,
},
settings::{SettingsPersister, model::surface_glare_from_percent},
};
use indicatrix::optics::{LightingPreset, materials::GemMaterial};
use slint::{ComponentHandle, SharedString};
use std::sync::{Arc, Mutex};
pub(in crate::gui) fn setup_material_changed_callback(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
settings_store: &Arc<SettingsPersister>,
) {
let render_ctx_mat = render_ctx.clone();
let settings_store_mat = settings_store.clone();
let ui_weak_mat = ui.as_weak();
ui.global::<ViewportModel>()
.on_material_changed(move |material: SharedString| {
let mut ctx = RenderContext::lock(&render_ctx_mat);
ctx.material_name = material.to_string();
ctx.material_override = None;
ctx.material_unresolved = None;
ctx.dirty = true;
let resolved = resolve_material(
&GemMaterial::all_materials(),
&ctx.custom_materials,
&material,
);
let available = resolved.as_ref().is_some_and(is_c_axis_override_available);
let ri_default = resolved.as_ref().map_or_else(
|| material_ri_at_sodium_d(&GemMaterial::diamond()),
material_ri_at_sodium_d,
);
drop(ctx);
settings_store_mat.update(|s| s.settings.selected_material = material.to_string());
if let Some(ui) = ui_weak_mat.upgrade() {
let was_linked = ui.global::<ViewportModel>().get_viewport_material_linked();
if was_linked {
ui.global::<ViewportModel>()
.set_viewport_material_linked(false);
}
ui.global::<SettingsModel>()
.set_c_axis_override_available(available);
ui.global::<LibraryModel>()
.set_ri_material_default(ri_default as f32);
let message = if was_linked {
format!("Material switched to {material} \u{2014} unlinked from the design.")
} else {
format!("Material switched to {material}")
};
show_toast(&ui, &message, "info");
}
});
}
pub(in crate::gui) fn setup_material_and_quality_callbacks(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
settings_store: &Arc<SettingsPersister>,
) {
let render_ctx_lit = render_ctx.clone();
let settings_store_lit = settings_store.clone();
let ui_weak_lit = ui.as_weak();
ui.global::<ViewportModel>()
.on_lighting_changed(move |idx: i32| {
let preset = LightingPreset::from_index(idx);
let mut ctx = RenderContext::lock(&render_ctx_lit);
ctx.lighting_preset = preset;
ctx.dirty = true;
drop(ctx);
settings_store_lit.update(|s| s.settings.lighting_rig = preset.label().to_string());
if let Some(ui) = ui_weak_lit.upgrade() {
show_toast(&ui, &format!("Lighting preset: {}", preset.label()), "info");
}
});
let render_ctx_samples = render_ctx.clone();
let settings_store_samples = settings_store.clone();
let ui_weak_samples = ui.as_weak();
ui.global::<SettingsModel>()
.on_target_samples_changed(move |exponent: i32| {
let target_samples = exponent_to_count(u32::try_from(exponent).unwrap_or(0));
let mut ctx = RenderContext::lock(&render_ctx_samples);
ctx.target_samples = target_samples;
ctx.dirty = true;
drop(ctx);
settings_store_samples.update(|s| s.settings.target_samples = target_samples);
if let Some(ui) = ui_weak_samples.upgrade() {
show_toast(&ui, &format!("Target samples: {target_samples}"), "info");
}
});
let render_ctx_res = render_ctx.clone();
let settings_store_res = settings_store.clone();
let ui_weak_res = ui.as_weak();
ui.global::<SettingsModel>()
.on_resolution_changed(move |width: i32, height: i32| {
let (width, height) = (width as u32, height as u32);
let mut ctx = RenderContext::lock(&render_ctx_res);
ctx.width = width;
ctx.height = height;
ctx.dirty = true;
drop(ctx);
settings_store_res.update(|s| {
s.settings.render_width = width;
s.settings.render_height = height;
});
if let Some(ui) = ui_weak_res.upgrade() {
show_toast(&ui, &format!("Render resolution: {width}x{height}"), "info");
}
});
setup_local_render_path_callbacks(ui, render_ctx, settings_store);
let render_ctx_inc = render_ctx.clone();
let settings_store_inc = settings_store.clone();
ui.global::<SettingsModel>()
.on_inclusion_changed(move |sigma_s: f32| {
let clamped = sigma_s.clamp(0.0, 3.0);
let mut ctx = RenderContext::lock(&render_ctx_inc);
ctx.inclusion_sigma_s = clamped;
ctx.dirty = true;
drop(ctx);
settings_store_inc.update(|s| s.settings.inclusion_sigma_s = clamped);
});
let render_ctx_pause = render_ctx.clone();
ui.global::<ViewportModel>()
.on_pause_toggled(move |paused: bool| {
let mut ctx = RenderContext::lock(&render_ctx_pause);
ctx.paused = paused;
});
let render_ctx_bnc = render_ctx.clone();
let settings_store_bnc = settings_store.clone();
ui.global::<SettingsModel>()
.on_bounces_changed(move |bounces: i32| {
let clamped = (bounces as u32).max(1);
let mut ctx = RenderContext::lock(&render_ctx_bnc);
ctx.max_bounces = clamped;
ctx.dirty = true;
drop(ctx);
settings_store_bnc.update(|s| s.settings.max_bounces = clamped);
});
let render_ctx_exp = render_ctx.clone();
let settings_store_exp = settings_store.clone();
ui.global::<SettingsModel>()
.on_exposure_changed(move |exposure: f32| {
let clamped = exposure.clamp(0.2, 5.0);
let mut ctx = RenderContext::lock(&render_ctx_exp);
ctx.exposure = clamped;
ctx.dirty = true;
drop(ctx);
settings_store_exp.update(|s| s.settings.exposure = clamped);
});
}
fn setup_local_render_path_callbacks(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
settings_store: &Arc<SettingsPersister>,
) {
let render_ctx_preview = render_ctx.clone();
let settings_store_preview = settings_store.clone();
ui.global::<SettingsModel>()
.on_local_preview_scale_changed(move |index: i32| {
let scale = local_preview_scale_from_index(index);
RenderContext::lock(&render_ctx_preview).local_preview_scale = scale;
settings_store_preview.update(|s| s.settings.local_preview_scale = scale);
});
let render_ctx_local_compute = render_ctx.clone();
let settings_store_local_compute = settings_store.clone();
ui.global::<SettingsModel>()
.on_local_compute_target_changed(move |index: i32| {
let target = local_compute_target_from_index(index);
RenderContext::lock(&render_ctx_local_compute).local_compute_target = target;
settings_store_local_compute.update(|s| s.settings.local_compute_target = target);
});
}
pub(in crate::gui) fn setup_material_effect_override_callbacks(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
settings_store: &Arc<SettingsPersister>,
) {
let render_ctx_axis_angles = render_ctx.clone();
let settings_store_axis_angles = settings_store.clone();
ui.global::<SettingsModel>().on_c_axis_angles_changed(
move |tilt_deg: f32, azimuth_deg: f32| {
let tilt_deg = tilt_deg.clamp(0.0, 90.0);
let azimuth_deg = azimuth_deg.clamp(0.0, 360.0);
let mut ctx = RenderContext::lock(&render_ctx_axis_angles);
ctx.c_axis_override = Some(angles_to_c_axis(tilt_deg, azimuth_deg));
ctx.dirty = true;
drop(ctx);
settings_store_axis_angles.update(|s| {
s.settings.c_axis_tilt_deg = tilt_deg;
s.settings.c_axis_azimuth_deg = azimuth_deg;
});
},
);
let render_ctx_axis_toggle = render_ctx.clone();
let settings_store_axis_toggle = settings_store.clone();
let ui_weak_axis_toggle = ui.as_weak();
ui.global::<SettingsModel>()
.on_c_axis_override_changed(move |enabled: bool| {
let mut ctx = RenderContext::lock(&render_ctx_axis_toggle);
if enabled {
let base = resolve_material(
&GemMaterial::all_materials(),
&ctx.custom_materials,
&ctx.material_name,
)
.unwrap_or_else(GemMaterial::diamond);
let (tilt_deg, azimuth_deg) = c_axis_to_angles(base.c_axis);
ctx.c_axis_override = Some(angles_to_c_axis(tilt_deg, azimuth_deg));
ctx.dirty = true;
drop(ctx);
settings_store_axis_toggle.update(|s| {
s.settings.c_axis_override_enabled = true;
s.settings.c_axis_tilt_deg = tilt_deg;
s.settings.c_axis_azimuth_deg = azimuth_deg;
});
if let Some(ui) = ui_weak_axis_toggle.upgrade() {
ui.global::<SettingsModel>().set_c_axis_tilt_deg(tilt_deg);
ui.global::<SettingsModel>()
.set_c_axis_azimuth_deg(azimuth_deg);
}
} else {
ctx.c_axis_override = None;
ctx.dirty = true;
drop(ctx);
settings_store_axis_toggle.update(|s| s.settings.c_axis_override_enabled = false);
}
});
let render_ctx_girdle = render_ctx.clone();
let settings_store_girdle = settings_store.clone();
ui.global::<SettingsModel>()
.on_girdle_frosted_changed(move |frosted: bool| {
let mut ctx = RenderContext::lock(&render_ctx_girdle);
ctx.girdle_frosted = frosted;
ctx.dirty = true;
drop(ctx);
settings_store_girdle.update(|s| s.settings.girdle_frosted = frosted);
});
let render_ctx_edge = render_ctx.clone();
let settings_store_edge = settings_store.clone();
ui.global::<SettingsModel>()
.on_edge_rounding_changed(move |radius: f32| {
let clamped = radius.clamp(0.0, 0.03);
let mut ctx = RenderContext::lock(&render_ctx_edge);
ctx.edge_rounding_radius = clamped;
ctx.dirty = true;
drop(ctx);
settings_store_edge.update(|s| s.settings.edge_rounding_radius = clamped);
});
let render_ctx_stone = render_ctx.clone();
let settings_store_stone = settings_store.clone();
ui.global::<SettingsModel>()
.on_stone_width_changed(move |width_mm: f32| {
let clamped = width_mm.max(0.0);
let mut ctx = RenderContext::lock(&render_ctx_stone);
ctx.stone_width_mm = clamped;
ctx.dirty = true;
drop(ctx);
settings_store_stone.update(|s| s.settings.stone_width_mm = clamped);
});
}
pub(in crate::gui) fn setup_surface_glare_callback(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
settings_store: &Arc<SettingsPersister>,
) {
let render_ctx = render_ctx.clone();
let settings_store = settings_store.clone();
ui.global::<SettingsModel>()
.on_surface_glare_changed(move |percent: f32| {
let glare = surface_glare_from_percent(percent);
let mut ctx = RenderContext::lock(&render_ctx);
if ctx.surface_glare.to_bits() == glare.to_bits() {
return;
}
ctx.surface_glare = glare;
ctx.dirty = true;
drop(ctx);
settings_store.update(|s| s.settings.surface_glare = glare);
});
}
pub(in crate::gui) fn setup_backdrop_callback(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
settings_store: &Arc<SettingsPersister>,
) {
let render_ctx = render_ctx.clone();
let settings_store = settings_store.clone();
ui.global::<SettingsModel>()
.on_backdrop_changed(move |index: i32| {
let backdrop = crate::settings::model::Backdrop::from_index(index);
let mut ctx = RenderContext::lock(&render_ctx);
ctx.backdrop = backdrop;
ctx.dirty = true;
drop(ctx);
settings_store.update(|s| s.settings.backdrop = backdrop);
});
}