diff --git a/src/color_theme.zig b/src/color_theme.zig new file mode 100644 index 0000000..d68ce99 --- /dev/null +++ b/src/color_theme.zig @@ -0,0 +1,25 @@ +const zig_sdl = @import("zig-sdl"); +const sdl = zig_sdl.sdl; + +pub const ColorTheme = struct { + bg: sdl.Color, + fg: sdl.Color, + treeBg: sdl.Color, + treeFileFg: sdl.Color, + treeFolderFg: sdl.Color, + treeEntryHighlightBg: sdl.Color, + + fn init() ColorTheme { + var fg = sdl.Color{ .r = 56, .g = 58, .b = 68 }; + return ColorTheme{ + .bg = sdl.Color{ .r = 250, .g = 250, .b = 250 }, + .fg = fg, + .treeBg = sdl.Color{ .r = 234, .g = 234, .b = 235 }, + .treeFileFg = fg, + .treeFolderFg = sdl.Color{ .r = 64, .g = 120, .b = 242 }, + .treeEntryHighlightBg = sdl.Color{ .r = 229, .g = 229, .b = 229 }, + }; + } +}; + +pub const colorTheme = ColorTheme.init(); diff --git a/src/cursor_manager.zig b/src/cursor_manager.zig new file mode 100644 index 0000000..95de9ec --- /dev/null +++ b/src/cursor_manager.zig @@ -0,0 +1,51 @@ +const std = @import("std"); +const zig_sdl = @import("zig-sdl"); +const sdl = zig_sdl.sdl; + +pub const CursorManager = struct { + currentCursor: sdl.SystemCursor, + activeCursor: ?sdl.SystemCursor, + + cursorArrow: sdl.Cursor, + cursorWait: sdl.Cursor, + cursorHand: sdl.Cursor, + + pub fn init() !CursorManager { + return CursorManager{ + .currentCursor = .Arrow, + .activeCursor = null, + + .cursorArrow = try sdl.Cursor.initFromSystemCursor(.Arrow), + .cursorWait = try sdl.Cursor.initFromSystemCursor(.Wait), + .cursorHand = try sdl.Cursor.initFromSystemCursor(.Hand), + }; + } + + pub fn deinit(self: *CursorManager) void { + self.cursorArrow.deinit(); + self.cursorWait.deinit(); + self.cursorHand.deinit(); + } + + pub fn set(self: *CursorManager, comptime cursor: sdl.SystemCursor) void { + self.currentCursor = switch (cursor) { + .Arrow, .Wait, .Hand => cursor, + else => @compileError("Invalid cursor: " ++ cursor), + }; + } + + pub fn update(self: *CursorManager) !void { + if (self.activeCursor == null or self.activeCursor.? != self.currentCursor) { + var cursor = switch (self.currentCursor) { + .Arrow => self.cursorArrow, + .Wait => self.cursorWait, + .Hand => self.cursorHand, + else => return error.InvalidCursor, + }; + cursor.activate(); + self.activeCursor = self.currentCursor; + } + + self.currentCursor = .Arrow; + } +}; diff --git a/src/docview.zig b/src/docview.zig new file mode 100644 index 0000000..bbbdf1b --- /dev/null +++ b/src/docview.zig @@ -0,0 +1,100 @@ +const std = @import("std"); +const zig_sdl = @import("zig-sdl"); +const sdl = zig_sdl.sdl; + +const Ui = @import("ui.zig").Ui; +const TreeView = @import("treeview.zig").TreeView; +const colorTheme = @import("color_theme.zig").colorTheme; + +pub const DocView = struct { + pub const Line = struct { + const height: i32 = 20; + + string: []const u8, + + texture: ?sdl.Texture = null, + + y: i32 = 0, + textWidth: i32 = 0, + textHeight: i32 = 0, + + fn prerender(self: *Line, ui: *Ui) !void { + if (self.texture) |texture| { + texture.deinit(); + } + + var color = colorTheme.fg; + var line = try std.mem.dupeZ(std.heap.c_allocator, u8, self.string); + var surface = try ui.codeFont.renderBlended(line, color); + defer surface.deinit(); + + self.textWidth = surface.width(); + self.textHeight = surface.height(); + std.debug.warn("surface size: {}x{}\n", .{ self.textWidth, self.textHeight }); + self.texture = try ui.renderer.createTextureFromSurface(surface); + } + + pub fn render(self: *Line, ui: *Ui) !void { + if (self.string.len == 0) { + return; + } + + if (self.texture == null) { + try self.prerender(ui); + } + + var dst = sdl.Rect{ + .x = ui.scale(i32, DocView.x + 10), + .y = ui.scale(i32, self.y), + .w = self.textWidth, + .h = self.textHeight, + }; + if (self.texture) |texture| { + try ui.renderer.copy(texture, null, dst); + } + } + }; + + pub const x = TreeView.width + 10; + + path: ?[]const u8 = null, + + // TODO Do allocator stuff + count: u64 = 0, + lines: [4096]Line = undefined, + + pub fn init() DocView { + return DocView{}; + } + + pub fn render(self: *DocView, ui: *Ui) !void { + for (self.lines[0..self.count]) |*line| { + try line.render(ui); + } + } + + pub fn open(self: *DocView, path: []const u8) !void { + self.path = path; + + const file = std.fs.cwd().openFile(self.path.?, .{}) catch |err| { + std.debug.warn("unable to open file: {}\n", .{err}); + return; + }; + defer file.close(); + + var line_buf: [1024 * 1024]u8 = undefined; + var len = file.readAll(&line_buf) catch unreachable; + + var iterator = std.mem.split(line_buf[0..len], "\n"); + var index: u32 = 0; + while (iterator.next()) |line| { + std.debug.warn("line {}: '{}'\n", .{ index, line }); + self.lines[index] = Line{ + .string = line, + .y = @bitCast(i32, index) * Line.height + 10, + }; + index += 1; + } + self.count = index; + } +}; diff --git a/src/main.zig b/src/main.zig index 03bd454..fff6484 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,174 +1,33 @@ const std = @import("std"); -const sdl = @import("sdl.zig"); -const ttf = @import("ttf.zig"); -const dirent = @cImport(@cInclude("dirent.h")); +const zig_sdl = @import("zig-sdl"); +const sdl = zig_sdl.sdl; +const ttf = zig_sdl.ttf; -const Ui = struct { - window: sdl.Window, - renderer: sdl.Renderer, - - uiFont: ttf.Font, - codeFont: ttf.Font, - - treeWidget: TreeView, - - pub fn init() !Ui { - try sdl.init(); - var window = try sdl.Window.init("Zite", 1280, 720, sdl.Window.Shown); - var renderer = try window.createRenderer(null, 0); - - try ttf.init(); - var uiFont = try ttf.Font.init("/usr/share/fonts/ubuntu/Ubuntu-R.ttf", 16); - var codeFont = try ttf.Font.init("/usr/share/fonts/TTF/Cascadia.ttf", 16); - - var treeWidget = TreeView.init(); - - return Ui{ - .window = window, - .renderer = renderer, - - .uiFont = uiFont, - .codeFont = codeFont, - - .treeWidget = treeWidget, - }; - } - - pub fn deinit(self: *Ui) void { - self.treeWidget.deinit(); - - self.uiFont.deinit(); - self.codeFont.deinit(); - ttf.quit(); - - self.renderer.deinit(); - self.window.deinit(); - sdl.quit(); - } - - pub fn update(self: *Ui) !void { - while (sdl.Event.poll()) |event| { - var type_ = event.type_(); - // std.debug.warn("event: {}\n", .{@tagName(type_)}); - - switch (type_) { - .Quit => return error.Exiting, - .MouseMotion => { - var motion = event.mouseMotion().?; - self.treeWidget.mouseMotion(motion); - }, - else => {}, - } - } - } - - pub fn render(self: *Ui) !void { - try self.renderer.setDrawColor(.{ .r = 30, .g = 30, .b = 30 }); - try self.renderer.clear(); - - try self.treeWidget.render(self.*); - - self.renderer.present(); - } -}; - -const TreeView = struct { - const Entry = struct { - name: []const u8, - texture: ?sdl.Texture = null, - y: i32, - width: i32 = 0, - height: i32 = 0, - - highlighted: bool = false, - - fn prerender(self: *Entry, ui: Ui) !void { - var surface = try ui.uiFont.renderBlended(self.name, .{ .r = 255, .g = 255, .b = 255 }); - self.texture = try ui.renderer.createTextureFromSurface(surface); - self.width = surface.width(); - self.height = surface.height(); - - // We don't need the surface anymore. - surface.deinit(); - } - - pub fn render(self: *Entry, ui: Ui) !void { - if (self.texture == null) { - try self.prerender(ui); - } - - if (self.highlighted) { - try ui.renderer.setDrawColor(.{ .r = 42, .g = 45, .b = 46 }); - try ui.renderer.fillRect(sdl.Rect{ .x = 0, .y = self.y, .w = 200, .h = 25 }); - } - - var y = @divTrunc(25 - self.height, 2); - var dst = sdl.Rect{ .x = 10, .y = self.y + y, .w = self.width, .h = self.height }; - try ui.renderer.copy(self.texture.?, null, dst); - } - - pub fn mouseMotion(self: *Entry, event: sdl.MouseMotionEvent) void { - self.highlighted = event.y > self.y and event.y < self.y + 20; - } - }; - - cursorHand: *sdl.c.SDL_Cursor, - count: u64 = 0, - - // Temporary, std.heap.c_allocator was broken, so this for now - entries: [256]Entry = undefined, - - pub fn init() TreeView { - var cursorHand = sdl.c.SDL_CreateSystemCursor(@intToEnum(sdl.c.SDL_SystemCursor, sdl.c.SDL_SYSTEM_CURSOR_HAND)).?; - sdl.c.SDL_SetCursor(cursorHand); - var self = TreeView{ .cursorHand = cursorHand }; - - self.refresh() catch unreachable; - return self; - } - - pub fn deinit(self: TreeView) void { - sdl.c.SDL_FreeCursor(self.cursorHand); - for (self.entries[0..self.count]) |entry| { - if (entry.texture) |texture| { - texture.deinit(); - } - } - } - - pub fn render(self: *TreeView, ui: Ui) !void { - try ui.renderer.setDrawColor(.{ .r = 38, .g = 38, .b = 38 }); - try ui.renderer.fillRect(sdl.Rect{ .x = 0, .y = 0, .w = 200, .h = 720 }); - - for (self.entries[0..self.count]) |*entry, i| { - try entry.render(ui); - } - } - - pub fn refresh(self: *TreeView) !void { - var dir = try std.fs.cwd().openDir("./", .{ .iterate = true }); - var iterator = dir.iterate(); - var index: u31 = 0; - while (try iterator.next()) |file| { - self.entries[index] = Entry{ .name = file.name, .y = index * 25 + 10 }; - index += 1; - } - self.count = index; - } - - pub fn mouseMotion(self: *TreeView, event: sdl.MouseMotionEvent) void { - for (self.entries[0..self.count]) |*entry, i| { - entry.mouseMotion(event); - } - } -}; +const TreeView = @import("treeview.zig").TreeView; +const Ui = @import("ui.zig").Ui; pub fn main() !void { - var ui = try Ui.init(); + var ui = try Ui.init(false); defer ui.deinit(); + const windowSize = 512; + var times = [_]f64{0} ** windowSize; + var index: u32 = 0; + while (true) { + var perf = sdl.PerformanceCounter.now(); try ui.update(); try ui.render(); + + times[index] = perf.elapsed().ms(); + index += 1; + if (index == windowSize) { + var sum: f64 = 0.0; + for (times) |ms| { + sum += ms; + } + std.debug.warn("{d:.2} ms\n", .{sum / windowSize}); + index = 0; + } } } diff --git a/src/treeview.zig b/src/treeview.zig new file mode 100644 index 0000000..d911c63 --- /dev/null +++ b/src/treeview.zig @@ -0,0 +1,139 @@ +const std = @import("std"); +const zig_sdl = @import("zig-sdl"); +const sdl = zig_sdl.sdl; + +const Ui = @import("ui.zig").Ui; +const colorTheme = @import("color_theme.zig").colorTheme; + +pub const TreeView = struct { + const Entry = struct { + pub const height: i32 = 25; + + name: []const u8 = "", + kind: std.fs.Dir.Entry.Kind, + + texture: ?sdl.Texture = null, + + y: i32 = 0, + textWidth: i32 = 0, + textHeight: i32 = 0, + + highlighted: bool = false, + + fn prerender(self: *Entry, ui: *Ui) !void { + if (self.texture) |texture| { + texture.deinit(); + } + + var color = if (self.kind == .Directory) colorTheme.treeFolderFg else colorTheme.treeFileFg; + var name = try std.mem.dupeZ(std.heap.c_allocator, u8, self.name); + var surface = try ui.uiFont.renderBlended(name, color); + defer surface.deinit(); + + self.texture = try ui.renderer.createTextureFromSurface(surface); + + self.textWidth = surface.width(); + self.textHeight = surface.height(); + } + + pub fn render(self: *Entry, ui: *Ui) !void { + if (self.texture == null) { + try self.prerender(ui); + } + + if (self.highlighted) { + try ui.renderer.setDrawColor(colorTheme.treeEntryHighlightBg); + try ui.renderer.fillRect(sdl.Rect{ + .x = 0, + .y = ui.scale(i32, self.y), + .w = ui.scale(i32, TreeView.width), + .h = ui.scale(i32, Entry.height), + }); + ui.cursorManager.set(.Hand); + } + + var y = @divTrunc(ui.scale(i32, Entry.height) - self.textHeight, 2); + var dst = sdl.Rect{ + .x = ui.scale(i32, 10), + .y = ui.scale(i32, self.y) + y, + .w = self.textWidth, + .h = self.textHeight, + }; + + if (self.texture) |texture| { + try ui.renderer.copy(texture, null, dst); + } + } + + pub fn mouseMotion(self: *Entry, event: sdl.MouseMotionEvent) void { + self.highlighted = event.y > self.y and event.y <= self.y + Entry.height and event.x <= TreeView.width; + } + + pub fn mouseButton(self: *Entry, ui: *Ui, event: sdl.MouseButtonEvent) void { + if (event.state == .Released and self.highlighted) { + ui.openFile(self.name); + } + } + }; + + pub const width = 200; + + height: i32 = 0, + + // Temporary, std.heap.c_allocator was broken, so this for now + count: u64 = 0, + entries: [256]Entry = undefined, + + pub fn init() TreeView { + return TreeView{}; + } + + pub fn deinit(self: TreeView) void { + for (self.entries[0..self.count]) |entry| { + if (entry.texture) |texture| { + texture.deinit(); + } + } + } + + pub fn render(self: *TreeView, ui: *Ui) !void { + try ui.renderer.setDrawColor(colorTheme.treeBg); + try ui.renderer.fillRect(sdl.Rect{ + .x = 0, + .y = 0, + .w = ui.scale(i32, 200), + .h = ui.scale(i32, ui.height), + }); + + for (self.entries[0..self.count]) |*entry, i| { + try entry.render(ui); + } + } + + pub fn refresh(self: *TreeView) !void { + var dir = try std.fs.cwd().openDir("./", .{ .iterate = true }); + var iterator = dir.iterate(); + var index: u32 = 0; + while (try iterator.next()) |file| { + self.entries[index] = Entry{ + .name = file.name, + .kind = file.kind, + .y = @bitCast(i32, index) * Entry.height + 10, + }; + index += 1; + } + self.count = index; + } + + pub fn mouseMotion(self: *TreeView, event: sdl.MouseMotionEvent) void { + for (self.entries[0..self.count]) |*entry, i| { + entry.mouseMotion(event); + } + } + + pub fn mouseButton(self: *TreeView, ui: *Ui, event: sdl.MouseButtonEvent) void { + for (self.entries[0..self.count]) |*entry, i| { + entry.mouseButton(ui, event); + } + } +}; diff --git a/src/ui.zig b/src/ui.zig new file mode 100644 index 0000000..58b7b9f --- /dev/null +++ b/src/ui.zig @@ -0,0 +1,146 @@ +const std = @import("std"); +const zig_sdl = @import("zig-sdl"); +const sdl = zig_sdl.sdl; +const ttf = zig_sdl.ttf; + +const CursorManager = @import("cursor_manager.zig").CursorManager; +const TreeView = @import("treeview.zig").TreeView; +const DocView = @import("docview.zig").DocView; +const colorTheme = @import("color_theme.zig").colorTheme; + +pub const Ui = struct { + window: sdl.Window, + renderer: sdl.Renderer, + + uiFont: ttf.Font, + codeFont: ttf.Font, + + cursorManager: CursorManager, + + treeView: TreeView, + docView: DocView, + + width: i32, + height: i32, + scaleFactor: f64, + + pub fn init(vsync: bool) !Ui { + try sdl.init(); + var window = try sdl.Window.init( + "Zite", + 1280, + 720, + sdl.WindowFlags.Resizable | sdl.WindowFlags.AllowHighDpi, + ); + + var flags = sdl.RendererFlags.Accelerated; + flags |= sdl.RendererFlags.PresentVSync; + + var renderer = try window.createRenderer(null, flags); + + try ttf.init(); + var uiFont = try ttf.Font.init("/usr/share/fonts/ubuntu/Ubuntu-R.ttf", 12); + var codeFont = try ttf.Font.init("/usr/share/fonts/TTF/Cascadia.ttf", 12); + + var cursorManager = try CursorManager.init(); + + var treeView = TreeView.init(); + try treeView.refresh(); + + var docView = DocView.init(); + + return Ui{ + .window = window, + .renderer = renderer, + + .uiFont = uiFont, + .codeFont = codeFont, + + .cursorManager = cursorManager, + .treeView = treeView, + .docView = docView, + + .width = 0, + .height = 0, + .scaleFactor = 1.0, + }; + } + + pub fn deinit(self: *Ui) void { + self.treeView.deinit(); + self.cursorManager.deinit(); + + self.uiFont.deinit(); + self.codeFont.deinit(); + ttf.quit(); + + self.renderer.deinit(); + self.window.deinit(); + sdl.quit(); + } + + pub fn reloadFonts(self: *Ui) !void { + var size = @floatToInt(i32, 16.0 * self.scaleFactor); + self.uiFont = try ttf.Font.init("/usr/share/fonts/ubuntu/Ubuntu-R.ttf", size); + self.codeFont = try ttf.Font.init("/usr/share/fonts/TTF/Cascadia.ttf", size); + } + + pub fn scale(self: Ui, comptime T: type, input: T) T { + var float = @intToFloat(f64, input); + return @floatToInt(T, float * self.scaleFactor); + } + + pub fn update(self: *Ui) !void { + while (sdl.Event.poll()) |event| { + var kind = event.kind(); + + switch (kind) { + .Quit => std.os.exit(0), + .MouseMotion => { + var motion = event.mouseMotion().?; + self.treeView.mouseMotion(motion); + }, + .WindowEvent => { + var window = event.window().?; + switch (window.event) { + .Resized => { + self.width = window.data1; + self.height = window.data2; + + var size = self.window.glDrawableSize(); + var scaleFactor = @intToFloat(f64, size.h) / @intToFloat(f64, window.data2); + if (scaleFactor != self.scaleFactor) { + self.scaleFactor = scaleFactor; + try self.reloadFonts(); + } + }, + else => {}, + } + }, + .MouseButtonDown, .MouseButtonUp => { + var button = event.button().?; + self.treeView.mouseButton(self, button); + }, + else => { + std.debug.warn("event: {}\n", .{@tagName(kind)}); + }, + } + } + } + + pub fn render(self: *Ui) !void { + try self.renderer.setDrawColor(colorTheme.bg); + try self.renderer.clear(); + + try self.treeView.render(self); + try self.docView.render(self); + + try self.cursorManager.update(); + self.renderer.present(); + } + + pub fn openFile(self: *Ui, name: []const u8) void { + std.debug.warn("opening file {}\n", .{name}); + self.docView.open(name) catch unreachable; + } +};