diff --git a/docs/emgui_wasm_bg.wasm b/docs/emgui_wasm_bg.wasm index 04cd0991..3bc1b878 100644 Binary files a/docs/emgui_wasm_bg.wasm and b/docs/emgui_wasm_bg.wasm differ diff --git a/docs/frontend.js b/docs/frontend.js index ff2f2b4c..9e92e013 100644 --- a/docs/frontend.js +++ b/docs/frontend.js @@ -1,75 +1,3 @@ -// ---------------------------------------------------------------------------- -// Canvas painting: -function style_from_color(color) { - return "rgba(" + color.r + ", " + color.g + ", " + color.b + ", " + color.a / 255.0 + ")"; -} -function paint_command(canvas, cmd) { - var ctx = canvas.getContext("2d"); - // console.log(`cmd: ${JSON.stringify(cmd)}`); - switch (cmd.kind) { - case "circle": - ctx.beginPath(); - ctx.arc(cmd.center.x, cmd.center.y, cmd.radius, 0, 2 * Math.PI, false); - if (cmd.fill_color) { - ctx.fillStyle = style_from_color(cmd.fill_color); - ctx.fill(); - } - if (cmd.outline) { - ctx.lineWidth = cmd.outline.width; - ctx.strokeStyle = style_from_color(cmd.outline.color); - ctx.stroke(); - } - return; - case "clear": - ctx.fillStyle = style_from_color(cmd.fill_color); - ctx.clearRect(0, 0, canvas.width, canvas.height); - return; - case "line": - ctx.beginPath(); - ctx.moveTo(cmd.points[0].x, cmd.points[0].y); - for (var _i = 0, _a = cmd.points; _i < _a.length; _i++) { - var point = _a[_i]; - ctx.lineTo(point.x, point.y); - } - ctx.lineWidth = cmd.width; - ctx.strokeStyle = style_from_color(cmd.color); - ctx.stroke(); - return; - case "rect": - var x = cmd.pos.x; - var y = cmd.pos.y; - var width = cmd.size.x; - var height = cmd.size.y; - var r = Math.min(cmd.corner_radius, width / 2, height / 2); - ctx.beginPath(); - ctx.moveTo(x + r, y); - ctx.lineTo(x + width - r, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + r); - ctx.lineTo(x + width, y + height - r); - ctx.quadraticCurveTo(x + width, y + height, x + width - r, y + height); - ctx.lineTo(x + r, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - r); - ctx.lineTo(x, y + r); - ctx.quadraticCurveTo(x, y, x + r, y); - ctx.closePath(); - if (cmd.fill_color) { - ctx.fillStyle = style_from_color(cmd.fill_color); - ctx.fill(); - } - if (cmd.outline) { - ctx.lineWidth = cmd.outline.width; - ctx.strokeStyle = style_from_color(cmd.outline.color); - ctx.stroke(); - } - return; - case "text": - ctx.fillStyle = style_from_color(cmd.fill_color); - ctx.font = cmd.font_size + "px " + cmd.font_name; - ctx.textBaseline = "middle"; - ctx.fillText(cmd.text, cmd.pos.x, cmd.pos.y); - return; - } -} // we'll defer our execution until the wasm is ready to go function wasm_loaded() { console.log("wasm loaded"); @@ -79,55 +7,24 @@ function wasm_loaded() { // initialization and return to us a promise when it's done wasm_bindgen("./emgui_wasm_bg.wasm") .then(wasm_loaded)["catch"](console.error); -function rust_gui(input) { - return JSON.parse(wasm_bindgen.show_gui(JSON.stringify(input))); -} // ---------------------------------------------------------------------------- -function js_gui(input) { - var commands = []; - commands.push({ - fillStyle: "#111111", - kind: "clear" - }); - commands.push({ - fillStyle: "#ff1111", - kind: "rect", - pos: { x: 100, y: 100 }, - radius: 20, - size: { x: 200, y: 100 } - }); - return commands; -} -var WEB_GL = true; var g_webgl_painter = null; function paint_gui(canvas, input) { - if (WEB_GL) { - if (g_webgl_painter === null) { - g_webgl_painter = wasm_bindgen.new_webgl_painter("canvas"); - } - wasm_bindgen.paint_webgl(g_webgl_painter, JSON.stringify(input)); - } - else { - var commands = rust_gui(input); - for (var _i = 0, commands_1 = commands; _i < commands_1.length; _i++) { - var cmd = commands_1[_i]; - commands.unshift({ - fill_color: { r: 0, g: 0, b: 0, a: 0 }, - kind: "clear" - }); - paint_command(canvas, cmd); - } + if (g_webgl_painter === null) { + g_webgl_painter = wasm_bindgen.new_webgl_painter("canvas"); } + wasm_bindgen.paint_webgl(g_webgl_painter, JSON.stringify(input)); } // ---------------------------------------------------------------------------- var g_mouse_pos = { x: -1000.0, y: -1000.0 }; var g_mouse_down = false; function auto_resize_canvas(canvas) { - if (WEB_GL) { + if (true) { canvas.setAttribute("width", window.innerWidth); canvas.setAttribute("height", window.innerHeight); } else { + // TODO: this stuff var pixels_per_point = window.devicePixelRatio || 1; var ctx = canvas.getContext("2d"); ctx.scale(pixels_per_point, pixels_per_point); diff --git a/docs/frontend.ts b/docs/frontend.ts index 6a3d33c9..09fe4ccc 100644 --- a/docs/frontend.ts +++ b/docs/frontend.ts @@ -3,145 +3,6 @@ interface Vec2 { y: number; } -// ---------------------------------------------------------------------------- -// Paint module: - -/// 0-255 sRGBA -interface Color { - r: number; - g: number; - b: number; - a: number; -} - -interface Outline { - color: Color; - width: number; -} - -interface Clear { - kind: "clear"; - fill_color: Color; -} - -interface Line { - kind: "line"; - color: Color; - points: Vec2[]; - width: number; -} - -interface Circle { - kind: "circle"; - center: Vec2; - fill_color: Color | null; - outline: Outline | null; - radius: number; -} - -interface Rect { - kind: "rect"; - corner_radius: number; - fill_color: Color | null; - outline: Outline | null; - pos: Vec2; - size: Vec2; -} - -interface Text { - kind: "text"; - fill_color: Color | null; - font_name: string; // e.g. "Palatino" - font_size: number; // Height in pixels, e.g. 12 - pos: Vec2; - stroke_color: Color | null; - text: string; -} - -type PaintCmd = Circle | Clear | Line | Rect | Text; - -// ---------------------------------------------------------------------------- -// Canvas painting: - -function style_from_color(color: Color): string { - return `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a / 255.0})`; -} - -function paint_command(canvas, cmd: PaintCmd) { - const ctx = canvas.getContext("2d"); - - // console.log(`cmd: ${JSON.stringify(cmd)}`); - - switch (cmd.kind) { - case "circle": - ctx.beginPath(); - ctx.arc(cmd.center.x, cmd.center.y, cmd.radius, 0, 2 * Math.PI, false); - if (cmd.fill_color) { - ctx.fillStyle = style_from_color(cmd.fill_color); - ctx.fill(); - } - if (cmd.outline) { - ctx.lineWidth = cmd.outline.width; - ctx.strokeStyle = style_from_color(cmd.outline.color); - ctx.stroke(); - } - return; - - case "clear": - ctx.fillStyle = style_from_color(cmd.fill_color); - ctx.clearRect(0, 0, canvas.width, canvas.height); - return; - - case "line": - ctx.beginPath(); - ctx.moveTo(cmd.points[0].x, cmd.points[0].y); - for (const point of cmd.points) { - ctx.lineTo(point.x, point.y); - } - ctx.lineWidth = cmd.width; - ctx.strokeStyle = style_from_color(cmd.color); - ctx.stroke(); - return; - - case "rect": - const x = cmd.pos.x; - const y = cmd.pos.y; - const width = cmd.size.x; - const height = cmd.size.y; - const r = Math.min(cmd.corner_radius, width / 2, height / 2); - ctx.beginPath(); - ctx.moveTo(x + r, y); - ctx.lineTo(x + width - r, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + r); - ctx.lineTo(x + width, y + height - r); - ctx.quadraticCurveTo(x + width, y + height, x + width - r, y + height); - ctx.lineTo(x + r, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - r); - ctx.lineTo(x, y + r); - ctx.quadraticCurveTo(x, y, x + r, y); - ctx.closePath(); - if (cmd.fill_color) { - ctx.fillStyle = style_from_color(cmd.fill_color); - ctx.fill(); - } - if (cmd.outline) { - ctx.lineWidth = cmd.outline.width; - ctx.strokeStyle = style_from_color(cmd.outline.color); - ctx.stroke(); - } - return; - - case "text": - ctx.fillStyle = style_from_color(cmd.fill_color); - ctx.font = `${cmd.font_size}px ${cmd.font_name}`; - ctx.textBaseline = "middle"; - ctx.fillText(cmd.text, cmd.pos.x, cmd.pos.y); - return; - } -} - -// ---------------------------------------------------------------------------- - /// What the integration gives to the gui. interface RawInput { /// Is the button currently down? @@ -171,50 +32,15 @@ wasm_bindgen("./emgui_wasm_bg.wasm") .then(wasm_loaded) .catch(console.error); -function rust_gui(input: RawInput): PaintCmd[] { - return JSON.parse(wasm_bindgen.show_gui(JSON.stringify(input))); -} - // ---------------------------------------------------------------------------- -function js_gui(input: RawInput): PaintCmd[] { - const commands = []; - - commands.push({ - fillStyle: "#111111", - kind: "clear", - }); - - commands.push({ - fillStyle: "#ff1111", - kind: "rect", - pos: { x: 100, y: 100 }, - radius: 20, - size: { x: 200, y: 100 }, - }); - - return commands; -} - -const WEB_GL = true; let g_webgl_painter = null; function paint_gui(canvas, input: RawInput) { - if (WEB_GL) { - if (g_webgl_painter === null) { - g_webgl_painter = wasm_bindgen.new_webgl_painter("canvas"); - } - wasm_bindgen.paint_webgl(g_webgl_painter, JSON.stringify(input)); - } else { - const commands = rust_gui(input); - for (const cmd of commands) { - commands.unshift({ - fill_color: { r: 0, g: 0, b: 0, a: 0 }, - kind: "clear", - }); - paint_command(canvas, cmd); - } + if (g_webgl_painter === null) { + g_webgl_painter = wasm_bindgen.new_webgl_painter("canvas"); } + wasm_bindgen.paint_webgl(g_webgl_painter, JSON.stringify(input)); } // ---------------------------------------------------------------------------- @@ -223,10 +49,11 @@ let g_mouse_pos = { x: -1000.0, y: -1000.0 }; let g_mouse_down = false; function auto_resize_canvas(canvas) { - if (WEB_GL) { + if (true) { canvas.setAttribute("width", window.innerWidth); canvas.setAttribute("height", window.innerHeight); } else { + // TODO: this stuff const pixels_per_point = window.devicePixelRatio || 1; const ctx = canvas.getContext("2d"); diff --git a/emgui/src/painter.rs b/emgui/src/painter.rs index 171f03ae..2e534c4b 100644 --- a/emgui/src/painter.rs +++ b/emgui/src/painter.rs @@ -1,5 +1,8 @@ #![allow(unused_variables)] +const ANTI_ALIAS: bool = true; +const AA_SIZE: f32 = 1.0; + /// Outputs render info in a format suitable for e.g. OpenGL. use crate::{ font::Font, @@ -33,15 +36,17 @@ pub enum PathType { use self::PathType::*; impl Frame { + fn triangle(&mut self, a: u32, b: u32, c: u32) { + self.indices.push(a); + self.indices.push(b); + self.indices.push(c); + } + /// Uniformly colored rectangle pub fn add_rect(&mut self, top_left: Vertex, bottom_right: Vertex) { let idx = self.vertices.len() as u32; - self.indices.push(idx + 0); - self.indices.push(idx + 1); - self.indices.push(idx + 2); - self.indices.push(idx + 2); - self.indices.push(idx + 1); - self.indices.push(idx + 3); + self.triangle(idx + 0, idx + 1, idx + 2); + self.triangle(idx + 2, idx + 1, idx + 3); let top_right = Vertex { pos: vec2(bottom_right.pos.x, top_left.pos.y), @@ -60,19 +65,37 @@ impl Frame { } pub fn fill_closed_path(&mut self, points: &[Vec2], normals: &[Vec2], color: Color) { - // TODO: use normals for anti-aliasing assert_eq!(points.len(), normals.len()); let n = points.len() as u32; - let idx = self.vertices.len() as u32; - self.vertices.extend(points.iter().map(|&pos| Vertex { + let vert = |pos, color| Vertex { pos, uv: (0, 0), color, - })); - for i in 2..n { - self.indices.push(idx); - self.indices.push(idx + i - 1); - self.indices.push(idx + i); + }; + if ANTI_ALIAS { + let color_outer = color.transparent(); + let idx_inner = self.vertices.len() as u32; + let idx_outer = idx_inner + 1; + for i in 2..n { + self.triangle(idx_inner + 2 * (i - 1), idx_inner, idx_inner + 2 * i); + } + let mut i0 = n - 1; + for i1 in 0..n { + let dm = normals[i1 as usize] * AA_SIZE * 0.5; + self.vertices.push(vert(points[i1 as usize] - dm, color)); + self.vertices + .push(vert(points[i1 as usize] + dm, color_outer)); + self.triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0); + self.triangle(idx_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1); + i0 = i1; + } + } else { + let idx = self.vertices.len() as u32; + self.vertices + .extend(points.iter().map(|&pos| vert(pos, color))); + for i in 2..n { + self.triangle(idx, idx + i - 1, idx + i); + } } } @@ -84,33 +107,87 @@ impl Frame { color: Color, width: f32, ) { - // TODO: anti-aliasing assert_eq!(points.len(), normals.len()); let n = points.len() as u32; let hw = width / 2.0; - let idx = self.vertices.len() as u32; - let last_index = if path_type == Closed { n } else { n - 1 }; - for i in 0..last_index { - self.indices.push(idx + (2 * i + 0) % (2 * n)); - self.indices.push(idx + (2 * i + 1) % (2 * n)); - self.indices.push(idx + (2 * i + 2) % (2 * n)); - self.indices.push(idx + (2 * i + 2) % (2 * n)); - self.indices.push(idx + (2 * i + 1) % (2 * n)); - self.indices.push(idx + (2 * i + 3) % (2 * n)); - } - for (&p, &n) in points.iter().zip(normals) { - self.vertices.push(Vertex { - pos: p + hw * n, - uv: (0, 0), - color, - }); - self.vertices.push(Vertex { - pos: p - hw * n, - uv: (0, 0), - color, - }); + let vert = |pos, color| Vertex { + pos, + uv: (0, 0), + color, + }; + + if ANTI_ALIAS { + let color_outer = color.transparent(); + let thin_line = width <= 1.0; + let mut color_inner = color; + if thin_line { + // Fade out as it gets thinner: + color_inner.a = (color_inner.a as f32 * width).round() as u8; + } + // TODO: line caps ? + let mut i0 = n - 1; + for i1 in 0..n { + let connect_with_previous = path_type == PathType::Closed || i1 > 0; + if thin_line { + let hw = (width - AA_SIZE) * 0.5; + let p = points[i1 as usize]; + let n = normals[i1 as usize]; + self.vertices.push(vert(p + n * AA_SIZE, color_outer)); + self.vertices.push(vert(p, color_inner)); + self.vertices.push(vert(p - n * AA_SIZE, color_outer)); + + if connect_with_previous { + self.triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0); + self.triangle(idx + 3 * i0 + 1, idx + 3 * i1 + 0, idx + 3 * i1 + 1); + + self.triangle(idx + 3 * i0 + 1, idx + 3 * i0 + 2, idx + 3 * i1 + 1); + self.triangle(idx + 3 * i0 + 2, idx + 3 * i1 + 1, idx + 3 * i1 + 2); + } + } else { + let hw = (width - AA_SIZE) * 0.5; + let p = points[i1 as usize]; + let n = normals[i1 as usize]; + self.vertices + .push(vert(p + n * (hw + AA_SIZE), color_outer)); + self.vertices.push(vert(p + n * (hw + 0.0), color_inner)); + self.vertices.push(vert(p - n * (hw + 0.0), color_inner)); + self.vertices + .push(vert(p - n * (hw + AA_SIZE), color_outer)); + + if connect_with_previous { + self.triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0); + self.triangle(idx + 4 * i0 + 1, idx + 4 * i1 + 0, idx + 4 * i1 + 1); + + self.triangle(idx + 4 * i0 + 1, idx + 4 * i0 + 2, idx + 4 * i1 + 1); + self.triangle(idx + 4 * i0 + 2, idx + 4 * i1 + 1, idx + 4 * i1 + 2); + + self.triangle(idx + 4 * i0 + 2, idx + 4 * i0 + 3, idx + 4 * i1 + 2); + self.triangle(idx + 4 * i0 + 3, idx + 4 * i1 + 2, idx + 4 * i1 + 3); + } + } + i0 = i1; + } + } else { + let last_index = if path_type == Closed { n } else { n - 1 }; + for i in 0..last_index { + self.triangle( + idx + (2 * i + 0) % (2 * n), + idx + (2 * i + 1) % (2 * n), + idx + (2 * i + 2) % (2 * n), + ); + self.triangle( + idx + (2 * i + 2) % (2 * n), + idx + (2 * i + 1) % (2 * n), + idx + (2 * i + 3) % (2 * n), + ); + } + + for (&p, &n) in points.iter().zip(normals) { + self.vertices.push(vert(p + hw * n, color)); + self.vertices.push(vert(p - hw * n, color)); + } } } } @@ -200,11 +277,11 @@ impl Painter { } } PaintCmd::Rect { + corner_radius, fill_color, outline, pos, size, - .. } => { path_points.clear(); path_normals.clear(); @@ -212,15 +289,43 @@ impl Painter { let min = *pos; let max = *pos + *size; - // TODO: rounded corners - path_points.push(vec2(min.x, min.y)); - path_normals.push(vec2(-1.0, -1.0)); - path_points.push(vec2(max.x, min.y)); - path_normals.push(vec2(1.0, -1.0)); - path_points.push(vec2(max.x, max.y)); - path_normals.push(vec2(1.0, 1.0)); - path_points.push(vec2(min.x, max.y)); - path_normals.push(vec2(-1.0, 1.0)); + let cr = corner_radius.min(size.x * 0.5).min(size.y * 0.5); + + if cr < 1.0 { + path_points.push(vec2(min.x, min.y)); + path_normals.push(vec2(-1.0, -1.0)); + path_points.push(vec2(max.x, min.y)); + path_normals.push(vec2(1.0, -1.0)); + path_points.push(vec2(max.x, max.y)); + path_normals.push(vec2(1.0, 1.0)); + path_points.push(vec2(min.x, max.y)); + path_normals.push(vec2(-1.0, 1.0)); + } else { + let n = 8; + + let mut add_arc = |c, quadrant| { + let quadrant = quadrant as f32; + + const RIGHT_ANGLE: f32 = TAU / 4.0; + for i in 0..=n { + let angle = remap( + i as f32, + 0.0, + n as f32, + quadrant * RIGHT_ANGLE, + (quadrant + 1.0) * RIGHT_ANGLE, + ); + let normal = vec2(angle.cos(), angle.sin()); + path_points.push(c + cr * normal); + path_normals.push(normal); + } + }; + + add_arc(vec2(max.x - cr, max.y - cr), 0); + add_arc(vec2(min.x + cr, max.y - cr), 1); + add_arc(vec2(min.x + cr, min.y + cr), 2); + add_arc(vec2(max.x - cr, min.y + cr), 3); + } if let Some(color) = fill_color { frame.fill_closed_path(&path_points, &path_normals, *color); diff --git a/emgui/src/types.rs b/emgui/src/types.rs index ec6b6529..09449461 100644 --- a/emgui/src/types.rs +++ b/emgui/src/types.rs @@ -59,6 +59,15 @@ pub struct Color { impl Color { pub const WHITE: Color = srgba(255, 255, 255, 255); + + pub fn transparent(self) -> Color { + Color { + r: self.r, + g: self.g, + b: self.b, + a: 0, + } + } } pub const fn srgba(r: u8, g: u8, b: u8, a: u8) -> Color {