mirror of
https://github.com/tiennm99/claude-code-usage-bubble.git
synced 2026-09-09 16:17:42 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2e48cc119 | ||
|
|
bee9f3cfa0 | ||
|
|
860eb62d1b | ||
|
|
c424b17bd7 |
Generated
+1
-1
@@ -59,7 +59,7 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "claude-code-usage-bubble"
|
name = "claude-code-usage-bubble"
|
||||||
version = "0.3.5"
|
version = "0.3.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs",
|
"dirs",
|
||||||
"embed-resource",
|
"embed-resource",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "claude-code-usage-bubble"
|
name = "claude-code-usage-bubble"
|
||||||
version = "0.3.5"
|
version = "0.3.7"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
description = "Floating bubble showing Claude Code and Codex usage on Windows"
|
description = "Floating bubble showing Claude Code and Codex usage on Windows"
|
||||||
|
|||||||
+38
-5
@@ -1288,9 +1288,12 @@ fn paint_bubble_pixmap(layout: &BubbleLayout, inputs: &PaintInputs) -> Option<Pi
|
|||||||
let mut stroke = Stroke::default();
|
let mut stroke = Stroke::default();
|
||||||
stroke.width = layout.time_ring_stroke_w;
|
stroke.width = layout.time_ring_stroke_w;
|
||||||
stroke.line_cap = LineCap::Round;
|
stroke.line_cap = LineCap::Round;
|
||||||
if let Some(path) =
|
if let Some(path) = build_remaining_arc(
|
||||||
build_arc(layout.ring_cx, layout.ring_cy, layout.time_ring_radius, frac)
|
layout.ring_cx,
|
||||||
{
|
layout.ring_cy,
|
||||||
|
layout.time_ring_radius,
|
||||||
|
frac,
|
||||||
|
) {
|
||||||
pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), None);
|
pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1338,9 +1341,11 @@ fn paint_bubble_pixmap(layout: &BubbleLayout, inputs: &PaintInputs) -> Option<Pi
|
|||||||
inputs.weekly_resets_at,
|
inputs.weekly_resets_at,
|
||||||
window_duration_secs(inputs.model, UsageWindowKind::Secondary),
|
window_duration_secs(inputs.model, UsageWindowKind::Secondary),
|
||||||
) {
|
) {
|
||||||
let fill_w = bar_w * frac;
|
let fill_w = (bar_w * frac).min(bar_w);
|
||||||
if fill_w > 0.0 {
|
if fill_w > 0.0 {
|
||||||
paint_pill(&mut pixmap, bar_x, bar_y, fill_w.min(bar_w), bar_h, cap, time_fill);
|
// Anchor on the right edge so the bar shrinks toward the right as time passes.
|
||||||
|
let fill_x = bar_x + bar_w - fill_w;
|
||||||
|
paint_pill(&mut pixmap, fill_x, bar_y, fill_w, bar_h, cap, time_fill);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1391,6 +1396,34 @@ fn build_arc(cx: f32, cy: f32, radius: f32, sweep_fraction: f32) -> Option<tiny_
|
|||||||
pb.finish()
|
pb.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clockwise arc that ENDS at 12 o'clock; the consumed wedge grows clockwise
|
||||||
|
/// from 12 as `remaining_fraction` shrinks, mirroring a clock-hand countdown.
|
||||||
|
fn build_remaining_arc(
|
||||||
|
cx: f32,
|
||||||
|
cy: f32,
|
||||||
|
radius: f32,
|
||||||
|
remaining_fraction: f32,
|
||||||
|
) -> Option<tiny_skia::Path> {
|
||||||
|
let frac = remaining_fraction.clamp(0.0, 1.0);
|
||||||
|
let segments = ((frac * 64.0).ceil() as usize).max(1);
|
||||||
|
let mut pb = PathBuilder::new();
|
||||||
|
let twelve: f32 = -std::f32::consts::FRAC_PI_2;
|
||||||
|
let total = frac * std::f32::consts::TAU;
|
||||||
|
let start_angle = twelve + (std::f32::consts::TAU - total);
|
||||||
|
for i in 0..=segments {
|
||||||
|
let t = i as f32 / segments as f32;
|
||||||
|
let a = start_angle + t * total;
|
||||||
|
let x = cx + a.cos() * radius;
|
||||||
|
let y = cy + a.sin() * radius;
|
||||||
|
if i == 0 {
|
||||||
|
pb.move_to(x, y);
|
||||||
|
} else {
|
||||||
|
pb.line_to(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pb.finish()
|
||||||
|
}
|
||||||
|
|
||||||
/// Copy a premultiplied-RGBA `Pixmap` into the 32bpp BI_RGB DIB the bubble
|
/// Copy a premultiplied-RGBA `Pixmap` into the 32bpp BI_RGB DIB the bubble
|
||||||
/// uses for `UpdateLayeredWindow`. The DIB stores BGRA bytes (little-endian
|
/// uses for `UpdateLayeredWindow`. The DIB stores BGRA bytes (little-endian
|
||||||
/// `0xAARRGGBB` when read as u32); tiny-skia's premultiplied alpha is exactly
|
/// `0xAARRGGBB` when read as u32); tiny-skia's premultiplied alpha is exactly
|
||||||
|
|||||||
+15
-8
@@ -5,20 +5,27 @@
|
|||||||
use crate::os::Rgb as Color;
|
use crate::os::Rgb as Color;
|
||||||
use crate::usage::ProviderId;
|
use crate::usage::ProviderId;
|
||||||
|
|
||||||
/// Per-provider identity color. Claude = warm orange `#D97757`. Codex =
|
/// Per-provider identity color. Claude = warm orange `#D97757`. Codex tracks
|
||||||
/// OpenAI brand teal `#10A37F`, used consistently across dark and light
|
/// the OpenAI Codex monochrome palette — near-white `#E5E5E5` on dark themes,
|
||||||
/// themes so the badge/bubble/panel never disagree on Codex identity.
|
/// near-black `#1A1A1A` on light — so the accent stays readable against both
|
||||||
pub fn accent_color_for(model: ProviderId, _is_dark: bool) -> Color {
|
/// the dark bubble surface and the `#F3F3F3` light background.
|
||||||
|
pub fn accent_color_for(model: ProviderId, is_dark: bool) -> Color {
|
||||||
match model {
|
match model {
|
||||||
ProviderId::Claude => Color::from_hex("#D97757"),
|
ProviderId::Claude => Color::from_hex("#D97757"),
|
||||||
ProviderId::ChatGpt => Color::from_hex("#10A37F"),
|
ProviderId::ChatGpt => {
|
||||||
|
if is_dark {
|
||||||
|
Color::from_hex("#E5E5E5")
|
||||||
|
} else {
|
||||||
|
Color::from_hex("#1A1A1A")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Discrete 4-band fill color. The "safe" band uses the provider's identity
|
/// Discrete 4-band fill color. The "safe" band uses the provider's identity
|
||||||
/// color so Codex bars stay white-on-dark while Claude bars stay orange; the
|
/// color so Codex bars render monochrome (light-on-dark / dark-on-light) while
|
||||||
/// warning bands are theme-aware so light-mode amber stays readable against
|
/// Claude bars stay orange; the warning bands are theme-aware so light-mode
|
||||||
/// the `#F3F3F3` background.
|
/// amber stays readable against the `#F3F3F3` background.
|
||||||
///
|
///
|
||||||
/// - <60% → provider accent
|
/// - <60% → provider accent
|
||||||
/// - 60–80% → amber (dark `#E0A040`, light `#B47A20` for WCAG AA contrast)
|
/// - 60–80% → amber (dark `#E0A040`, light `#B47A20` for WCAG AA contrast)
|
||||||
|
|||||||
Reference in New Issue
Block a user