From e9d95547447ad8cf1d2326b42606d84d46296630 Mon Sep 17 00:00:00 2001 From: "Carlos Miguel C. Resurreccion" Date: Fri, 15 May 2026 16:23:46 +0800 Subject: [PATCH 1/5] feat: add pace indicator localization Adds the pace indicator strings to the Strings struct and provides translations for all eight supported locales: the show_pace_indicator menu item, the pace_indicator_style submenu label, and the Tick and Solid style names. The strings are wired into the renderer and the right-click menu in the following commit. --- src/localization/dutch.rs | 4 ++++ src/localization/english.rs | 4 ++++ src/localization/french.rs | 4 ++++ src/localization/german.rs | 4 ++++ src/localization/japanese.rs | 4 ++++ src/localization/korean.rs | 4 ++++ src/localization/mod.rs | 4 ++++ src/localization/spanish.rs | 4 ++++ src/localization/traditional_chinese.rs | 4 ++++ 9 files changed, 36 insertions(+) diff --git a/src/localization/dutch.rs b/src/localization/dutch.rs index 0eb486d..d684a17 100644 --- a/src/localization/dutch.rs +++ b/src/localization/dutch.rs @@ -16,6 +16,10 @@ pub(super) const STRINGS: Strings = Strings { settings: "Instellingen", start_with_windows: "Opstarten met Windows", reset_position: "Positie herstellen", + show_pace_indicator: "Tempo-indicator tonen", + pace_indicator_style: "Stijl van tempo-indicator", + pace_style_tick: "Streepje", + pace_style_solid: "Gevuld", language: "Taal", system_default: "Systeemstandaard", check_for_updates: "Controleren op updates", diff --git a/src/localization/english.rs b/src/localization/english.rs index 2b92f36..f84b054 100644 --- a/src/localization/english.rs +++ b/src/localization/english.rs @@ -16,6 +16,10 @@ pub(super) const STRINGS: Strings = Strings { settings: "Settings", start_with_windows: "Start with Windows", reset_position: "Reset Position", + show_pace_indicator: "Show pace indicator", + pace_indicator_style: "Pace indicator style", + pace_style_tick: "Tick", + pace_style_solid: "Solid", language: "Language", system_default: "System Default", check_for_updates: "Check for Updates", diff --git a/src/localization/french.rs b/src/localization/french.rs index fa448fb..a7fa382 100644 --- a/src/localization/french.rs +++ b/src/localization/french.rs @@ -16,6 +16,10 @@ pub(super) const STRINGS: Strings = Strings { settings: "Paramètres", start_with_windows: "Démarrer avec Windows", reset_position: "Réinitialiser la position", + show_pace_indicator: "Afficher l'indicateur de cadence", + pace_indicator_style: "Style de l'indicateur de cadence", + pace_style_tick: "Repère", + pace_style_solid: "Plein", language: "Langue", system_default: "Par défaut du système", check_for_updates: "Vérifier les mises à jour", diff --git a/src/localization/german.rs b/src/localization/german.rs index 5c7bb23..580d31d 100644 --- a/src/localization/german.rs +++ b/src/localization/german.rs @@ -16,6 +16,10 @@ pub(super) const STRINGS: Strings = Strings { settings: "Einstellungen", start_with_windows: "Mit Windows starten", reset_position: "Position zurücksetzen", + show_pace_indicator: "Tempo-Anzeige einblenden", + pace_indicator_style: "Stil der Tempo-Anzeige", + pace_style_tick: "Strich", + pace_style_solid: "Gefüllt", language: "Sprache", system_default: "Systemstandard", check_for_updates: "Nach Updates suchen", diff --git a/src/localization/japanese.rs b/src/localization/japanese.rs index 0020018..2e734f1 100644 --- a/src/localization/japanese.rs +++ b/src/localization/japanese.rs @@ -16,6 +16,10 @@ pub(super) const STRINGS: Strings = Strings { settings: "設定", start_with_windows: "Windows と同時に開始", reset_position: "位置をリセット", + show_pace_indicator: "ペース表示", + pace_indicator_style: "ペース表示スタイル", + pace_style_tick: "目盛り", + pace_style_solid: "塗りつぶし", language: "言語", system_default: "システム既定", check_for_updates: "更新を確認", diff --git a/src/localization/korean.rs b/src/localization/korean.rs index 59e3829..0168ee5 100644 --- a/src/localization/korean.rs +++ b/src/localization/korean.rs @@ -16,6 +16,10 @@ pub(super) const STRINGS: Strings = Strings { settings: "설정", start_with_windows: "Windows 시작 시 자동 실행", reset_position: "위치 초기화", + show_pace_indicator: "사용 속도 표시", + pace_indicator_style: "사용 속도 표시 스타일", + pace_style_tick: "눈금", + pace_style_solid: "채우기", language: "언어", system_default: "시스템 기본값", check_for_updates: "업데이트 확인", diff --git a/src/localization/mod.rs b/src/localization/mod.rs index 146b419..188ab08 100644 --- a/src/localization/mod.rs +++ b/src/localization/mod.rs @@ -134,6 +134,10 @@ pub struct Strings { pub settings: &'static str, pub start_with_windows: &'static str, pub reset_position: &'static str, + pub show_pace_indicator: &'static str, + pub pace_indicator_style: &'static str, + pub pace_style_tick: &'static str, + pub pace_style_solid: &'static str, pub language: &'static str, pub system_default: &'static str, pub check_for_updates: &'static str, diff --git a/src/localization/spanish.rs b/src/localization/spanish.rs index 8e6513e..fa829ba 100644 --- a/src/localization/spanish.rs +++ b/src/localization/spanish.rs @@ -16,6 +16,10 @@ pub(super) const STRINGS: Strings = Strings { settings: "Configuración", start_with_windows: "Iniciar con Windows", reset_position: "Restablecer posición", + show_pace_indicator: "Mostrar indicador de ritmo", + pace_indicator_style: "Estilo del indicador de ritmo", + pace_style_tick: "Marca", + pace_style_solid: "Relleno", language: "Idioma", system_default: "Predeterminado del sistema", check_for_updates: "Buscar actualizaciones", diff --git a/src/localization/traditional_chinese.rs b/src/localization/traditional_chinese.rs index 809ebba..4f45fbd 100644 --- a/src/localization/traditional_chinese.rs +++ b/src/localization/traditional_chinese.rs @@ -16,6 +16,10 @@ pub(super) const STRINGS: Strings = Strings { settings: "設定", start_with_windows: "開機時啟動", reset_position: "重置位置", + show_pace_indicator: "顯示使用步調", + pace_indicator_style: "使用步調樣式", + pace_style_tick: "刻度線", + pace_style_solid: "實心", language: "語言", system_default: "系統預設", check_for_updates: "檢查更新", From 5489a85fce26bcb8facf76c4229c09959ae8dd31 Mon Sep 17 00:00:00 2001 From: "Carlos Miguel C. Resurreccion" Date: Fri, 15 May 2026 16:23:58 +0800 Subject: [PATCH 2/5] feat: add a pace indicator to the usage bars Adds an opt-in pace indicator that marks where usage should be if it were spread evenly across the window, so the bar shows whether the current burn rate is ahead of or behind pace. The expected position is derived from each section's resets_at timestamp and the fixed window length (5h or 7d). It is drawn red when actual usage is ahead of pace and green when behind, in one of two styles: - Tick: a thin vertical bar at the expected-pace position. - Solid: a filled band spanning the gap between actual usage and the expected-pace position. Two right-click Settings controls drive it: a 'Show pace indicator' checkbox (off by default) and a 'Pace indicator style' submenu with Tick and Solid options (Solid by default). Both persist to settings.json via #[serde(default)] fields so existing files migrate cleanly. The indicator is drawn inside the existing bar segments, so the widget width is unchanged. --- README.md | 1 + src/window.rs | 341 +++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 312 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 08c8a28..a171ccb 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Once running, it will appear in your taskbar and as one or more tray icons in th - Right-click the taskbar widget or tray icon for refresh, displayed models, update frequency, Start with Windows, reset position, language, updates, and exit - Left-click the tray icon to toggle the taskbar widget on or off - Enable `Start with Windows` from the right-click menu if you want it to launch automatically when you sign in +- Enable `Show pace indicator` under right-click `Settings` to mark where your usage should be for the time elapsed, coloured red when you are ahead of pace and green when you are behind; choose a `Tick` or `Solid` style under `Pace indicator style` ### Models diff --git a/src/window.rs b/src/window.rs index 31955f5..476a7fe 100644 --- a/src/window.rs +++ b/src/window.rs @@ -65,6 +65,8 @@ struct AppState { codex_weekly_text: String, show_claude_code: bool, show_codex: bool, + show_pace_indicator: bool, + pace_indicator_solid: bool, data: Option, @@ -121,6 +123,14 @@ const IDM_LANG_KOREAN: u16 = 47; const IDM_LANG_TRADITIONAL_CHINESE: u16 = 48; const IDM_MODEL_CLAUDE_CODE: u16 = 60; const IDM_MODEL_CODEX: u16 = 61; +const IDM_SHOW_PACE_INDICATOR: u16 = 71; +const IDM_PACE_STYLE_TICK: u16 = 72; +const IDM_PACE_STYLE_SOLID: u16 = 73; + +// 5 hours and 7 days, in seconds. Used to compute "where pace says you +// should be" by comparing remaining time against the full window length. +const SESSION_WINDOW_SECS: u64 = 5 * 3600; +const WEEKLY_WINDOW_SECS: u64 = 7 * 86400; const DIVIDER_HIT_ZONE: i32 = 13; // LEFT_DIVIDER_W + DIVIDER_RIGHT_MARGIN @@ -213,6 +223,10 @@ struct SettingsFile { show_claude_code: bool, #[serde(default = "default_show_codex")] show_codex: bool, + #[serde(default)] + show_pace_indicator: bool, + #[serde(default = "default_pace_indicator_solid")] + pace_indicator_solid: bool, } impl Default for SettingsFile { @@ -225,6 +239,8 @@ impl Default for SettingsFile { widget_visible: true, show_claude_code: true, show_codex: false, + show_pace_indicator: false, + pace_indicator_solid: default_pace_indicator_solid(), } } } @@ -245,6 +261,10 @@ fn default_show_codex() -> bool { false } +fn default_pace_indicator_solid() -> bool { + true +} + fn load_settings() -> SettingsFile { let content = match std::fs::read_to_string(settings_path()) { Ok(c) => c, @@ -280,10 +300,47 @@ fn save_state_settings() { widget_visible: s.widget_visible, show_claude_code: s.show_claude_code, show_codex: s.show_codex, + show_pace_indicator: s.show_pace_indicator, + pace_indicator_solid: s.pace_indicator_solid, }); } } +/// Where pace says you should be, as a 0-100 percentage of the window +/// consumed by now. `None` if there is no reset timestamp yet, if the +/// window has already reset (data is stale), or if the remaining time +/// exceeds the window length (unexpected, but guarded against). +fn expected_pace_pct(resets_at: Option, window_secs: u64) -> Option { + let reset = resets_at?; + let remaining = reset.duration_since(SystemTime::now()).ok()?; + let remaining_secs = remaining.as_secs(); + if remaining_secs > window_secs { + return None; + } + let elapsed = window_secs - remaining_secs; + Some(elapsed as f64 / window_secs as f64 * 100.0) +} + +/// Pace values for the four usage cells, in the order +/// (claude session, claude weekly, codex session, codex weekly). +/// Returns all `None` when the indicator is disabled, when there is no +/// usage data yet, or when individual reset timestamps are missing. +fn pace_values_from_state( + s: &AppState, +) -> (Option, Option, Option, Option) { + if !s.show_pace_indicator { + return (None, None, None, None); + } + let claude = s.data.as_ref().and_then(|d| d.claude_code.as_ref()); + let codex = s.data.as_ref().and_then(|d| d.codex.as_ref()); + ( + claude.and_then(|c| expected_pace_pct(c.session.resets_at, SESSION_WINDOW_SECS)), + claude.and_then(|c| expected_pace_pct(c.weekly.resets_at, WEEKLY_WINDOW_SECS)), + codex.and_then(|c| expected_pace_pct(c.session.resets_at, SESSION_WINDOW_SECS)), + codex.and_then(|c| expected_pace_pct(c.weekly.resets_at, WEEKLY_WINDOW_SECS)), + ) +} + fn tray_icon_data_from_state() -> Vec { let state = lock_state(); match state.as_ref() { @@ -1013,6 +1070,8 @@ pub fn run() { codex_weekly_text: "--".to_string(), show_claude_code: settings.show_claude_code, show_codex: settings.show_codex, + show_pace_indicator: settings.show_pace_indicator, + pace_indicator_solid: settings.pace_indicator_solid, data: None, poll_interval_ms: settings.poll_interval_ms, retry_count: 0, @@ -1152,25 +1211,39 @@ fn render_layered() { codex_weekly_text, show_claude_code, show_codex, + session_pace_pct, + weekly_pace_pct, + codex_session_pace_pct, + codex_weekly_pace_pct, + pace_solid, ) = { let state = lock_state(); match state.as_ref() { - Some(s) => ( - s.hwnd, - s.is_dark, - s.embedded, - s.language.strings(), - s.session_percent, - s.session_text.clone(), - s.weekly_percent, - s.weekly_text.clone(), - s.codex_session_percent, - s.codex_session_text.clone(), - s.codex_weekly_percent, - s.codex_weekly_text.clone(), - s.show_claude_code, - s.show_codex, - ), + Some(s) => { + let (session_pace, weekly_pace, codex_session_pace, codex_weekly_pace) = + pace_values_from_state(s); + ( + s.hwnd, + s.is_dark, + s.embedded, + s.language.strings(), + s.session_percent, + s.session_text.clone(), + s.weekly_percent, + s.weekly_text.clone(), + s.codex_session_percent, + s.codex_session_text.clone(), + s.codex_weekly_percent, + s.codex_weekly_text.clone(), + s.show_claude_code, + s.show_codex, + session_pace, + weekly_pace, + codex_session_pace, + codex_weekly_pace, + s.pace_indicator_solid, + ) + } None => return, } }; @@ -1260,6 +1333,11 @@ fn render_layered() { show_claude_code, show_codex, &codex_accent, + session_pace_pct, + weekly_pace_pct, + codex_session_pace_pct, + codex_weekly_pace_pct, + pace_solid, ); // Background pixels → alpha 1 (nearly invisible but still hittable for right-click). @@ -1330,6 +1408,11 @@ fn paint_content( show_claude_code: bool, show_codex: bool, codex_accent: &Color, + session_pace_pct: Option, + weekly_pace_pct: Option, + codex_session_pace_pct: Option, + codex_weekly_pace_pct: Option, + pace_solid: bool, ) { unsafe { let client_rect = RECT { @@ -1422,6 +1505,9 @@ fn paint_content( accent, codex_accent, track, + session_pace_pct, + codex_session_pace_pct, + pace_solid, ); draw_row( hdc, @@ -1439,6 +1525,9 @@ fn paint_content( accent, codex_accent, track, + weekly_pace_pct, + codex_weekly_pace_pct, + pace_solid, ); SelectObject(hdc, old_font); @@ -2200,6 +2289,26 @@ unsafe extern "system" fn wnd_proc( IDM_START_WITH_WINDOWS => { set_startup_enabled(!is_startup_enabled()); } + IDM_SHOW_PACE_INDICATOR => { + { + let mut state = lock_state(); + if let Some(s) = state.as_mut() { + s.show_pace_indicator = !s.show_pace_indicator; + } + } + save_state_settings(); + render_layered(); + } + IDM_PACE_STYLE_TICK | IDM_PACE_STYLE_SOLID => { + { + let mut state = lock_state(); + if let Some(s) = state.as_mut() { + s.pace_indicator_solid = id == IDM_PACE_STYLE_SOLID; + } + } + save_state_settings(); + render_layered(); + } IDM_FREQ_1MIN | IDM_FREQ_5MIN | IDM_FREQ_15MIN | IDM_FREQ_1HOUR => { let new_interval = match id { IDM_FREQ_1MIN => POLL_1_MIN, @@ -2327,6 +2436,8 @@ fn show_context_menu(hwnd: HWND) { widget_visible, show_claude_code, show_codex, + show_pace_indicator, + pace_indicator_solid, ) = { let state = lock_state(); match state.as_ref() { @@ -2340,6 +2451,8 @@ fn show_context_menu(hwnd: HWND) { s.widget_visible, s.show_claude_code, s.show_codex, + s.show_pace_indicator, + s.pace_indicator_solid, ), None => ( POLL_15_MIN, @@ -2351,6 +2464,8 @@ fn show_context_menu(hwnd: HWND) { true, true, false, + false, + true, ), } }; @@ -2456,6 +2571,52 @@ fn show_context_menu(hwnd: HWND) { PCWSTR::from_raw(reset_pos_str.as_ptr()), ); + let pace_str = native_interop::wide_str(strings.show_pace_indicator); + let pace_flags = if show_pace_indicator { + MF_CHECKED + } else { + MENU_ITEM_FLAGS(0) + }; + let _ = AppendMenuW( + settings_menu, + pace_flags, + IDM_SHOW_PACE_INDICATOR as usize, + PCWSTR::from_raw(pace_str.as_ptr()), + ); + + let pace_style_menu = CreatePopupMenu().unwrap(); + let pace_tick_str = native_interop::wide_str(strings.pace_style_tick); + let pace_tick_flags = if pace_indicator_solid { + MENU_ITEM_FLAGS(0) + } else { + MF_CHECKED + }; + let _ = AppendMenuW( + pace_style_menu, + pace_tick_flags, + IDM_PACE_STYLE_TICK as usize, + PCWSTR::from_raw(pace_tick_str.as_ptr()), + ); + let pace_solid_str = native_interop::wide_str(strings.pace_style_solid); + let pace_solid_flags = if pace_indicator_solid { + MF_CHECKED + } else { + MENU_ITEM_FLAGS(0) + }; + let _ = AppendMenuW( + pace_style_menu, + pace_solid_flags, + IDM_PACE_STYLE_SOLID as usize, + PCWSTR::from_raw(pace_solid_str.as_ptr()), + ); + let pace_style_label = native_interop::wide_str(strings.pace_indicator_style); + let _ = AppendMenuW( + settings_menu, + MF_POPUP, + pace_style_menu.0 as usize, + PCWSTR::from_raw(pace_style_label.as_ptr()), + ); + let language_menu = CreatePopupMenu().unwrap(); let system_label = native_interop::wide_str(strings.system_default); let system_flags = if language_override.is_none() { @@ -2577,23 +2738,37 @@ fn paint(hdc: HDC, hwnd: HWND) { codex_weekly_text, show_claude_code, show_codex, + session_pace_pct, + weekly_pace_pct, + codex_session_pace_pct, + codex_weekly_pace_pct, + pace_solid, ) = { let state = lock_state(); match state.as_ref() { - Some(s) => ( - s.is_dark, - s.language.strings(), - s.session_percent, - s.session_text.clone(), - s.weekly_percent, - s.weekly_text.clone(), - s.codex_session_percent, - s.codex_session_text.clone(), - s.codex_weekly_percent, - s.codex_weekly_text.clone(), - s.show_claude_code, - s.show_codex, - ), + Some(s) => { + let (session_pace, weekly_pace, codex_session_pace, codex_weekly_pace) = + pace_values_from_state(s); + ( + s.is_dark, + s.language.strings(), + s.session_percent, + s.session_text.clone(), + s.weekly_percent, + s.weekly_text.clone(), + s.codex_session_percent, + s.codex_session_text.clone(), + s.codex_weekly_percent, + s.codex_weekly_text.clone(), + s.show_claude_code, + s.show_codex, + session_pace, + weekly_pace, + codex_session_pace, + codex_weekly_pace, + s.pace_indicator_solid, + ) + } None => return, } }; @@ -2651,6 +2826,11 @@ fn paint(hdc: HDC, hwnd: HWND) { show_claude_code, show_codex, &codex_accent, + session_pace_pct, + weekly_pace_pct, + codex_session_pace_pct, + codex_weekly_pace_pct, + pace_solid, ); let _ = BitBlt(hdc, 0, 0, width, height, mem_dc, 0, 0, SRCCOPY); @@ -2677,6 +2857,9 @@ fn draw_row( claude_accent: &Color, codex_accent: &Color, track: &Color, + claude_pace_pct: Option, + codex_pace_pct: Option, + pace_solid: bool, ) { let seg_h = sc(SEGMENT_H); let active_models = active_model_count(show_claude_code, show_codex); @@ -2721,6 +2904,9 @@ fn draw_row( claude_accent, track, &claude_value_color, + claude_pace_pct, + is_dark, + pace_solid, ); model_x += model_usage_width(segment_count) + sc(MODEL_RIGHT_MARGIN); } @@ -2735,6 +2921,9 @@ fn draw_row( codex_accent, track, &codex_value_color, + codex_pace_pct, + is_dark, + pace_solid, ); } } @@ -2756,6 +2945,9 @@ fn draw_usage_bar( accent: &Color, track: &Color, text_color: &Color, + pace_pct: Option, + is_dark: bool, + pace_solid: bool, ) { let seg_w = sc(SEGMENT_W); let seg_h = sc(SEGMENT_H); @@ -2811,6 +3003,95 @@ fn draw_usage_bar( } } + // Pace indicator. Two styles, selected by pace_solid: + // solid — a filled band spanning the gap between actual usage and + // where pace says the user should be (red overage over the + // orange accent, green headroom over the grey track). + // tick — a thick vertical bar at the expected-pace position. + // Red means actual usage is ahead of pace, green means behind it. + if let Some(pace) = pace_pct { + let expected = pace.clamp(0.0, 100.0); + let actual = percent_clamped; + let _ = is_dark; + let pace_color = if expected < actual { + Color::from_hex("#E53935") // red — ahead of pace + } else { + Color::from_hex("#43A047") // green — behind pace + }; + + if pace_solid { + if (actual - expected).abs() > 0.01 { + let (band_lo, band_hi) = if actual > expected { + (expected, actual) + } else { + (actual, expected) + }; + let band_brush = CreateSolidBrush(COLORREF(pace_color.to_colorref())); + for i in 0..segment_count { + let seg_x = bar_x + i * (seg_w + seg_gap); + let seg_start = (i as f64) * segment_percent; + let seg_end = seg_start + segment_percent; + + let overlap_start = band_lo.max(seg_start); + let overlap_end = band_hi.min(seg_end); + if overlap_end <= overlap_start { + continue; + } + + let frac_start = (overlap_start - seg_start) / segment_percent; + let frac_end = (overlap_end - seg_start) / segment_percent; + let fill_left = seg_x + (seg_w as f64 * frac_start) as i32; + let fill_right = seg_x + (seg_w as f64 * frac_end) as i32; + if fill_right <= fill_left { + continue; + } + + let band_rect = RECT { + left: fill_left, + top: y, + right: fill_right, + bottom: y + seg_h, + }; + let rgn = CreateRoundRectRgn( + seg_x, + y, + seg_x + seg_w + 1, + y + seg_h + 1, + corner_r * 2, + corner_r * 2, + ); + let _ = SelectClipRgn(hdc, rgn); + FillRect(hdc, &band_rect, band_brush); + let _ = SelectClipRgn(hdc, HRGN::default()); + let _ = DeleteObject(rgn); + } + let _ = DeleteObject(band_brush); + } + } else { + let seg_idx = + ((expected / segment_percent).floor() as i32).clamp(0, segment_count - 1); + let frac_in_seg = + (expected - (seg_idx as f64) * segment_percent) / segment_percent; + let seg_x = bar_x + seg_idx * (seg_w + seg_gap); + let tick_center = seg_x + (seg_w as f64 * frac_in_seg).round() as i32; + + let bar_left = bar_x; + let bar_right = bar_x + segment_count * (seg_w + seg_gap) - seg_gap; + let tick_w = sc(3).max(2); + let tick_left = (tick_center - tick_w / 2).clamp(bar_left, bar_right - tick_w); + + let tick_rect = RECT { + left: tick_left, + top: y, + right: tick_left + tick_w, + bottom: y + seg_h, + }; + let tick_brush = CreateSolidBrush(COLORREF(pace_color.to_colorref())); + FillRect(hdc, &tick_rect, tick_brush); + let _ = DeleteObject(tick_brush); + } + } + let text_x = bar_x + segment_count * (seg_w + seg_gap) - seg_gap + sc(BAR_RIGHT_MARGIN); let mut text_wide: Vec = text.encode_utf16().collect(); let mut text_rect = RECT { From 85e8dcb6620176a8dc50ebf064238d39d9c791bc Mon Sep 17 00:00:00 2001 From: "Carlos Miguel C. Resurreccion" Date: Mon, 18 May 2026 11:11:09 +0800 Subject: [PATCH 3/5] refactor: make the pace indicator a single Off/Tick/Solid submenu "Show pace indicator" becomes a submenu whose Off, Tick, and Solid items form a radio group. The parent entry shows a check while a style is active; selecting Off clears it and turns the indicator off. This replaces the separate "Show pace indicator" checkbox and "Pace indicator style" submenu with a single settings entry. --- README.md | 2 +- src/localization/dutch.rs | 2 +- src/localization/english.rs | 2 +- src/localization/french.rs | 2 +- src/localization/german.rs | 2 +- src/localization/japanese.rs | 2 +- src/localization/korean.rs | 2 +- src/localization/mod.rs | 2 +- src/localization/spanish.rs | 2 +- src/localization/traditional_chinese.rs | 2 +- src/window.rs | 70 ++++++++++++++----------- 11 files changed, 48 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index a171ccb..d5d8864 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Once running, it will appear in your taskbar and as one or more tray icons in th - Right-click the taskbar widget or tray icon for refresh, displayed models, update frequency, Start with Windows, reset position, language, updates, and exit - Left-click the tray icon to toggle the taskbar widget on or off - Enable `Start with Windows` from the right-click menu if you want it to launch automatically when you sign in -- Enable `Show pace indicator` under right-click `Settings` to mark where your usage should be for the time elapsed, coloured red when you are ahead of pace and green when you are behind; choose a `Tick` or `Solid` style under `Pace indicator style` +- Under right-click `Settings` → `Show pace indicator`, pick a `Tick` or `Solid` style to mark where your usage should be for the time elapsed, coloured red when you are ahead of pace and green when you are behind; pick `Off` to turn it off ### Models diff --git a/src/localization/dutch.rs b/src/localization/dutch.rs index d684a17..63ee1c7 100644 --- a/src/localization/dutch.rs +++ b/src/localization/dutch.rs @@ -17,7 +17,7 @@ pub(super) const STRINGS: Strings = Strings { start_with_windows: "Opstarten met Windows", reset_position: "Positie herstellen", show_pace_indicator: "Tempo-indicator tonen", - pace_indicator_style: "Stijl van tempo-indicator", + pace_style_off: "Uit (standaard)", pace_style_tick: "Streepje", pace_style_solid: "Gevuld", language: "Taal", diff --git a/src/localization/english.rs b/src/localization/english.rs index f84b054..2beff27 100644 --- a/src/localization/english.rs +++ b/src/localization/english.rs @@ -17,7 +17,7 @@ pub(super) const STRINGS: Strings = Strings { start_with_windows: "Start with Windows", reset_position: "Reset Position", show_pace_indicator: "Show pace indicator", - pace_indicator_style: "Pace indicator style", + pace_style_off: "Off (default)", pace_style_tick: "Tick", pace_style_solid: "Solid", language: "Language", diff --git a/src/localization/french.rs b/src/localization/french.rs index a7fa382..35d2cb5 100644 --- a/src/localization/french.rs +++ b/src/localization/french.rs @@ -17,7 +17,7 @@ pub(super) const STRINGS: Strings = Strings { start_with_windows: "Démarrer avec Windows", reset_position: "Réinitialiser la position", show_pace_indicator: "Afficher l'indicateur de cadence", - pace_indicator_style: "Style de l'indicateur de cadence", + pace_style_off: "Désactivé (par défaut)", pace_style_tick: "Repère", pace_style_solid: "Plein", language: "Langue", diff --git a/src/localization/german.rs b/src/localization/german.rs index 580d31d..503aefe 100644 --- a/src/localization/german.rs +++ b/src/localization/german.rs @@ -17,7 +17,7 @@ pub(super) const STRINGS: Strings = Strings { start_with_windows: "Mit Windows starten", reset_position: "Position zurücksetzen", show_pace_indicator: "Tempo-Anzeige einblenden", - pace_indicator_style: "Stil der Tempo-Anzeige", + pace_style_off: "Aus (Standard)", pace_style_tick: "Strich", pace_style_solid: "Gefüllt", language: "Sprache", diff --git a/src/localization/japanese.rs b/src/localization/japanese.rs index 2e734f1..921035c 100644 --- a/src/localization/japanese.rs +++ b/src/localization/japanese.rs @@ -17,7 +17,7 @@ pub(super) const STRINGS: Strings = Strings { start_with_windows: "Windows と同時に開始", reset_position: "位置をリセット", show_pace_indicator: "ペース表示", - pace_indicator_style: "ペース表示スタイル", + pace_style_off: "オフ(デフォルト)", pace_style_tick: "目盛り", pace_style_solid: "塗りつぶし", language: "言語", diff --git a/src/localization/korean.rs b/src/localization/korean.rs index 0168ee5..5435204 100644 --- a/src/localization/korean.rs +++ b/src/localization/korean.rs @@ -17,7 +17,7 @@ pub(super) const STRINGS: Strings = Strings { start_with_windows: "Windows 시작 시 자동 실행", reset_position: "위치 초기화", show_pace_indicator: "사용 속도 표시", - pace_indicator_style: "사용 속도 표시 스타일", + pace_style_off: "끄기(기본값)", pace_style_tick: "눈금", pace_style_solid: "채우기", language: "언어", diff --git a/src/localization/mod.rs b/src/localization/mod.rs index 188ab08..13468a4 100644 --- a/src/localization/mod.rs +++ b/src/localization/mod.rs @@ -135,7 +135,7 @@ pub struct Strings { pub start_with_windows: &'static str, pub reset_position: &'static str, pub show_pace_indicator: &'static str, - pub pace_indicator_style: &'static str, + pub pace_style_off: &'static str, pub pace_style_tick: &'static str, pub pace_style_solid: &'static str, pub language: &'static str, diff --git a/src/localization/spanish.rs b/src/localization/spanish.rs index fa829ba..45d9d36 100644 --- a/src/localization/spanish.rs +++ b/src/localization/spanish.rs @@ -17,7 +17,7 @@ pub(super) const STRINGS: Strings = Strings { start_with_windows: "Iniciar con Windows", reset_position: "Restablecer posición", show_pace_indicator: "Mostrar indicador de ritmo", - pace_indicator_style: "Estilo del indicador de ritmo", + pace_style_off: "Desactivado (predeterminado)", pace_style_tick: "Marca", pace_style_solid: "Relleno", language: "Idioma", diff --git a/src/localization/traditional_chinese.rs b/src/localization/traditional_chinese.rs index 4f45fbd..5206287 100644 --- a/src/localization/traditional_chinese.rs +++ b/src/localization/traditional_chinese.rs @@ -17,7 +17,7 @@ pub(super) const STRINGS: Strings = Strings { start_with_windows: "開機時啟動", reset_position: "重置位置", show_pace_indicator: "顯示使用步調", - pace_indicator_style: "使用步調樣式", + pace_style_off: "關閉(預設)", pace_style_tick: "刻度線", pace_style_solid: "實心", language: "語言", diff --git a/src/window.rs b/src/window.rs index 476a7fe..4396840 100644 --- a/src/window.rs +++ b/src/window.rs @@ -123,7 +123,7 @@ const IDM_LANG_KOREAN: u16 = 47; const IDM_LANG_TRADITIONAL_CHINESE: u16 = 48; const IDM_MODEL_CLAUDE_CODE: u16 = 60; const IDM_MODEL_CODEX: u16 = 61; -const IDM_SHOW_PACE_INDICATOR: u16 = 71; +const IDM_PACE_STYLE_OFF: u16 = 71; const IDM_PACE_STYLE_TICK: u16 = 72; const IDM_PACE_STYLE_SOLID: u16 = 73; @@ -2289,21 +2289,16 @@ unsafe extern "system" fn wnd_proc( IDM_START_WITH_WINDOWS => { set_startup_enabled(!is_startup_enabled()); } - IDM_SHOW_PACE_INDICATOR => { + IDM_PACE_STYLE_OFF | IDM_PACE_STYLE_TICK | IDM_PACE_STYLE_SOLID => { { let mut state = lock_state(); if let Some(s) = state.as_mut() { - s.show_pace_indicator = !s.show_pace_indicator; - } - } - save_state_settings(); - render_layered(); - } - IDM_PACE_STYLE_TICK | IDM_PACE_STYLE_SOLID => { - { - let mut state = lock_state(); - if let Some(s) = state.as_mut() { - s.pace_indicator_solid = id == IDM_PACE_STYLE_SOLID; + if id == IDM_PACE_STYLE_OFF { + s.show_pace_indicator = false; + } else { + s.show_pace_indicator = true; + s.pace_indicator_solid = id == IDM_PACE_STYLE_SOLID; + } } } save_state_settings(); @@ -2571,50 +2566,61 @@ fn show_context_menu(hwnd: HWND) { PCWSTR::from_raw(reset_pos_str.as_ptr()), ); - let pace_str = native_interop::wide_str(strings.show_pace_indicator); - let pace_flags = if show_pace_indicator { - MF_CHECKED - } else { + // "Show pace indicator" is a submenu whose Off/Tick/Solid items form a + // radio group. The parent entry shows a check while a style is active; + // selecting Off clears that check and turns the indicator off. + let pace_menu = CreatePopupMenu().unwrap(); + + let pace_off_str = native_interop::wide_str(strings.pace_style_off); + let pace_off_flags = if show_pace_indicator { MENU_ITEM_FLAGS(0) + } else { + MF_CHECKED }; let _ = AppendMenuW( - settings_menu, - pace_flags, - IDM_SHOW_PACE_INDICATOR as usize, - PCWSTR::from_raw(pace_str.as_ptr()), + pace_menu, + pace_off_flags, + IDM_PACE_STYLE_OFF as usize, + PCWSTR::from_raw(pace_off_str.as_ptr()), ); - let pace_style_menu = CreatePopupMenu().unwrap(); let pace_tick_str = native_interop::wide_str(strings.pace_style_tick); - let pace_tick_flags = if pace_indicator_solid { - MENU_ITEM_FLAGS(0) - } else { + let pace_tick_flags = if show_pace_indicator && !pace_indicator_solid { MF_CHECKED + } else { + MENU_ITEM_FLAGS(0) }; let _ = AppendMenuW( - pace_style_menu, + pace_menu, pace_tick_flags, IDM_PACE_STYLE_TICK as usize, PCWSTR::from_raw(pace_tick_str.as_ptr()), ); + let pace_solid_str = native_interop::wide_str(strings.pace_style_solid); - let pace_solid_flags = if pace_indicator_solid { + let pace_solid_flags = if show_pace_indicator && pace_indicator_solid { MF_CHECKED } else { MENU_ITEM_FLAGS(0) }; let _ = AppendMenuW( - pace_style_menu, + pace_menu, pace_solid_flags, IDM_PACE_STYLE_SOLID as usize, PCWSTR::from_raw(pace_solid_str.as_ptr()), ); - let pace_style_label = native_interop::wide_str(strings.pace_indicator_style); + + let pace_label = native_interop::wide_str(strings.show_pace_indicator); + let pace_parent_flags = if show_pace_indicator { + MF_POPUP | MF_CHECKED + } else { + MF_POPUP + }; let _ = AppendMenuW( settings_menu, - MF_POPUP, - pace_style_menu.0 as usize, - PCWSTR::from_raw(pace_style_label.as_ptr()), + pace_parent_flags, + pace_menu.0 as usize, + PCWSTR::from_raw(pace_label.as_ptr()), ); let language_menu = CreatePopupMenu().unwrap(); From 60a188f1180b3857e5643c8cbe4139fdd79328f6 Mon Sep 17 00:00:00 2001 From: "Carlos Miguel C. Resurreccion" Date: Mon, 18 May 2026 12:24:12 +0800 Subject: [PATCH 4/5] docs: add pace indicator screenshots --- .github/screenshots/pace-solid-green.png | Bin 0 -> 5265 bytes .github/screenshots/pace-solid-red.png | Bin 0 -> 5222 bytes .github/screenshots/pace-tick-green.png | Bin 0 -> 5234 bytes .github/screenshots/pace-tick-red.png | Bin 0 -> 5305 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/screenshots/pace-solid-green.png create mode 100644 .github/screenshots/pace-solid-red.png create mode 100644 .github/screenshots/pace-tick-green.png create mode 100644 .github/screenshots/pace-tick-red.png diff --git a/.github/screenshots/pace-solid-green.png b/.github/screenshots/pace-solid-green.png new file mode 100644 index 0000000000000000000000000000000000000000..ccb954d9e847aab37f6c648dabf40a1ea304c5b7 GIT binary patch literal 5265 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 g(J&vhMG*lISL5Frp7p6!{6ocn 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 literal 0 HcmV?d00001 diff --git a/.github/screenshots/pace-tick-green.png b/.github/screenshots/pace-tick-green.png new file mode 100644 index 0000000000000000000000000000000000000000..8acb3b207c012d78b0d4e2d3fa031a449c688525 GIT binary patch literal 5234 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 literal 0 HcmV?d00001 diff --git a/.github/screenshots/pace-tick-red.png b/.github/screenshots/pace-tick-red.png new file mode 100644 index 0000000000000000000000000000000000000000..9547716a7e984211fcbd1d282d27ea4db74e0ec3 GIT binary patch literal 5305 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 Mm)tH^Ir=~P9|3{oHUIzs literal 0 HcmV?d00001 From 810f46baec1a27d6371e4bf321eef68e38989b2b Mon Sep 17 00:00:00 2001 From: "Carlos Miguel C. Resurreccion" Date: Mon, 18 May 2026 12:33:19 +0800 Subject: [PATCH 5/5] docs: frame pace colours as headroom vs quota-exhaustion risk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reword the README line and the draw_usage_bar comment so green and red describe what they mean for the user — green is headroom to ramp up, red is a trajectory to exhaust the quota before it resets — rather than the bare "ahead of / behind pace". --- README.md | 2 +- src/window.rs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d5d8864..b2b73fd 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Once running, it will appear in your taskbar and as one or more tray icons in th - Right-click the taskbar widget or tray icon for refresh, displayed models, update frequency, Start with Windows, reset position, language, updates, and exit - Left-click the tray icon to toggle the taskbar widget on or off - Enable `Start with Windows` from the right-click menu if you want it to launch automatically when you sign in -- Under right-click `Settings` → `Show pace indicator`, pick a `Tick` or `Solid` style to mark where your usage should be for the time elapsed, coloured red when you are ahead of pace and green when you are behind; pick `Off` to turn it off +- Under right-click `Settings` → `Show pace indicator`, pick a `Tick` or `Solid` style to mark where your usage should be for the time elapsed: green means you have headroom to ramp up, red means you are on a trajectory to exceed your quota before it resets (stop work or pay for extra usage); pick `Off` to turn it off ### Models diff --git a/src/window.rs b/src/window.rs index 4396840..6e03812 100644 --- a/src/window.rs +++ b/src/window.rs @@ -3014,15 +3014,16 @@ fn draw_usage_bar( // where pace says the user should be (red overage over the // orange accent, green headroom over the grey track). // tick — a thick vertical bar at the expected-pace position. - // Red means actual usage is ahead of pace, green means behind it. + // Green means usage is behind pace — headroom to ramp up. Red means + // usage is ahead of pace — on a trajectory to exhaust the quota early. if let Some(pace) = pace_pct { let expected = pace.clamp(0.0, 100.0); let actual = percent_clamped; let _ = is_dark; let pace_color = if expected < actual { - Color::from_hex("#E53935") // red — ahead of pace + Color::from_hex("#E53935") // red — ahead of pace, may exhaust quota early } else { - Color::from_hex("#43A047") // green — behind pace + Color::from_hex("#43A047") // green — behind pace, headroom to ramp up }; if pace_solid {