| 1 | use nox_gfx::canvas::{Canvas, Cap, Paint, Stroke}; |
| 2 | use nox_gfx::color::{hex, hexa, rgba, Color}; |
| 3 | use nox_gfx::geom::{pt, Rect, Transform, IDENTITY}; |
| 4 | use nox_gfx::path::Path; |
| 5 | |
| 6 | const BG0: Color = hex(0x0a0a10); |
| 7 | const BG1: Color = hex(0x141420); |
| 8 | const CARD: Color = hex(0x1a1a26); |
| 9 | const TEXT: Color = hex(0xe6e6f0); |
| 10 | const MUTED: Color = hex(0x6f6f88); |
| 11 | const ACCENT: Color = hex(0x7c5cff); |
| 12 | const CYAN: Color = hex(0x35d0ff); |
| 13 | |
| 14 | fn bar(c: &mut Canvas, x: f32, y: f32, w: f32, h: f32, col: Color) { |
| 15 | c.fill_rrect(Rect::xywh(x, y, w, h), h * 0.5, &Paint::Solid(col)); |
| 16 | } |
| 17 | |
| 18 | fn draw(c: &mut Canvas) { |
| 19 | let (w, h) = (c.w, c.h); |
| 20 | let mut frame = Path::new(); |
| 21 | frame.rrect(Rect::xywh(0.0, 0.0, w as f32, h as f32), 14.0); |
| 22 | c.fill(&frame, &IDENTITY, &Paint::vgrad(c.full(), BG1, BG0)); |
| 23 | |
| 24 | 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))); |
| 25 | 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))); |
| 26 | |
| 27 | let rail = Rect::xywh(0.0, 0.0, 224.0, h as f32); |
| 28 | let glass = c.blurred_layer(rail, 18.0); |
| 29 | let mut railp = Path::new(); |
| 30 | railp.rrect_corners(rail, 14.0, 0.0, 0.0, 14.0); |
| 31 | c.fill_layer(&railp, &IDENTITY, &glass, rgba(16, 16, 26, 190)); |
| 32 | c.fill( |
| 33 | &{ |
| 34 | let mut p = Path::new(); |
| 35 | p.rect(Rect::xywh(223.0, 0.0, 1.0, h as f32)); |
| 36 | p |
| 37 | }, |
| 38 | &IDENTITY, |
| 39 | &Paint::Solid(rgba(255, 255, 255, 18)), |
| 40 | ); |
| 41 | |
| 42 | let logo = pt(46.0, 46.0); |
| 43 | c.fill_circle(logo, 15.0, &Paint::Linear { |
| 44 | p0: pt(logo.x - 15.0, logo.y - 15.0), |
| 45 | p1: pt(logo.x + 15.0, logo.y + 15.0), |
| 46 | stops: vec![(0.0, ACCENT), (1.0, CYAN)], |
| 47 | }); |
| 48 | let mut hole = Path::new(); |
| 49 | hole.circle(logo, 6.0); |
| 50 | c.fill(&hole, &IDENTITY, &Paint::Solid(BG0)); |
| 51 | bar(c, 74.0, 38.0, 96.0, 9.0, TEXT.alpha(0.92)); |
| 52 | bar(c, 74.0, 53.0, 60.0, 6.0, MUTED); |
| 53 | |
| 54 | let items = ["Launch", "Accounts", "Mods", "FastFlags", "Settings"]; |
| 55 | for (i, _) in items.iter().enumerate() { |
| 56 | let y = 120.0 + i as f32 * 46.0; |
| 57 | let row = Rect::xywh(14.0, y, 196.0, 38.0); |
| 58 | if i == 0 { |
| 59 | let mut p = Path::new(); |
| 60 | p.rrect(row, 10.0); |
| 61 | c.shadow(&p, &IDENTITY, 10.0, ACCENT.alpha(0.55), pt(0.0, 3.0)); |
| 62 | c.fill(&p, &IDENTITY, &Paint::hgrad(row, ACCENT.with_a(200), ACCENT.with_a(90))); |
| 63 | bar(c, 4.0, y + 9.0, 3.0, 20.0, CYAN); |
| 64 | } |
| 65 | let icon = Rect::xywh(28.0, y + 11.0, 16.0, 16.0); |
| 66 | c.fill_rrect(icon, 4.0, &Paint::Solid(if i == 0 { TEXT } else { MUTED })); |
| 67 | bar(c, 56.0, y + 15.0, 74.0 - i as f32 * 6.0, 8.0, if i == 0 { TEXT } else { MUTED }); |
| 68 | } |
| 69 | |
| 70 | let hero = Rect::xywh(256.0, 40.0, 808.0, 232.0); |
| 71 | let mut heropath = Path::new(); |
| 72 | heropath.rrect(hero, 16.0); |
| 73 | c.shadow(&heropath, &IDENTITY, 26.0, hexa(0xb0000008), pt(0.0, 14.0)); |
| 74 | c.fill(&heropath, &IDENTITY, &Paint::Linear { |
| 75 | p0: pt(hero.x0, hero.y0), |
| 76 | p1: pt(hero.x1, hero.y1), |
| 77 | stops: vec![(0.0, hex(0x241f45)), (0.55, hex(0x191a2e)), (1.0, hex(0x141726))], |
| 78 | }); |
| 79 | c.stroke(&heropath, &IDENTITY, &Paint::Solid(rgba(255, 255, 255, 22)), &Stroke::new(1.0)); |
| 80 | |
| 81 | c.push_clip(hero.inset(1.0)); |
| 82 | 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))); |
| 83 | let mut ring = Path::new(); |
| 84 | ring.arc(pt(hero.x1 - 120.0, hero.y0 + 116.0), 78.0, -2.4, 1.5, true); |
| 85 | c.stroke(&ring, &IDENTITY, &Paint::Solid(CYAN.alpha(0.55)), &Stroke::round(3.0)); |
| 86 | c.pop_clip(); |
| 87 | |
| 88 | bar(c, hero.x0 + 34.0, hero.y0 + 40.0, 210.0, 16.0, TEXT.alpha(0.95)); |
| 89 | bar(c, hero.x0 + 34.0, hero.y0 + 68.0, 320.0, 10.0, MUTED); |
| 90 | bar(c, hero.x0 + 34.0, hero.y0 + 86.0, 260.0, 10.0, MUTED.alpha(0.7)); |
| 91 | |
| 92 | let play = Rect::xywh(hero.x0 + 34.0, hero.y0 + 130.0, 190.0, 56.0); |
| 93 | let mut playp = Path::new(); |
| 94 | playp.rrect(play, 28.0); |
| 95 | c.shadow(&playp, &IDENTITY, 22.0, ACCENT.alpha(0.7), pt(0.0, 10.0)); |
| 96 | c.fill(&playp, &IDENTITY, &Paint::hgrad(play, ACCENT, hex(0x9b7bff))); |
| 97 | let mut tri = Path::new(); |
| 98 | tri.move_to(pt(play.x0 + 34.0, play.y0 + 17.0)); |
| 99 | tri.line_to(pt(play.x0 + 34.0, play.y1 - 17.0)); |
| 100 | tri.line_to(pt(play.x0 + 55.0, play.center().y)); |
| 101 | tri.close(); |
| 102 | c.fill(&tri, &IDENTITY, &Paint::Solid(Color { r: 255, g: 255, b: 255, a: 255 })); |
| 103 | bar(c, play.x0 + 70.0, play.center().y - 6.0, 84.0, 12.0, rgba(255, 255, 255, 235)); |
| 104 | |
| 105 | let track = Rect::xywh(hero.x0 + 250.0, play.center().y - 4.0, 300.0, 8.0); |
| 106 | c.fill_rrect(track, 4.0, &Paint::Solid(rgba(255, 255, 255, 26))); |
| 107 | let fill = Rect::new(track.x0, track.y0, track.x0 + 300.0 * 0.62, track.y1); |
| 108 | c.fill_rrect(fill, 4.0, &Paint::hgrad(fill, ACCENT, CYAN)); |
| 109 | c.fill_circle(pt(fill.x1, fill.center().y), 7.0, &Paint::Solid(Color { r: 255, g: 255, b: 255, a: 255 })); |
| 110 | |
| 111 | for i in 0..3 { |
| 112 | let card = Rect::xywh(256.0 + i as f32 * 272.0, 300.0, 256.0, 150.0); |
| 113 | let mut p = Path::new(); |
| 114 | p.rrect(card, 14.0); |
| 115 | c.shadow(&p, &IDENTITY, 16.0, hexa(0x90000006), pt(0.0, 8.0)); |
| 116 | c.fill(&p, &IDENTITY, &Paint::vgrad(card, CARD, hex(0x15151f))); |
| 117 | c.stroke(&p, &IDENTITY, &Paint::Solid(rgba(255, 255, 255, 16)), &Stroke::new(1.0)); |
| 118 | let sw = Rect::xywh(card.x0 + 18.0, card.y0 + 20.0, 34.0, 34.0); |
| 119 | c.fill_rrect(sw, 10.0, &Paint::vgrad(sw, [ACCENT, CYAN, hex(0xff7a59)][i], [CYAN, ACCENT, hex(0xffb15c)][i])); |
| 120 | bar(c, card.x0 + 64.0, card.y0 + 26.0, 120.0, 10.0, TEXT.alpha(0.9)); |
| 121 | bar(c, card.x0 + 64.0, card.y0 + 42.0, 78.0, 8.0, MUTED); |
| 122 | bar(c, card.x0 + 18.0, card.y0 + 78.0, 200.0, 8.0, MUTED.alpha(0.55)); |
| 123 | bar(c, card.x0 + 18.0, card.y0 + 94.0, 150.0, 8.0, MUTED.alpha(0.4)); |
| 124 | let toggle = Rect::xywh(card.x1 - 62.0, card.y1 - 40.0, 44.0, 24.0); |
| 125 | let on = i != 1; |
| 126 | c.fill_rrect(toggle, 12.0, &Paint::Solid(if on { ACCENT } else { rgba(255, 255, 255, 28) })); |
| 127 | let knob = if on { toggle.x1 - 12.0 } else { toggle.x0 + 12.0 }; |
| 128 | c.fill_circle(pt(knob, toggle.center().y), 9.0, &Paint::Solid(Color { r: 255, g: 255, b: 255, a: 255 })); |
| 129 | } |
| 130 | |
| 131 | let mut star = Path::new(); |
| 132 | let cc = pt(1000.0, 520.0); |
| 133 | for i in 0..10 { |
| 134 | let a = -core::f32::consts::FRAC_PI_2 + i as f32 * core::f32::consts::PI / 5.0; |
| 135 | let r = if i % 2 == 0 { 52.0 } else { 21.0 }; |
| 136 | let p = pt(cc.x + r * a.cos(), cc.y + r * a.sin()); |
| 137 | if i == 0 { |
| 138 | star.move_to(p); |
| 139 | } else { |
| 140 | star.line_to(p); |
| 141 | } |
| 142 | } |
| 143 | star.close(); |
| 144 | let spun = Transform::translate(-cc.x, -cc.y) |
| 145 | .then(&Transform::rotate(0.18)) |
| 146 | .then(&Transform::translate(cc.x, cc.y)); |
| 147 | c.fill(&star, &spun, &Paint::glow(cc, 56.0, hex(0xffd166), hex(0xff7a59))); |
| 148 | c.stroke(&star, &spun, &Paint::Solid(rgba(255, 255, 255, 120)), &Stroke { width: 1.5, cap: Cap::Round }); |
| 149 | |
| 150 | let mut wave = Path::new(); |
| 151 | wave.move_to(pt(270.0, 620.0)); |
| 152 | for i in 0..=60 { |
| 153 | let t = i as f32 / 60.0; |
| 154 | let x = 270.0 + t * 620.0; |
| 155 | let y = 600.0 - (t * 9.0).sin() * 26.0 - t * 18.0; |
| 156 | wave.line_to(pt(x, y)); |
| 157 | } |
| 158 | c.stroke(&wave, &IDENTITY, &Paint::hgrad(Rect::xywh(270.0, 0.0, 620.0, 1.0), CYAN, ACCENT), &Stroke::round(2.5)); |
| 159 | |
| 160 | } |
| 161 | |
| 162 | fn main() { |
| 163 | let (w, h) = (1100usize, 680usize); |
| 164 | let mut c = Canvas::new(w, h); |
| 165 | draw(&mut c); |
| 166 | let png = c.to_png(); |
| 167 | let out = std::env::args().nth(1).unwrap_or_else(|| "preview.png".to_string()); |
| 168 | std::fs::write(&out, &png).unwrap(); |
| 169 | println!("wrote {} ({} bytes, {}x{})", out, png.len(), w, h); |
| 170 | |
| 171 | let n = 120; |
| 172 | let t0 = std::time::Instant::now(); |
| 173 | for _ in 0..n { |
| 174 | draw(&mut c); |
| 175 | } |
| 176 | let el = t0.elapsed(); |
| 177 | println!( |
| 178 | "{} frames in {:?} -> {:.2} ms/frame ({:.0} fps)", |
| 179 | n, |
| 180 | el, |
| 181 | el.as_secs_f64() * 1000.0 / n as f64, |
| 182 | n as f64 / el.as_secs_f64() |
| 183 | ); |
| 184 | } |