nitai/projects
noxstrap / src / crates / nox-gfx / examples / preview.rs
184 lines · 7.8 KB Raw
1use nox_gfx::canvas::{Canvas, Cap, Paint, Stroke};
2use nox_gfx::color::{hex, hexa, rgba, Color};
3use nox_gfx::geom::{pt, Rect, Transform, IDENTITY};
4use nox_gfx::path::Path;
5
6const BG0: Color = hex(0x0a0a10);
7const BG1: Color = hex(0x141420);
8const CARD: Color = hex(0x1a1a26);
9const TEXT: Color = hex(0xe6e6f0);
10const MUTED: Color = hex(0x6f6f88);
11const ACCENT: Color = hex(0x7c5cff);
12const CYAN: Color = hex(0x35d0ff);
13
14fn 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
18fn 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
162fn 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}