Big commit, selection structure and code.

This commit is contained in:
KikooDX 2021-01-28 13:46:02 +01:00
parent c4125520ea
commit 0b52b35238
4 changed files with 205 additions and 40 deletions

View File

@ -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]);
}

View File

@ -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);
}
}

View File

@ -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);
}

30
src/rect.zig Normal file
View File

@ -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);
}