feat: redesign bubble as two-bar rounded rect, finish port wiring

Bubble shape changes from circle to rounded rectangle showing two stacked
horizontal bars — top: session (5h), bottom: weekly (7d) — each followed
by a right-aligned "X% · Yh Zm" string (percent + countdown).

Bubble surface:
- BubbleConfig/BubbleState carry session+weekly percents and texts (mirrors
  PanelData); update_percentage renamed to update_data
- Aspect ratio fixed at 3:1; size_logical is interpreted as width with
  height derived. Clamp is 120..360 (was 32..128 square)
- Hit-testing uses a rounded-rect predicate (point_in_rounded_rect) shared
  with the alpha mask so paint and click area can't drift
- New rgb_to_dib helper for direct DIB writes — BI_RGB 32bpp stores B,G,R,X
  in memory which is the opposite of COLORREF. The previous code wrote
  COLORREF-packed u32 straight into DIB pixels; invisible while every color
  was gray, but the new orange/red bar fills would have rendered blue
- bar_h capped at h/4 (range 6..18) so the text font derived from it stays
  small enough that "100% · 23h" fits in right_text_w (= 6×bar_h, min 56);
  the first iteration had a 19-px font in a 60-px column and ellipsized
  away the countdown
- Initial session_text/weekly_text seeded with "…" so the bubble has
  visible feedback during the first poll instead of two empty grey tracks

Compile + cleanup needed to make the port build at all:
- Color::from_hex added back as an infallible wrapper around parse_hex
  (15 call sites in bubble.rs/panel.rs assumed the old infallible API)
- Color::to_colorref → into_colorref at 5 call sites
- GetModuleFileNameW added to the LibraryLoader import in bubble.rs
- usage::Error gains Creds(#[from] creds::Error) so `?` works in the
  Anthropic and ChatGPT providers
- FlattenBoxed::flatten renamed to flatten_box — the std Option::flatten
  was shadowing it and yielding Option<Box<T>> instead of Option<T>
- PanelState marked unsafe Send (HWND has *mut c_void; state is only
  touched from the UI thread, Mutex is for OnceLock satisfaction)
- Crate-level #![allow(dead_code)] for in-progress port API surface
  (creds, usage, update, os::dpi); unused pub-use re-exports removed

App wiring:
- propagate_to_ui now feeds both windows + their formatted texts into
  update_data (was a single percent)
This commit is contained in:
tiennm99 committed 2026-05-16 11:11:34 +07:00
1 parent aa6217d2cf
commit ee4e1c9b26
10 files changed
+1839 -149

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
use serde::Deserialize;
use crate::creds::{CredentialSource, Locator};
use crate::creds::Locator;
use crate::net::Client;
use crate::usage::{headers, Error, ProviderId, UsageProvider, UsageWindows, Window};
+9 -7
View File
@@ -7,7 +7,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
use serde::Deserialize;
use crate::creds::{CredentialSource, Locator};
use crate::creds::Locator;
use crate::net::Client;
use crate::usage::{Error, ProviderId, UsageProvider, UsageWindows, Window};
@@ -67,16 +67,16 @@ impl UsageProvider for ChatGptProvider {
}
fn envelope_to_windows(envelope: Envelope) -> Option<UsageWindows> {
let rl = envelope.rate_limit.flatten()?;
let rl = envelope.rate_limit.flatten_box()?;
Some(UsageWindows {
primary: rl
.primary_window
.flatten()
.flatten_box()
.map(window_from)
.unwrap_or_default(),
secondary: rl
.secondary_window
.flatten()
.flatten_box()
.map(window_from)
.unwrap_or_default(),
})
@@ -114,12 +114,14 @@ struct ApiWindow {
reset_at: i64,
}
// Helpers used to make `Option<Option<Box<…>>>` flatten cleanly.
// Helpers used to make `Option<Option<Box<…>>>` flatten cleanly. We can't
// reuse the std `Option::flatten` name — the inherent method (which returns
// `Option<Box<T>>`) would shadow this trait method.
trait FlattenBoxed<T> {
fn flatten(self) -> Option<T>;
fn flatten_box(self) -> Option<T>;
}
impl<T> FlattenBoxed<T> for Option<Option<Box<T>>> {
fn flatten(self) -> Option<T> {
fn flatten_box(self) -> Option<T> {
self.and_then(|inner| inner.map(|b| *b))
}
}
+3 -1
View File
@@ -12,7 +12,7 @@ pub mod registry;
pub mod types;
pub use registry::Registry;
pub use types::{ProviderId, ProviderSnapshot, UsageWindows, Window};
pub use types::{ProviderId, UsageWindows, Window};
#[derive(Debug, thiserror::Error)]
pub enum Error {
@@ -24,6 +24,8 @@ pub enum Error {
TokenExpired,
#[error("network: {0}")]
Network(#[from] crate::net::Error),
#[error("credentials: {0}")]
Creds(#[from] crate::creds::Error),
#[error("unexpected response shape: {0}")]
BadResponse(String),
}