mirror of https://git.sr.ht/~kikoodx/kble
Big commit, selection structure and code.
This commit is contained in:
parent
c4125520ea
commit
0b52b35238
115
src/level.zig
115
src/level.zig
|
@ -6,6 +6,8 @@ const std = @import("std");
|
|||
const expect = std.testing.expect;
|
||||
|
||||
const Vec2 = @import("vec2.zig");
|
||||
const Rect = @import("rect.zig");
|
||||
const SelectionUpdate = @import("movement.zig").SelectionUpdate;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
|
@ -14,7 +16,7 @@ const cell_t = u16;
|
|||
width: u16,
|
||||
height: u16,
|
||||
content: []cell_t,
|
||||
selected: []bool,
|
||||
selection: []bool,
|
||||
|
||||
/// Create structure and allocate required memory. The `content` array size will
|
||||
/// be equal to `width` times `height`.
|
||||
|
@ -23,27 +25,27 @@ pub fn init(allocator: *std.mem.Allocator, width: u16, height: u16) !Self {
|
|||
.width = width,
|
||||
.height = height,
|
||||
.content = undefined,
|
||||
.selected = undefined,
|
||||
.selection = undefined,
|
||||
};
|
||||
|
||||
// Try to allocate necessary memory.
|
||||
const size: u32 = @intCast(u32, width) * @intCast(u32, height);
|
||||
self.content = try allocator.alloc(cell_t, size);
|
||||
errdefer allocator.free(self.content);
|
||||
self.selected = try allocator.alloc(bool, size);
|
||||
self.selection = try allocator.alloc(bool, size);
|
||||
|
||||
// Fill with 0s to avoid undefined behavior.
|
||||
var i: u32 = 0;
|
||||
while (i < size) : (i += 1) {
|
||||
self.content[i] = 0;
|
||||
self.selected[i] = false;
|
||||
self.selection[i] = false;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Free the level content.
|
||||
pub fn deinit(self: *Self, allocator: *std.mem.Allocator) void {
|
||||
allocator.free(self.selected);
|
||||
allocator.free(self.selection);
|
||||
allocator.free(self.content);
|
||||
}
|
||||
|
||||
|
@ -57,7 +59,7 @@ pub fn draw(self: *Self, offset: Vec2) void {
|
|||
while (cx < self.width) {
|
||||
var cy: Vec2.int_type = offset.y;
|
||||
while (cy < self.height) {
|
||||
const cell_content: cell_t = self.content[cx * self.width + cy];
|
||||
const cell_content: cell_t = self.content[cy * self.width + cx];
|
||||
const color = switch (cell_content) {
|
||||
0 => ray.GRAY,
|
||||
1 => ray.WHITE,
|
||||
|
@ -73,6 +75,67 @@ pub fn draw(self: *Self, offset: Vec2) void {
|
|||
}
|
||||
}
|
||||
|
||||
/// Draw selection from `offset` to fill the window.
|
||||
pub fn draw_selection(self: *Self, offset: Vec2) void {
|
||||
// Pixel position (were we draw).
|
||||
var x: Vec2.int_type = 0;
|
||||
var y: Vec2.int_type = 0;
|
||||
// Cursor position.
|
||||
var cx: Vec2.int_type = offset.x;
|
||||
while (cx < self.width) {
|
||||
var cy: Vec2.int_type = offset.y;
|
||||
while (cy < self.height) {
|
||||
if (self.selection[cy * self.width + cx])
|
||||
ray.DrawPixel(x, y, ray.GREEN);
|
||||
y += 1;
|
||||
cy += 1;
|
||||
}
|
||||
y = 0;
|
||||
x += 1;
|
||||
cx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the `state` of a cell at `cursor` if possible
|
||||
fn select_cell(self: *Self, cursor: Vec2, state: bool) void {
|
||||
const target: u32 = @intCast(u32, cursor.y) * self.width + @intCast(u32, cursor.x);
|
||||
if (target < self.width * self.height and target >= 0)
|
||||
self.selection[target] = state;
|
||||
}
|
||||
|
||||
/// Reset state of selection: `state` everywhere.
|
||||
fn select_clear(self: *Self, state: bool) void {
|
||||
const size: u32 = @intCast(u32, self.width) * @intCast(u32, self.height);
|
||||
var i: u32 = 0;
|
||||
while (i < size) : (i += 1) {
|
||||
self.selection[i] = state;
|
||||
}
|
||||
}
|
||||
|
||||
/// Change state of all the cells in the rectangle range.
|
||||
fn select_rect(self: *Self, rect: Rect, state: bool) void {
|
||||
var cx: Rect.int_type = rect.left_x;
|
||||
while (cx <= rect.right_x) {
|
||||
defer cx += 1;
|
||||
var cy = rect.top_y;
|
||||
while (cy <= rect.bottom_y) {
|
||||
defer cy += 1;
|
||||
self.select_cell(Vec2{ .x = cx, .y = cy }, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_selection_update(self: *Self, selection_update: SelectionUpdate) void {
|
||||
// The update is exclusive, forget everything before it.
|
||||
if (selection_update.exclusive) {
|
||||
// Clear selection.
|
||||
self.select_clear(false);
|
||||
}
|
||||
|
||||
// Apply changes.
|
||||
self.select_rect(selection_update.area, selection_update.state);
|
||||
}
|
||||
|
||||
test "create level buffer" {
|
||||
// Create allocator.
|
||||
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||
|
@ -83,14 +146,48 @@ test "create level buffer" {
|
|||
var level: Self = try Self.init(allocator, 64, 32);
|
||||
level.deinit(allocator);
|
||||
level = try Self.init(allocator, 256, 256);
|
||||
|
||||
defer level.deinit(allocator);
|
||||
|
||||
level.content[128] = 32;
|
||||
level.selected[128] = true;
|
||||
level.selection[128] = true;
|
||||
|
||||
expect(level.width == 256);
|
||||
expect(level.height == 256);
|
||||
expect(level.content[128] == 32);
|
||||
expect(level.selected[128]);
|
||||
expect(level.selection[128]);
|
||||
}
|
||||
|
||||
test "clear selection" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||
defer arena.deinit();
|
||||
const allocator = &arena.allocator;
|
||||
|
||||
var level: Self = try Self.init(allocator, 16, 16);
|
||||
defer level.deinit(allocator);
|
||||
|
||||
level.selection[255] = true;
|
||||
level.select_clear(false);
|
||||
expect(!level.selection[255]);
|
||||
}
|
||||
|
||||
test "select rectangle area" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||
defer arena.deinit();
|
||||
const allocator = &arena.allocator;
|
||||
|
||||
var level: Self = try Self.init(allocator, 16, 16);
|
||||
defer level.deinit(allocator);
|
||||
|
||||
level.select_rect(Rect{
|
||||
.left_x = 4,
|
||||
.right_x = 20,
|
||||
.top_y = 1,
|
||||
.bottom_y = 14,
|
||||
}, true);
|
||||
|
||||
expect(!level.selection[14]);
|
||||
expect(!level.selection[19]);
|
||||
expect(level.selection[20]);
|
||||
expect(level.selection[31]);
|
||||
expect(!level.selection[245]);
|
||||
}
|
||||
|
|
26
src/main.zig
26
src/main.zig
|
@ -31,6 +31,9 @@ pub fn main() !void {
|
|||
// Create camera.
|
||||
var camera: Vec2 = Vec2.init(0, 0);
|
||||
|
||||
// Create cursor.
|
||||
var cursor: Vec2 = Vec2.init(0, 0);
|
||||
|
||||
// Create input buffer (ASCII).
|
||||
const input_buffer_len = 255;
|
||||
var input_buffer: [input_buffer_len]u32 = undefined;
|
||||
|
@ -63,13 +66,19 @@ pub fn main() !void {
|
|||
input_cursor -= 1;
|
||||
const action = input_buffer[input_cursor];
|
||||
|
||||
switch (action) {
|
||||
'h' => movement.move_left(&camera, 1),
|
||||
'j' => {},
|
||||
'k' => {},
|
||||
'l' => movement.move_right(&camera, 1),
|
||||
else => log("No action mapped to this key.", .{}),
|
||||
}
|
||||
const selection_update: movement.SelectionUpdate = switch (action) {
|
||||
'h' => movement.move_left(&cursor, 1, true),
|
||||
'l' => movement.move_right(&cursor, 1, true),
|
||||
'H' => movement.move_left(&cursor, 1, false),
|
||||
'L' => movement.move_right(&cursor, 1, false),
|
||||
else => {
|
||||
log("No action mapped to key {}.", .{action});
|
||||
break undefined;
|
||||
},
|
||||
};
|
||||
|
||||
if (selection_update.exclusive != undefined)
|
||||
level.apply_selection_update(selection_update);
|
||||
}
|
||||
|
||||
ray.BeginDrawing();
|
||||
|
@ -77,6 +86,7 @@ pub fn main() !void {
|
|||
|
||||
ray.ClearBackground(ray.BLACK);
|
||||
level.draw(camera);
|
||||
ray.DrawFPS(0, 0);
|
||||
level.draw_selection(camera);
|
||||
//ray.DrawFPS(0, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,53 +4,81 @@ const expect = std.testing.expect;
|
|||
const maxInt = std.math.maxInt;
|
||||
|
||||
const Vec2 = @import("vec2.zig");
|
||||
const Rect = @import("rect.zig");
|
||||
|
||||
const maxIntVec2 = comptime maxInt(Vec2.int_type);
|
||||
|
||||
pub fn move_left(cursor: *Vec2, n: u64) void {
|
||||
/// Describe changes to make on selection.
|
||||
pub const SelectionUpdate = struct {
|
||||
exclusive: bool,
|
||||
area: Rect,
|
||||
state: bool,
|
||||
};
|
||||
|
||||
/// Try to move the cursor `n` times to the left.
|
||||
pub fn move_left(cursor: *Vec2, n: u64, exclusive_selection: bool) SelectionUpdate {
|
||||
const before: Vec2 = cursor.*;
|
||||
if (n > maxIntVec2) {
|
||||
cursor.x = 0;
|
||||
return;
|
||||
} else {
|
||||
const steps = @intCast(Vec2.int_type, n);
|
||||
|
||||
if (cursor.x >= steps)
|
||||
cursor.x -= steps
|
||||
else
|
||||
cursor.x = 0;
|
||||
}
|
||||
|
||||
const steps = @intCast(Vec2.int_type, n);
|
||||
|
||||
if (cursor.x >= steps)
|
||||
cursor.x -= steps
|
||||
else
|
||||
cursor.x = 0;
|
||||
return SelectionUpdate {
|
||||
.exclusive = exclusive_selection,
|
||||
.area = if (exclusive_selection)
|
||||
Rect.init_from_vec2(cursor.*, cursor.*)
|
||||
else
|
||||
Rect.init_from_vec2(before, cursor.*),
|
||||
.state = true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn move_right(cursor: *Vec2, n: u64) void {
|
||||
/// Try to move the cursor `n` times to the right.
|
||||
pub fn move_right(cursor: *Vec2, n: u64, exclusive_selection: bool) SelectionUpdate {
|
||||
const before: Vec2 = cursor.*;
|
||||
if (n > maxIntVec2) {
|
||||
cursor.x = maxIntVec2;
|
||||
return;
|
||||
} else {
|
||||
const steps = @intCast(Vec2.int_type, n);
|
||||
|
||||
if (maxIntVec2 - cursor.x >= steps)
|
||||
cursor.x += steps
|
||||
else
|
||||
cursor.x = maxIntVec2;
|
||||
}
|
||||
|
||||
const steps = @intCast(Vec2.int_type, n);
|
||||
|
||||
if (maxIntVec2 - cursor.x >= steps)
|
||||
cursor.x += steps
|
||||
else
|
||||
cursor.x = maxIntVec2;
|
||||
// TODO: remove code duplication.
|
||||
return SelectionUpdate {
|
||||
.exclusive = exclusive_selection,
|
||||
.area = if (exclusive_selection)
|
||||
Rect.init_from_vec2(cursor.*, cursor.*)
|
||||
else
|
||||
Rect.init_from_vec2(before, cursor.*),
|
||||
.state = true,
|
||||
};
|
||||
}
|
||||
|
||||
test "move left" {
|
||||
var cursor = Vec2.init(42, 51);
|
||||
move_left(&cursor, 2);
|
||||
move_left(&cursor, 2, false);
|
||||
expect(cursor.x == 40);
|
||||
|
||||
move_left(&cursor, 38);
|
||||
move_left(&cursor, 38, false);
|
||||
expect(cursor.x == 2);
|
||||
|
||||
move_left(&cursor, 7548748948487);
|
||||
move_left(&cursor, 7548748948487, false);
|
||||
expect(cursor.x == 0);
|
||||
}
|
||||
|
||||
test "move right" {
|
||||
var cursor = Vec2.init(564, 42);
|
||||
move_right(&cursor, 51);
|
||||
move_right(&cursor, 51, false);
|
||||
expect(cursor.x == 615);
|
||||
|
||||
move_right(&cursor, 51204758045045);
|
||||
move_right(&cursor, 51204758045045, false);
|
||||
expect(cursor.x == maxIntVec2);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
const std = @import("std");
|
||||
const expect = std.testing.expect;
|
||||
|
||||
const Vec2 = @import("vec2.zig");
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const int_type = Vec2.int_type;
|
||||
|
||||
left_x: int_type,
|
||||
top_y: int_type,
|
||||
right_x: int_type,
|
||||
bottom_y: int_type,
|
||||
|
||||
pub fn init_from_vec2(first: Vec2, second: Vec2) Self {
|
||||
return Self{
|
||||
.left_x = if (first.x < second.x) first.x else second.x,
|
||||
.top_y = if (first.y < second.y) first.y else second.y,
|
||||
.right_x = if (first.x > second.x) first.x else second.x,
|
||||
.bottom_y = if (first.y > second.y) first.y else second.y,
|
||||
};
|
||||
}
|
||||
|
||||
test "create Rect from two Vec2" {
|
||||
const rect_1 = Self.init_from_vec2(Vec2{ .x = 5, .y = 2 }, Vec2{ .x = 2, .y = 9 });
|
||||
expect(rect_1.left_x == 2);
|
||||
expect(rect_1.top_y == 2);
|
||||
expect(rect_1.right_x == 5);
|
||||
expect(rect_1.bottom_y == 9);
|
||||
}
|
Loading…
Reference in New Issue