Parcourir la source

Init HelloWorld!

刘策 il y a 1 semaine
Parent
commit
516e5e84b5
12 fichiers modifiés avec 504 ajouts et 0 suppressions
  1. 50 0
      Readme.md
  2. BIN
      assets/font.ttf
  3. 104 0
      build.zig
  4. 37 0
      build.zig.zon
  5. 31 0
      newbuild.zig
  6. 1 0
      src/.gitignore
  7. 62 0
      src/canvas.zig
  8. 104 0
      src/main.zig
  9. 10 0
      src/root.zig
  10. 41 0
      src/tools.zig
  11. 56 0
      src/ui.zig
  12. 8 0
      src/utils.zig

+ 50 - 0
Readme.md

@@ -0,0 +1,50 @@
+Readme.md
+
+```
+drawing_app/
+├── src/
+│   ├── main.zig          // 主程序入口
+│   ├── canvas.zig        // 画布逻辑
+│   ├── tools.zig         // 绘图工具(画笔、橡皮擦等)
+│   ├── ui.zig            // UI 组件(颜色选择、工具栏等)
+│   └── utils.zig         // 通用工具函数
+├── build.zig             // Zig 构建文件
+├── assets/               // 静态资源(如图标、默认画布等)
+│   └── icon.png
+└── README.md
+
+
+```
+
+# Drawing App
+
+A simple drawing application built with Zig and SDL2, inspired by Windows Paint and Photoshop.
+
+## Features
+- Basic brush and eraser tools
+- Adjustable brush size
+- Color selection (R/G/B)
+- Canvas rendering with SDL2
+
+## Requirements
+- Zig compiler (0.13.0 or later)
+- SDL2 and SDL2_ttf libraries
+- A TTF font file in `assets/font.ttf` (e.g., download from Google Fonts)
+
+## Build and Run
+1. Install dependencies (see Requirements).
+2. Run `zig build run` in the project directory.
+
+## Controls
+- Left Mouse: Draw with selected tool
+- B: Select Brush
+- E: Select Eraser
+- +/-: Adjust brush size
+- R/G/B: Change color to Red/Green/Blue
+
+## Future Improvements
+- Save/load canvas
+- Undo/redo
+- Layers
+- Advanced tools (e.g., selection, fill)
+

BIN
assets/font.ttf


+ 104 - 0
build.zig

@@ -0,0 +1,104 @@
+const std = @import("std");
+
+// Although this function looks imperative, note that its job is to
+// declaratively construct a build graph that will be executed by an external
+// runner.
+pub fn build(b: *std.Build) void {
+    // Standard target options allows the person running `zig build` to choose
+    // what target to build for. Here we do not override the defaults, which
+    // means any target is allowed, and the default is native. Other options
+    // for restricting supported target set are available.
+    const target = b.standardTargetOptions(.{});
+
+    // Standard optimization options allow the person running `zig build` to select
+    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
+    // set a preferred release mode, allowing the user to decide how to optimize.
+    const optimize = b.standardOptimizeOption(.{});
+
+    const lib = b.addStaticLibrary(.{
+        .name = "sdl-window-test",
+        // In this case the main source file is merely a path, however, in more
+        // complicated build scripts, this could be a generated file.
+        .root_source_file = b.path("src/root.zig"),
+        .target = target,
+        .optimize = optimize,
+    });
+
+    // This declares intent for the library to be installed into the standard
+    // location when the user invokes the "install" step (the default step when
+    // running `zig build`).
+    b.installArtifact(lib);
+
+    const exe = b.addExecutable(.{
+        .name = "sdl-window-test",
+        .root_source_file = b.path("src/main.zig"),
+        .target = target,
+        .optimize = optimize,
+    });
+
+    if (target.result.os.tag == .linux) {
+        // The SDL package doesn't work for Linux yet, so we rely on system
+        // packages for now.
+        exe.linkSystemLibrary("SDL2");
+        exe.linkLibC();
+    } else {
+        const sdl_dep = b.dependency("SDL", .{
+            .optimize = .ReleaseFast,
+            .target = target,
+        });
+        exe.linkLibrary(sdl_dep.artifact("SDL2"));
+    }
+
+    // This declares intent for the executable to be installed into the
+    // standard location when the user invokes the "install" step (the default
+    // step when running `zig build`).
+    b.installArtifact(exe);
+
+    // This *creates* a Run step in the build graph, to be executed when another
+    // step is evaluated that depends on it. The next line below will establish
+    // such a dependency.
+    const run_cmd = b.addRunArtifact(exe);
+
+    // By making the run step depend on the install step, it will be run from the
+    // installation directory rather than directly from within the cache directory.
+    // This is not necessary, however, if the application depends on other installed
+    // files, this ensures they will be present and in the expected location.
+    run_cmd.step.dependOn(b.getInstallStep());
+
+    // This allows the user to pass arguments to the application in the build
+    // command itself, like this: `zig build run -- arg1 arg2 etc`
+    if (b.args) |args| {
+        run_cmd.addArgs(args);
+    }
+
+    // This creates a build step. It will be visible in the `zig build --help` menu,
+    // and can be selected like this: `zig build run`
+    // This will evaluate the `run` step rather than the default, which is "install".
+    const run_step = b.step("run", "Run the app");
+    run_step.dependOn(&run_cmd.step);
+
+    // Creates a step for unit testing. This only builds the test executable
+    // but does not run it.
+    const lib_unit_tests = b.addTest(.{
+        .root_source_file = b.path("src/root.zig"),
+        .target = target,
+        .optimize = optimize,
+    });
+
+    const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
+
+    const exe_unit_tests = b.addTest(.{
+        .root_source_file = b.path("src/main.zig"),
+        .target = target,
+        .optimize = optimize,
+    });
+
+    const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+    // Similar to creating the run step earlier, this exposes a `test` step to
+    // the `zig build --help` menu, providing a way for the user to request
+    // running the unit tests.
+    const test_step = b.step("test", "Run unit tests");
+    test_step.dependOn(&run_lib_unit_tests.step);
+    test_step.dependOn(&run_exe_unit_tests.step);
+}

+ 37 - 0
build.zig.zon

@@ -0,0 +1,37 @@
+.{
+    .name = "sdl_window_test",
+    // This is a [Semantic Version](https://semver.org/).
+    // In a future version of Zig it will be used for package deduplication.
+    .version = "0.0.0",
+
+    .fingerprint = 0x9d6f711a37cd1ba7,
+    // This field is optional.
+    // This is currently advisory only; Zig does not yet do anything
+    // with this value.
+    //.minimum_zig_version = "0.11.0",
+
+    // This field is optional.
+    // Each dependency must either provide a `url` and `hash`, or a `path`.
+    // `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
+    // Once all dependencies are fetched, `zig build` no longer requires
+    // internet connectivity.
+    .dependencies = .{
+        .SDL = .{
+            .url = "https://github.com/pwbh/SDL/archive/refs/tags/release-2.30.3.tar.gz",
+            .hash ="12203d06a751586e5cf3f8b61f9a0012c70ada050ce33b6bf79d4741902f9c344239",
+        },
+    },
+    .paths = .{
+        // This makes *all* files, recursively, included in this package. It is generally
+        // better to explicitly list the files and directories instead, to insure that
+        // fetching from tarballs, file system paths, and version control all result
+        // in the same contents hash.
+        "",
+        // For example...
+        //"build.zig",
+        //"build.zig.zon",
+        //"src",
+        //"LICENSE",
+        //"README.md",
+    },
+}

+ 31 - 0
newbuild.zig

@@ -0,0 +1,31 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+    const target = b.standardTargetOptions(.{});
+    const optimize = b.standardOptimizeOption(.{});
+
+    const exe = b.addExecutable(.{
+        .name = "drawing_app",
+        .root_source_file = b.path("src/main.zig"),
+        .target = target,
+        .optimize = optimize,
+    });
+
+    // 链接 SDL2 和 SDL2_ttf
+    exe.linkSystemLibrary("SDL2");
+    exe.linkSystemLibrary("SDL2_ttf");
+    exe.linkLibC();
+
+    // 安装可执行文件
+    b.installArtifact(exe);
+
+    // 运行命令
+    const run_cmd = b.addRunArtifact(exe);
+    run_cmd.step.dependOn(b.getInstallStep());
+    if (b.args) |args| {
+        run_cmd.addArgs(args);
+    }
+
+    const run_step = b.step("run", "Run the app");
+    run_step.dependOn(&run_cmd.step);
+}

+ 1 - 0
src/.gitignore

@@ -0,0 +1 @@
+.zig-cache

+ 62 - 0
src/canvas.zig

@@ -0,0 +1,62 @@
+const std = @import("std");
+const sdl = @cImport({
+    @cInclude("SDL2/SDL.h");
+});
+
+pub const Canvas = struct {
+    allocator: std.mem.Allocator,
+    pixels: []u32, // 存储画布像素数据(RGBA)
+    width: u32,
+    height: u32,
+    texture: *sdl.SDL_Texture,
+
+    pub fn init(allocator: std.mem.Allocator, renderer: *sdl.SDL_Renderer, width: u32, height: u32) !Canvas {
+        var pixels = try allocator.alloc(u32, width * height);
+        @memset(pixels, 0xFFFFFFFF); // 初始化为白色
+
+        const texture = sdl.SDL_CreateTexture(
+            renderer,
+            sdl.SDL_PIXELFORMAT_RGBA8888,
+            sdl.SDL_TEXTUREACCESS_STREAMING,
+            @intCast(width),
+            @intCast(height),
+        ) orelse {
+            return error.TextureCreationFailed;
+        };
+
+        pixels[0] = 0xFFFFFFFE;
+
+        return Canvas{
+            .allocator = allocator,
+            .pixels = pixels,
+            .width = width,
+            .height = height,
+            .texture = texture,
+        };
+    }
+
+    pub fn deinit(self: *Canvas) void {
+        self.allocator.free(self.pixels);
+        sdl.SDL_DestroyTexture(self.texture);
+    }
+
+    pub fn setPixel(self: *Canvas, x: i32, y: i32, color: u32) void {
+        if (x >= 0 and x < self.width and y >= 0 and y < self.height) {
+            // const index = @intCast(usize)(y * @as(i32, self.width) + x);
+            // self.pixels[index] = color;
+            self.pixels[@intCast(y * self.width + x)] = color;
+        }
+    }
+
+    pub fn render(self: *Canvas, renderer: *sdl.SDL_Renderer) !void {
+        var pixels: ?*anyopaque = undefined;
+        var pitch: c_int = undefined;
+        if (sdl.SDL_LockTexture(self.texture, null, &pixels, &pitch) != 0) {
+            return error.TextureLockFailed;
+        }
+        defer sdl.SDL_UnlockTexture(self.texture);
+
+        @memcpy(@as([*]u8, @ptrCast(pixels.?))[0 .. self.pixels.len * 4], self.pixels);
+        _ = sdl.SDL_RenderCopy(renderer, self.texture, null, null);
+    }
+};

+ 104 - 0
src/main.zig

@@ -0,0 +1,104 @@
+const std = @import("std");
+
+const c = @cImport({
+    @cInclude("SDL2/SDL.h");
+//    @cInclude("SDL2/SDL_ttf.h");
+});
+
+const Canvas = @import("canvas.zig").Canvas;
+const Tools = @import("tools.zig").Tools;
+// const UI = @import("ui.zig").UI;
+
+
+pub fn main() !void {
+    // 初始化 SDL
+    if (c.SDL_Init(c.SDL_INIT_VIDEO) != 0) {
+        c.SDL_Log("Unable to initialize SDL: %s", c.SDL_GetError());
+        
+        return error.SDLInitializationFailed;
+    }
+    defer c.SDL_Quit();
+
+    //// 初始化 SDL_ttf
+    //if (c.TTF_Init() != 0) {
+    //    std.log.err("TTF_Init failed: {s}", .{c.TTF_GetError()});
+    //    return error.TTFInitFailed;
+    //}
+    //defer c.TTF_Quit();
+
+
+    const screen = c.SDL_CreateWindow(
+            "TurboZig v0.0.1", 
+            c.SDL_WINDOWPOS_UNDEFINED, 
+            c.SDL_WINDOWPOS_UNDEFINED, 
+            1024, 768, 
+            c.SDL_WINDOW_OPENGL) orelse //c.SDL_WINDOW_SHOWN | c.SDL_WINDOW_RESIZABLE,
+        {
+            c.SDL_Log("Unable to create window: %s", c.SDL_GetError());
+            return error.SDLInitializationFailed;
+        };
+    defer c.SDL_DestroyWindow(screen);
+
+    const renderer = c.SDL_CreateRenderer(screen, -1, 0) orelse {
+        c.SDL_Log("Unable to create renderer: %s", c.SDL_GetError());
+        return error.SDLInitializationFailed;
+    };
+    defer c.SDL_DestroyRenderer(renderer);
+
+    // 初始化画布和工具
+    var canvas = try Canvas.init(std.heap.page_allocator, renderer, 800, 600);
+    defer canvas.deinit();
+    var tools = Tools.init();
+    // var ui = try UI.init(renderer);
+
+    // 主循环
+    var quit = false;
+
+    while (!quit) {
+        var event: c.SDL_Event = undefined;
+
+        while (c.SDL_PollEvent(&event) != 0) {
+            switch (event.type) {
+                c.SDL_QUIT => {
+                    quit = true;
+                },
+                c.SDL_MOUSEBUTTONDOWN => {
+                    if (event.button.button == c.SDL_BUTTON_LEFT) {
+                        tools.handleMouseDown(event.button.x, event.button.y, &canvas);
+                    }
+                },
+                c.SDL_MOUSEMOTION => {
+                    if (event.motion.state & c.SDL_BUTTON_LMASK != 0) {
+                        tools.handleMouseMotion(event.motion.x, event.motion.y, &canvas);
+                    }
+                },
+                c.SDL_KEYDOWN => {
+                    // try ui.handleKeyDown(event.key.keysym.sym, &tools, &canvas);
+                    c.SDL_Log("Key Pressed: %s", event.key.keysym.sym);
+                },
+                else => {},
+            }
+        }
+
+        // 渲染
+        _ = c.SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
+        _ = c.SDL_RenderClear(renderer);
+        try canvas.render(renderer);
+        // try ui.render(renderer);
+        c.SDL_RenderPresent(renderer);
+
+        var delay: i32 = 0;
+        if(delay>1000)
+        {
+            quit = true;
+        }
+        else
+        {
+            delay += 1;
+            c.SDL_Delay(1000);
+        }
+
+    }
+}
+
+

+ 10 - 0
src/root.zig

@@ -0,0 +1,10 @@
+const std = @import("std");
+const testing = std.testing;
+
+export fn add(a: i32, b: i32) i32 {
+    return a + b;
+}
+
+test "basic add functionality" {
+    try testing.expect(add(3, 7) == 10);
+}

+ 41 - 0
src/tools.zig

@@ -0,0 +1,41 @@
+const std = @import("std");
+const Canvas = @import("canvas.zig").Canvas;
+
+pub const ToolType = enum {
+    Brush,
+    Eraser,
+};
+
+pub const Tools = struct {
+    current_tool: ToolType,
+    brush_size: u32,
+    color: u32, // RGBA 格式
+
+    pub fn init() Tools {
+        return Tools{
+            .current_tool = .Brush,
+            .brush_size = 5,
+            .color = 0xFF0000FF, // 默认红色
+        };
+    }
+
+    pub fn handleMouseDown(self: *Tools, x: i32, y: i32, canvas: *Canvas) void {
+        self.draw(x, y, canvas);
+    }
+
+    pub fn handleMouseMotion(self: *Tools, x: i32, y: i32, canvas: *Canvas) void {
+        self.draw(x, y, canvas);
+    }
+
+    fn draw(self: *Tools, x: i32, y: i32, canvas: *Canvas) void {
+        const color = if (self.current_tool == .Eraser) 0xFFFFFFFF else self.color;
+        const size = self.brush_size;
+        var dy: i32 = -@as(i32, @intCast(size / 2));
+        while (dy <= size / 2) : (dy += 1) {
+            var dx: i32 = -@as(i32, @intCast(size / 2));
+            while (dx <= size / 2) : (dx += 1) {
+                canvas.setPixel(x + dx, y + dy, color);
+            }
+        }
+    }
+};

+ 56 - 0
src/ui.zig

@@ -0,0 +1,56 @@
+const std = @import("std");
+const sdl = @cImport({
+    @cInclude("SDL2/SDL.h");
+    //@cInclude("SDL2/SDL_ttf.h");
+});
+const Tools = @import("tools.zig").Tools;
+const Canvas = @import("canvas.zig").Canvas;
+
+pub const UI = struct {
+    // font: *sdl.TTF_Font,
+
+    pub fn init(renderer: *sdl.SDL_Renderer) !UI {
+        //const font = sdl.TTF_OpenFont("assets/font.ttf", 16) orelse {
+        //    return error.FontLoadFailed;
+        //};
+
+        // .font = font
+        return UI{};
+    }
+
+    pub fn deinit(self: *UI) void {
+        // sdl.TTF_CloseFont(self.font);
+    }
+
+    pub fn handleKeyDown(self: *UI, key: sdl.SDL_Keycode, tools: *Tools, canvas: *Canvas) !void {
+        _ = self;
+        _ = canvas;
+        switch (key) {
+            sdl.SDLK_b => tools.current_tool = .Brush,
+            sdl.SDLK_e => tools.current_tool = .Eraser,
+            sdl.SDLK_PLUS, sdl.SDLK_EQUALS => tools.brush_size += 1,
+            sdl.SDLK_MINUS => if (tools.brush_size > 1) tools.brush_size -= 1,
+            sdl.SDLK_r => tools.color = 0xFF0000FF, // 红色
+            sdl.SDLK_g => tools.color = 0x00FF00FF, // 绿色
+            sdl.SDLK_b => tools.color = 0x0000FFFF, // 蓝色
+            else => {},
+        }
+    }
+
+    pub fn render(self: *UI, renderer: *sdl.SDL_Renderer) !void {
+        const text = "Tools: B (Brush), E (Eraser), +/- (Size), R/G/B (Color)";
+        const color = sdl.SDL_Color{ .r = 0, .g = 0, .b = 0, .a = 255 };
+        //const surface = sdl.TTF_RenderText_Solid(self.font, text, color) orelse {
+        //    return error.TextRenderFailed;
+        //};
+        defer sdl.SDL_FreeSurface(surface);
+
+        const texture = sdl.SDL_CreateTextureFromSurface(renderer, surface) orelse {
+            return error.TextureCreationFailed;
+        };
+        defer sdl.SDL_DestroyTexture(texture);
+
+        var dst = sdl.SDL_Rect{ .x = 10, .y = 10, .w = surface.*.w, .h = surface.*.h };
+        _ = sdl.SDL_RenderCopy(renderer, texture, null, &dst);
+    }
+};

+ 8 - 0
src/utils.zig

@@ -0,0 +1,8 @@
+const std = @import("std");
+
+pub fn saveCanvas(canvas: *const Canvas, path: []const u8) !void {
+    // TODO: 实现保存画布为 PNG 文件的功能
+    _ = canvas;
+    _ = path;
+    std.log.info("Saving not implemented yet", .{});
+}