use nox_gfx::canvas::{Canvas, Cap, Paint, Stroke}; use nox_gfx::color::{hex, hexa, rgba, Color}; use nox_gfx::geom::{pt, Rect, Transform, IDENTITY}; use nox_gfx::path::Path; const BG0: Color = hex(0x0a0a10); const BG1: Color = hex(0x141420); const CARD: Color = hex(0x1a1a26); const TEXT: Color = hex(0xe6e6f0); const MUTED: Color = hex(0x6f6f88); const ACCENT: Color = hex(0x7c5cff); const CYAN: Color = hex(0x35d0ff); fn bar(c: &mut Canvas, x: f32, y: f32, w: f32, h: f32, col: Color) { c.fill_rrect(Rect::xywh(x, y, w, h), h * 0.5, &Paint::Solid(col)); } fn draw(c: &mut Canvas) { let (w, h) = (c.w, c.h); let mut frame = Path::new(); frame.rrect(Rect::xywh(0.0, 0.0, w as f32, h as f32), 14.0); c.fill(&frame, &IDENTITY, &Paint::vgrad(c.full(), BG1, BG0)); c.fill_circle(pt(210.0, 90.0), 320.0, &Paint::glow(pt(210.0, 90.0), 320.0, ACCENT.alpha(0.30), ACCENT.alpha(0.0))); c.fill_circle(pt(960.0, 620.0), 300.0, &Paint::glow(pt(960.0, 620.0), 300.0, CYAN.alpha(0.20), CYAN.alpha(0.0))); let rail = Rect::xywh(0.0, 0.0, 224.0, h as f32); let glass = c.blurred_layer(rail, 18.0); let mut railp = Path::new(); railp.rrect_corners(rail, 14.0, 0.0, 0.0, 14.0); c.fill_layer(&railp, &IDENTITY, &glass, rgba(16, 16, 26, 190)); c.fill( &{ let mut p = Path::new(); p.rect(Rect::xywh(223.0, 0.0, 1.0, h as f32)); p }, &IDENTITY, &Paint::Solid(rgba(255, 255, 255, 18)), ); let logo = pt(46.0, 46.0); c.fill_circle(logo, 15.0, &Paint::Linear { p0: pt(logo.x - 15.0, logo.y - 15.0), p1: pt(logo.x + 15.0, logo.y + 15.0), stops: vec![(0.0, ACCENT), (1.0, CYAN)], }); let mut hole = Path::new(); hole.circle(logo, 6.0); c.fill(&hole, &IDENTITY, &Paint::Solid(BG0)); bar(c, 74.0, 38.0, 96.0, 9.0, TEXT.alpha(0.92)); bar(c, 74.0, 53.0, 60.0, 6.0, MUTED); let items = ["Launch", "Accounts", "Mods", "FastFlags", "Settings"]; for (i, _) in items.iter().enumerate() { let y = 120.0 + i as f32 * 46.0; let row = Rect::xywh(14.0, y, 196.0, 38.0); if i == 0 { let mut p = Path::new(); p.rrect(row, 10.0); c.shadow(&p, &IDENTITY, 10.0, ACCENT.alpha(0.55), pt(0.0, 3.0)); c.fill(&p, &IDENTITY, &Paint::hgrad(row, ACCENT.with_a(200), ACCENT.with_a(90))); bar(c, 4.0, y + 9.0, 3.0, 20.0, CYAN); } let icon = Rect::xywh(28.0, y + 11.0, 16.0, 16.0); c.fill_rrect(icon, 4.0, &Paint::Solid(if i == 0 { TEXT } else { MUTED })); bar(c, 56.0, y + 15.0, 74.0 - i as f32 * 6.0, 8.0, if i == 0 { TEXT } else { MUTED }); } let hero = Rect::xywh(256.0, 40.0, 808.0, 232.0); let mut heropath = Path::new(); heropath.rrect(hero, 16.0); c.shadow(&heropath, &IDENTITY, 26.0, hexa(0xb0000008), pt(0.0, 14.0)); c.fill(&heropath, &IDENTITY, &Paint::Linear { p0: pt(hero.x0, hero.y0), p1: pt(hero.x1, hero.y1), stops: vec![(0.0, hex(0x241f45)), (0.55, hex(0x191a2e)), (1.0, hex(0x141726))], }); c.stroke(&heropath, &IDENTITY, &Paint::Solid(rgba(255, 255, 255, 22)), &Stroke::new(1.0)); c.push_clip(hero.inset(1.0)); c.fill_circle(pt(hero.x1 - 60.0, hero.y0 + 40.0), 190.0, &Paint::glow(pt(hero.x1 - 60.0, hero.y0 + 40.0), 190.0, CYAN.alpha(0.22), CYAN.alpha(0.0))); let mut ring = Path::new(); ring.arc(pt(hero.x1 - 120.0, hero.y0 + 116.0), 78.0, -2.4, 1.5, true); c.stroke(&ring, &IDENTITY, &Paint::Solid(CYAN.alpha(0.55)), &Stroke::round(3.0)); c.pop_clip(); bar(c, hero.x0 + 34.0, hero.y0 + 40.0, 210.0, 16.0, TEXT.alpha(0.95)); bar(c, hero.x0 + 34.0, hero.y0 + 68.0, 320.0, 10.0, MUTED); bar(c, hero.x0 + 34.0, hero.y0 + 86.0, 260.0, 10.0, MUTED.alpha(0.7)); let play = Rect::xywh(hero.x0 + 34.0, hero.y0 + 130.0, 190.0, 56.0); let mut playp = Path::new(); playp.rrect(play, 28.0); c.shadow(&playp, &IDENTITY, 22.0, ACCENT.alpha(0.7), pt(0.0, 10.0)); c.fill(&playp, &IDENTITY, &Paint::hgrad(play, ACCENT, hex(0x9b7bff))); let mut tri = Path::new(); tri.move_to(pt(play.x0 + 34.0, play.y0 + 17.0)); tri.line_to(pt(play.x0 + 34.0, play.y1 - 17.0)); tri.line_to(pt(play.x0 + 55.0, play.center().y)); tri.close(); c.fill(&tri, &IDENTITY, &Paint::Solid(Color { r: 255, g: 255, b: 255, a: 255 })); bar(c, play.x0 + 70.0, play.center().y - 6.0, 84.0, 12.0, rgba(255, 255, 255, 235)); let track = Rect::xywh(hero.x0 + 250.0, play.center().y - 4.0, 300.0, 8.0); c.fill_rrect(track, 4.0, &Paint::Solid(rgba(255, 255, 255, 26))); let fill = Rect::new(track.x0, track.y0, track.x0 + 300.0 * 0.62, track.y1); c.fill_rrect(fill, 4.0, &Paint::hgrad(fill, ACCENT, CYAN)); c.fill_circle(pt(fill.x1, fill.center().y), 7.0, &Paint::Solid(Color { r: 255, g: 255, b: 255, a: 255 })); for i in 0..3 { let card = Rect::xywh(256.0 + i as f32 * 272.0, 300.0, 256.0, 150.0); let mut p = Path::new(); p.rrect(card, 14.0); c.shadow(&p, &IDENTITY, 16.0, hexa(0x90000006), pt(0.0, 8.0)); c.fill(&p, &IDENTITY, &Paint::vgrad(card, CARD, hex(0x15151f))); c.stroke(&p, &IDENTITY, &Paint::Solid(rgba(255, 255, 255, 16)), &Stroke::new(1.0)); let sw = Rect::xywh(card.x0 + 18.0, card.y0 + 20.0, 34.0, 34.0); c.fill_rrect(sw, 10.0, &Paint::vgrad(sw, [ACCENT, CYAN, hex(0xff7a59)][i], [CYAN, ACCENT, hex(0xffb15c)][i])); bar(c, card.x0 + 64.0, card.y0 + 26.0, 120.0, 10.0, TEXT.alpha(0.9)); bar(c, card.x0 + 64.0, card.y0 + 42.0, 78.0, 8.0, MUTED); bar(c, card.x0 + 18.0, card.y0 + 78.0, 200.0, 8.0, MUTED.alpha(0.55)); bar(c, card.x0 + 18.0, card.y0 + 94.0, 150.0, 8.0, MUTED.alpha(0.4)); let toggle = Rect::xywh(card.x1 - 62.0, card.y1 - 40.0, 44.0, 24.0); let on = i != 1; c.fill_rrect(toggle, 12.0, &Paint::Solid(if on { ACCENT } else { rgba(255, 255, 255, 28) })); let knob = if on { toggle.x1 - 12.0 } else { toggle.x0 + 12.0 }; c.fill_circle(pt(knob, toggle.center().y), 9.0, &Paint::Solid(Color { r: 255, g: 255, b: 255, a: 255 })); } let mut star = Path::new(); let cc = pt(1000.0, 520.0); for i in 0..10 { let a = -core::f32::consts::FRAC_PI_2 + i as f32 * core::f32::consts::PI / 5.0; let r = if i % 2 == 0 { 52.0 } else { 21.0 }; let p = pt(cc.x + r * a.cos(), cc.y + r * a.sin()); if i == 0 { star.move_to(p); } else { star.line_to(p); } } star.close(); let spun = Transform::translate(-cc.x, -cc.y) .then(&Transform::rotate(0.18)) .then(&Transform::translate(cc.x, cc.y)); c.fill(&star, &spun, &Paint::glow(cc, 56.0, hex(0xffd166), hex(0xff7a59))); c.stroke(&star, &spun, &Paint::Solid(rgba(255, 255, 255, 120)), &Stroke { width: 1.5, cap: Cap::Round }); let mut wave = Path::new(); wave.move_to(pt(270.0, 620.0)); for i in 0..=60 { let t = i as f32 / 60.0; let x = 270.0 + t * 620.0; let y = 600.0 - (t * 9.0).sin() * 26.0 - t * 18.0; wave.line_to(pt(x, y)); } c.stroke(&wave, &IDENTITY, &Paint::hgrad(Rect::xywh(270.0, 0.0, 620.0, 1.0), CYAN, ACCENT), &Stroke::round(2.5)); } fn main() { let (w, h) = (1100usize, 680usize); let mut c = Canvas::new(w, h); draw(&mut c); let png = c.to_png(); let out = std::env::args().nth(1).unwrap_or_else(|| "preview.png".to_string()); std::fs::write(&out, &png).unwrap(); println!("wrote {} ({} bytes, {}x{})", out, png.len(), w, h); let n = 120; let t0 = std::time::Instant::now(); for _ in 0..n { draw(&mut c); } let el = t0.elapsed(); println!( "{} frames in {:?} -> {:.2} ms/frame ({:.0} fps)", n, el, el.as_secs_f64() * 1000.0 / n as f64, n as f64 / el.as_secs_f64() ); }