Skip to content

Commit 53df413

Browse files
authored
feat(config): add configurable unit preferences (#104)
1 parent 00018dd commit 53df413

File tree

10 files changed

+573
-72
lines changed

10 files changed

+573
-72
lines changed

cli/src/config.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ pub struct UserConfig {
119119
#[serde(default)]
120120
pub history: HistoryConfig,
121121
#[serde(default)]
122+
pub units: UnitsConfig,
123+
#[serde(default)]
122124
pub log_level: LogLevel,
123125
}
124126

@@ -136,6 +138,7 @@ impl Default for UserConfig {
136138
forecast_window_secs: 300,
137139
excluded_processes: Vec::new(),
138140
history: HistoryConfig::default(),
141+
units: UnitsConfig::default(),
139142
log_level: LogLevel::Info,
140143
}
141144
}
@@ -151,6 +154,117 @@ pub enum GraphMetric {
151154
Merged,
152155
}
153156

157+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
158+
#[serde(rename_all = "lowercase")]
159+
pub enum EnergyUnit {
160+
#[default]
161+
Wh,
162+
MAh,
163+
}
164+
165+
impl EnergyUnit {
166+
pub fn label(&self) -> &'static str {
167+
match self {
168+
EnergyUnit::Wh => "Wh",
169+
EnergyUnit::MAh => "mAh",
170+
}
171+
}
172+
173+
pub fn next(&self) -> Self {
174+
match self {
175+
EnergyUnit::Wh => EnergyUnit::MAh,
176+
EnergyUnit::MAh => EnergyUnit::Wh,
177+
}
178+
}
179+
180+
pub fn prev(&self) -> Self {
181+
match self {
182+
EnergyUnit::Wh => EnergyUnit::MAh,
183+
EnergyUnit::MAh => EnergyUnit::Wh,
184+
}
185+
}
186+
}
187+
188+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
189+
#[serde(rename_all = "lowercase")]
190+
pub enum TemperatureUnit {
191+
#[default]
192+
Celsius,
193+
Fahrenheit,
194+
}
195+
196+
impl TemperatureUnit {
197+
pub fn label(&self) -> &'static str {
198+
match self {
199+
TemperatureUnit::Celsius => "Celsius",
200+
TemperatureUnit::Fahrenheit => "Fahrenheit",
201+
}
202+
}
203+
204+
pub fn next(&self) -> Self {
205+
match self {
206+
TemperatureUnit::Celsius => TemperatureUnit::Fahrenheit,
207+
TemperatureUnit::Fahrenheit => TemperatureUnit::Celsius,
208+
}
209+
}
210+
211+
pub fn prev(&self) -> Self {
212+
match self {
213+
TemperatureUnit::Celsius => TemperatureUnit::Fahrenheit,
214+
TemperatureUnit::Fahrenheit => TemperatureUnit::Celsius,
215+
}
216+
}
217+
}
218+
219+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
220+
#[serde(rename_all = "lowercase")]
221+
pub enum DataSizeUnit {
222+
#[default]
223+
Si,
224+
Binary,
225+
}
226+
227+
impl DataSizeUnit {
228+
pub fn label(&self) -> &'static str {
229+
match self {
230+
DataSizeUnit::Si => "SI (KB, MB)",
231+
DataSizeUnit::Binary => "Binary (KiB, MiB)",
232+
}
233+
}
234+
235+
pub fn next(&self) -> Self {
236+
match self {
237+
DataSizeUnit::Si => DataSizeUnit::Binary,
238+
DataSizeUnit::Binary => DataSizeUnit::Si,
239+
}
240+
}
241+
242+
pub fn prev(&self) -> Self {
243+
match self {
244+
DataSizeUnit::Si => DataSizeUnit::Binary,
245+
DataSizeUnit::Binary => DataSizeUnit::Si,
246+
}
247+
}
248+
}
249+
250+
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
251+
#[serde(default)]
252+
pub struct UnitsConfig {
253+
pub energy: EnergyUnit,
254+
pub temperature: TemperatureUnit,
255+
pub data_size: DataSizeUnit,
256+
}
257+
258+
impl Default for UnitsConfig {
259+
fn default() -> Self {
260+
Self {
261+
energy: EnergyUnit::Wh,
262+
temperature: TemperatureUnit::Celsius,
263+
data_size: DataSizeUnit::Si,
264+
}
265+
}
266+
}
267+
154268
pub fn config_dir() -> PathBuf {
155269
dirs::config_dir()
156270
.unwrap_or_else(|| PathBuf::from("~/.config"))

cli/src/settings.rs

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
66
use crate::app::App;
77

8-
/// Unique identifier for each setting. Used for type-safe matching instead of strings.
98
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109
pub enum SettingId {
1110
// General
@@ -17,6 +16,10 @@ pub enum SettingId {
1716
MergeMode,
1817
ProcessCount,
1918
EnergyThreshold,
19+
// Units
20+
EnergyUnit,
21+
TemperatureUnit,
22+
DataSizeUnit,
2023
// Recording
2124
BackgroundRecording,
2225
SampleInterval,
@@ -103,7 +106,19 @@ pub const SETTINGS_LAYOUT: &[SettingsRow] = &[
103106
id: SettingId::EnergyThreshold,
104107
label: "Energy Threshold",
105108
},
106-
// Recording section
109+
SettingsRow::Section("Units"),
110+
SettingsRow::Item {
111+
id: SettingId::EnergyUnit,
112+
label: "Energy",
113+
},
114+
SettingsRow::Item {
115+
id: SettingId::TemperatureUnit,
116+
label: "Temperature",
117+
},
118+
SettingsRow::Item {
119+
id: SettingId::DataSizeUnit,
120+
label: "Data Size",
121+
},
107122
SettingsRow::Section("Recording"),
108123
SettingsRow::Item {
109124
id: SettingId::BackgroundRecording,
@@ -145,6 +160,9 @@ pub fn setting_value(app: &App, id: SettingId) -> String {
145160
SettingId::MergeMode => bool_label(app.merge_mode),
146161
SettingId::ProcessCount => app.config.user_config.process_count.to_string(),
147162
SettingId::EnergyThreshold => format!("{:.1}", app.config.user_config.energy_threshold),
163+
SettingId::EnergyUnit => app.config.user_config.units.energy.label().to_string(),
164+
SettingId::TemperatureUnit => app.config.user_config.units.temperature.label().to_string(),
165+
SettingId::DataSizeUnit => app.config.user_config.units.data_size.label().to_string(),
148166
SettingId::BackgroundRecording => {
149167
bool_label(app.config.user_config.history.background_recording)
150168
}
@@ -242,6 +260,24 @@ pub fn setting_apply(app: &mut App, id: SettingId, input: SettingInput) -> Setti
242260
10.0,
243261
0.5,
244262
),
263+
SettingId::EnergyUnit => apply_cycle(
264+
app,
265+
input,
266+
|a| a.config.user_config.units.energy,
267+
|a, v| a.config.user_config.units.energy = v,
268+
),
269+
SettingId::TemperatureUnit => apply_cycle(
270+
app,
271+
input,
272+
|a| a.config.user_config.units.temperature,
273+
|a, v| a.config.user_config.units.temperature = v,
274+
),
275+
SettingId::DataSizeUnit => apply_cycle(
276+
app,
277+
input,
278+
|a| a.config.user_config.units.data_size,
279+
|a, v| a.config.user_config.units.data_size = v,
280+
),
245281
SettingId::BackgroundRecording => apply_bool(
246282
app,
247283
input,
@@ -399,7 +435,6 @@ where
399435
SettingInput::Activate => return SettingOutcome::default(),
400436
};
401437

402-
// Use epsilon comparison for floats
403438
if (new_val - current).abs() > f64::EPSILON {
404439
set(app, new_val);
405440
let _ = app.config.user_config.save();
@@ -408,3 +443,57 @@ where
408443
SettingOutcome::default()
409444
}
410445
}
446+
447+
trait Cyclable: Copy {
448+
fn next(&self) -> Self;
449+
fn prev(&self) -> Self;
450+
}
451+
452+
impl Cyclable for crate::config::EnergyUnit {
453+
fn next(&self) -> Self {
454+
crate::config::EnergyUnit::next(self)
455+
}
456+
fn prev(&self) -> Self {
457+
crate::config::EnergyUnit::prev(self)
458+
}
459+
}
460+
461+
impl Cyclable for crate::config::TemperatureUnit {
462+
fn next(&self) -> Self {
463+
crate::config::TemperatureUnit::next(self)
464+
}
465+
fn prev(&self) -> Self {
466+
crate::config::TemperatureUnit::prev(self)
467+
}
468+
}
469+
470+
impl Cyclable for crate::config::DataSizeUnit {
471+
fn next(&self) -> Self {
472+
crate::config::DataSizeUnit::next(self)
473+
}
474+
fn prev(&self) -> Self {
475+
crate::config::DataSizeUnit::prev(self)
476+
}
477+
}
478+
479+
fn apply_cycle<T, G, S>(app: &mut App, input: SettingInput, get: G, set: S) -> SettingOutcome
480+
where
481+
T: Cyclable,
482+
G: Fn(&App) -> T,
483+
S: Fn(&mut App, T),
484+
{
485+
match input {
486+
SettingInput::Activate | SettingInput::Increment => {
487+
let new_val = get(app).next();
488+
set(app, new_val);
489+
let _ = app.config.user_config.save();
490+
SettingOutcome { open_modal: false }
491+
}
492+
SettingInput::Decrement => {
493+
let new_val = get(app).prev();
494+
set(app, new_val);
495+
let _ = app.config.user_config.save();
496+
SettingOutcome { open_modal: false }
497+
}
498+
}
499+
}

cli/src/ui/battery.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::data::battery::ChargeState;
1111
use crate::data::power::PowerMode;
1212
use crate::theme::ThemeColors;
1313

14-
use super::utils::color_for_percent;
14+
use super::utils::{color_for_percent, format_energy_ratio, format_temperature};
1515

1616
/// Returns the icon for the given power mode.
1717
fn power_mode_icon(mode: PowerMode) -> &'static str {
@@ -230,21 +230,23 @@ fn render_battery_info_card(frame: &mut Frame, area: Rect, app: &App, theme: &Th
230230
];
231231

232232
if let Some(temp) = app.battery.temperature_c() {
233+
let temp_unit = app.config.user_config.units.temperature;
233234
right_spans.push(Span::styled(" ", Style::default()));
234235
right_spans.push(Span::styled("Temp: ", theme.muted_style()));
235236
right_spans.push(Span::styled(
236-
format!("{:.1}°C", temp),
237+
format_temperature(temp, temp_unit),
237238
theme.warning_style(),
238239
));
239240
}
240241

242+
let energy_unit = app.config.user_config.units.energy;
241243
right_spans.push(Span::styled(" ", Style::default()));
242244
right_spans.push(Span::styled("Energy: ", theme.muted_style()));
243245
right_spans.push(Span::styled(
244-
format!(
245-
"{:.1}/{:.1}Wh",
246+
format_energy_ratio(
246247
app.battery.energy_wh(),
247-
app.battery.max_capacity_wh()
248+
app.battery.max_capacity_wh(),
249+
energy_unit,
248250
),
249251
theme.fg_style(),
250252
));

0 commit comments

Comments
 (0)