feat(ui): shareholder-grade redesign + live "Your Club" account panel
- New theme.rs design system: palette, embedded fonts, egui Visuals/Style, card()/status_pill() helpers. - Branded hero header (OF monogram), left nav rail, card-based dashboard, console-style Logs, themed Config tab, window/taskbar icon. - New account_monitor.rs: background AccountMonitor (mirrors HealthMonitor, 5s non-blocking poll) driving a live "Your Club" dashboard card (club name/abbr, manager, COINS hero number, level + XP bar, unopened packs, funds) with loading/offline/error states. - account_sync.rs/app.rs/config.rs/main.rs wired to the monitor + theme. All existing launch/health/preflight/service/config logic preserved.
This commit is contained in:
+91
-2
@@ -1,4 +1,5 @@
|
||||
mod account_sync;
|
||||
mod account_monitor;
|
||||
mod app;
|
||||
mod arm;
|
||||
mod config;
|
||||
@@ -10,13 +11,16 @@ mod logs;
|
||||
mod netcheck;
|
||||
mod preflight;
|
||||
mod setup;
|
||||
mod theme;
|
||||
|
||||
fn main() -> eframe::Result<()> {
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_title("OpenFUT Launcher")
|
||||
.with_inner_size([780.0, 560.0])
|
||||
.with_min_inner_size([600.0, 400.0]),
|
||||
.with_app_id("openfut-launcher")
|
||||
.with_icon(app_icon())
|
||||
.with_inner_size([1040.0, 720.0])
|
||||
.with_min_inner_size([880.0, 600.0]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -26,3 +30,88 @@ fn main() -> eframe::Result<()> {
|
||||
Box::new(|cc| Ok(Box::new(app::LauncherApp::new(cc)))),
|
||||
)
|
||||
}
|
||||
|
||||
/// The application / taskbar icon: the same "OF" monogram the header wordmark
|
||||
/// shows, drawn white on the signature accent tile. Generated in code (no PNG
|
||||
/// dependency) at 4x supersampling and box-downsampled to a crisp 64x64 RGBA —
|
||||
/// scales cleanly to the 32x32 the WM typically renders. Colours come from the
|
||||
/// theme palette so the icon never drifts from the in-app brand.
|
||||
fn app_icon() -> egui::IconData {
|
||||
const SIZE: usize = 64; // output edge
|
||||
const SS: usize = 4; // supersampling factor
|
||||
|
||||
let accent = theme::ACCENT;
|
||||
let fg = theme::ON_ACCENT;
|
||||
|
||||
// Rounded-square background: point inside the [0,SIZE]² square with corners
|
||||
// rounded to `round_r` (transparent outside, so the icon reads as a tile).
|
||||
let round_r = 13.0_f32;
|
||||
let inside_bg = |x: f32, y: f32| -> bool {
|
||||
let s = SIZE as f32;
|
||||
let cx = x.clamp(round_r, s - round_r);
|
||||
let cy = y.clamp(round_r, s - round_r);
|
||||
let (dx, dy) = (x - cx, y - cy);
|
||||
dx * dx + dy * dy <= round_r * round_r
|
||||
};
|
||||
|
||||
// "O" — an elliptical ring on the left.
|
||||
let inside_o = |x: f32, y: f32| -> bool {
|
||||
let (cx, cy) = (21.0_f32, 32.0_f32);
|
||||
let (dx, dy) = (x - cx, y - cy);
|
||||
let outer = (dx / 9.0).powi(2) + (dy / 14.0).powi(2) <= 1.0;
|
||||
let inner = (dx / 4.8).powi(2) + (dy / 9.5).powi(2) < 1.0;
|
||||
outer && !inner
|
||||
};
|
||||
|
||||
// "F" — a stem plus a top and middle bar on the right.
|
||||
let inside_f = |x: f32, y: f32| -> bool {
|
||||
let stem = (34.0..=39.0).contains(&x) && (18.0..=46.0).contains(&y);
|
||||
let top = (34.0..=52.0).contains(&x) && (18.0..=23.0).contains(&y);
|
||||
let mid = (34.0..=48.0).contains(&x) && (29.5..=34.0).contains(&y);
|
||||
stem || top || mid
|
||||
};
|
||||
|
||||
// Premultiplied-alpha accumulation per output pixel so antialiased edges
|
||||
// (both the rounded tile and the letters) never fringe dark.
|
||||
let mut rgba = vec![0u8; SIZE * SIZE * 4];
|
||||
for oy in 0..SIZE {
|
||||
for ox in 0..SIZE {
|
||||
let (mut ar, mut ag, mut ab, mut aa) = (0.0_f32, 0.0_f32, 0.0_f32, 0.0_f32);
|
||||
for sy in 0..SS {
|
||||
for sx in 0..SS {
|
||||
let x = ox as f32 + (sx as f32 + 0.5) / SS as f32;
|
||||
let y = oy as f32 + (sy as f32 + 0.5) / SS as f32;
|
||||
let (r, g, b, a) = if inside_o(x, y) || inside_f(x, y) {
|
||||
(fg.r(), fg.g(), fg.b(), 255u16)
|
||||
} else if inside_bg(x, y) {
|
||||
(accent.r(), accent.g(), accent.b(), 255u16)
|
||||
} else {
|
||||
(0, 0, 0, 0)
|
||||
};
|
||||
let af = a as f32 / 255.0;
|
||||
ar += r as f32 * af;
|
||||
ag += g as f32 * af;
|
||||
ab += b as f32 * af;
|
||||
aa += af;
|
||||
}
|
||||
}
|
||||
let samples = (SS * SS) as f32;
|
||||
let idx = (oy * SIZE + ox) * 4;
|
||||
let (r, g, b) = if aa > 0.0 {
|
||||
(ar / aa, ag / aa, ab / aa)
|
||||
} else {
|
||||
(0.0, 0.0, 0.0)
|
||||
};
|
||||
rgba[idx] = r.round() as u8;
|
||||
rgba[idx + 1] = g.round() as u8;
|
||||
rgba[idx + 2] = b.round() as u8;
|
||||
rgba[idx + 3] = (aa / samples * 255.0).round() as u8;
|
||||
}
|
||||
}
|
||||
|
||||
egui::IconData {
|
||||
rgba,
|
||||
width: SIZE as u32,
|
||||
height: SIZE as u32,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user