Compare commits

...

5 commits

6 changed files with 263 additions and 72 deletions

View file

@ -0,0 +1,117 @@
const std = @import("std");
const vaxis = @import("vaxis");
pub const Plane = @import("Plane.zig");
const Layer = @This();
view: View,
y_off: i32 = 0,
x_off: i32 = 0,
plane_: Plane,
const View = struct {
allocator: std.mem.Allocator,
screen: vaxis.Screen,
unicode: *const vaxis.Unicode,
pub const Config = struct {
h: u16,
w: u16,
};
pub fn init(allocator: std.mem.Allocator, unicode: *const vaxis.Unicode, config: Config) std.mem.Allocator.Error!View {
return .{
.allocator = allocator,
.screen = try vaxis.Screen.init(allocator, .{
.rows = config.h,
.cols = config.w,
.x_pixel = 0,
.y_pixel = 0,
}),
.unicode = unicode,
};
}
pub fn deinit(self: *View) void {
self.screen.deinit(self.allocator);
}
};
pub const Options = struct {
y: i32 = 0,
x: i32 = 0,
h: u16 = 0,
w: u16 = 0,
};
pub fn init(allocator: std.mem.Allocator, unicode: *const vaxis.Unicode, opts: Options) std.mem.Allocator.Error!*Layer {
const self = try allocator.create(Layer);
self.* = .{
.view = try View.init(allocator, unicode, .{
.h = opts.h,
.w = opts.w,
}),
.y_off = opts.y,
.x_off = opts.x,
.plane_ = undefined,
};
const name = "layer";
self.plane_ = .{
.window = self.window(),
.name_buf = undefined,
.name_len = name.len,
};
@memcpy(self.plane_.name_buf[0..name.len], name);
return self;
}
pub fn deinit(self: *Layer) void {
const allocator = self.view.allocator;
self.view.deinit();
allocator.destroy(self);
}
fn window(self: *Layer) vaxis.Window {
return .{
.x_off = 0,
.y_off = 0,
.parent_x_off = 0,
.parent_y_off = 0,
.width = self.view.screen.width,
.height = self.view.screen.height,
.screen = &self.view.screen,
.unicode = self.view.unicode,
};
}
pub fn plane(self: *Layer) *Plane {
return &self.plane_;
}
pub fn draw(self: *const Layer, plane_: Plane) void {
if (self.x_off >= plane_.window.width) return;
if (self.y_off >= plane_.window.height) return;
const src_y = 0;
const src_x = 0;
const src_h: usize = self.view.screen.height;
const src_w = self.view.screen.width;
const dst_dim_x: i32 = @intCast(plane_.dim_x());
// const dst_dim_y: i32 = @intCast(plane_.dim_y());
const dst_y = self.y_off;
const dst_x = self.x_off;
const dst_w = @min(src_w, dst_dim_x - dst_x);
// const dst_h = @min(src_h, dst_dim_y - dst_y);
for (src_y..src_h) |src_row_| {
const src_row: i32 = @intCast(src_row_);
const src_row_offset = src_row * src_w;
const dst_row_offset = (dst_y + src_row) * plane_.window.screen.width;
@memcpy(
plane_.window.screen.buf[@intCast(dst_row_offset + dst_x)..@intCast(dst_row_offset + dst_x + dst_w)],
self.view.screen.buf[@intCast(src_row_offset + src_x)..@intCast(src_row_offset + src_w)],
);
}
}

View file

@ -9,6 +9,7 @@ const builtin = @import("builtin");
const RGB = @import("color").RGB; const RGB = @import("color").RGB;
pub const Plane = @import("Plane.zig"); pub const Plane = @import("Plane.zig");
pub const Layer = @import("Layer.zig");
pub const Cell = @import("Cell.zig"); pub const Cell = @import("Cell.zig");
pub const CursorShape = vaxis.Cell.CursorShape; pub const CursorShape = vaxis.Cell.CursorShape;

View file

@ -1,4 +1,5 @@
const Plane = @import("renderer").Plane; const Plane = @import("renderer").Plane;
const Layer = @import("renderer").Layer;
const Self = @This(); const Self = @This();
@ -35,6 +36,15 @@ pub fn from(n: Plane) Self {
}; };
} }
pub fn to_layer(self: Self) Layer.Options {
return .{
.y = @intCast(self.y),
.x = @intCast(self.x),
.h = @intCast(self.h),
.w = @intCast(self.w),
};
}
pub fn is_abs_coord_inside(self: Self, y: usize, x: usize) bool { pub fn is_abs_coord_inside(self: Self, y: usize, x: usize) bool {
return y >= self.y and y < self.y + self.h and x >= self.x and x < self.x + self.w; return y >= self.y and y < self.y + self.h and x >= self.x and x < self.x + self.w;
} }

View file

@ -77,6 +77,8 @@ fn State(ctx_type: type) type {
plane: Plane, plane: Plane,
active: bool = false, active: bool = false,
hover: bool = false, hover: bool = false,
drag_anchor: ?Widget.Pos = null,
drag_pos: ?Widget.Pos = null,
opts: Options(ctx_type), opts: Options(ctx_type),
const Self = @This(); const Self = @This();
@ -120,6 +122,7 @@ fn State(ctx_type: type) type {
switch (btn_enum) { switch (btn_enum) {
input.mouse.BUTTON1 => { input.mouse.BUTTON1 => {
self.active = true; self.active = true;
self.drag_anchor = self.to_rel_cursor(x, y);
tui.need_render(); tui.need_render();
}, },
input.mouse.BUTTON4, input.mouse.BUTTON5 => { input.mouse.BUTTON4, input.mouse.BUTTON5 => {
@ -131,9 +134,12 @@ fn State(ctx_type: type) type {
return true; return true;
} else if (try m.match(.{ "B", input.event.release, tp.extract(&btn), tp.any, tp.extract(&x), tp.extract(&y), tp.any, tp.any })) { } else if (try m.match(.{ "B", input.event.release, tp.extract(&btn), tp.any, tp.extract(&x), tp.extract(&y), tp.any, tp.any })) {
self.call_click_handler(@enumFromInt(btn), self.to_rel_cursor(x, y)); self.call_click_handler(@enumFromInt(btn), self.to_rel_cursor(x, y));
self.drag_anchor = null;
self.drag_pos = null;
tui.need_render(); tui.need_render();
return true; return true;
} else if (try m.match(.{ "D", input.event.press, tp.extract(&btn), tp.more })) { } else if (try m.match(.{ "D", input.event.press, tp.extract(&btn), tp.any, tp.extract(&x), tp.extract(&y), tp.any, tp.any })) {
self.drag_pos = .{ .x = x, .y = y };
if (self.opts.on_event) |h| { if (self.opts.on_event) |h| {
self.active = false; self.active = false;
h.send(from, m) catch {}; h.send(from, m) catch {};
@ -145,6 +151,8 @@ fn State(ctx_type: type) type {
h.send(from, m) catch {}; h.send(from, m) catch {};
} }
self.call_click_handler(@enumFromInt(btn), self.to_rel_cursor(x, y)); self.call_click_handler(@enumFromInt(btn), self.to_rel_cursor(x, y));
self.drag_anchor = null;
self.drag_pos = null;
tui.need_render(); tui.need_render();
return true; return true;
} else if (try m.match(.{ "H", tp.extract(&self.hover) })) { } else if (try m.match(.{ "H", tp.extract(&self.hover) })) {

View file

@ -463,159 +463,182 @@ const Tab = struct {
fn render(self: *@This(), btn: *ButtonType, theme: *const Widget.Theme) bool { fn render(self: *@This(), btn: *ButtonType, theme: *const Widget.Theme) bool {
const active = self.tabbar.active_buffer_ref == self.buffer_ref; const active = self.tabbar.active_buffer_ref == self.buffer_ref;
const mode: Mode = if (btn.hover) .selected else if (active) .active else .inactive; if (btn.drag_pos) |pos| {
switch (mode) { self.render_dragging(&btn.plane, theme);
.selected => self.render_selected(btn, theme, active), const anchor: Widget.Pos = btn.drag_anchor orelse .{};
.active => self.render_active(btn, theme), var box = Widget.Box.from(btn.plane);
.inactive => self.render_inactive(btn, theme), box.y = @intCast(@max(pos.y, anchor.y) - anchor.y);
box.x = @intCast(@max(pos.x, anchor.x) - anchor.x);
if (tui.top_layer(box.to_layer())) |top_layer| {
self.render_selected(top_layer, btn.opts.label, false, theme, active);
}
} else {
const mode: Mode = if (btn.hover) .selected else if (active) .active else .inactive;
switch (mode) {
.selected => self.render_selected(&btn.plane, btn.opts.label, btn.hover, theme, active),
.active => self.render_active(&btn.plane, btn.opts.label, btn.hover, theme),
.inactive => self.render_inactive(&btn.plane, btn.opts.label, btn.hover, theme),
}
} }
return false; return false;
} }
fn render_selected(self: *@This(), btn: *ButtonType, theme: *const Widget.Theme, active: bool) void { fn render_selected(self: *@This(), plane: *Plane, label: []const u8, hover: bool, theme: *const Widget.Theme, active: bool) void {
btn.plane.set_base_style(theme.editor); plane.set_base_style(theme.editor);
btn.plane.erase(); plane.erase();
btn.plane.home(); plane.home();
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.inactive_fg.from_theme(theme), .fg = self.tab_style.inactive_fg.from_theme(theme),
.bg = self.tab_style.inactive_bg.from_theme(theme), .bg = self.tab_style.inactive_bg.from_theme(theme),
}); });
btn.plane.fill(" "); plane.fill(" ");
btn.plane.home(); plane.home();
if (active) { if (active) {
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.selected_fg.from_theme(theme), .fg = self.tab_style.selected_fg.from_theme(theme),
.bg = self.tab_style.selected_bg.from_theme(theme), .bg = self.tab_style.selected_bg.from_theme(theme),
}); });
btn.plane.fill(" "); plane.fill(" ");
btn.plane.home(); plane.home();
} }
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.selected_left_fg.from_theme(theme), .fg = self.tab_style.selected_left_fg.from_theme(theme),
.bg = self.tab_style.selected_left_bg.from_theme(theme), .bg = self.tab_style.selected_left_bg.from_theme(theme),
}); });
_ = btn.plane.putstr(self.tab_style.selected_left) catch {}; _ = plane.putstr(self.tab_style.selected_left) catch {};
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.selected_fg.from_theme(theme), .fg = self.tab_style.selected_fg.from_theme(theme),
.bg = self.tab_style.selected_bg.from_theme(theme), .bg = self.tab_style.selected_bg.from_theme(theme),
}); });
self.render_content(btn, self.tab_style.selected_fg.from_theme(theme), theme); self.render_content(plane, label, hover, self.tab_style.selected_fg.from_theme(theme), theme);
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.selected_right_fg.from_theme(theme), .fg = self.tab_style.selected_right_fg.from_theme(theme),
.bg = self.tab_style.selected_right_bg.from_theme(theme), .bg = self.tab_style.selected_right_bg.from_theme(theme),
}); });
_ = btn.plane.putstr(self.tab_style.selected_right) catch {}; _ = plane.putstr(self.tab_style.selected_right) catch {};
} }
fn render_active(self: *@This(), btn: *ButtonType, theme: *const Widget.Theme) void { fn render_active(self: *@This(), plane: *Plane, label: []const u8, hover: bool, theme: *const Widget.Theme) void {
btn.plane.set_base_style(theme.editor); plane.set_base_style(theme.editor);
btn.plane.erase(); plane.erase();
btn.plane.home(); plane.home();
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.inactive_fg.from_theme(theme), .fg = self.tab_style.inactive_fg.from_theme(theme),
.bg = self.tab_style.inactive_bg.from_theme(theme), .bg = self.tab_style.inactive_bg.from_theme(theme),
}); });
btn.plane.fill(" "); plane.fill(" ");
btn.plane.home(); plane.home();
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.active_fg.from_theme(theme), .fg = self.tab_style.active_fg.from_theme(theme),
.bg = self.tab_style.active_bg.from_theme(theme), .bg = self.tab_style.active_bg.from_theme(theme),
}); });
btn.plane.fill(" "); plane.fill(" ");
btn.plane.home(); plane.home();
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.active_left_fg.from_theme(theme), .fg = self.tab_style.active_left_fg.from_theme(theme),
.bg = self.tab_style.active_left_bg.from_theme(theme), .bg = self.tab_style.active_left_bg.from_theme(theme),
}); });
_ = btn.plane.putstr(self.tab_style.active_left) catch {}; _ = plane.putstr(self.tab_style.active_left) catch {};
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.active_fg.from_theme(theme), .fg = self.tab_style.active_fg.from_theme(theme),
.bg = self.tab_style.active_bg.from_theme(theme), .bg = self.tab_style.active_bg.from_theme(theme),
}); });
self.render_content(btn, self.tab_style.active_fg.from_theme(theme), theme); self.render_content(plane, label, hover, self.tab_style.active_fg.from_theme(theme), theme);
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.active_right_fg.from_theme(theme), .fg = self.tab_style.active_right_fg.from_theme(theme),
.bg = self.tab_style.active_right_bg.from_theme(theme), .bg = self.tab_style.active_right_bg.from_theme(theme),
}); });
_ = btn.plane.putstr(self.tab_style.active_right) catch {}; _ = plane.putstr(self.tab_style.active_right) catch {};
} }
fn render_inactive(self: *@This(), btn: *ButtonType, theme: *const Widget.Theme) void { fn render_inactive(self: *@This(), plane: *Plane, label: []const u8, hover: bool, theme: *const Widget.Theme) void {
btn.plane.set_base_style(theme.editor); plane.set_base_style(theme.editor);
btn.plane.erase(); plane.erase();
btn.plane.home(); plane.home();
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.inactive_fg.from_theme(theme), .fg = self.tab_style.inactive_fg.from_theme(theme),
.bg = self.tab_style.inactive_bg.from_theme(theme), .bg = self.tab_style.inactive_bg.from_theme(theme),
}); });
btn.plane.fill(" "); plane.fill(" ");
btn.plane.home(); plane.home();
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.inactive_left_fg.from_theme(theme), .fg = self.tab_style.inactive_left_fg.from_theme(theme),
.bg = self.tab_style.inactive_left_bg.from_theme(theme), .bg = self.tab_style.inactive_left_bg.from_theme(theme),
}); });
_ = btn.plane.putstr(self.tab_style.inactive_left) catch {}; _ = plane.putstr(self.tab_style.inactive_left) catch {};
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.inactive_fg.from_theme(theme), .fg = self.tab_style.inactive_fg.from_theme(theme),
.bg = self.tab_style.inactive_bg.from_theme(theme), .bg = self.tab_style.inactive_bg.from_theme(theme),
}); });
self.render_content(btn, self.tab_style.inactive_fg.from_theme(theme), theme); self.render_content(plane, label, hover, self.tab_style.inactive_fg.from_theme(theme), theme);
btn.plane.set_style(.{ plane.set_style(.{
.fg = self.tab_style.inactive_right_fg.from_theme(theme), .fg = self.tab_style.inactive_right_fg.from_theme(theme),
.bg = self.tab_style.inactive_right_bg.from_theme(theme), .bg = self.tab_style.inactive_right_bg.from_theme(theme),
}); });
_ = btn.plane.putstr(self.tab_style.inactive_right) catch {}; _ = plane.putstr(self.tab_style.inactive_right) catch {};
} }
fn render_content(self: *@This(), btn: *ButtonType, fg: ?Widget.Theme.Color, theme: *const Widget.Theme) void { fn render_dragging(self: *@This(), plane: *Plane, theme: *const Widget.Theme) void {
plane.set_base_style(theme.editor);
plane.erase();
plane.home();
plane.set_style(.{
.fg = self.tab_style.inactive_fg.from_theme(theme),
.bg = self.tab_style.inactive_bg.from_theme(theme),
});
plane.fill(" ");
plane.home();
}
fn render_content(self: *@This(), plane: *Plane, label: []const u8, hover: bool, fg: ?Widget.Theme.Color, theme: *const Widget.Theme) void {
const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager"); const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager");
const buffer_ = buffer_manager.buffer_from_ref(self.buffer_ref); const buffer_ = buffer_manager.buffer_from_ref(self.buffer_ref);
const is_dirty = if (buffer_) |buffer| buffer.is_dirty() else false; const is_dirty = if (buffer_) |buffer| buffer.is_dirty() else false;
self.render_padding(&btn.plane, .left); self.render_padding(plane, .left);
if (self.tab_style.file_type_icon) if (buffer_) |buffer| if (buffer.file_type_icon) |icon| { if (self.tab_style.file_type_icon) if (buffer_) |buffer| if (buffer.file_type_icon) |icon| {
const color_: ?u24 = if (buffer.file_type_color) |color| if (!(color == 0xFFFFFF or color == 0x000000 or color == 0x000001)) color else null else null; const color_: ?u24 = if (buffer.file_type_color) |color| if (!(color == 0xFFFFFF or color == 0x000000 or color == 0x000001)) color else null else null;
if (color_) |color| if (color_) |color|
btn.plane.set_style(.{ .fg = .{ .color = color } }); plane.set_style(.{ .fg = .{ .color = color } });
_ = btn.plane.putstr(icon) catch {}; _ = plane.putstr(icon) catch {};
if (color_) |_| if (color_) |_|
btn.plane.set_style(.{ .fg = fg }); plane.set_style(.{ .fg = fg });
_ = btn.plane.putstr(" ") catch {}; _ = plane.putstr(" ") catch {};
}; };
_ = btn.plane.putstr(btn.opts.label) catch {}; _ = plane.putstr(label) catch {};
_ = btn.plane.putstr(" ") catch {}; _ = plane.putstr(" ") catch {};
self.close_pos = null; self.close_pos = null;
self.save_pos = null; self.save_pos = null;
if (btn.hover) { if (hover) {
if (is_dirty) { if (is_dirty) {
if (self.tab_style.save_icon_fg) |color| if (self.tab_style.save_icon_fg) |color|
btn.plane.set_style(.{ .fg = color.from_theme(theme) }); plane.set_style(.{ .fg = color.from_theme(theme) });
self.save_pos = btn.plane.cursor_x(); self.save_pos = plane.cursor_x();
_ = btn.plane.putstr(self.tabbar.tab_style.save_icon) catch {}; _ = plane.putstr(self.tabbar.tab_style.save_icon) catch {};
} else { } else {
btn.plane.set_style(.{ .fg = self.tab_style.close_icon_fg.from_theme(theme) }); plane.set_style(.{ .fg = self.tab_style.close_icon_fg.from_theme(theme) });
self.close_pos = btn.plane.cursor_x(); self.close_pos = plane.cursor_x();
_ = btn.plane.putstr(self.tabbar.tab_style.close_icon) catch {}; _ = plane.putstr(self.tabbar.tab_style.close_icon) catch {};
} }
} else if (is_dirty) { } else if (is_dirty) {
if (self.tab_style.dirty_indicator_fg) |color| if (self.tab_style.dirty_indicator_fg) |color|
btn.plane.set_style(.{ .fg = color.from_theme(theme) }); plane.set_style(.{ .fg = color.from_theme(theme) });
_ = btn.plane.putstr(self.tabbar.tab_style.dirty_indicator) catch {}; _ = plane.putstr(self.tabbar.tab_style.dirty_indicator) catch {};
} else { } else {
if (self.tab_style.clean_indicator_fg) |color| if (self.tab_style.clean_indicator_fg) |color|
btn.plane.set_style(.{ .fg = color.from_theme(theme) }); plane.set_style(.{ .fg = color.from_theme(theme) });
_ = btn.plane.putstr(self.tabbar.tab_style.clean_indicator) catch {}; _ = plane.putstr(self.tabbar.tab_style.clean_indicator) catch {};
} }
btn.plane.set_style(.{ .fg = fg }); plane.set_style(.{ .fg = fg });
self.render_padding(&btn.plane, .right); self.render_padding(plane, .right);
} }
fn render_padding(self: *@This(), plane: *Plane, side: enum { left, right }) void { fn render_padding(self: *@This(), plane: *Plane, side: enum { left, right }) void {

View file

@ -31,6 +31,7 @@ const Allocator = std.mem.Allocator;
allocator: Allocator, allocator: Allocator,
rdr_: renderer, rdr_: renderer,
top_layer_: ?*renderer.Layer = null,
config_: @import("config"), config_: @import("config"),
config_bufs: [][]const u8, config_bufs: [][]const u8,
session_tab_width: ?usize = null, session_tab_width: ?usize = null,
@ -507,12 +508,25 @@ fn render(self: *Self) void {
break :ret if (self.mainview_) |mv| mv.render(self.current_theme()) else false; break :ret if (self.mainview_) |mv| mv.render(self.current_theme()) else false;
}; };
if (self.top_layer_) |top_layer_| {
const frame = tracy.initZone(@src(), .{ .name = "tui blit top layer" });
defer frame.deinit();
self.logger.print("top_layer: {}:{}:{}:{}", .{
top_layer_.y_off,
top_layer_.x_off,
top_layer_.view.screen.height,
top_layer_.view.screen.width,
});
top_layer_.draw(self.rdr_.stdplane());
}
{ {
const frame = tracy.initZone(@src(), .{ .name = renderer.log_name ++ " render" }); const frame = tracy.initZone(@src(), .{ .name = renderer.log_name ++ " render" });
defer frame.deinit(); defer frame.deinit();
self.rdr_.render() catch |e| self.logger.err("render", e); self.rdr_.render() catch |e| self.logger.err("render", e);
tracy.frameMark(); tracy.frameMark();
} }
self.top_layer_reset();
self.idle_frame_count = if (self.unrendered_input_events_count > 0) self.idle_frame_count = if (self.unrendered_input_events_count > 0)
0 0
@ -1427,6 +1441,24 @@ fn stdplane(self: *Self) renderer.Plane {
return self.rdr_.stdplane(); return self.rdr_.stdplane();
} }
pub fn top_layer(opts: renderer.Layer.Options) ?*renderer.Plane {
const self = current();
if (self.top_layer_) |_| return null;
self.top_layer_ = renderer.Layer.init(
self.allocator,
self.rdr_.stdplane().window.unicode,
opts,
) catch @panic("OOM toplayer");
return self.top_layer_.?.plane();
}
fn top_layer_reset(self: *Self) void {
if (self.top_layer_) |top_layer_| {
top_layer_.deinit();
self.top_layer_ = null;
}
}
pub fn egc_chunk_width(chunk: []const u8, abs_col: usize, tab_width: usize) usize { pub fn egc_chunk_width(chunk: []const u8, abs_col: usize, tab_width: usize) usize {
return plane().egc_chunk_width(chunk, abs_col, tab_width); return plane().egc_chunk_width(chunk, abs_col, tab_width);
} }