Vẽ
Khi các hộp, văn bản và biểu tượng không đủ, hãy vẽ: ui.canvas cung cấp chức năng vẽ ui.Painter, chức năng này sẽ lấp đầy và vẽ các đường dẫn bằng màu sắc và chuyển màu, thông qua các phép biến đổi và clip. ui.svg vẽ tài liệu SVG theo cách tương tự. Biểu đồ cũng được vẽ trên canvas (Charts); để biết hiệu ứng trên mỗi pixel, hãy xem Shaders.
Trang Vẽ của thư viện có các ví dụ bên dưới (examples/gallery/drawing.zig, zig build run-gallery).

Đường vẽ canvas, gradient, nét đứt, hình khối và ảnh SVG.
Một bức vẽ
const Wave = struct {
phase: f32 = 0,
fn draw(s: *Wave, p: *ui.Painter) void {
var line = p.path();
for (0..101) |i| {
const x = p.size.w * @as(f32, @floatFromInt(i)) / 100;
const y = p.size.h / 2 + 30 * @sin(@as(f32, @floatFromInt(i)) / 8 + s.phase);
if (i == 0) line.moveTo(x, y) else line.lineTo(x, y);
}
p.stroke(&line, .color(.hex(0x0a84ff)), .{ .width = 2, .join = .round });
}
fn view(s: *Wave, c: *ui.Context) void {
_ = ui.canvas(c, .{ .w = 300, .h = 120 }, .{ .radius = 8 }, s, draw);
_ = ui.slider(c, &s.phase, .{ .min = 0, .max = 6.28, .label = "Phase" });
}
};ui.canvas(c, size, options, ctx, draw) tạo một hộp size có điểm lớn và gọi draw(ctx, painter) để ghi lại những gì nó hiển thị. Cạnh 0 lấp đầy phần gốc dọc theo nó: khung vẽ được vẽ ở kích thước có trong bố cục cuối cùng, điều mà p.size cho biết. ui.CanvasOptions có key (id vẫn tồn tại sau khi sắp xếp lại), opacity và radius, làm tròn các góc của bức tranh và cắt những gì được vẽ ở đó.
p.visible() là một phần của khung vẽ được hiển thị, theo tọa độ mà nó vẽ: hộp của nó nằm trong các hộp cuộn và các kẹp xung quanh nó và cửa sổ, khi khung cuối cùng vẽ nó, được đưa trở lại thông qua các biến đổi của các hộp của nó và của chính họa sĩ, do đó, một khung vẽ được phóng to sẽ thấy ít bản thân nó hơn nhiều như hiển thị. Một bản vẽ cao hơn cửa sổ (một tài liệu dài, nhiều điểm trên biểu đồ) bỏ qua những gì nằm bên ngoài nó; một khung cuộn nó vẽ lại một lần nữa với phần như hiện tại. Cho đến khi một khung vẽ nó, nó là toàn bộ bức tranh. Bên ngoài painter, ui.canvasVisibility(c, id, whole) trả lời điều tương tự cho khung vẽ sẽ nhận id hộp id (c.peekId), để một hoạt ảnh có thể tạm dừng khi khung vẽ của nó bị cuộn ra khỏi tầm nhìn (plugin loading làm vậy).
const v = p.visible();
for (s.lines, 0..) |l, i| {
const y = @as(f32, @floatFromInt(i)) * 20;
if (y + 20 < v.y or y > v.y + v.h) continue;
p.fillRect(.{ .x = 4, .y = y, .w = l.width, .h = 14 }, th.text);
}Canvas trả về chỉ mục của hộp, vì vậy nó có thể lấy con trỏ giống như bất kỳ hộp nào:
const box = ui.canvas(c, .{ .w = 0, .h = 200 }, .{}, s, draw);
const it = c.interact(box, .{});
if (it.clicked) if (it.rect) |r| s.addPoint(c.input.mouse.x - r.x, c.input.mouse.y - r.y);Nó được vẽ như thế nào
Painter ghi lại các lệnh; bản thân nó không vẽ gì cả. Bản ghi được rasterized trên CPU (src/ui/canvas_render.zig), với tính năng khử răng cưa, ở tỷ lệ của cửa sổ, thành một hình ảnh trong atlas hình ảnh mà mọi bộ kết xuất sẽ vẽ khi nó vẽ ui.image. Một khung vẽ chỉ được phân loại lại khi nội dung nó ghi thay đổi: hình ảnh được khóa bằng hàm băm của bản ghi, do đó, một khung vẽ cùng một thứ chỉ tốn phí ghi và hàm băm. Một khung vẽ thay đổi mọi frame (hình động) sẽ được raster hóa mọi frame, vì vậy hãy giữ những frame đó ở kích thước nhỏ hoặc di chuyển phần hoạt hình sang phần biến đổi của hộp (Transforms).
Các đường dẫn, điểm dừng chuyển màu và dấu gạch ngang được cung cấp cho Họa sĩ sẽ được sao chép: chuyển bất kỳ bộ nhớ nào, thậm chí cả ngăn xếp.
Đường dẫn
p.path() tạo ui.Path trong bộ nhớ của khung. Tọa độ là các điểm từ trên cùng bên trái của khung vẽ, y đi xuống.
| phương pháp | hòa |
|---|---|
moveTo(x, y) | bắt đầu một đường dẫn phụ mới |
lineTo(x, y) | một đoạn thẳng |
quadTo(cx, cy, x, y) | Bézier bậc hai |
cubicTo(c1x, c1y, c2x, c2y, x, y) | một khối Bézier |
close() | đóng đường dẫn phụ |
arc(cx, cy, r, a0, a1, ccw) | cung tròn, góc tính bằng radian |
svgArc(rx, ry, rotation, large, sweep, x, y) | Vòng cung hình elip của SVG tới một điểm |
rect(x, y, w, h) | một hình chữ nhật |
roundRect(x, y, w, h, rx, ry) | một hình chữ nhật tròn |
ellipse(cx, cy, rx, ry), circle(cx, cy, r) | hình elip |
polyline(points, closed) | [2]f32 điểm đã tham gia |
append(data, transform) | ui.PathData của đường dẫn khác, đã được chuyển đổi |
Một đường dẫn được tạo bằng bộ cấp phát riêng (ui.Path.init(gpa)) có thể được giữ trên các khung và được sử dụng lại: reset() làm trống nó, deinit() giải phóng nó và data() cung cấp ui.PathData (động từ và điểm) cho p.fillData.
Làm đầy và vuốt ve
p.fill(&path, .color(.hex(0xaf52de)));
p.fillRule(&path, .color(c.theme.text), .evenodd); // holes where subpaths overlap
p.stroke(&path, .color(accent), .{ .width = 2.5, .join = .round, .cap = .round });ui.FillRule là .nonzero (mặc định là fill) hoặc .evenodd. ui.Stroke:
| lĩnh vực | mặc định | ý nghĩa |
|---|---|---|
width | 1 | tính bằng điểm |
join | .miter | ui.LineJoin: .miter, .round, .bevel |
cap | .butt | ui.LineCap: .butt, .round, .square |
miter_limit | 4 | một miter có thể dài nhất tính theo chiều rộng trước khi nó được vát góc |
dash | không có | bật và tắt độ dài, lặp đi lặp lại (số lẻ được tính hai lần) |
dash_offset | 0 | nơi dấu gạch ngang bắt đầu |
Viết tắt: fillRect(rect, color), fillRoundRect(rect, radius, paint), fillCircle(cx, cy, r, paint), strokeCircle(cx, cy, r, width, color) và line(x0, y0, x1, y1, width, color).
Sơn và độ dốc
ui.Paint là .color(c) (.solid), .linear hoặc .radial, dưới dạng chuyển màu của SVG và Canvas 2D:
p.fill(&area, .{ .linear = .{
.from = .{ 0, 0 }, .to = .{ 0, h },
.stops = &.{ .{ .offset = 0, .color = accent.alpha(0.45) }, .{ .offset = 1, .color = accent.alpha(0) } },
} });
p.fillCircle(50, 50, 40, .{ .radial = .{
.center = .{ 40, 40 }, .radius = 48,
.stops = &.{ .{ .offset = 0, .color = .hex(0xffe066) }, .{ .offset = 1, .color = .hex(0xff6b35) } },
} });ui.LinearGradient đi from một điểm to điểm khác; ui.RadialGradient đi ra từ focal (trung tâm trừ khi được chỉ định) đến vòng tròn radius xung quanh center. Cả hai đều lấy stops (ui.GradientStop: offset 0 đến 1 và color), spread (.pad, .reflect hoặc .repeat: những gì xảy ra ở phần cuối) và transform từ không gian của gradient đến bản vẽ (SVG's gradientTransform).
Chuyển đổi, clip và lớp
Painter giữ lại biến đổi, clip và độ mờ hiện tại:
p.save();
p.rotate(s.turn * std.math.pi / 180, 150, 50); // radians, about (150, 50)
p.fill(&star, .color(.hex(0xaf52de)));
p.restore();save()vàrestore()giữ và mang lại các biến đổi, clip và độ mờ.transform(t)rút ra những gì tiếp theo thông quaui.Transform(sau khi các phép biến đổi đã được đặt);translate(x, y),scaleBy(sx, sy)vàrotate(radians, cx, cy)là viết tắt.ui.Transformcótranslation,scaling,rotation(radian),skewX,skewY,then,applyvàinvert. (Các hộp được chuyển đổi bằngui.Affine, tính bằng độ; canvas cóui.Transform, tính bằng radian, giống như Canvas 2D.)clip(path, rule)vàclipRect(rect)giữ những gì tiếp theo bên trong, trong các clip đã được đặt.setAlpha(a)mờ dần những gì tiếp theo cho đếnrestore.beginLayer(opacity)vàendLayer()tách một nhóm ra, sau đó trộn nó vớiopacity: một nhóm mờ có phần chồng lên nhau không hiển thị.
SVG
const logo = @embedFile("logo.svg");
_ = ui.svg(c, logo, .{ .height = 32 });
_ = ui.svg(c, gear, .{ .width = 16, .color = c.theme.accent }); // its currentColorui.svg(c, bytes, options) vẽ tài liệu SVG dưới dạng canvas, theo tỷ lệ của cửa sổ và trả về false (không vẽ gì) nếu các byte không phải là một. ui.SvgOptions:
| lĩnh vực | ý nghĩa |
|---|---|
width, height | kích thước của nó tính bằng điểm; một bên bằng 0 giữ tỷ lệ của tài liệu, cả hai bên bằng 0 đều có kích thước riêng |
color | currentColor là gì; null là màu văn bản của chủ đề nên các biểu tượng sẽ có màu sáng và tối |
opacity | độ mờ của nó |
key | một id trong số anh chị em của nó |
Hình ảnh được giữ lại: một khung hiển thị các byte giống nhau ở cùng kích thước và màu sắc sẽ vẽ nó mà không cần phân tích cú pháp lại.
Những gì SVG được vẽ (src/ui/svg_draw.zig): viewBox của gốc được trang bị preserveAspectRatio, các nhóm và svg lồng nhau, các đường dẫn và hình dạng cơ bản, use của các phần tử và ký hiệu, biến đổi, tô màu và nét theo màu sắc và chuyển màu (tuyến tính và xuyên tâm, theo tham chiếu, trong cả hai hệ thống đơn vị), độ mờ của chúng, độ mờ nhóm và đường cắt. Màu sắc có thể là hex, rgb(), hsl(), tên hoặc currentColor và kiểu có thể đến từ các thuộc tính, style hoặc các quy tắc CSS đơn giản trong <style> (bộ chọn theo thành phần, lớp và id). Văn bản, mặt nạ, bộ lọc, điểm đánh dấu và mẫu bị loại bỏ: chuyển đổi văn bản thành đường dẫn khi bạn xuất tệp.
ui.rasterizeSvg(gpa, bytes, w, h, current_color) vẽ tài liệu thành các pixel BGRA được nhân sẵn, w × h, không có cửa sổ: dành cho các biểu tượng được tạo trước thời hạn và các bài kiểm tra. Giải phóng kết quả bằng gpa.
Hình ảnh
Hình ảnh từ các tệp (PNG và trên macOS bất kể ImageIO đọc gì) không được vẽ trên canvas mà được hiển thị bằng ui.image; xem Văn bản và Nút và phương tiện.
Bản vẽ thử nghiệm
Một canvas vẽ trong Trình kiểm tra như trong một cửa sổ, thông qua cùng một trình rasterizer, do đó, một kiểm thử có thể hiển thị view và xem các pixel:
var t: ui.Tester = undefined;
try t.init(gpa, .init(&s, Wave.view), .{ .width = 320, .height = 160, .scale = 1 });
defer t.deinit();
const px = try t.render(gpa);
defer px.deinit(gpa);Xem Thử nghiệm.
