Первые попытки рисовать
This commit is contained in:
@@ -3,7 +3,9 @@ const builtin = @import("builtin");
|
|||||||
const dvui = @import("dvui");
|
const dvui = @import("dvui");
|
||||||
const Document = @import("models/Document.zig");
|
const Document = @import("models/Document.zig");
|
||||||
const RenderEngine = @import("render/RenderEngine.zig").RenderEngine;
|
const RenderEngine = @import("render/RenderEngine.zig").RenderEngine;
|
||||||
const ImageRect = @import("models/basic_models.zig").ImageRect;
|
const basic_models = @import("models/basic_models.zig");
|
||||||
|
const ImageRect = basic_models.ImageRect;
|
||||||
|
const ImageSize = basic_models.ImageSize;
|
||||||
const Point2 = @import("models/basic_models.zig").Point2;
|
const Point2 = @import("models/basic_models.zig").Point2;
|
||||||
const Color = dvui.Color;
|
const Color = dvui.Color;
|
||||||
|
|
||||||
@@ -25,6 +27,8 @@ _zoom: f32 = 1,
|
|||||||
_redraw_pending: bool = false,
|
_redraw_pending: bool = false,
|
||||||
_last_redraw_time_ms: i64 = 0,
|
_last_redraw_time_ms: i64 = 0,
|
||||||
cursor_document_point: ?Point2 = null,
|
cursor_document_point: ?Point2 = null,
|
||||||
|
/// true — рисовать документ (render), false — пример (gradient/squares).
|
||||||
|
draw_document: bool = true,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, document: *Document, engine: RenderEngine) Canvas {
|
pub fn init(allocator: std.mem.Allocator, document: *Document, engine: RenderEngine) Canvas {
|
||||||
return .{
|
return .{
|
||||||
@@ -41,7 +45,7 @@ pub fn deinit(self: *Canvas) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redrawExample(self: *Canvas) !void {
|
fn redraw(self: *Canvas) !void {
|
||||||
const full = self.getZoomedImageSize();
|
const full = self.getZoomedImageSize();
|
||||||
|
|
||||||
const vis: ImageRect = self._visible_rect orelse ImageRect{ .x = 0, .y = 0, .w = 0, .h = 0 };
|
const vis: ImageRect = self._visible_rect orelse ImageRect{ .x = 0, .y = 0, .w = 0, .h = 0 };
|
||||||
@@ -54,7 +58,11 @@ pub fn redrawExample(self: *Canvas) !void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const new_texture = self.render_engine.example(.{ .w = full.w, .h = full.h }, vis) catch null;
|
const canvas_size: ImageSize = .{ .w = full.w, .h = full.h };
|
||||||
|
const new_texture = if (self.draw_document)
|
||||||
|
self.render_engine.render(self.document, canvas_size, vis) catch null
|
||||||
|
else
|
||||||
|
self.render_engine.example(canvas_size, vis) catch null;
|
||||||
|
|
||||||
if (new_texture) |tex| {
|
if (new_texture) |tex| {
|
||||||
if (self.texture) |old_tex| {
|
if (self.texture) |old_tex| {
|
||||||
@@ -68,7 +76,7 @@ pub fn redrawExample(self: *Canvas) !void {
|
|||||||
|
|
||||||
pub fn exampleReset(self: *Canvas) !void {
|
pub fn exampleReset(self: *Canvas) !void {
|
||||||
self.render_engine.exampleReset();
|
self.render_engine.exampleReset();
|
||||||
try self.redrawExample();
|
try self.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setZoom(self: *Canvas, value: f32) void {
|
pub fn setZoom(self: *Canvas, value: f32) void {
|
||||||
@@ -88,14 +96,14 @@ pub fn processPendingRedraw(self: *Canvas) !void {
|
|||||||
if (!self._redraw_pending) return;
|
if (!self._redraw_pending) return;
|
||||||
if (self.redraw_throttle_ms == 0) {
|
if (self.redraw_throttle_ms == 0) {
|
||||||
self._redraw_pending = false;
|
self._redraw_pending = false;
|
||||||
try self.redrawExample();
|
try self.redraw();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const now_ms = std.time.milliTimestamp();
|
const now_ms = std.time.milliTimestamp();
|
||||||
const elapsed: i64 = if (self._last_redraw_time_ms == 0) self.redraw_throttle_ms else now_ms - self._last_redraw_time_ms;
|
const elapsed: i64 = if (self._last_redraw_time_ms == 0) self.redraw_throttle_ms else now_ms - self._last_redraw_time_ms;
|
||||||
if (elapsed < @as(i64, @intCast(self.redraw_throttle_ms))) return;
|
if (elapsed < @as(i64, @intCast(self.redraw_throttle_ms))) return;
|
||||||
self._redraw_pending = false;
|
self._redraw_pending = false;
|
||||||
try self.redrawExample();
|
try self.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getZoomedImageSize(self: Canvas) ImageRect {
|
pub fn getZoomedImageSize(self: Canvas) ImageRect {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ const std = @import("std");
|
|||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const dvui = @import("dvui");
|
const dvui = @import("dvui");
|
||||||
const RenderEngine = @import("RenderEngine.zig").RenderEngine;
|
const RenderEngine = @import("RenderEngine.zig").RenderEngine;
|
||||||
|
const Document = @import("../models/Document.zig");
|
||||||
|
const shape = @import("../models/shape/shape.zig");
|
||||||
const basic_models = @import("../models/basic_models.zig");
|
const basic_models = @import("../models/basic_models.zig");
|
||||||
const ImageSize = basic_models.ImageSize;
|
const ImageSize = basic_models.ImageSize;
|
||||||
const ImageRect = basic_models.ImageRect;
|
const ImageRect = basic_models.ImageRect;
|
||||||
@@ -171,3 +173,95 @@ pub fn example(self: CpuRenderEngine, canvas_size: ImageSize, visible_rect: Imag
|
|||||||
pub fn renderEngine(self: *CpuRenderEngine) RenderEngine {
|
pub fn renderEngine(self: *CpuRenderEngine) RenderEngine {
|
||||||
return .{ .cpu = self };
|
return .{ .cpu = self };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Растеризует документ: фон + контуры AABB всех объектов по getLocalBounds и position.
|
||||||
|
pub fn renderDocument(self: *CpuRenderEngine, document: *const Document, canvas_size: ImageSize, visible_rect: ImageRect) !?dvui.Texture {
|
||||||
|
const width = visible_rect.w;
|
||||||
|
const height = visible_rect.h;
|
||||||
|
const pixels = try self._allocator.alloc(Color.PMA, @as(usize, width) * height);
|
||||||
|
defer self._allocator.free(pixels);
|
||||||
|
|
||||||
|
const scale_x: f32 = if (document.size.width > 0) @as(f32, @floatFromInt(canvas_size.w)) / document.size.width else 0;
|
||||||
|
const scale_y: f32 = if (document.size.height > 0) @as(f32, @floatFromInt(canvas_size.h)) / document.size.height else 0;
|
||||||
|
|
||||||
|
// Фон
|
||||||
|
for (pixels) |*p| p.* = .{ .r = 255, .g = 255, .b = 255, .a = 255 };
|
||||||
|
|
||||||
|
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
|
||||||
|
for (document.objects.items) |*obj| {
|
||||||
|
const local_bounds = shape.getLocalBounds(obj) catch continue;
|
||||||
|
const pos = if (obj.getProperty(.position)) |p| p.position else basic_models.Point2{ .x = 0, .y = 0 };
|
||||||
|
const world_x = local_bounds.x + pos.x;
|
||||||
|
const world_y = local_bounds.y + pos.y;
|
||||||
|
const px_min_x: i32 = @intFromFloat(world_x * scale_x);
|
||||||
|
const px_min_y: i32 = @intFromFloat(world_y * scale_y);
|
||||||
|
const px_max_x: i32 = @intFromFloat((world_x + local_bounds.w) * scale_x);
|
||||||
|
const px_max_y: i32 = @intFromFloat((world_y + local_bounds.h) * scale_y);
|
||||||
|
|
||||||
|
const stroke = if (obj.getProperty(.stroke_rgba)) |s| rgbaToPma(s.stroke_rgba) else default_stroke;
|
||||||
|
drawRectOutline(pixels, width, height, visible_rect, px_min_x, px_min_y, px_max_x, px_max_y, stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
return try dvui.textureCreate(pixels, width, height, .nearest);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rgbaToPma(rgba: u32) Color.PMA {
|
||||||
|
const r: u8 = @intCast((rgba >> 0) & 0xFF);
|
||||||
|
const g: u8 = @intCast((rgba >> 8) & 0xFF);
|
||||||
|
const b: u8 = @intCast((rgba >> 16) & 0xFF);
|
||||||
|
const a: u8 = @intCast((rgba >> 24) & 0xFF);
|
||||||
|
if (a == 0) return .{ .r = 0, .g = 0, .b = 0, .a = 0 };
|
||||||
|
const af: f32 = @as(f32, @floatFromInt(a)) / 255.0;
|
||||||
|
return .{
|
||||||
|
.r = @intFromFloat(@as(f32, @floatFromInt(r)) * af),
|
||||||
|
.g = @intFromFloat(@as(f32, @floatFromInt(g)) * af),
|
||||||
|
.b = @intFromFloat(@as(f32, @floatFromInt(b)) * af),
|
||||||
|
.a = a,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drawRectOutline(
|
||||||
|
pixels: []Color.PMA,
|
||||||
|
buf_width: u32,
|
||||||
|
buf_height: u32,
|
||||||
|
visible_rect: ImageRect,
|
||||||
|
px_min_x: i32,
|
||||||
|
px_min_y: i32,
|
||||||
|
px_max_x: i32,
|
||||||
|
px_max_y: i32,
|
||||||
|
color: Color.PMA,
|
||||||
|
) void {
|
||||||
|
_ = buf_height;
|
||||||
|
const vx: i32 = @intCast(visible_rect.x);
|
||||||
|
const vy: i32 = @intCast(visible_rect.y);
|
||||||
|
const vw: i32 = @intCast(visible_rect.w);
|
||||||
|
const vh: i32 = @intCast(visible_rect.h);
|
||||||
|
|
||||||
|
const x0 = std.math.clamp(px_min_x, vx, vx + vw - 1);
|
||||||
|
const x1 = std.math.clamp(px_max_x, vx, vx + vw - 1);
|
||||||
|
const y0 = std.math.clamp(px_min_y, vy, vy + vh - 1);
|
||||||
|
const y1 = std.math.clamp(px_max_y, vy, vy + vh - 1);
|
||||||
|
|
||||||
|
const y_edges = [_]i32{ y0, y1 };
|
||||||
|
for (y_edges) |cy| {
|
||||||
|
if (cy < vy or cy >= vy + vh) continue;
|
||||||
|
const by: u32 = @intCast(cy - vy);
|
||||||
|
var cx = x0;
|
||||||
|
while (cx <= x1) : (cx += 1) {
|
||||||
|
if (cx < vx or cx >= vx + vw) continue;
|
||||||
|
const bx: u32 = @intCast(cx - vx);
|
||||||
|
pixels[by * buf_width + bx] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const x_edges = [_]i32{ x0, x1 };
|
||||||
|
for (x_edges) |cx| {
|
||||||
|
if (cx < vx or cx >= vx + vw) continue;
|
||||||
|
const bx: u32 = @intCast(cx - vx);
|
||||||
|
var cy = y0;
|
||||||
|
while (cy <= y1) : (cy += 1) {
|
||||||
|
if (cy < vy or cy >= vy + vh) continue;
|
||||||
|
const by: u32 = @intCast(cy - vy);
|
||||||
|
pixels[by * buf_width + bx] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const dvui = @import("dvui");
|
const dvui = @import("dvui");
|
||||||
const CpuRenderEngine = @import("CpuRenderEngine.zig");
|
const CpuRenderEngine = @import("CpuRenderEngine.zig");
|
||||||
|
const Document = @import("../models/Document.zig");
|
||||||
const basic_models = @import("../models/basic_models.zig");
|
const basic_models = @import("../models/basic_models.zig");
|
||||||
|
|
||||||
pub const RenderEngine = union(enum) {
|
pub const RenderEngine = union(enum) {
|
||||||
@@ -16,4 +17,11 @@ pub const RenderEngine = union(enum) {
|
|||||||
.cpu => |cpu_r| cpu_r.example(canvas_size, visible_rect),
|
.cpu => |cpu_r| cpu_r.example(canvas_size, visible_rect),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Растеризует документ в текстуру (размер и видимая область в пикселях холста).
|
||||||
|
pub fn render(self: RenderEngine, document: *const Document, canvas_size: basic_models.ImageSize, visible_rect: basic_models.ImageRect) !?dvui.Texture {
|
||||||
|
return switch (self) {
|
||||||
|
.cpu => |cpu_r| cpu_r.renderDocument(document, canvas_size, visible_rect),
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -112,13 +112,18 @@ pub fn leftPanel(ctx: *WindowContext) void {
|
|||||||
if (active_doc) |doc| {
|
if (active_doc) |doc| {
|
||||||
const canvas = &doc.canvas;
|
const canvas = &doc.canvas;
|
||||||
if (dvui.checkbox(@src(), &canvas.native_scaling, "Scaling", .{})) {}
|
if (dvui.checkbox(@src(), &canvas.native_scaling, "Scaling", .{})) {}
|
||||||
|
if (dvui.checkbox(@src(), &canvas.draw_document, "Draw document", .{})) {
|
||||||
|
canvas.requestRedraw();
|
||||||
|
}
|
||||||
|
if (!canvas.draw_document) {
|
||||||
if (dvui.button(@src(), if (doc.cpu_render.type == .Gradient) "Gradient" else "Squares", .{}, .{})) {
|
if (dvui.button(@src(), if (doc.cpu_render.type == .Gradient) "Gradient" else "Squares", .{}, .{})) {
|
||||||
if (doc.cpu_render.type == .Gradient) {
|
if (doc.cpu_render.type == .Gradient) {
|
||||||
doc.cpu_render.type = .Squares;
|
doc.cpu_render.type = .Squares;
|
||||||
} else {
|
} else {
|
||||||
doc.cpu_render.type = .Gradient;
|
doc.cpu_render.type = .Gradient;
|
||||||
}
|
}
|
||||||
canvas.redrawExample() catch {};
|
canvas.requestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dvui.label(@src(), "No document", .{}, .{});
|
dvui.label(@src(), "No document", .{}, .{});
|
||||||
|
|||||||
Reference in New Issue
Block a user