mirror of
https://github.com/tiennm99/claude-code-usage-bubble.git
synced 2026-09-09 02:17:12 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
391ad0cba2 | ||
|
|
77325b1e00 | ||
|
|
c3d01f36d2 | ||
|
|
7bbf80e5f7 |
Generated
+1
-1
@@ -59,7 +59,7 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "claude-code-usage-bubble"
|
||||
version = "0.1.13"
|
||||
version = "0.1.15"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"embed-resource",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "claude-code-usage-bubble"
|
||||
version = "0.1.13"
|
||||
version = "0.1.15"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
description = "Floating bubble showing Claude Code and Codex usage on Windows"
|
||||
|
||||
+36
-9
@@ -979,8 +979,8 @@ fn compute_bubble_layout(size_logical: i32, dpi: u32, mem_dc: HDC) -> BubbleLayo
|
||||
let height_px = scale_to_dpi(bubble_height_logical(size_logical), dpi);
|
||||
let head_diameter = height_px;
|
||||
|
||||
let head_pad = scale_to_dpi(6, dpi);
|
||||
let ring_stroke_w = scale_to_dpi(3, dpi).max(2) as f32;
|
||||
let head_pad = scale_to_dpi(4, dpi);
|
||||
let ring_stroke_w = scale_to_dpi(3, dpi).clamp(2, 4) as f32;
|
||||
let ring_cx = (head_diameter as f32) / 2.0;
|
||||
let ring_cy = (height_px as f32) / 2.0;
|
||||
// Ring centerline: midway between outer and inner edge, then keep stroke
|
||||
@@ -988,13 +988,14 @@ fn compute_bubble_layout(size_logical: i32, dpi: u32, mem_dc: HDC) -> BubbleLayo
|
||||
let ring_outer = (head_diameter as f32) / 2.0 - (head_pad as f32);
|
||||
let ring_radius = ring_outer - ring_stroke_w / 2.0;
|
||||
|
||||
let big_font_px = (head_diameter * 35 / 100).max(scale_to_dpi(12, dpi));
|
||||
let small_font_px = ((big_font_px * 45) / 100).max(scale_to_dpi(8, dpi));
|
||||
let big_font_px = (head_diameter * 26 / 100).max(scale_to_dpi(11, dpi));
|
||||
let small_font_px = ((big_font_px * 55) / 100).max(scale_to_dpi(9, dpi));
|
||||
let main_font_px = small_font_px;
|
||||
|
||||
let head_label_h = small_font_px + scale_to_dpi(2, dpi);
|
||||
let head_pct_h = big_font_px + scale_to_dpi(2, dpi);
|
||||
let head_total_h = head_label_h + head_pct_h;
|
||||
let label_pct_gap = (big_font_px * 15 / 100).max(scale_to_dpi(2, dpi));
|
||||
let head_total_h = head_label_h + label_pct_gap + head_pct_h;
|
||||
let head_text_top = (height_px - head_total_h) / 2;
|
||||
let head_label_rect = RECT {
|
||||
left: scale_to_dpi(4, dpi),
|
||||
@@ -1004,7 +1005,7 @@ fn compute_bubble_layout(size_logical: i32, dpi: u32, mem_dc: HDC) -> BubbleLayo
|
||||
};
|
||||
let head_pct_rect = RECT {
|
||||
left: scale_to_dpi(4, dpi),
|
||||
top: head_text_top + head_label_h,
|
||||
top: head_text_top + head_label_h + label_pct_gap,
|
||||
right: head_diameter - scale_to_dpi(4, dpi),
|
||||
bottom: head_text_top + head_total_h,
|
||||
};
|
||||
@@ -1023,7 +1024,7 @@ fn compute_bubble_layout(size_logical: i32, dpi: u32, mem_dc: HDC) -> BubbleLayo
|
||||
let tail_bar_left = tail_label_right + pad;
|
||||
let tail_bar_right =
|
||||
(tail_countdown_left - pad).max(tail_bar_left + scale_to_dpi(20, dpi));
|
||||
let tail_bar_h = scale_to_dpi(6, dpi);
|
||||
let tail_bar_h = scale_to_dpi(5, dpi);
|
||||
let tail_bar_top = (height_px - tail_bar_h) / 2;
|
||||
|
||||
BubbleLayout {
|
||||
@@ -1225,6 +1226,19 @@ fn copy_pixmap_to_dib(pixmap: &Pixmap, dst: &mut [u32]) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-stamp the alpha byte of every DIB pixel from the source `Pixmap`. Used
|
||||
/// after GDI text rendering, which writes RGB but leaves the BI_RGB DIB's
|
||||
/// "reserved" alpha byte at zero — making glyph pixels appear transparent
|
||||
/// when `UpdateLayeredWindow` composites with `AC_SRC_ALPHA`.
|
||||
fn restore_alpha_from_pixmap(pixmap: &Pixmap, dst: &mut [u32]) {
|
||||
let src = pixmap.data();
|
||||
let pixel_count = (pixmap.width() * pixmap.height()) as usize;
|
||||
for i in 0..pixel_count {
|
||||
let a = src[i * 4 + 3] as u32;
|
||||
dst[i] = (dst[i] & 0x00FF_FFFF) | (a << 24);
|
||||
}
|
||||
}
|
||||
|
||||
fn measure_text_w(hdc: HDC, text: &str, font_height_px: i32) -> i32 {
|
||||
use windows::Win32::Foundation::SIZE;
|
||||
let font_name = wide_str("Segoe UI");
|
||||
@@ -1325,12 +1339,25 @@ fn render(hwnd: HWND) {
|
||||
|
||||
// Paint shape via tiny-skia (AA), then copy into the DIB. GDI text
|
||||
// overlays on top of the resulting bitmap.
|
||||
if let Some(pixmap) = paint_bubble_pixmap(&layout, &inputs) {
|
||||
copy_pixmap_to_dib(&pixmap, pixels);
|
||||
let pixmap_opt = paint_bubble_pixmap(&layout, &inputs);
|
||||
if let Some(ref pixmap) = pixmap_opt {
|
||||
copy_pixmap_to_dib(pixmap, pixels);
|
||||
} else {
|
||||
pixels.fill(0);
|
||||
}
|
||||
paint_bubble_text(mem_dc, &layout, &inputs);
|
||||
// GDI text writes RGB into the 32bpp BI_RGB DIB but does not preserve
|
||||
// the alpha byte (per the BITMAPINFOHEADER contract: byte 3 is
|
||||
// "reserved/0" for BI_RGB). UpdateLayeredWindow with AC_SRC_ALPHA then
|
||||
// reads those zeroed alpha bytes and paints the glyph pixels as fully
|
||||
// transparent — desktop bleeds through. Fix: re-stamp the alpha
|
||||
// channel from the tiny-skia Pixmap we still have in scope. This
|
||||
// preserves the AA alpha on the stadium's curved perimeter and forces
|
||||
// glyph pixels back to the opacity tiny-skia computed for that
|
||||
// location (255 in the interior, 0 outside).
|
||||
if let Some(ref pixmap) = pixmap_opt {
|
||||
restore_alpha_from_pixmap(pixmap, pixels);
|
||||
}
|
||||
|
||||
let mut wr = RECT::default();
|
||||
let _ = GetWindowRect(hwnd, &mut wr);
|
||||
|
||||
Reference in New Issue
Block a user