diff --git a/src/Canvas.zig b/src/Canvas.zig new file mode 100644 index 0000000..3e20140 --- /dev/null +++ b/src/Canvas.zig @@ -0,0 +1,82 @@ +const std = @import("std"); +const dvui = @import("dvui"); +const Color = dvui.Color; + +const Canvas = @This(); + +allocator: std.mem.Allocator, +texture: ?dvui.Texture = null, +width: u32 = 400, +height: u32 = 300, +pos: dvui.Point = dvui.Point{ .x = 0, .y = 0 }, +scroll: dvui.ScrollInfo = .{ + .vertical = .given, + .horizontal = .given, + .virtual_size = .{ .w = 2000, .h = 2000 }, +}, + +pub fn init(allocator: std.mem.Allocator) Canvas { + return .{ .allocator = allocator }; +} + +pub fn deinit(self: *Canvas) void { + if (self.texture) |texture| { + dvui.Texture.destroyLater(texture); + self.texture = null; + } +} + +/// Заполнить canvas случайным цветом на CPU +pub fn fillRandomColor(self: *Canvas) !void { + var prng = std.Random.DefaultPrng.init(@intCast(std.time.microTimestamp())); + const random = prng.random(); + + // Выделить буфер пиксельных данных + const pixels = try self.allocator.alloc(Color.PMA, @as(usize, self.width) * self.height); + defer self.allocator.free(pixels); + + // Заполнить случайными цветами + const r = random.int(u8); + const g = random.int(u8); + const b = random.int(u8); + + var prev: dvui.Color.PMA = .{ .r = r, .g = g, .b = b, .a = 255 }; + for (pixels) |*pixel| { + const r_delta = random.intRangeAtMost(i16, -1, 1); + const g_delta = random.intRangeAtMost(i16, -1, 1); + const b_delta = random.intRangeAtMost(i16, -1, 1); + + const r_new: i16 = @as(i16, prev.r) + r_delta; + const g_new: i16 = @as(i16, prev.g) + g_delta; + const b_new: i16 = @as(i16, prev.b) + b_delta; + + pixel.* = .{ + .r = @intCast(std.math.clamp(r_new, 0, 255)), + .g = @intCast(std.math.clamp(g_new, 0, 255)), + .b = @intCast(std.math.clamp(b_new, 0, 255)), + .a = 255, + }; + prev = pixel.*; + } + + // Удалить старую текстуру + if (self.texture) |tex| { + dvui.Texture.destroyLater(tex); + } + + // Создать новую текстуру из пиксельных данных + self.texture = try dvui.textureCreate(pixels, self.width, self.height, .linear); + + // Дать скроллам ощутимый диапазон сразу (минимум 2000x2000) + self.scroll.virtual_size = .{ + .w = @max(2000, @as(f32, @floatFromInt(self.width))), + .h = @max(2000, @as(f32, @floatFromInt(self.height))), + }; +} + +/// Отобразить canvas в UI +pub fn render(self: Canvas, rect: dvui.Rect.Physical) !void { + if (self.texture) |texture| { + try dvui.renderTexture(texture, .{ .r = rect }, .{}); + } +} diff --git a/src/WindowContext.zig b/src/WindowContext.zig index c8bd763..89c1cc5 100644 --- a/src/WindowContext.zig +++ b/src/WindowContext.zig @@ -1,88 +1,28 @@ const std = @import("std"); const dvui = @import("dvui"); -const Color = dvui.Color; +const Canvas = @import("Canvas.zig"); const WindowContext = @This(); allocator: std.mem.Allocator, -canvas_texture: ?dvui.Texture = null, -canvas_width: u32 = 400, -canvas_height: u32 = 300, -canvas_pos: dvui.Point = dvui.Point{ .x = 0, .y = 0 }, -canvas_scroll: dvui.ScrollInfo = .{ - .vertical = .given, - .horizontal = .given, - .virtual_size = .{ .w = 2000, .h = 2000 }, -}, +canvas: Canvas, pub fn init(allocator: std.mem.Allocator) WindowContext { return .{ .allocator = allocator, + .canvas = Canvas.init(allocator), }; } -/// Заполнить canvas случайным цветом на CPU pub fn fillRandomColor(self: *WindowContext) !void { - var prng = std.Random.DefaultPrng.init(@intCast(std.time.microTimestamp())); - const random = prng.random(); - - // Выделить буфер пиксельных данных - const pixels = try self.allocator.alloc(Color.PMA, @as(usize, self.canvas_width) * self.canvas_height); - defer self.allocator.free(pixels); - - // Заполнить случайными цветами - const r = random.int(u8); - const g = random.int(u8); - const b = random.int(u8); - - var prev: dvui.Color.PMA = .{ - .r = r, - .g = g, - .b = b, - .a = 255, - }; - for (pixels) |*pixel| { - const r_delta = random.intRangeAtMost(i16, -1, 1); - const g_delta = random.intRangeAtMost(i16, -1, 1); - const b_delta = random.intRangeAtMost(i16, -1, 1); - - const r_new: i16 = @as(i16, prev.r) + r_delta; - const g_new: i16 = @as(i16, prev.g) + g_delta; - const b_new: i16 = @as(i16, prev.b) + b_delta; - - pixel.* = .{ - .r = @intCast(std.math.clamp(r_new, 0, 255)), - .g = @intCast(std.math.clamp(g_new, 0, 255)), - .b = @intCast(std.math.clamp(b_new, 0, 255)), - .a = 255, - }; - prev = pixel.*; - } - - // Удалить старую текстуру - if (self.canvas_texture) |tex| { - dvui.Texture.destroyLater(tex); - } - - // Создать новую текстуру из пиксельных данных - self.canvas_texture = try dvui.textureCreate(pixels, self.canvas_width, self.canvas_height, .linear); - - // Дать скроллам ощутимый диапазон сразу (минимум 2000x2000) - self.canvas_scroll.virtual_size = .{ - .w = @max(2000, @as(f32, @floatFromInt(self.canvas_width))), - .h = @max(2000, @as(f32, @floatFromInt(self.canvas_height))), - }; + try self.canvas.fillRandomColor(); } /// Отобразить canvas в UI pub fn render(self: WindowContext, rect: dvui.Rect.Physical) !void { - if (self.canvas_texture) |texture| { - try dvui.renderTexture(texture, .{ .r = rect }, .{}); - } + try self.canvas.render(rect); } pub fn deinit(self: *WindowContext) void { - if (self.canvas_texture) |texture| { - dvui.Texture.destroyLater(texture); - } + self.canvas.deinit(); } diff --git a/src/main.zig b/src/main.zig index d0f91ef..a431f01 100644 --- a/src/main.zig +++ b/src/main.zig @@ -90,7 +90,7 @@ fn gui_frame(ctx: *WindowContext) bool { ctx.fillRandomColor() catch |err| { std.debug.print("Error filling canvas: {}\n", .{err}); }; - ctx.canvas_pos = .{ .x = 400, .y = 400 }; + ctx.canvas.pos = .{ .x = 400, .y = 400 }; } } left_panel.deinit(); @@ -128,7 +128,7 @@ fn gui_frame(ctx: *WindowContext) bool { var scroll = dvui.scrollArea( @src(), .{ - .scroll_info = &ctx.canvas_scroll, + .scroll_info = &ctx.canvas.scroll, .vertical_bar = .auto, .horizontal_bar = .auto, }, @@ -137,14 +137,14 @@ fn gui_frame(ctx: *WindowContext) bool { { // Отобразить canvas внутри scroll area. // ScrollArea сам двигает дочерние виджеты, поэтому margin не нужен. - if (ctx.canvas_texture) |texture| { + if (ctx.canvas.texture) |texture| { _ = dvui.image(@src(), .{ .source = .{ .texture = texture }, }, .{ - .margin = .{ .x = ctx.canvas_pos.x, .y = ctx.canvas_pos.y }, + .margin = .{ .x = ctx.canvas.pos.x, .y = ctx.canvas.pos.y }, .min_size_content = .{ - .w = @floatFromInt(ctx.canvas_width), - .h = @floatFromInt(ctx.canvas_height), + .w = @floatFromInt(ctx.canvas.width), + .h = @floatFromInt(ctx.canvas.height), }, }); }