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 {
178178
         Rect::new(x, y, dialog_width, dialog_height)
179179
     }
180180
 
181
-    /// Wrap text to fit within max_width (simple word wrapping).
182
-    fn wrap_text(text: &str, max_chars: usize) -> Vec<String> {
181
+    /// Wrap text to fit within max_width pixels using actual text measurement.
182
+    fn wrap_text_to_width(text: &str, max_width: u32, renderer: &Renderer, style: &TextStyle) -> Vec<String> {
183183
         let mut lines = Vec::new();
184
+
184185
         for line in text.lines() {
185
-            if line.len() <= max_chars {
186
-                lines.push(line.to_string());
187
-            } else {
188
-                // Word wrap
189
-                let mut current_line = String::new();
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
-                    }
186
+            // Check if the whole line fits
187
+            if let Ok(metrics) = renderer.measure_text(line, style) {
188
+                if metrics.width <= max_width {
189
+                    lines.push(line.to_string());
190
+                    continue;
205191
                 }
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
207214
                     lines.push(current_line);
215
+                    current_line = word.to_string();
208216
                 }
209217
             }
218
+
219
+            if !current_line.is_empty() {
220
+                lines.push(current_line);
221
+            }
210222
         }
223
+
211224
         lines
212225
     }
213226
 
@@ -264,13 +277,13 @@ impl ConfirmDialog {
264277
             .font_size(theme.font_size)
265278
             .color(theme.item_foreground);
266279
 
267
-        // Wrap and render message lines (use conservative char width estimate)
268
-        let max_chars = ((dialog_rect.width - 50) as f64 / (theme.font_size * 0.55)) as usize;
269
-        let wrapped_lines = Self::wrap_text(&self.message, max_chars.max(25));
280
+        // Wrap text using pixel-based measurement (40px padding on each side)
281
+        let available_width = dialog_rect.width.saturating_sub(40);
282
+        let wrapped_lines = Self::wrap_text_to_width(&self.message, available_width, renderer, &msg_style);
270283
         let mut y = dialog_rect.y + 56;
271
-        for line in wrapped_lines {
284
+        for line in &wrapped_lines {
272285
             renderer.text(
273
-                &line,
286
+                line,
274287
                 (dialog_rect.x + 20) as f64,
275288
                 y as f64,
276289
                 &msg_style,
@@ -299,10 +312,10 @@ impl ConfirmDialog {
299312
             .font_size(theme.font_size)
300313
             .color(theme.foreground);
301314
 
302
-        let confirm_text_width = renderer.measure_text(&self.confirm_label, &button_style)?.width;
303
-        let confirm_text_x = confirm_rect.x + (confirm_rect.width as i32 - confirm_text_width as i32) / 2;
304
-        let button_text_y = confirm_rect.y + (confirm_rect.height as i32 - theme.font_size as i32) / 2;
305
-        renderer.text(&self.confirm_label, confirm_text_x as f64, button_text_y as f64, &button_style)?;
315
+        let confirm_metrics = renderer.measure_text(&self.confirm_label, &button_style)?;
316
+        let confirm_text_x = confirm_rect.x + (confirm_rect.width as i32 - confirm_metrics.width as i32) / 2;
317
+        let confirm_text_y = confirm_rect.y + (confirm_rect.height as i32 - confirm_metrics.height as i32) / 2;
318
+        renderer.text(&self.confirm_label, confirm_text_x as f64, confirm_text_y as f64, &button_style)?;
306319
 
307320
         // Cancel button
308321
         let cancel_focused = self.focused_button == 1;
@@ -317,9 +330,10 @@ impl ConfirmDialog {
317330
             renderer.stroke_rounded_rect(cancel_rect, 4.0, theme.foreground, 2.0)?;
318331
         }
319332
 
320
-        let cancel_text_width = renderer.measure_text(&self.cancel_label, &button_style)?.width;
321
-        let cancel_text_x = cancel_rect.x + (cancel_rect.width as i32 - cancel_text_width as i32) / 2;
322
-        renderer.text(&self.cancel_label, cancel_text_x as f64, button_text_y as f64, &button_style)?;
333
+        let cancel_metrics = renderer.measure_text(&self.cancel_label, &button_style)?;
334
+        let cancel_text_x = cancel_rect.x + (cancel_rect.width as i32 - cancel_metrics.width as i32) / 2;
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)?;
323337
 
324338
         Ok(())
325339
     }
@@ -906,9 +920,9 @@ impl ConflictDialog {
906920
                 renderer.stroke_rounded_rect(*rect, 4.0, theme.foreground, 2.0)?;
907921
             }
908922
 
909
-            let text_width = renderer.measure_text(label, &button_style)?.width;
910
-            let text_x = rect.x + (rect.width as i32 - text_width as i32) / 2;
911
-            let text_y = rect.y + (rect.height as i32 - (theme.font_size - 1.0) as i32) / 2;
923
+            let text_metrics = renderer.measure_text(label, &button_style)?;
924
+            let text_x = rect.x + (rect.width as i32 - text_metrics.width as i32) / 2;
925
+            let text_y = rect.y + (rect.height as i32 - text_metrics.height as i32) / 2;
912926
             renderer.text(label, text_x as f64, text_y as f64, &button_style)?;
913927
         }
914928
 
@@ -1245,10 +1259,10 @@ impl InputDialog {
12451259
         }
12461260
 
12471261
         let ok_text = "OK";
1248
-        let ok_width = renderer.measure_text(ok_text, &button_style)?.width;
1249
-        let ok_x = ok_rect.x + (ok_rect.width as i32 - ok_width as i32) / 2;
1250
-        let button_text_y = ok_rect.y + (ok_rect.height as i32 - theme.font_size as i32) / 2;
1251
-        renderer.text(ok_text, ok_x as f64, button_text_y as f64, &button_style)?;
1262
+        let ok_metrics = renderer.measure_text(ok_text, &button_style)?;
1263
+        let ok_x = ok_rect.x + (ok_rect.width as i32 - ok_metrics.width as i32) / 2;
1264
+        let ok_text_y = ok_rect.y + (ok_rect.height as i32 - ok_metrics.height as i32) / 2;
1265
+        renderer.text(ok_text, ok_x as f64, ok_text_y as f64, &button_style)?;
12521266
 
12531267
         // Cancel button
12541268
         let cancel_focused = self.focused == 2;
@@ -1264,9 +1278,10 @@ impl InputDialog {
12641278
         }
12651279
 
12661280
         let cancel_text = "Cancel";
1267
-        let cancel_width = renderer.measure_text(cancel_text, &button_style)?.width;
1268
-        let cancel_x = cancel_rect.x + (cancel_rect.width as i32 - cancel_width as i32) / 2;
1269
-        renderer.text(cancel_text, cancel_x as f64, button_text_y as f64, &button_style)?;
1281
+        let cancel_metrics = renderer.measure_text(cancel_text, &button_style)?;
1282
+        let cancel_x = cancel_rect.x + (cancel_rect.width as i32 - cancel_metrics.width as i32) / 2;
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)?;
12701285
 
12711286
         Ok(())
12721287
     }