gardesk/garfield / 189be9d

Browse files

dialog: use pixel-based text wrapping and measured height for button centering

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
189be9dc4272cb14d805168d28c6455f9420430a
Parents
9ef2e3a
Tree
cd18c61

1 changed file

StatusFile+-
M garfield/src/ui/dialog.rs 60 45
garfield/src/ui/dialog.rsmodified
@@ -178,36 +178,49 @@ impl ConfirmDialog {
178
         Rect::new(x, y, dialog_width, dialog_height)
178
         Rect::new(x, y, dialog_width, dialog_height)
179
     }
179
     }
180
 
180
 
181
-    /// Wrap text to fit within max_width (simple word wrapping).
181
+    /// Wrap text to fit within max_width pixels using actual text measurement.
182
-    fn wrap_text(text: &str, max_chars: usize) -> Vec<String> {
182
+    fn wrap_text_to_width(text: &str, max_width: u32, renderer: &Renderer, style: &TextStyle) -> Vec<String> {
183
         let mut lines = Vec::new();
183
         let mut lines = Vec::new();
184
+
184
         for line in text.lines() {
185
         for line in text.lines() {
185
-            if line.len() <= max_chars {
186
+            // Check if the whole line fits
186
-                lines.push(line.to_string());
187
+            if let Ok(metrics) = renderer.measure_text(line, style) {
187
-            } else {
188
+                if metrics.width <= max_width {
188
-                // Word wrap
189
+                    lines.push(line.to_string());
189
-                let mut current_line = String::new();
190
+                    continue;
190
-                for word in line.split_whitespace() {
191
-                    if current_line.is_empty() {
192
-                        if word.len() > max_chars {
193
-                            // Word too long, truncate with ellipsis
194
-                            lines.push(format!("{}...", &word[..max_chars.saturating_sub(3)]));
195
-                        } else {
196
-                            current_line = word.to_string();
197
-                        }
198
-                    } else if current_line.len() + 1 + word.len() <= max_chars {
199
-                        current_line.push(' ');
200
-                        current_line.push_str(word);
201
-                    } else {
202
-                        lines.push(current_line);
203
-                        current_line = word.to_string();
204
-                    }
205
                 }
191
                 }
206
-                if !current_line.is_empty() {
192
+            }
193
+
194
+            // Need to wrap - build line word by word using pixel measurement
195
+            let mut current_line = String::new();
196
+            for word in line.split_whitespace() {
197
+                let test_line = if current_line.is_empty() {
198
+                    word.to_string()
199
+                } else {
200
+                    format!("{} {}", current_line, word)
201
+                };
202
+
203
+                let fits = renderer.measure_text(&test_line, style)
204
+                    .map(|m| m.width <= max_width)
205
+                    .unwrap_or(false);
206
+
207
+                if fits {
208
+                    current_line = test_line;
209
+                } else if current_line.is_empty() {
210
+                    // Single word doesn't fit - add it anyway (will overflow but better than infinite loop)
211
+                    lines.push(word.to_string());
212
+                } else {
213
+                    // Push current line and start new one with this word
207
                     lines.push(current_line);
214
                     lines.push(current_line);
215
+                    current_line = word.to_string();
208
                 }
216
                 }
209
             }
217
             }
218
+
219
+            if !current_line.is_empty() {
220
+                lines.push(current_line);
221
+            }
210
         }
222
         }
223
+
211
         lines
224
         lines
212
     }
225
     }
213
 
226
 
@@ -264,13 +277,13 @@ impl ConfirmDialog {
264
             .font_size(theme.font_size)
277
             .font_size(theme.font_size)
265
             .color(theme.item_foreground);
278
             .color(theme.item_foreground);
266
 
279
 
267
-        // Wrap and render message lines (use conservative char width estimate)
280
+        // Wrap text using pixel-based measurement (40px padding on each side)
268
-        let max_chars = ((dialog_rect.width - 50) as f64 / (theme.font_size * 0.55)) as usize;
281
+        let available_width = dialog_rect.width.saturating_sub(40);
269
-        let wrapped_lines = Self::wrap_text(&self.message, max_chars.max(25));
282
+        let wrapped_lines = Self::wrap_text_to_width(&self.message, available_width, renderer, &msg_style);
270
         let mut y = dialog_rect.y + 56;
283
         let mut y = dialog_rect.y + 56;
271
-        for line in wrapped_lines {
284
+        for line in &wrapped_lines {
272
             renderer.text(
285
             renderer.text(
273
-                &line,
286
+                line,
274
                 (dialog_rect.x + 20) as f64,
287
                 (dialog_rect.x + 20) as f64,
275
                 y as f64,
288
                 y as f64,
276
                 &msg_style,
289
                 &msg_style,
@@ -299,10 +312,10 @@ impl ConfirmDialog {
299
             .font_size(theme.font_size)
312
             .font_size(theme.font_size)
300
             .color(theme.foreground);
313
             .color(theme.foreground);
301
 
314
 
302
-        let confirm_text_width = renderer.measure_text(&self.confirm_label, &button_style)?.width;
315
+        let confirm_metrics = renderer.measure_text(&self.confirm_label, &button_style)?;
303
-        let confirm_text_x = confirm_rect.x + (confirm_rect.width as i32 - confirm_text_width as i32) / 2;
316
+        let confirm_text_x = confirm_rect.x + (confirm_rect.width as i32 - confirm_metrics.width as i32) / 2;
304
-        let button_text_y = confirm_rect.y + (confirm_rect.height as i32 - theme.font_size as i32) / 2;
317
+        let confirm_text_y = confirm_rect.y + (confirm_rect.height as i32 - confirm_metrics.height as i32) / 2;
305
-        renderer.text(&self.confirm_label, confirm_text_x as f64, button_text_y as f64, &button_style)?;
318
+        renderer.text(&self.confirm_label, confirm_text_x as f64, confirm_text_y as f64, &button_style)?;
306
 
319
 
307
         // Cancel button
320
         // Cancel button
308
         let cancel_focused = self.focused_button == 1;
321
         let cancel_focused = self.focused_button == 1;
@@ -317,9 +330,10 @@ impl ConfirmDialog {
317
             renderer.stroke_rounded_rect(cancel_rect, 4.0, theme.foreground, 2.0)?;
330
             renderer.stroke_rounded_rect(cancel_rect, 4.0, theme.foreground, 2.0)?;
318
         }
331
         }
319
 
332
 
320
-        let cancel_text_width = renderer.measure_text(&self.cancel_label, &button_style)?.width;
333
+        let cancel_metrics = renderer.measure_text(&self.cancel_label, &button_style)?;
321
-        let cancel_text_x = cancel_rect.x + (cancel_rect.width as i32 - cancel_text_width as i32) / 2;
334
+        let cancel_text_x = cancel_rect.x + (cancel_rect.width as i32 - cancel_metrics.width as i32) / 2;
322
-        renderer.text(&self.cancel_label, cancel_text_x as f64, button_text_y as f64, &button_style)?;
335
+        let cancel_text_y = cancel_rect.y + (cancel_rect.height as i32 - cancel_metrics.height as i32) / 2;
336
+        renderer.text(&self.cancel_label, cancel_text_x as f64, cancel_text_y as f64, &button_style)?;
323
 
337
 
324
         Ok(())
338
         Ok(())
325
     }
339
     }
@@ -906,9 +920,9 @@ impl ConflictDialog {
906
                 renderer.stroke_rounded_rect(*rect, 4.0, theme.foreground, 2.0)?;
920
                 renderer.stroke_rounded_rect(*rect, 4.0, theme.foreground, 2.0)?;
907
             }
921
             }
908
 
922
 
909
-            let text_width = renderer.measure_text(label, &button_style)?.width;
923
+            let text_metrics = renderer.measure_text(label, &button_style)?;
910
-            let text_x = rect.x + (rect.width as i32 - text_width as i32) / 2;
924
+            let text_x = rect.x + (rect.width as i32 - text_metrics.width as i32) / 2;
911
-            let text_y = rect.y + (rect.height as i32 - (theme.font_size - 1.0) as i32) / 2;
925
+            let text_y = rect.y + (rect.height as i32 - text_metrics.height as i32) / 2;
912
             renderer.text(label, text_x as f64, text_y as f64, &button_style)?;
926
             renderer.text(label, text_x as f64, text_y as f64, &button_style)?;
913
         }
927
         }
914
 
928
 
@@ -1245,10 +1259,10 @@ impl InputDialog {
1245
         }
1259
         }
1246
 
1260
 
1247
         let ok_text = "OK";
1261
         let ok_text = "OK";
1248
-        let ok_width = renderer.measure_text(ok_text, &button_style)?.width;
1262
+        let ok_metrics = renderer.measure_text(ok_text, &button_style)?;
1249
-        let ok_x = ok_rect.x + (ok_rect.width as i32 - ok_width as i32) / 2;
1263
+        let ok_x = ok_rect.x + (ok_rect.width as i32 - ok_metrics.width as i32) / 2;
1250
-        let button_text_y = ok_rect.y + (ok_rect.height as i32 - theme.font_size as i32) / 2;
1264
+        let ok_text_y = ok_rect.y + (ok_rect.height as i32 - ok_metrics.height as i32) / 2;
1251
-        renderer.text(ok_text, ok_x as f64, button_text_y as f64, &button_style)?;
1265
+        renderer.text(ok_text, ok_x as f64, ok_text_y as f64, &button_style)?;
1252
 
1266
 
1253
         // Cancel button
1267
         // Cancel button
1254
         let cancel_focused = self.focused == 2;
1268
         let cancel_focused = self.focused == 2;
@@ -1264,9 +1278,10 @@ impl InputDialog {
1264
         }
1278
         }
1265
 
1279
 
1266
         let cancel_text = "Cancel";
1280
         let cancel_text = "Cancel";
1267
-        let cancel_width = renderer.measure_text(cancel_text, &button_style)?.width;
1281
+        let cancel_metrics = renderer.measure_text(cancel_text, &button_style)?;
1268
-        let cancel_x = cancel_rect.x + (cancel_rect.width as i32 - cancel_width as i32) / 2;
1282
+        let cancel_x = cancel_rect.x + (cancel_rect.width as i32 - cancel_metrics.width as i32) / 2;
1269
-        renderer.text(cancel_text, cancel_x as f64, button_text_y as f64, &button_style)?;
1283
+        let cancel_text_y = cancel_rect.y + (cancel_rect.height as i32 - cancel_metrics.height as i32) / 2;
1284
+        renderer.text(cancel_text, cancel_x as f64, cancel_text_y as f64, &button_style)?;
1270
 
1285
 
1271
         Ok(())
1286
         Ok(())
1272
     }
1287
     }