mirror of
https://github.com/CodeZeno/Claude-Code-Usage-Monitor.git
synced 2026-06-20 10:19:27 +08:00
Better peak indicator shape
This commit is contained in:
parent
5b7c6a6f68
commit
2f3c747e4a
@ -50,4 +50,7 @@ pub(super) const STRINGS: Strings = Strings {
|
||||
peak_err_tz_format: "UTC offset must be a number of hours (e.g. 10 for UTC+10, -5 for UTC-5).",
|
||||
peak_err_tz_range: "UTC offset must be between -12 and +14 hours.",
|
||||
peak_err_start_equals_end: "Start and end times must be different.",
|
||||
peak_status_ok: "OK",
|
||||
peak_status_warn: "WARN",
|
||||
peak_status_peak: "PEAK",
|
||||
};
|
||||
|
||||
@ -49,4 +49,7 @@ pub(super) const STRINGS: Strings = Strings {
|
||||
peak_err_tz_format: "Le décalage UTC doit être un nombre d'heures (ex. 10 pour UTC+10, -5 pour UTC-5).",
|
||||
peak_err_tz_range: "Le décalage UTC doit être entre -12 et +14 heures.",
|
||||
peak_err_start_equals_end: "Les heures de début et de fin doivent être différentes.",
|
||||
peak_status_ok: "OK",
|
||||
peak_status_warn: "ALERTE",
|
||||
peak_status_peak: "PIC",
|
||||
};
|
||||
|
||||
@ -49,4 +49,7 @@ pub(super) const STRINGS: Strings = Strings {
|
||||
peak_err_tz_format: "UTC-Abweichung muss eine Anzahl von Stunden sein (z.B. 10 für UTC+10, -5 für UTC-5).",
|
||||
peak_err_tz_range: "UTC-Abweichung muss zwischen -12 und +14 Stunden liegen.",
|
||||
peak_err_start_equals_end: "Start- und Endzeit müssen unterschiedlich sein.",
|
||||
peak_status_ok: "OK",
|
||||
peak_status_warn: "WARN",
|
||||
peak_status_peak: "PEAK",
|
||||
};
|
||||
|
||||
@ -50,4 +50,7 @@ pub(super) const STRINGS: Strings = Strings {
|
||||
peak_err_tz_format: "UTCオフセットは時間数で入力してください(例: UTC+10は10、UTC-5は-5)。",
|
||||
peak_err_tz_range: "UTCオフセットは-12から+14時間の範囲で入力してください。",
|
||||
peak_err_start_equals_end: "開始時刻と終了時刻は異なる必要があります。",
|
||||
peak_status_ok: "OK",
|
||||
peak_status_warn: "注意",
|
||||
peak_status_peak: "ピーク",
|
||||
};
|
||||
|
||||
@ -50,4 +50,7 @@ pub(super) const STRINGS: Strings = Strings {
|
||||
peak_err_tz_format: "UTC 오프셋은 시간 단위의 숫자여야 합니다 (예: UTC+10은 10, UTC-5는 -5).",
|
||||
peak_err_tz_range: "UTC 오프셋은 -12에서 +14 시간 사이여야 합니다.",
|
||||
peak_err_start_equals_end: "시작 시간과 종료 시간은 달라야 합니다.",
|
||||
peak_status_ok: "OK",
|
||||
peak_status_warn: "주의",
|
||||
peak_status_peak: "피크",
|
||||
};
|
||||
|
||||
@ -141,6 +141,9 @@ pub struct Strings {
|
||||
pub peak_err_tz_format: &'static str,
|
||||
pub peak_err_tz_range: &'static str,
|
||||
pub peak_err_start_equals_end: &'static str,
|
||||
pub peak_status_ok: &'static str,
|
||||
pub peak_status_warn: &'static str,
|
||||
pub peak_status_peak: &'static str,
|
||||
}
|
||||
|
||||
pub fn resolve_language(language_override: Option<LanguageId>) -> LanguageId {
|
||||
|
||||
@ -49,4 +49,7 @@ pub(super) const STRINGS: Strings = Strings {
|
||||
peak_err_tz_format: "El desfase UTC debe ser un número de horas (ej. 10 para UTC+10, -5 para UTC-5).",
|
||||
peak_err_tz_range: "El desfase UTC debe estar entre -12 y +14 horas.",
|
||||
peak_err_start_equals_end: "Las horas de inicio y fin deben ser diferentes.",
|
||||
peak_status_ok: "OK",
|
||||
peak_status_warn: "AVISO",
|
||||
peak_status_peak: "PICO",
|
||||
};
|
||||
|
||||
@ -9,6 +9,7 @@ use std::sync::Mutex;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::Win32::Foundation::{HINSTANCE, HWND, LPARAM, WPARAM};
|
||||
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
|
||||
use windows::Win32::System::SystemInformation::{GetLocalTime, GetSystemTime};
|
||||
use windows::Win32::UI::WindowsAndMessaging::*;
|
||||
|
||||
use crate::localization::Strings;
|
||||
@ -216,6 +217,34 @@ fn get_text(hdlg: HWND, id: i32) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// ── System timezone helpers ─────────────────────────────────────────────────────
|
||||
|
||||
/// Returns a string like "UTC+10", "UTC-5:30", or "UTC" for the current local offset.
|
||||
fn local_tz_offset_str() -> String {
|
||||
let (utc, local) = unsafe { (GetSystemTime(), GetLocalTime()) };
|
||||
let mut offset_min = (local.wHour as i32 - utc.wHour as i32) * 60
|
||||
+ (local.wMinute as i32 - utc.wMinute as i32);
|
||||
// Clamp across day boundaries (offsets are always in [-12h, +14h])
|
||||
if offset_min > 14 * 60 {
|
||||
offset_min -= 24 * 60;
|
||||
} else if offset_min < -12 * 60 {
|
||||
offset_min += 24 * 60;
|
||||
}
|
||||
if offset_min == 0 {
|
||||
"UTC".to_string()
|
||||
} else {
|
||||
let sign = if offset_min > 0 { "+" } else { "-" };
|
||||
let abs = offset_min.unsigned_abs();
|
||||
let h = abs / 60;
|
||||
let m = abs % 60;
|
||||
if m == 0 {
|
||||
format!("UTC{}{}", sign, h)
|
||||
} else {
|
||||
format!("UTC{}{}:{:02}", sign, h, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── In-memory DLGTEMPLATE builder ─────────────────────────────────────────────
|
||||
|
||||
struct Buf(Vec<u8>);
|
||||
@ -352,7 +381,8 @@ fn build_template(strings: &Strings) -> Vec<u8> {
|
||||
strings.peak_tz_label
|
||||
);
|
||||
item!(edt, 93, 44, 55, 12, IDC_TZ as u16, ATOM_EDIT, "");
|
||||
// Hint text
|
||||
// Hint text (with resolved system timezone appended)
|
||||
let hint_text = format!("{} [{}]", strings.peak_tz_hint, local_tz_offset_str());
|
||||
item!(
|
||||
lbl,
|
||||
7,
|
||||
@ -361,7 +391,7 @@ fn build_template(strings: &Strings) -> Vec<u8> {
|
||||
8,
|
||||
0xFFFF_u16,
|
||||
ATOM_STATIC,
|
||||
strings.peak_tz_hint
|
||||
&hint_text
|
||||
);
|
||||
// Buttons
|
||||
item!(
|
||||
|
||||
@ -133,10 +133,10 @@ const IDM_PEAK_HOURS: u16 = 50;
|
||||
|
||||
const DIVIDER_HIT_ZONE: i32 = 13; // LEFT_DIVIDER_W + DIVIDER_RIGHT_MARGIN
|
||||
|
||||
/// Diameter of the peak-hours status circle (px at 96 DPI).
|
||||
/// Equals 2 × SEGMENT_H + row gap = 2 × 13 + 10 = 36, spanning both meter rows.
|
||||
/// Width of the peak-status indicator column (px at 96 DPI).
|
||||
/// Contains a colored rectangle on row 1 and a short text label on row 2.
|
||||
const PEAK_ICON_W: i32 = 36;
|
||||
/// Gap between the right edge of the circle and the first meter label.
|
||||
/// Gap between the right edge of the indicator and the first meter label.
|
||||
const PEAK_ICON_GAP: i32 = 6;
|
||||
|
||||
const WM_DPICHANGED_MSG: u32 = 0x02E0;
|
||||
@ -1362,7 +1362,7 @@ fn paint_content(
|
||||
let row1_y = row2_y - sc(10) - sc(SEGMENT_H);
|
||||
|
||||
if peak_status != PeakStatus::NoPeakHours {
|
||||
draw_peak_circle(hdc, icon_x, height, peak_status, is_dark);
|
||||
draw_peak_indicator(hdc, icon_x, height, peak_status, is_dark, text_color, strings);
|
||||
}
|
||||
|
||||
let _ = SetBkMode(hdc, TRANSPARENT);
|
||||
@ -2507,15 +2507,23 @@ fn draw_rounded_rect(hdc: HDC, rect: &RECT, color: &Color, radius: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw the peak-hours status circle.
|
||||
/// Draw the peak-hours status indicator as two rows matching the meter layout.
|
||||
///
|
||||
/// The circle is a filled GDI ellipse whose diameter spans the full height of
|
||||
/// both meter rows (= PEAK_ICON_W = 36 px at 96 DPI), centred vertically in
|
||||
/// the widget. Colours adapt to the current theme:
|
||||
/// Row 1: a colored rounded rectangle (same height as a bar segment).
|
||||
/// Row 2: a short text label ("OK", "WARN", "PEAK" or localised equivalent).
|
||||
/// Colours adapt to the current theme:
|
||||
/// - Outside (>60 min until peak): green
|
||||
/// - Warning (<60 min until peak): amber
|
||||
/// - Inside (within peak hours): red
|
||||
fn draw_peak_circle(hdc: HDC, x: i32, height: i32, status: PeakStatus, is_dark: bool) {
|
||||
fn draw_peak_indicator(
|
||||
hdc: HDC,
|
||||
x: i32,
|
||||
height: i32,
|
||||
status: PeakStatus,
|
||||
is_dark: bool,
|
||||
text_color: &Color,
|
||||
strings: Strings,
|
||||
) {
|
||||
let color = match (status, is_dark) {
|
||||
(PeakStatus::Outside, true) => Color::from_hex("#22C55E"),
|
||||
(PeakStatus::Outside, false) => Color::from_hex("#16A34A"),
|
||||
@ -2526,23 +2534,66 @@ fn draw_peak_circle(hdc: HDC, x: i32, height: i32, status: PeakStatus, is_dark:
|
||||
(PeakStatus::NoPeakHours, _) => return,
|
||||
};
|
||||
|
||||
// Align circle top/bottom with the meter rows.
|
||||
let row2_y = height - sc(5) - sc(SEGMENT_H);
|
||||
let row1_y = row2_y - sc(10) - sc(SEGMENT_H);
|
||||
let top = row1_y;
|
||||
let bottom = row2_y + sc(SEGMENT_H);
|
||||
let right = x + sc(PEAK_ICON_W);
|
||||
let label = match status {
|
||||
PeakStatus::Outside => strings.peak_status_ok,
|
||||
PeakStatus::Warning => strings.peak_status_warn,
|
||||
PeakStatus::Inside => strings.peak_status_peak,
|
||||
PeakStatus::NoPeakHours => return,
|
||||
};
|
||||
|
||||
let seg_h = sc(SEGMENT_H);
|
||||
let row2_y = height - sc(5) - seg_h;
|
||||
let row1_y = row2_y - sc(10) - seg_h;
|
||||
let w = sc(PEAK_ICON_W);
|
||||
|
||||
// Row 1: colored rectangle
|
||||
let rect = RECT {
|
||||
left: x,
|
||||
top: row1_y,
|
||||
right: x + w,
|
||||
bottom: row1_y + seg_h,
|
||||
};
|
||||
draw_rounded_rect(hdc, &rect, &color, sc(CORNER_RADIUS));
|
||||
|
||||
// Row 2: text label
|
||||
unsafe {
|
||||
let brush = CreateSolidBrush(COLORREF(color.to_colorref()));
|
||||
let old_brush = SelectObject(hdc, brush);
|
||||
let null_pen = GetStockObject(NULL_PEN);
|
||||
let old_pen = SelectObject(hdc, null_pen);
|
||||
let _ = SetBkMode(hdc, TRANSPARENT);
|
||||
let _ = SetTextColor(hdc, COLORREF(text_color.to_colorref()));
|
||||
|
||||
let _ = Ellipse(hdc, x, top, right, bottom);
|
||||
let font_name = native_interop::wide_str("Segoe UI");
|
||||
let font = CreateFontW(
|
||||
sc(-10),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
FW_MEDIUM.0 as i32,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
DEFAULT_CHARSET.0 as u32,
|
||||
OUT_TT_PRECIS.0 as u32,
|
||||
CLIP_DEFAULT_PRECIS.0 as u32,
|
||||
CLEARTYPE_QUALITY.0 as u32,
|
||||
(DEFAULT_PITCH.0 | FF_DONTCARE.0) as u32,
|
||||
PCWSTR::from_raw(font_name.as_ptr()),
|
||||
);
|
||||
let old_font = SelectObject(hdc, font);
|
||||
|
||||
SelectObject(hdc, old_pen);
|
||||
SelectObject(hdc, old_brush);
|
||||
let _ = DeleteObject(brush);
|
||||
let mut label_wide: Vec<u16> = label.encode_utf16().collect();
|
||||
let mut label_rect = RECT {
|
||||
left: x,
|
||||
top: row2_y,
|
||||
right: x + w,
|
||||
bottom: row2_y + seg_h,
|
||||
};
|
||||
let _ = DrawTextW(
|
||||
hdc,
|
||||
&mut label_wide,
|
||||
&mut label_rect,
|
||||
DT_CENTER | DT_VCENTER | DT_SINGLELINE,
|
||||
);
|
||||
|
||||
SelectObject(hdc, old_font);
|
||||
let _ = DeleteObject(font);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user