refactor(tui): extract shared setting edit overlay (#1776)

The draw_edit_overlay function in sandbox_settings and global_settings
was nearly identical — the only difference was which settings slice the
entry was resolved from (app.sandbox_settings vs app.global_settings).

Extract the common rendering logic into draw_setting_edit_overlay in
ui/mod.rs, taking key/kind/edit/area/theme as parameters. Each pane
now resolves its own entry and delegates to the single implementation.

Net: -75 lines, +61 lines across the three files.
This commit is contained in:
Eric Curtin
2026-06-05 10:03:43 -07:00
committed by GitHub
parent 97986d9059
commit 35afcf8aa1
3 changed files with 61 additions and 75 deletions
+1 -37
View File
@@ -120,46 +120,10 @@ fn draw_edit_overlay(
edit: &crate::app::SettingEditState,
area: Rect,
) {
let t = &app.theme;
let Some(entry) = app.global_settings.get(edit.index) else {
return;
};
let title = format!(" Edit: {} ({}) ", entry.key, entry.kind.as_str());
let mut lines = vec![
Line::from(Span::styled(&title, t.heading)),
Line::from(""),
Line::from(vec![
Span::styled("Value: ", t.muted),
Span::styled(&edit.input, t.text),
Span::styled("_", t.accent),
]),
];
if let Some(ref err) = edit.error {
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(err, t.status_err)));
}
lines.push(Line::from(""));
lines.push(Line::from(vec![
Span::styled("[Enter]", t.key_hint),
Span::styled(" Confirm ", t.muted),
Span::styled("[Esc]", t.key_hint),
Span::styled(" Cancel", t.muted),
]));
// content lines + 2 for border
let popup_height = u16::try_from(lines.len() + 2).unwrap_or(u16::MAX);
let popup = centered_rect(50, popup_height, area);
frame.render_widget(Clear, popup);
let block = Block::default()
.borders(Borders::ALL)
.border_style(t.border_focused)
.padding(Padding::horizontal(1));
frame.render_widget(Paragraph::new(lines).block(block), popup);
super::draw_setting_edit_overlay(frame, &entry.key, entry.kind, edit, area, &app.theme);
}
fn draw_confirm_set(frame: &mut Frame<'_>, app: &App, idx: usize, area: Rect) {
+59 -2
View File
@@ -18,9 +18,10 @@ use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Paragraph};
use ratatui::widgets::{Block, Borders, Clear, Padding, Paragraph};
use crate::app::{self, App, Focus, InputMode, Screen};
use crate::app::{self, App, Focus, InputMode, Screen, SettingEditState};
use crate::theme::Theme;
pub fn draw(frame: &mut Frame<'_>, app: &mut App) {
// Splash screen is a full-screen takeover — no chrome.
@@ -512,6 +513,62 @@ pub fn centered_rect(width: u16, height: u16, area: Rect) -> Rect {
horiz[1]
}
// ---------------------------------------------------------------------------
// Shared setting-edit overlay (used by both global and sandbox settings panes)
// ---------------------------------------------------------------------------
/// Render the generic setting-edit text-input overlay.
///
/// Both the global-settings and sandbox-settings panes show the same editor
/// overlay — an input box with the setting key/type in the title, an error
/// line when validation fails, and `[Enter]`/`[Esc]` hints. This function
/// is the single implementation; each pane resolves its own entry and then
/// delegates here.
fn draw_setting_edit_overlay(
frame: &mut Frame<'_>,
key: &str,
kind: openshell_core::settings::SettingValueKind,
edit: &SettingEditState,
area: Rect,
theme: &Theme,
) {
let t = theme;
let title = format!(" Edit: {} ({}) ", key, kind.as_str());
let mut lines = vec![
Line::from(Span::styled(&title, t.heading)),
Line::from(""),
Line::from(vec![
Span::styled("Value: ", t.muted),
Span::styled(&edit.input, t.text),
Span::styled("_", t.accent),
]),
];
if let Some(ref err) = edit.error {
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(err.as_str(), t.status_err)));
}
lines.push(Line::from(""));
lines.push(Line::from(vec![
Span::styled("[Enter]", t.key_hint),
Span::styled(" Confirm ", t.muted),
Span::styled("[Esc]", t.key_hint),
Span::styled(" Cancel", t.muted),
]));
let popup_height = u16::try_from(lines.len() + 2).unwrap_or(u16::MAX);
let popup = centered_popup(50, popup_height, area);
frame.render_widget(Clear, popup);
let block = Block::default()
.borders(Borders::ALL)
.border_style(t.border_focused)
.padding(Padding::horizontal(1));
frame.render_widget(Paragraph::new(lines).block(block), popup);
}
/// Center a popup rectangle within `area` using percentage-based width and
/// an absolute height (in rows).
pub fn centered_popup(percent_x: u16, height: u16, area: Rect) -> Rect {
@@ -128,45 +128,10 @@ fn draw_edit_overlay(
edit: &crate::app::SettingEditState,
area: Rect,
) {
let t = &app.theme;
let Some(entry) = app.sandbox_settings.get(edit.index) else {
return;
};
let title = format!(" Edit: {} ({}) ", entry.key, entry.kind.as_str());
let mut lines = vec![
Line::from(Span::styled(&title, t.heading)),
Line::from(""),
Line::from(vec![
Span::styled("Value: ", t.muted),
Span::styled(&edit.input, t.text),
Span::styled("_", t.accent),
]),
];
if let Some(ref err) = edit.error {
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(err, t.status_err)));
}
lines.push(Line::from(""));
lines.push(Line::from(vec![
Span::styled("[Enter]", t.key_hint),
Span::styled(" Confirm ", t.muted),
Span::styled("[Esc]", t.key_hint),
Span::styled(" Cancel", t.muted),
]));
let popup_height = u16::try_from(lines.len() + 2).unwrap_or(u16::MAX);
let popup = centered_rect(50, popup_height, area);
frame.render_widget(Clear, popup);
let block = Block::default()
.borders(Borders::ALL)
.border_style(t.border_focused)
.padding(Padding::horizontal(1));
frame.render_widget(Paragraph::new(lines).block(block), popup);
super::draw_setting_edit_overlay(frame, &entry.key, entry.kind, edit, area, &app.theme);
}
fn draw_confirm_set(frame: &mut Frame<'_>, app: &App, idx: usize, area: Rect) {