Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions src/app_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ pub struct SettingsFile {
/// the widget counts down towards a limit rather than up from zero.
#[serde(default)]
pub usage_countdown: bool,
#[serde(default = "default_true")]
pub show_pace: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub active_theme_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -139,6 +141,7 @@ impl Default for SettingsFile {
show_copilot: false,
custom_theme_enabled: true,
usage_countdown: false,
show_pace: true,
active_theme_path: None,
dashboard_width: None,
dashboard_height: None,
Expand Down Expand Up @@ -660,6 +663,27 @@ mod tests {
assert_eq!(settings_json(&counting_down)["usage_countdown"], true);
}

#[test]
fn show_pace_defaults_to_on_and_round_trips() {
let settings = SettingsFile::default();
assert!(settings.show_pace);
assert_eq!(settings_json(&settings)["show_pace"], true);

let legacy = decode_settings(r#"{"poll_interval_ms":900000}"#).unwrap();
assert!(legacy.show_pace);

for show_pace in [false, true] {
let settings = SettingsFile {
show_pace,
..Default::default()
};
let json = settings_json(&settings);
assert_eq!(json["show_pace"], show_pace);
let loaded = decode_settings(&json.to_string()).unwrap();
assert_eq!(loaded.show_pace, show_pace);
}
}

#[test]
fn settings_always_use_the_theme_widget() {
let mut settings = SettingsFile {
Expand Down
16 changes: 16 additions & 0 deletions src/context_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub enum ContextMenuAction {
ToggleAntigravityExternal,
ToggleStartup,
ToggleTaskbarLock,
ToggleShowPace,
ToggleWidget,
/// Accepted only so menus saved by older versions can be loaded and
/// cleaned up. New menus cannot create or execute this legacy action.
Expand Down Expand Up @@ -325,6 +326,7 @@ pub fn rendered_label(
"Check for updates" => language.text("Check for updates"),
"Lock in taskbar" => language.text("Lock in taskbar"),
"Show widget" => language.text("Show widget"),
"Show pace" => language.text("Show pace"),
_ => label,
};
crate::theme_engine::format_template(translated, context)
Expand Down Expand Up @@ -457,6 +459,7 @@ pub fn classic_context_menu() -> ContextMenuDocument {
Action::ToggleStartup,
),
ContextMenuItem::action("lock-taskbar", "Lock in taskbar", Action::ToggleTaskbarLock),
ContextMenuItem::action("show-pace", "Show pace", Action::ToggleShowPace),
ContextMenuItem::submenu("language", "Language", languages),
ContextMenuItem::separator("settings-separator"),
ContextMenuItem::action(
Expand Down Expand Up @@ -718,6 +721,19 @@ mod tests {
assert!(menu.validate().is_empty());
assert!(menu.items.iter().any(|item| item.id == "refresh"));
assert!(menu.items.iter().any(|item| item.id == "update-frequency"));
let settings = menu.items.iter().find(|item| item.id == "settings").unwrap();
let ContextMenuItemKind::Submenu { items } = &settings.kind else {
panic!("Settings must be a submenu");
};
assert!(items.iter().any(|item| {
item.label == "Show pace"
&& matches!(
&item.kind,
ContextMenuItemKind::Action {
action: ContextMenuAction::ToggleShowPace
}
)
}));
assert!(serde_json::to_string(&menu)
.unwrap()
.contains("provider-opencode"));
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/de.toml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ copilot_window_title = "Copilot-Nutzungsmonitor"
"Short duration" = "Kurze Dauer"
"Show context menu" = "Kontextmenü anzeigen"
"Show dashboard" = "Dashboard anzeigen"
"Show pace" = "Show pace"
"Show widget" = "Widget anzeigen"
"Size" = "Größe"
"Smaller value" = "Kleinerer Wert"
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/en.toml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ copilot_window_title = "Copilot Usage Monitor"
"Short duration" = "Short duration"
"Show context menu" = "Show context menu"
"Show dashboard" = "Show dashboard"
"Show pace" = "Show pace"
"Show widget" = "Show widget"
"Size" = "Size"
"Smaller value" = "Smaller value"
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/es.toml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ copilot_window_title = "Monitor de uso de Copilot"
"Short duration" = "Duración corta"
"Show context menu" = "Mostrar menú contextual"
"Show dashboard" = "Mostrar panel"
"Show pace" = "Show pace"
"Show widget" = "Mostrar widget"
"Size" = "Tamaño"
"Smaller value" = "Valor menor"
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/fr.toml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ copilot_window_title = "Moniteur d'utilisation Copilot"
"Short duration" = "Durée courte"
"Show context menu" = "Afficher le menu contextuel"
"Show dashboard" = "Afficher le tableau de bord"
"Show pace" = "Show pace"
"Show widget" = "Afficher le widget"
"Size" = "Taille"
"Smaller value" = "Valeur la plus petite"
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/ja.toml
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ copilot_window_title = "Copilot 使用量モニター"
"Short duration" = "短い期間"
"Show context menu" = "コンテキストメニューを表示"
"Show dashboard" = "ダッシュボードを表示"
"Show pace" = "Show pace"
"Show widget" = "ウィジェットを表示"
"Size" = "サイズ"
"Smaller value" = "小さい値"
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/ko.toml
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ copilot_window_title = "Copilot 사용량 모니터"
"Short duration" = "간단한 기간"
"Show context menu" = "컨텍스트 메뉴 표시"
"Show dashboard" = "대시보드 표시"
"Show pace" = "Show pace"
"Show widget" = "위젯 표시"
"Size" = "크기"
"Smaller value" = "더 작은 값"
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/nl.toml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ copilot_window_title = "Copilot-gebruiksmonitor"
"Short duration" = "Korte duur"
"Show context menu" = "Contextmenu tonen"
"Show dashboard" = "Dashboard tonen"
"Show pace" = "Show pace"
"Show widget" = "Widget tonen"
"Size" = "Grootte"
"Smaller value" = "Kleinste waarde"
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/pl.toml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ copilot_window_title = "Monitor zużycia Copilot"
"Short duration" = "Skrócony czas trwania"
"Show context menu" = "Pokaż menu kontekstowe"
"Show dashboard" = "Pokaż panel"
"Show pace" = "Show pace"
"Show widget" = "Pokaż widżet"
"Size" = "Rozmiar"
"Smaller value" = "Mniejsza z wartości"
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/pt-BR.toml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ copilot_window_title = "Monitor de uso do Copilot"
"Short duration" = "Duração curta"
"Show context menu" = "Mostrar menu de contexto"
"Show dashboard" = "Mostrar painel"
"Show pace" = "Show pace"
"Show widget" = "Mostrar widget"
"Size" = "Tamanho"
"Smaller value" = "Menor valor"
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/ru.toml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ copilot_window_title = "Монитор использования Copilot"
"Short duration" = "Краткая длительность"
"Show context menu" = "Показать контекстное меню"
"Show dashboard" = "Показать панель"
"Show pace" = "Показывать темп"
"Show widget" = "Показать виджет"
"Size" = "Размер"
"Smaller value" = "Меньшее значение"
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/th.toml
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ copilot_window_title = "ตัวติดตามการใช้งาน C
"Short duration" = "ระยะเวลาแบบสั้น"
"Show context menu" = "แสดงเมนูคลิกขวา"
"Show dashboard" = "แสดงแดชบอร์ด"
"Show pace" = "Show pace"
"Show widget" = "แสดงวิดเจ็ต"
"Size" = "ขนาด"
"Smaller value" = "ค่าที่น้อยกว่า"
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/tr.toml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ copilot_window_title = "Copilot Kullanım İzleyicisi"
"Short duration" = "Kısa süre"
"Show context menu" = "Bağlam menüsünü göster"
"Show dashboard" = "Panoyu göster"
"Show pace" = "Show pace"
"Show widget" = "Pencere öğesini göster"
"Size" = "Boyut"
"Smaller value" = "Küçük değer"
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/zh-CN.toml
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ copilot_window_title = "Copilot 使用量监控"
"Short duration" = "简短时长"
"Show context menu" = "显示上下文菜单"
"Show dashboard" = "显示仪表板"
"Show pace" = "Show pace"
"Show widget" = "显示小组件"
"Size" = "大小"
"Smaller value" = "较小的值"
Expand Down
1 change: 1 addition & 0 deletions src/localization/locales/zh-TW.toml
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ copilot_window_title = "Copilot 使用量監控"
"Short duration" = "簡短時間"
"Show context menu" = "顯示快顯功能表"
"Show dashboard" = "顯示儀表板"
"Show pace" = "Show pace"
"Show widget" = "顯示小工具"
"Size" = "大小"
"Smaller value" = "較小的值"
Expand Down
2 changes: 2 additions & 0 deletions src/studio_app/studio_context_menu_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ pub(super) fn context_menu_action_script(action: &ContextMenuAction) -> String {
ContextMenuAction::ToggleAntigravityExternal => "toggle_antigravity_external()".into(),
ContextMenuAction::ToggleStartup => "toggle_startup()".into(),
ContextMenuAction::ToggleTaskbarLock => "toggle_taskbar_lock()".into(),
ContextMenuAction::ToggleShowPace => "toggle_show_pace()".into(),
ContextMenuAction::ToggleWidget => "toggle_widget()".into(),
ContextMenuAction::LegacyResetPosition => String::new(),
ContextMenuAction::SetLanguage { language } => {
Expand Down Expand Up @@ -352,6 +353,7 @@ pub(super) fn parse_context_menu_action_script(script: &str) -> Result<ContextMe
("refresh", ContextMenuAction::Refresh),
("toggle_startup", ContextMenuAction::ToggleStartup),
("toggle_taskbar_lock", ContextMenuAction::ToggleTaskbarLock),
("toggle_show_pace", ContextMenuAction::ToggleShowPace),
("toggle_widget", ContextMenuAction::ToggleWidget),
("check_for_updates", ContextMenuAction::CheckForUpdates),
("toggle_antigravity_gemini", ContextMenuAction::ToggleAntigravityGemini),
Expand Down
1 change: 1 addition & 0 deletions src/studio_app/studio_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ impl StudioApp {
.with_poll_state(self.usage_poll_ok, self.usage_has_error)
.with_language(language)
.with_countdown(self.settings.usage_countdown)
.with_show_pace(self.settings.show_pace)
.with_antigravity_pools(
self.settings.antigravity_gemini_enabled(),
self.settings.antigravity_external_enabled(),
Expand Down
8 changes: 8 additions & 0 deletions src/studio_app/studio_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,13 @@ const MENU_ACTIONS: &[ActionSpec] = &[
"check_for_updates()",
"check_for_updates",
),
(
"toggle_show_pace",
SETTINGS,
"Show pace",
"toggle_show_pace()",
"toggle_show_pace",
),
("exit", APP, "Exit", "exit()", "exit"),
(
"set_update_frequency",
Expand Down Expand Up @@ -1370,6 +1377,7 @@ fn menu_action_details(
"toggle_widget" => ContextMenuAction::ToggleWidget,
"toggle_startup" => ContextMenuAction::ToggleStartup,
"toggle_taskbar_lock" => ContextMenuAction::ToggleTaskbarLock,
"toggle_show_pace" => ContextMenuAction::ToggleShowPace,
"check_for_updates" => ContextMenuAction::CheckForUpdates,
"exit" => ContextMenuAction::Exit,
"set_update_frequency" => {
Expand Down
1 change: 1 addition & 0 deletions src/studio_app/studio_native_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,7 @@ pub(super) fn context_menu_preview_action_checked(
ContextMenuAction::ToggleProvider { provider } => settings.provider_enabled(*provider),
ContextMenuAction::ToggleStartup => startup_enabled,
ContextMenuAction::ToggleTaskbarLock => settings.lock_taskbar,
ContextMenuAction::ToggleShowPace => settings.show_pace,
ContextMenuAction::ToggleWidget => theme
.surfaces
.iter()
Expand Down
1 change: 1 addition & 0 deletions src/studio_app/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,7 @@ fn context_menu_action_scripts_round_trip_every_action_kind() {
},
ContextMenuAction::ToggleStartup,
ContextMenuAction::ToggleTaskbarLock,
ContextMenuAction::ToggleShowPace,
ContextMenuAction::ToggleWidget,
ContextMenuAction::SetLanguage {
language: "en-US".into(),
Expand Down
44 changes: 44 additions & 0 deletions src/theme_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,7 @@ pub struct ThemeRuntime {
/// the `.display` values and summaries using them follow this; `.percentage`
/// always means consumption so severity thresholds keep their meaning.
pub countdown: bool,
pub show_pace: bool,
pub surface_nest: SurfaceNest,
pub floating_card_opacity: u8,
pub antigravity_gemini_enabled: bool,
Expand All @@ -1351,6 +1352,7 @@ impl Default for ThemeRuntime {
has_error: false,
language: LanguageId::English,
countdown: false,
show_pace: true,
surface_nest: SurfaceNest::Taskbar,
floating_card_opacity: 85,
antigravity_gemini_enabled: true,
Expand Down Expand Up @@ -1385,6 +1387,7 @@ impl ThemeRuntime {
has_error: false,
language: LanguageId::English,
countdown: false,
show_pace: true,
surface_nest: SurfaceNest::Taskbar,
floating_card_opacity: 85,
antigravity_gemini_enabled: agy,
Expand Down Expand Up @@ -1434,6 +1437,11 @@ impl ThemeRuntime {
self
}

pub fn with_show_pace(mut self, show_pace: bool) -> Self {
self.show_pace = show_pace;
self
}

/// Supply the selected native host's 96-DPI logical dimensions. Theme
/// expressions consume these as `host.width` and `host.height`.
pub fn with_host_dimensions(mut self, width: u32, height: u32) -> Self {
Expand All @@ -1455,6 +1463,21 @@ impl ThemeRuntime {
}
}

const FIVE_HOURS: std::time::Duration = std::time::Duration::from_secs(5 * 3600);
const WEEK: std::time::Duration = std::time::Duration::from_secs(7 * 24 * 3600);

/// Percent of a window of `length` that has passed by now, given when it
/// resets; `None` when the reset is not within one window from now.
fn expected_used(resets_at: std::time::SystemTime, length: std::time::Duration) -> Option<f64> {
let left = resets_at
.duration_since(std::time::SystemTime::now())
.ok()?;
// A minute of slack for poll latency right after a reset.
(!length.is_zero() && left <= length + std::time::Duration::from_secs(60)).then(|| {
(1.0 - left.min(length).as_secs_f64() / length.as_secs_f64()) * 100.0
})
}

impl DataContext {
pub fn from_usage(data: Option<&AppUsageData>, canvas: &Canvas) -> Self {
Self::from_usage_with_runtime(data, canvas, ThemeRuntime::default())
Expand Down Expand Up @@ -1531,6 +1554,7 @@ impl DataContext {
);
}
context.insert("display.countdown", runtime.countdown as u8 as f64);
context.insert("display.show_pace", runtime.show_pace as u8 as f64);
context.insert(
"antigravity.gemini.enabled",
runtime.antigravity_gemini_enabled as u8 as f64,
Expand Down Expand Up @@ -1716,6 +1740,26 @@ impl DataContext {
display(weekly)
};
self.insert(&format!("{name}.weekly.display"), shown_weekly);
// Pace: the share a window would have spent by now at an even rate.
// `expected_display` follows the countdown setting like `display`.
let five_hour_section = usage.filter(|usage| usage.session.available).map(|usage| &usage.session);
let weekly_section = usage.filter(|usage| usage.weekly.available).map(|usage| &usage.weekly);
let session_section = if use_codex_session_fallback {
weekly_section.map(|section| (section, WEEK))
} else {
five_hour_section.map(|section| (section, FIVE_HOURS))
};
for (window, section) in [
("session", session_section),
("five_hour", five_hour_section.map(|section| (section, FIVE_HOURS))),
("weekly", weekly_section.map(|section| (section, WEEK))),
] {
let expected = section.and_then(|(section, length)| expected_used(section.resets_at?, length));
self.insert(&format!("{name}.{window}.expected_available"), expected.is_some() as u8 as f64);
let expected = expected.unwrap_or(0.0);
self.insert(&format!("{name}.{window}.expected"), expected);
self.insert(&format!("{name}.{window}.expected_display"), display(expected));
}
let monthly = usage.and_then(|usage| usage.monthly.as_ref());
self.insert_string(&format!("{name}.monthly.label"), "30d");
if let Some(monthly) = monthly {
Expand Down
Loading