use crate::{
DiagramItem, LibraryModel, MainWindow,
bridge::library::source::{self as library_source, LibrarySource},
settings::WorkerSettings,
};
use anyhow::Result;
use indicatrix_net::library::{
DesignSummary, LibraryRequest, LibraryResponse, PerformanceAggregateWire, PerformanceBoundWire,
PerformanceFilterWire, PerformanceMetricWire, RangeFilterWire,
};
use indicatrix_vault::{
db::sqlite::Database,
model::{
entry::DiagramListItem,
filter::RangeFilter,
performance::{
PerformanceAggregate, PerformanceBound, PerformanceFilter, PerformanceMetric,
},
},
};
use slint::{ComponentHandle, Model, ModelRc, VecModel};
use std::sync::{Arc, Mutex};
use tracing::error;
fn active_bound(value: f32, bound_edge: f32) -> Option<f64> {
let moved_off_edge = (value - bound_edge).abs() >= 1e-6;
moved_off_edge.then_some(f64::from(value))
}
#[must_use]
pub fn read_range_filter(ui: &MainWindow) -> RangeFilter {
RangeFilter {
ri_min: active_bound(
ui.global::<LibraryModel>().get_ri_filter_min(),
ui.global::<LibraryModel>().get_ri_bounds_min(),
),
ri_max: active_bound(
ui.global::<LibraryModel>().get_ri_filter_max(),
ui.global::<LibraryModel>().get_ri_bounds_max(),
),
lw_min: active_bound(
ui.global::<LibraryModel>().get_lw_filter_min(),
ui.global::<LibraryModel>().get_lw_bounds_min(),
),
lw_max: active_bound(
ui.global::<LibraryModel>().get_lw_filter_max(),
ui.global::<LibraryModel>().get_lw_bounds_max(),
),
volume_min: active_bound(
ui.global::<LibraryModel>().get_volume_filter_min(),
ui.global::<LibraryModel>().get_volume_bounds_min(),
),
volume_max: active_bound(
ui.global::<LibraryModel>().get_volume_filter_max(),
ui.global::<LibraryModel>().get_volume_bounds_max(),
),
facets_min: active_bound(
ui.global::<LibraryModel>().get_facets_filter_min(),
ui.global::<LibraryModel>().get_facets_bounds_min(),
)
.map(|v| v.round() as i64),
facets_max: active_bound(
ui.global::<LibraryModel>().get_facets_filter_max(),
ui.global::<LibraryModel>().get_facets_bounds_max(),
)
.map(|v| v.round() as i64),
ri_tolerance: ui
.global::<LibraryModel>()
.get_ri_tolerance_enabled()
.then(|| {
(
f64::from(ui.global::<LibraryModel>().get_ri_tolerance_center()),
f64::from(ui.global::<LibraryModel>().get_ri_tolerance_value()),
)
}),
performance: read_performance_filters(ui),
include_ignored: ui.global::<LibraryModel>().get_show_ignored(),
}
}
fn read_performance_filters(ui: &MainWindow) -> Vec<PerformanceFilter> {
let rows = ui.global::<LibraryModel>().get_performance_filters();
let mut filters = Vec::with_capacity(rows.row_count());
let mut rejected = 0usize;
for row in rows.iter() {
let metric = match row.metric_index {
0 => PerformanceMetric::Brilliance,
1 => PerformanceMetric::Extinction,
_ => PerformanceMetric::Windowing,
};
let bound = if row.bound_index == 0 {
PerformanceBound::AtMost(row.threshold_pct)
} else {
PerformanceBound::AtLeast(row.threshold_pct)
};
let aggregate = if row.aggregate_index == 0 {
PerformanceAggregate::Worst
} else {
PerformanceAggregate::Mean
};
match PerformanceFilter::new(metric, bound, row.tilt_radius_deg, aggregate) {
Ok(filter) => filters.push(filter),
Err(_) => rejected += 1,
}
}
if rejected > 0 {
ui.global::<LibraryModel>().set_status_message(
format!(
"{rejected} tilt-performance filter(s) had an invalid tilt radius and were \
ignored."
)
.into(),
);
}
filters
}
pub fn refresh_diagram_list_via_source(
ui: &MainWindow,
db_mutex: &Arc<Mutex<Database>>,
source: &Arc<Mutex<LibrarySource>>,
search: &str,
shape_filter: &str,
gear_filter: &str,
) {
let current = source
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
match current {
LibrarySource::Local => {
refresh_diagram_list(ui, db_mutex, search, shape_filter, gear_filter);
}
LibrarySource::Remote(worker) => {
refresh_diagram_list_remote(ui, worker, search, shape_filter, gear_filter);
}
}
}
pub(crate) fn refresh_diagram_list_remote(
ui: &MainWindow,
worker: WorkerSettings,
search: &str,
shape_filter: &str,
gear_filter: &str,
) {
let clean_shape = if shape_filter == "All Shapes" {
"All".to_string()
} else {
shape_filter.to_string()
};
let clean_gear = if gear_filter == "All Gears" {
"All".to_string()
} else {
gear_filter.to_string()
};
let range = to_range_wire(&read_range_filter(ui));
let request = LibraryRequest::Search {
query: search.to_string(),
shape_filter: clean_shape,
gear_filter: clean_gear,
range,
};
library_source::spawn_library_request(
ui.as_weak(),
worker,
request,
|ui, result| match result {
Ok(LibraryResponse::SearchResults {
items,
excluded_for_missing_curves,
}) => {
let total = items.len();
let slint_items: Vec<DiagramItem> = items.iter().map(to_diagram_item).collect();
ui.global::<LibraryModel>()
.set_diagram_list(ModelRc::new(VecModel::from(slint_items)));
ui.global::<LibraryModel>().set_total_count(total as i32);
ui.global::<LibraryModel>()
.set_performance_excluded_count(excluded_for_missing_curves as i32);
}
Ok(_) => {
ui.global::<LibraryModel>()
.set_status_message("Unexpected reply searching the remote library.".into());
}
Err(e) => {
error!("Remote search failed: {e}");
ui.global::<LibraryModel>()
.set_status_message(format!("Remote search failed: {e}").into());
}
},
);
}
fn to_range_wire(range: &RangeFilter) -> RangeFilterWire {
RangeFilterWire {
ri_min: range.ri_min,
ri_max: range.ri_max,
lw_min: range.lw_min,
lw_max: range.lw_max,
volume_min: range.volume_min,
volume_max: range.volume_max,
facets_min: range.facets_min,
facets_max: range.facets_max,
ri_tolerance: range.ri_tolerance,
include_ignored: range.include_ignored,
performance: range
.performance
.iter()
.map(to_performance_filter_wire)
.collect(),
}
}
const fn to_performance_filter_wire(filter: &PerformanceFilter) -> PerformanceFilterWire {
let metric = match filter.metric {
PerformanceMetric::Brilliance => PerformanceMetricWire::Brilliance,
PerformanceMetric::Extinction => PerformanceMetricWire::Extinction,
PerformanceMetric::Windowing => PerformanceMetricWire::Windowing,
};
let bound = match filter.bound {
PerformanceBound::AtMost(t) => PerformanceBoundWire::AtMost(t),
PerformanceBound::AtLeast(t) => PerformanceBoundWire::AtLeast(t),
};
let aggregate = match filter.aggregate {
PerformanceAggregate::Worst => PerformanceAggregateWire::Worst,
PerformanceAggregate::Mean => PerformanceAggregateWire::Mean,
};
PerformanceFilterWire {
metric,
bound,
tilt_radius_deg: filter.tilt_radius_deg,
aggregate,
}
}
pub(crate) fn to_diagram_item(item: &DesignSummary) -> DiagramItem {
DiagramItem {
id: item.entry_id as i32,
title: item.title.clone().into(),
shape: item.shape.clone().unwrap_or_default().into(),
gear: item.index_gear.clone().unwrap_or_default().into(),
facets: item.facets_count.clone().unwrap_or_default().into(),
designer: item.designer_info.clone().unwrap_or_default().into(),
lw_ratio: item.lw_ratio.clone().unwrap_or_default().into(),
ri: item.refractive_index.clone().unwrap_or_default().into(),
ignored: item.ignored,
}
}
pub struct DiagramListRow {
pub item: DiagramListItem,
pub ignored: bool,
}
pub struct FetchedDiagramList {
pub rows: Vec<DiagramListRow>,
pub total: usize,
pub excluded_for_missing_curves: usize,
}
pub fn fetch_diagram_list(
db_mutex: &Arc<Mutex<Database>>,
search: &str,
shape_filter: &str,
gear_filter: &str,
range: &RangeFilter,
) -> Result<FetchedDiagramList> {
let clean_shape = if shape_filter == "All Shapes" {
"All"
} else {
shape_filter
};
let clean_gear = if gear_filter == "All Gears" {
"All"
} else {
gear_filter
};
let (rows, total, excluded_for_missing_curves) = {
let db = db_mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let result =
db.search_diagrams_with_performance_exclusions(search, clean_shape, clean_gear, range)?;
let total = db.get_total_count().unwrap_or(result.items.len());
let rows: Vec<DiagramListRow> = result
.items
.into_iter()
.map(|item| {
let ignored = item.ignored;
DiagramListRow { item, ignored }
})
.collect();
drop(db);
(rows, total, result.excluded_for_missing_curves)
};
Ok(FetchedDiagramList {
rows,
total,
excluded_for_missing_curves,
})
}
pub fn apply_diagram_list_to_ui(ui: &MainWindow, fetched: FetchedDiagramList) {
let slint_items: Vec<DiagramItem> = fetched
.rows
.into_iter()
.map(|row| DiagramItem {
id: row.item.id as i32,
title: row.item.title.into(),
shape: row.item.shape.unwrap_or_default().into(),
gear: row.item.index_gear.unwrap_or_default().into(),
facets: row.item.facets_count.unwrap_or_default().into(),
designer: row.item.designer_info.unwrap_or_default().into(),
lw_ratio: row.item.lw_ratio.unwrap_or_default().into(),
ri: row.item.refractive_index.unwrap_or_default().into(),
ignored: row.ignored,
})
.collect();
ui.global::<LibraryModel>()
.set_diagram_list(ModelRc::new(VecModel::from(slint_items)));
ui.global::<LibraryModel>()
.set_total_count(fetched.total as i32);
ui.global::<LibraryModel>()
.set_performance_excluded_count(fetched.excluded_for_missing_curves as i32);
}
pub fn refresh_diagram_list(
ui: &MainWindow,
db_mutex: &Arc<Mutex<Database>>,
search: &str,
shape_filter: &str,
gear_filter: &str,
) {
let range = read_range_filter(ui);
match fetch_diagram_list(db_mutex, search, shape_filter, gear_filter, &range) {
Ok(fetched) => apply_diagram_list_to_ui(ui, fetched),
Err(e) => {
error!("Failed to search diagrams: {:?}", e);
ui.global::<LibraryModel>()
.set_status_message(format!("Error searching database: {e}").into());
}
}
}