@@ -79,6 +79,9 @@ const DIAGNOSTICS_PANEL_MIN_HEIGHT: f32 = 120.0;
7979const POINTS_PARSE_DEBOUNCE_MS : u64 = 180 ;
8080const POINTS_HISTORY_LIMIT : usize = 256 ;
8181const POINTS_PARSE_ERROR_PREFIX : & str = "Points parse error: " ;
82+ const UI_CORNER_RADIUS : u8 = 6 ;
83+ const PANEL_INNER_MARGIN_X : i8 = 10 ;
84+ const PANEL_INNER_MARGIN_Y : i8 = 8 ;
8285const APP_VERSION_LABEL : & str = concat ! ( "v" , env!( "CARGO_PKG_VERSION" ) ) ;
8386const APP_REPOSITORY_URL : & str = env ! ( "CARGO_PKG_REPOSITORY" ) ;
8487
@@ -487,9 +490,7 @@ pub struct CurveFitApp {
487490 show_left_panel : bool ,
488491 show_right_panel : bool ,
489492 show_diagnostics_panel : bool ,
490- diagnostics_loss_axis_width : f32 ,
491- diagnostics_residual_axis_width : f32 ,
492- diagnostics_params_axis_width : f32 ,
493+ diagnostics_shared_axis_width : f32 ,
493494 iteration_delay_seconds : f64 ,
494495 spline_knots : usize ,
495496 spline_knot_strategy : SplineKnotStrategy ,
@@ -1065,6 +1066,141 @@ impl CurveFitApp {
10651066 self . status = Some ( self . idle_status_after_points_edit ( ) ) ;
10661067 }
10671068 }
1069+
1070+ fn fill_points_with_residuals ( & mut self ) {
1071+ if self . residual_plot_points . is_empty ( ) {
1072+ return ;
1073+ }
1074+
1075+ let points = match self
1076+ . residual_plot_points
1077+ . iter ( )
1078+ . map ( |point| Point :: try_new ( point. x , point. y ) )
1079+ . collect :: < Result < Vec < _ > , _ > > ( )
1080+ {
1081+ Ok ( points) => points,
1082+ Err ( error) => {
1083+ self . status = Some ( StatusMessage :: Error ( format ! (
1084+ "Failed to convert residual into point: {error}"
1085+ ) ) ) ;
1086+ return ;
1087+ }
1088+ } ;
1089+
1090+ self . write_points_text ( & points, true ) ;
1091+ }
1092+
1093+ fn apply_visual_style ( ctx : & egui:: Context ) {
1094+ ctx. style_mut ( |style| {
1095+ style. spacing . item_spacing = egui:: vec2 ( 10.0 , 8.0 ) ;
1096+ style. spacing . button_padding = egui:: vec2 ( 8.0 , 5.0 ) ;
1097+ style. spacing . interact_size = egui:: vec2 ( 44.0 , 26.0 ) ;
1098+ style. spacing . slider_width = 170.0 ;
1099+ style. spacing . combo_width = 180.0 ;
1100+ style. spacing . indent = 14.0 ;
1101+
1102+ style. text_styles . insert (
1103+ egui:: TextStyle :: Heading ,
1104+ egui:: FontId :: new ( 21.0 , egui:: FontFamily :: Proportional ) ,
1105+ ) ;
1106+ style. text_styles . insert (
1107+ egui:: TextStyle :: Body ,
1108+ egui:: FontId :: new ( 14.0 , egui:: FontFamily :: Proportional ) ,
1109+ ) ;
1110+ style. text_styles . insert (
1111+ egui:: TextStyle :: Button ,
1112+ egui:: FontId :: new ( 14.0 , egui:: FontFamily :: Proportional ) ,
1113+ ) ;
1114+ style. text_styles . insert (
1115+ egui:: TextStyle :: Monospace ,
1116+ egui:: FontId :: new ( 13.0 , egui:: FontFamily :: Monospace ) ,
1117+ ) ;
1118+ style. text_styles . insert (
1119+ egui:: TextStyle :: Small ,
1120+ egui:: FontId :: new ( 12.0 , egui:: FontFamily :: Proportional ) ,
1121+ ) ;
1122+
1123+ let visuals = & mut style. visuals ;
1124+ visuals. widgets . noninteractive . corner_radius =
1125+ egui:: CornerRadius :: same ( UI_CORNER_RADIUS ) ;
1126+ visuals. widgets . inactive . corner_radius = egui:: CornerRadius :: same ( UI_CORNER_RADIUS ) ;
1127+ visuals. widgets . hovered . corner_radius = egui:: CornerRadius :: same ( UI_CORNER_RADIUS ) ;
1128+ visuals. widgets . active . corner_radius = egui:: CornerRadius :: same ( UI_CORNER_RADIUS ) ;
1129+ visuals. widgets . open . corner_radius = egui:: CornerRadius :: same ( UI_CORNER_RADIUS ) ;
1130+
1131+ if visuals. dark_mode {
1132+ visuals. panel_fill = egui:: Color32 :: from_rgb ( 14 , 17 , 22 ) ;
1133+ visuals. window_fill = egui:: Color32 :: from_rgb ( 17 , 20 , 26 ) ;
1134+ visuals. faint_bg_color = egui:: Color32 :: from_rgb ( 24 , 30 , 38 ) ;
1135+ visuals. extreme_bg_color = egui:: Color32 :: from_rgb ( 8 , 11 , 16 ) ;
1136+ visuals. code_bg_color = egui:: Color32 :: from_rgb ( 10 , 20 , 28 ) ;
1137+ visuals. window_stroke = egui:: Stroke :: new ( 1.0 , egui:: Color32 :: from_rgb ( 52 , 70 , 85 ) ) ;
1138+ visuals. selection . bg_fill = egui:: Color32 :: from_rgb ( 22 , 88 , 120 ) ;
1139+ visuals. selection . stroke =
1140+ egui:: Stroke :: new ( 1.0 , egui:: Color32 :: from_rgb ( 152 , 226 , 255 ) ) ;
1141+ visuals. hyperlink_color = egui:: Color32 :: from_rgb ( 94 , 204 , 255 ) ;
1142+ visuals. widgets . inactive . weak_bg_fill = egui:: Color32 :: from_rgb ( 28 , 35 , 44 ) ;
1143+ visuals. widgets . inactive . bg_stroke =
1144+ egui:: Stroke :: new ( 1.0 , egui:: Color32 :: from_rgb ( 52 , 70 , 85 ) ) ;
1145+ visuals. widgets . hovered . weak_bg_fill = egui:: Color32 :: from_rgb ( 34 , 49 , 61 ) ;
1146+ visuals. widgets . hovered . bg_stroke =
1147+ egui:: Stroke :: new ( 1.0 , egui:: Color32 :: from_rgb ( 70 , 113 , 138 ) ) ;
1148+ visuals. widgets . active . weak_bg_fill = egui:: Color32 :: from_rgb ( 27 , 84 , 108 ) ;
1149+ visuals. widgets . active . bg_stroke =
1150+ egui:: Stroke :: new ( 1.0 , egui:: Color32 :: from_rgb ( 86 , 171 , 211 ) ) ;
1151+ visuals. widgets . open . weak_bg_fill = egui:: Color32 :: from_rgb ( 33 , 57 , 73 ) ;
1152+ visuals. widgets . open . bg_stroke =
1153+ egui:: Stroke :: new ( 1.0 , egui:: Color32 :: from_rgb ( 72 , 122 , 150 ) ) ;
1154+ } else {
1155+ visuals. panel_fill = egui:: Color32 :: from_rgb ( 239 , 245 , 249 ) ;
1156+ visuals. window_fill = egui:: Color32 :: from_rgb ( 246 , 250 , 252 ) ;
1157+ visuals. faint_bg_color = egui:: Color32 :: from_rgb ( 225 , 236 , 242 ) ;
1158+ visuals. extreme_bg_color = egui:: Color32 :: from_rgb ( 251 , 253 , 255 ) ;
1159+ visuals. code_bg_color = egui:: Color32 :: from_rgb ( 235 , 245 , 250 ) ;
1160+ visuals. window_stroke =
1161+ egui:: Stroke :: new ( 1.0 , egui:: Color32 :: from_rgb ( 165 , 188 , 201 ) ) ;
1162+ visuals. selection . bg_fill = egui:: Color32 :: from_rgb ( 150 , 214 , 235 ) ;
1163+ visuals. selection . stroke =
1164+ egui:: Stroke :: new ( 1.0 , egui:: Color32 :: from_rgb ( 20 , 76 , 96 ) ) ;
1165+ visuals. hyperlink_color = egui:: Color32 :: from_rgb ( 0 , 118 , 163 ) ;
1166+ visuals. widgets . inactive . weak_bg_fill = egui:: Color32 :: from_rgb ( 220 , 234 , 241 ) ;
1167+ visuals. widgets . inactive . bg_stroke =
1168+ egui:: Stroke :: new ( 1.0 , egui:: Color32 :: from_rgb ( 163 , 189 , 203 ) ) ;
1169+ visuals. widgets . hovered . weak_bg_fill = egui:: Color32 :: from_rgb ( 208 , 227 , 237 ) ;
1170+ visuals. widgets . hovered . bg_stroke =
1171+ egui:: Stroke :: new ( 1.0 , egui:: Color32 :: from_rgb ( 128 , 170 , 192 ) ) ;
1172+ visuals. widgets . active . weak_bg_fill = egui:: Color32 :: from_rgb ( 183 , 220 , 236 ) ;
1173+ visuals. widgets . active . bg_stroke =
1174+ egui:: Stroke :: new ( 1.0 , egui:: Color32 :: from_rgb ( 87 , 151 , 182 ) ) ;
1175+ visuals. widgets . open . weak_bg_fill = egui:: Color32 :: from_rgb ( 198 , 224 , 236 ) ;
1176+ visuals. widgets . open . bg_stroke =
1177+ egui:: Stroke :: new ( 1.0 , egui:: Color32 :: from_rgb ( 103 , 160 , 188 ) ) ;
1178+ }
1179+ } ) ;
1180+ }
1181+
1182+ fn side_panel_frame ( style : & egui:: Style ) -> egui:: Frame {
1183+ egui:: Frame :: side_top_panel ( style)
1184+ . inner_margin ( egui:: Margin :: symmetric (
1185+ PANEL_INNER_MARGIN_X ,
1186+ PANEL_INNER_MARGIN_Y ,
1187+ ) )
1188+ . fill ( style. visuals . panel_fill )
1189+ . stroke ( egui:: Stroke :: new (
1190+ 1.0 ,
1191+ style. visuals . widgets . noninteractive . bg_stroke . color ,
1192+ ) )
1193+ }
1194+
1195+ fn top_bottom_panel_frame ( style : & egui:: Style ) -> egui:: Frame {
1196+ egui:: Frame :: side_top_panel ( style)
1197+ . inner_margin ( egui:: Margin :: symmetric ( PANEL_INNER_MARGIN_X , 6 ) )
1198+ . fill ( style. visuals . panel_fill )
1199+ . stroke ( egui:: Stroke :: new (
1200+ 1.0 ,
1201+ style. visuals . widgets . noninteractive . bg_stroke . color ,
1202+ ) )
1203+ }
10681204}
10691205
10701206impl Default for CurveFitApp {
@@ -1121,9 +1257,7 @@ impl Default for CurveFitApp {
11211257 spline_initial_knot_y_inputs : Vec :: new ( ) ,
11221258 show_right_panel : true ,
11231259 show_diagnostics_panel : true ,
1124- diagnostics_loss_axis_width : 0.0 ,
1125- diagnostics_residual_axis_width : 0.0 ,
1126- diagnostics_params_axis_width : 0.0 ,
1260+ diagnostics_shared_axis_width : 0.0 ,
11271261 iteration_delay_seconds : 0.25 ,
11281262 fit_in_progress : false ,
11291263 fit_preview_params : None ,
@@ -1153,6 +1287,7 @@ impl Default for CurveFitApp {
11531287
11541288impl eframe:: App for CurveFitApp {
11551289 fn update ( & mut self , ctx : & egui:: Context , _frame : & mut eframe:: Frame ) {
1290+ Self :: apply_visual_style ( ctx) ;
11561291 self . poll_fit_worker ( ctx) ;
11571292 self . maybe_refresh_points_cache_after_debounce ( ) ;
11581293
@@ -1172,64 +1307,43 @@ impl eframe::App for CurveFitApp {
11721307 }
11731308 }
11741309
1175- egui:: TopBottomPanel :: top ( "header_panel" ) . show ( ctx, |ui| {
1176- self . ui_header ( ui) ;
1177- } ) ;
1310+ let panel_style = ctx. style ( ) ;
1311+ let panel_style = panel_style. as_ref ( ) ;
11781312
1179- egui:: TopBottomPanel :: bottom ( "status_bar" ) . show ( ctx, |ui| {
1180- self . ui_status_bar ( ui) ;
1181- } ) ;
1313+ egui:: TopBottomPanel :: top ( "header_panel" )
1314+ . frame ( Self :: top_bottom_panel_frame ( panel_style) )
1315+ . show ( ctx, |ui| {
1316+ self . ui_header ( ui) ;
1317+ } ) ;
1318+
1319+ egui:: TopBottomPanel :: bottom ( "status_bar" )
1320+ . frame ( Self :: top_bottom_panel_frame ( panel_style) )
1321+ . show ( ctx, |ui| {
1322+ self . ui_status_bar ( ui) ;
1323+ } ) ;
11821324
11831325 if self . show_left_panel {
11841326 egui:: SidePanel :: left ( "points_panel" )
11851327 . default_width ( 340.0 )
11861328 . resizable ( true )
1329+ . frame ( Self :: side_panel_frame ( panel_style) )
11871330 . show ( ctx, |ui| {
1188- self . ui_tools ( ui ) ;
1189- ui . separator ( ) ;
1190- self . ui_points_editor ( ui) ;
1331+ ui . spacing_mut ( ) . item_spacing = egui :: vec2 ( 10.0 , 8.0 ) ;
1332+ Self :: panel_card_frame ( ui ) . show ( ui , |ui| self . ui_tools ( ui ) ) ;
1333+ Self :: panel_card_frame ( ui ) . show ( ui , |ui| self . ui_points_editor ( ui) ) ;
11911334 } ) ;
11921335 }
11931336
11941337 if self . show_right_panel {
11951338 egui:: SidePanel :: right ( "settings_panel" )
11961339 . default_width ( 320.0 )
11971340 . resizable ( true )
1341+ . frame ( Self :: side_panel_frame ( panel_style) )
11981342 . show ( ctx, |ui| {
1199- let icon_tint = ui. visuals ( ) . text_color ( ) ;
1200- self . ui_family_and_params ( ui) ;
1201- self . ui_optimizer ( ui) ;
1202-
1203- ui. separator ( ) ;
1204- let action_button = if self . fit_in_progress {
1205- egui:: Button :: image_and_text (
1206- stop_icon_image ( icon_tint) ,
1207- tr ( self . ui_language , "Stop" , "Стоп" ) ,
1208- )
1209- } else {
1210- egui:: Button :: image_and_text (
1211- fit_icon_image ( icon_tint) ,
1212- tr ( self . ui_language , "Fit" , "Фитинг" ) ,
1213- )
1214- } ;
1215- if ui. add ( action_button) . clicked ( ) {
1216- if self . fit_in_progress {
1217- self . request_stop_fit ( ) ;
1218- } else {
1219- self . run_fit ( ) ;
1220- }
1221- }
1222- if self . fit_in_progress
1223- && let Some ( iteration) = self . fit_preview_iteration
1224- {
1225- ui. label ( format ! (
1226- "{}: {iteration}" ,
1227- tr( self . ui_language, "Iteration" , "Итерация" )
1228- ) ) ;
1229- }
1230-
1231- ui. separator ( ) ;
1232- self . ui_result ( ui) ;
1343+ ui. spacing_mut ( ) . item_spacing = egui:: vec2 ( 10.0 , 8.0 ) ;
1344+ Self :: panel_card_frame ( ui) . show ( ui, |ui| self . ui_family_and_params ( ui) ) ;
1345+ Self :: panel_card_frame ( ui) . show ( ui, |ui| self . ui_optimizer ( ui) ) ;
1346+ Self :: panel_card_frame ( ui) . show ( ui, |ui| self . ui_result ( ui) ) ;
12331347 } ) ;
12341348 }
12351349
@@ -1238,6 +1352,7 @@ impl eframe::App for CurveFitApp {
12381352 . resizable ( true )
12391353 . default_height ( DIAGNOSTICS_PANEL_DEFAULT_HEIGHT )
12401354 . min_height ( DIAGNOSTICS_PANEL_MIN_HEIGHT )
1355+ . frame ( Self :: top_bottom_panel_frame ( panel_style) )
12411356 . show ( ctx, |ui| {
12421357 let available_height = ui. available_height ( ) ;
12431358 ui. set_height ( available_height) ;
0 commit comments