Compare commits

...

3 Commits

Author SHA1 Message Date
KikooDX 25335e94a6 Allow user to configure keybindings. 2021-02-25 23:31:23 +01:00
KikooDX 75351eb8b5 src/conf.zig can now be used to configure color theme.
Related things were improved.
2021-02-25 22:58:39 +01:00
KikooDX a0528de619 Repetition/parameter system, implemented and integrated. 2021-02-25 17:13:12 +01:00
13 changed files with 230 additions and 114 deletions

View File

@ -9,7 +9,7 @@ Subject to change.
* Fully keyboard driven modal editing. Mouse support is secondary.
* GUI software made in [Zig](https://ziglang.org/) with
[raylib](https://www.raylib.com/).
* Configurable (format unknown).
* Configurable (editing `src/conf.zig`).
# Installation
Runtime requirements: [raylib](https://www.raylib.com).
@ -40,7 +40,18 @@ $ kble sample.kble
$ kble $HOME/projects/kble/sample.kble
```
# Configuration.
In `src/conf.zig`, edit values from `BEGIN USER CONFIG`.
The configuration is applied at compile time. You have the possibility to:
* Disable mouse support.
* Swap mouse buttons.
* Change and add colors.
* Edit keybindings.
# Default keybindings
Repetition/parameter:
* `0``9`: behavior similar to Vi.
Movement:
* `h`: left
* `j`: down
@ -54,7 +65,7 @@ Movement:
Verbs:
* `<space>`: clear selection
* `d`: delete selection
* `r`: replace selection
* `r`: replace selection with parameter
* `+`: increase scale (zoom)
* `-`: decrease scale (dezoom)
* `=`: reset scale

View File

@ -1,11 +0,0 @@
#!/usr/bin/lua
local out = io.open("sample.kble", "wb")
out:write(string.char(
2,
0, 2,
0, 2,
0, 1,
0, 0,
0, 0,
1, 1))
out:close()

View File

@ -6,7 +6,7 @@
const std = @import("std");
const expect = std.testing.expect;
const conf = @import("conf.zig");
const Parameter = @import("parameter.zig");
const movement = @import("movement.zig");
const scaling = @import("scaling.zig");
const verbs = @import("verbs.zig");
@ -16,6 +16,7 @@ const Mode = @import("modes.zig").Mode;
pub const ActionCat = enum {
none, // do nothing
parameter, // add key to Parameter buffer
movement, // move and change selection
verb, // do stuff with selection
scale, // change draw scaling
@ -26,9 +27,10 @@ pub const ActionCat = enum {
pub const Action = struct {
category: ActionCat,
// Only one of these should be set, and only one should be used.
function_move: fn (*Vec2, conf.arg_type) movement.SelectionUpdate = movement.move_left,
function_verb: fn (*Level, conf.arg_type) void = verbs.delete,
function_scale: fn (scaling.scale_type) scaling.scale_type = scaling.scale_reset,
function_parameter: fn (*Parameter, u8) void = Parameter.process_key,
function_move: fn (*Vec2, Parameter.buffer_type) movement.SelectionUpdate = movement.move_left,
function_verb: fn (*Level, Parameter.buffer_type) void = verbs.delete,
function_scale: fn (scaling.scale_type, Parameter.buffer_type) scaling.scale_type = scaling.scale_reset,
function_file: fn (*Level, *std.mem.Allocator, [*:0]const u8) void = Level.action_write,
next_mode: Mode = Mode.normal,
};
@ -37,6 +39,12 @@ pub const ActionsDef = .{
.none = Action{
.category = ActionCat.none,
},
// Parameter/repetition.
.parameter = Action{
.category = ActionCat.parameter,
.function_parameter = Parameter.process_key,
},
// Movement.
// Reset selection.
.move_but_dont = Action{

View File

@ -3,12 +3,63 @@
// This file is part of [KBLE](https://sr.ht/~kikoodx/kble), which is
// MIT licensed. The MIT license requires this copyright notice to be
// included in all copies and substantial portions of the software.
//! Values and types used by different files and that don't fit in any.
//! Read /etc/kble.conf and execute content.
/// Number passed to commands.
const std = @import("std");
//! User defined settings.
const ray = @import("raylib.zig");
const cell_type = @import("level.zig").cell_type;
pub const arg_type: type = u16;
/// raylib.Color initialisation wrapper.
fn col(r: u8, g: u8, b: u8) ray.Color {
return ray.Color{
.r = r,
.g = g,
.b = b,
.a = 255,
};
}
// BEGIN USER CONFIG
pub const mouse_enabled: bool = true;
pub const mouse_left_btn: c_int = 0;
pub const mouse_right_btn: c_int = 1;
pub const theme = .{
.background = ray.BLACK,
.mode = .{
.normal = ray.GRAY,
.select = ray.BLUE,
.select_rect = ray.SKYBLUE,
.camera = ray.PURPLE,
},
};
/// Return user defined color for corresponding cell ID.
pub fn cell_color(cell: cell_type) ray.Color {
return switch (cell) {
0 => comptime col(026, 026, 026), // air
1 => comptime col(144, 144, 144), // solid
2 => comptime col(240, 010, 050), // red thing
else => ray.PURPLE, // undefined
};
}
/// Used to set keybindings.
/// Only bind to default keybindings, use 0 to unbind a key.
pub fn bind_key(key: u8) u8 {
return switch (key) {
// // jkl; movement.
// 'j' => 'h',
// 'k' => 'j',
// 'l' => 'k',
// ';' => 'l',
// // Backward movement with HJKL.
// 'H' => 'l',
// 'J' => 'k',
// 'K' => 'j',
// 'L' => 'h',
// // Use x instead of return for normal mode.
// '\n' => 0,
// 'x' => '\n',
else => key
};
}
// END USER CONFIG

View File

@ -3,10 +3,9 @@
// This file is part of [KBLE](https://sr.ht/~kikoodx/kble), which is
// MIT licensed. The MIT license requires this copyright notice to be
// included in all copies and substantial portions of the software.
const ray = @cImport({
@cInclude("raylib.h");
});
const ray = @import("raylib.zig");
const conf = @import("conf.zig");
const Vec2 = @import("vec2.zig");
const Rect = @import("rect.zig");
const scaling = @import("scaling.zig");
@ -17,10 +16,10 @@ pub fn cursor(scale: scaling.scale_type, offset: Vec2, pos: Vec2, mode: Mode) vo
const x = (pos.x - offset.y) * scale;
const y = (pos.y - offset.y) * scale;
const color: ray.Color = switch (mode) {
.normal => ray.GRAY,
.select => ray.BLUE,
.rectangle => ray.SKYBLUE,
.camera => ray.PURPLE,
.normal => conf.theme.mode.normal,
.select => conf.theme.mode.select,
.rectangle => conf.theme.mode.select_rect,
.camera => conf.theme.mode.camera,
};
ray.DrawRectangleLines(x, y, scale, scale, color);
}
@ -31,5 +30,5 @@ pub fn rectangle_selection(scale: scaling.scale_type, offset: Vec2, rect: Rect)
const w = (rect.right_x - rect.left_x + 1) * scale;
const y = (rect.top_y - offset.y) * scale;
const h = (rect.bottom_y - rect.top_y + 1) * scale;
ray.DrawRectangleLines(x, y, w, h, ray.SKYBLUE);
ray.DrawRectangleLines(x, y, w, h, conf.theme.mode.select_rect);
}

View File

@ -4,12 +4,11 @@
// MIT licensed. The MIT license requires this copyright notice to be
// included in all copies and substantial portions of the software.
//! Level structure, grid containing tile data.
const ray = @cImport({
@cInclude("raylib.h");
});
const ray = @import("raylib.zig");
const std = @import("std");
const expect = std.testing.expect;
const conf = @import("conf.zig");
const Vec2 = @import("vec2.zig");
const Rect = @import("rect.zig");
const SelectionUpdate = @import("movement.zig").SelectionUpdate;
@ -190,11 +189,7 @@ pub fn draw(self: Self, scale: u16, offset: Vec2) void {
var cy: Vec2.int_type = offset.y;
while (cy < self.height) {
const cell_content: cell_type = self.content[cy * self.width + cx];
const color = switch (cell_content) {
0 => ray.Color{ .r = 26, .g = 26, .b = 26, .a = 255 },
1 => ray.Color{ .r = 144, .g = 144, .b = 144, .a = 255 },
else => ray.PURPLE, //unknown
};
const color: ray.Color = conf.cell_color(cell_content);
ray.DrawRectangle(x + 1, y + 1, scale - 2, scale - 2, color);
y += scale;
cy += 1;

View File

@ -3,9 +3,7 @@
// This file is part of [KBLE](https://sr.ht/~kikoodx/kble), which is
// MIT licensed. The MIT license requires this copyright notice to be
// included in all copies and substantial portions of the software.
const ray = @cImport({
@cInclude("raylib.h");
});
const ray = @import("raylib.zig");
const std = @import("std");
const maxInt = std.math.maxInt;
const log = std.log.info;
@ -16,6 +14,7 @@ const Vec2 = @import("vec2.zig");
const Rect = @import("rect.zig");
const Mouse = @import("mouse.zig");
const draw = @import("draw.zig");
const Parameter = @import("parameter.zig");
const movement = @import("movement.zig");
const verbs = @import("verbs.zig");
const scaling = @import("scaling.zig");
@ -72,34 +71,53 @@ pub fn main() void {
// Set default mode.
var mode: Mode = Mode.normal;
// Parameter buffer.
var parameter: Parameter = Parameter{};
// Create binding "table".
var bindings = [_]*const actions.Action{&actions.ActionsDef.none} ** char_range;
// Set default bindings.
// Movement.
bindings['h'] = &ActionsDef.move_left;
bindings['j'] = &ActionsDef.move_down;
bindings['k'] = &ActionsDef.move_up;
bindings['l'] = &ActionsDef.move_right;
bindings['y'] = &ActionsDef.move_up_left;
bindings['u'] = &ActionsDef.move_up_right;
bindings['b'] = &ActionsDef.move_down_left;
bindings['n'] = &ActionsDef.move_down_right;
// Verbs.
bindings[' '] = &ActionsDef.verb_clear_selection;
bindings['d'] = &ActionsDef.verb_delete;
bindings['r'] = &ActionsDef.verb_replace;
// Scale.
bindings['='] = &ActionsDef.scale_reset;
bindings['+'] = &ActionsDef.scale_up;
bindings['-'] = &ActionsDef.scale_down;
// File.
bindings['e'] = &ActionsDef.file_read;
bindings['w'] = &ActionsDef.file_write;
// Mode.
bindings['\n'] = &ActionsDef.mode_normal;
bindings['i'] = &ActionsDef.mode_select;
bindings['v'] = &ActionsDef.mode_rectangle;
bindings['c'] = &ActionsDef.mode_camera;
comptime {
// Set default bindings.
var default_bindings = [_]*const actions.Action{&actions.ActionsDef.none} ** char_range;
comptime {
var i: u8 = 0;
while (i < 10) : (i += 1) {
default_bindings['0' + i] = &ActionsDef.parameter;
}
}
// Movement.
default_bindings['h'] = &ActionsDef.move_left;
default_bindings['j'] = &ActionsDef.move_down;
default_bindings['k'] = &ActionsDef.move_up;
default_bindings['l'] = &ActionsDef.move_right;
default_bindings['y'] = &ActionsDef.move_up_left;
default_bindings['u'] = &ActionsDef.move_up_right;
default_bindings['b'] = &ActionsDef.move_down_left;
default_bindings['n'] = &ActionsDef.move_down_right;
// Verbs.
default_bindings[' '] = &ActionsDef.verb_clear_selection;
default_bindings['d'] = &ActionsDef.verb_delete;
default_bindings['r'] = &ActionsDef.verb_replace;
// Scale.
default_bindings['='] = &ActionsDef.scale_reset;
default_bindings['+'] = &ActionsDef.scale_up;
default_bindings['-'] = &ActionsDef.scale_down;
// File.
default_bindings['e'] = &ActionsDef.file_read;
default_bindings['w'] = &ActionsDef.file_write;
// Mode.
default_bindings['\n'] = &ActionsDef.mode_normal;
default_bindings['i'] = &ActionsDef.mode_select;
default_bindings['v'] = &ActionsDef.mode_rectangle;
default_bindings['c'] = &ActionsDef.mode_camera;
// Map user bindings.
comptime {
var i: u8 = 0;
while (i < char_range) : (i += 1) {
bindings[i] = default_bindings[conf.bind_key(i)];
}
}
}
// Create input buffer.
const input_buffer_len = 255;
@ -142,8 +160,8 @@ pub fn main() void {
// Read the buffer backwards. This is placeholder logic.
while (input_cursor > 0) {
input_cursor -= 1;
const key = if (input_buffer[input_cursor] <= char_range)
input_buffer[input_cursor]
const key: u8 = if (input_buffer[input_cursor] <= char_range)
@intCast(u8, input_buffer[input_cursor])
else
0;
@ -153,18 +171,21 @@ pub fn main() void {
switch (action.category) {
.none => std.log.info("No action bound to {}.", .{key}),
.parameter => {
action.function_parameter(&parameter, key);
},
.movement => {
const selection_update: movement.SelectionUpdate =
action.function_move(&cursor, 1);
action.function_move(&cursor, parameter.pop(1));
if (apply_selection and selection_update.active) {
level.apply_selection_update(selection_update);
}
},
.verb => {
action.function_verb(&level, 1);
action.function_verb(&level, parameter.pop(1));
},
.scale => {
scale = action.function_scale(scale);
scale = action.function_scale(scale, parameter.pop(1));
},
.file => {
action.function_file(&level, allocator, level_path);
@ -243,13 +264,13 @@ pub fn main() void {
ray.BeginDrawing();
defer ray.EndDrawing();
ray.ClearBackground(ray.BLACK);
ray.ClearBackground(conf.theme.background);
level.draw(scale, camera);
level.draw_selection(scale, camera);
draw.cursor(scale, camera, cursor, mode);
if (mode == Mode.rectangle)
draw.rectangle_selection(scale, camera, Rect.init_from_vec2(cursor, cursor_before));
mouse.draw(scale, camera);
//ray.DrawFPS(0, 0);
if (conf.mouse_enabled)
mouse.draw(scale, camera);
}
}

View File

@ -4,11 +4,10 @@
// MIT licensed. The MIT license requires this copyright notice to be
// included in all copies and substantial portions of the software.
//! Mouse (eurk) logic and operations.
const ray = @cImport({
@cInclude("raylib.h");
});
const ray = @import("raylib.zig");
const std = @import("std");
const conf = @import("conf.zig");
const Vec2 = @import("vec2.zig");
const Rect = @import("rect.zig");
const scaling = @import("scaling.zig");
@ -42,7 +41,7 @@ pub fn draw(self: *Self, scale: scaling.scale_type, offset: Vec2) void {
MouseMode.sel => {
const x = (self.pos.x - offset.y) * scale;
const y = (self.pos.y - offset.y) * scale;
ray.DrawRectangleLines(x, y, scale, scale, ray.SKYBLUE);
ray.DrawRectangleLines(x, y, scale, scale, conf.theme.mode.select);
},
MouseMode.rect_sel => {
const rect = Rect.init_from_vec2(self.pos, self.start_pos);
@ -51,3 +50,4 @@ pub fn draw(self: *Self, scale: scaling.scale_type, offset: Vec2) void {
else => {},
}
}

View File

@ -8,7 +8,7 @@ const std = @import("std");
const expect = std.testing.expect;
const maxInt = std.math.maxInt;
const conf = @import("conf.zig");
const Parameter = @import("parameter.zig");
const Vec2 = @import("vec2.zig");
const Rect = @import("rect.zig");
@ -22,27 +22,27 @@ pub const SelectionUpdate = struct {
};
/// Universal move system, prefer direction wrappers.
fn move(cursor: *Vec2, arg: conf.arg_type, dx: i2, dy: i2) SelectionUpdate {
fn move(cursor: *Vec2, arg: Parameter.buffer_type, dx: i2, dy: i2) SelectionUpdate {
const before: Vec2 = cursor.*;
if (dx > 0) {
var i: conf.arg_type = 0;
var i: Parameter.buffer_type= 0;
while (i != arg and cursor.x < maxIntVec2) : (i += 1) {
cursor.x += 1;
}
} else if (dx < 0) {
var i: conf.arg_type = 0;
var i: Parameter.buffer_type= 0;
while (i != arg and cursor.x > 0) : (i += 1) {
cursor.x -= 1;
}
}
if (dy > 0) {
var i: conf.arg_type = 0;
var i: Parameter.buffer_type= 0;
while (i != arg and cursor.y < maxIntVec2) : (i += 1) {
cursor.y += 1;
}
} else if (dy < 0) {
var i: conf.arg_type = 0;
var i: Parameter.buffer_type= 0;
while (i != arg and cursor.y > 0) : (i += 1) {
cursor.y -= 1;
}
@ -56,47 +56,47 @@ fn move(cursor: *Vec2, arg: conf.arg_type, dx: i2, dy: i2) SelectionUpdate {
}
/// Just don't move.
pub fn move_but_dont(cursor: *Vec2, arg: conf.arg_type) SelectionUpdate {
pub fn move_but_dont(cursor: *Vec2, arg: Parameter.buffer_type) SelectionUpdate {
return move(cursor, arg, 0, 0);
}
/// Try to move the cursor `n` times left.
pub fn move_left(cursor: *Vec2, arg: conf.arg_type) SelectionUpdate {
pub fn move_left(cursor: *Vec2, arg: Parameter.buffer_type) SelectionUpdate {
return move(cursor, arg, -1, 0);
}
/// Try to move the cursor `n` times right.
pub fn move_right(cursor: *Vec2, arg: conf.arg_type) SelectionUpdate {
pub fn move_right(cursor: *Vec2, arg: Parameter.buffer_type) SelectionUpdate {
return move(cursor, arg, 1, 0);
}
/// Try to move the cursor `n` times up.
pub fn move_up(cursor: *Vec2, arg: conf.arg_type) SelectionUpdate {
pub fn move_up(cursor: *Vec2, arg: Parameter.buffer_type) SelectionUpdate {
return move(cursor, arg, 0, -1);
}
/// Try to move the cursor `n` times down.
pub fn move_down(cursor: *Vec2, arg: conf.arg_type) SelectionUpdate {
pub fn move_down(cursor: *Vec2, arg: Parameter.buffer_type) SelectionUpdate {
return move(cursor, arg, 0, 1);
}
/// Try to move the cursor `n` times up and left.
pub fn move_up_left(cursor: *Vec2, arg: conf.arg_type) SelectionUpdate {
pub fn move_up_left(cursor: *Vec2, arg: Parameter.buffer_type) SelectionUpdate {
return move(cursor, arg, -1, -1);
}
/// Try to move the cursor `n` times up and right.
pub fn move_up_right(cursor: *Vec2, arg: conf.arg_type) SelectionUpdate {
pub fn move_up_right(cursor: *Vec2, arg: Parameter.buffer_type) SelectionUpdate {
return move(cursor, arg, 1, -1);
}
/// Try to move the cursor `n` times down and left.
pub fn move_down_left(cursor: *Vec2, arg: conf.arg_type) SelectionUpdate {
pub fn move_down_left(cursor: *Vec2, arg: Parameter.buffer_type) SelectionUpdate {
return move(cursor, arg, -1, 1);
}
/// Try to move the cursor `n` times down and right.
pub fn move_down_right(cursor: *Vec2, arg: conf.arg_type) SelectionUpdate {
pub fn move_down_right(cursor: *Vec2, arg: Parameter.buffer_type) SelectionUpdate {
return move(cursor, arg, 1, 1);
}

36
src/parameter.zig Normal file
View File

@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 KikooDX
// This file is part of [KBLE](https://sr.ht/~kikoodx/kble), which is
// MIT licensed. The MIT license requires this copyright notice to be
// included in all copies and substantial portions of the software.
const std = @import("std");
const Self = @This();
pub const buffer_type = u16;
pub const buffer_limit = std.math.maxInt(buffer_type) / 10;
buffer: buffer_type = 0, // hold current number
used: bool = false, // keep track of buffer usage
/// Only accept key between '0' and '9' inclusive. Add a number to the buffer.
pub fn process_key(self: *Self, key: u8) void {
if (key < '0' or key > '9') unreachable;
self.used = true;
const number: buffer_type = key - '0';
if (self.buffer < buffer_limit) {
self.buffer *= 10;
self.buffer += number;
std.log.info("Parameter buffer: {}", .{self});
} else {
std.log.warn("Buffer limit reached.", .{});
}
}
/// Return the buffer value if used and reset it. Otherwise return `default`.
pub fn pop(self: *Self, default: buffer_type) buffer_type {
defer self.buffer = 0;
defer self.used = false;
return if (self.used) self.buffer else default;
}

4
src/raylib.zig Normal file
View File

@ -0,0 +1,4 @@
/// Simplest imaginable raylib wrapper
pub usingnamespace @cImport({
@cInclude("raylib.h");
});

View File

@ -8,38 +8,40 @@ const std = @import("std");
const expect = std.testing.expect;
const maxInt = std.math.maxInt;
const Parameter = @import("parameter.zig");
pub const scale_type = u8;
pub const scale_default: comptime_int = 16;
const scale_min: comptime_int = 3;
const scale_max: comptime_int = maxInt(scale_type);
const scale_min = 3;
const scale_max = maxInt(scale_type);
pub fn scale_reset(current_scale: scale_type) scale_type {
pub fn scale_reset(current_scale: scale_type, arg: Parameter.buffer_type) scale_type {
return scale_default;
}
pub fn scale_up(current_scale: scale_type) scale_type {
if (current_scale < scale_max) {
return current_scale + 1;
} else return current_scale;
pub fn scale_up(current_scale: scale_type, arg: Parameter.buffer_type) scale_type {
if (@intCast(u32, current_scale) + @intCast(u32, arg) < @intCast(u32, scale_max)) {
return current_scale + @intCast(u8, arg);
} else return scale_max;
}
pub fn scale_down(current_scale: scale_type) scale_type {
if (current_scale > scale_min) {
return current_scale - 1;
} else return current_scale;
pub fn scale_down(current_scale: scale_type, arg: Parameter.buffer_type) scale_type {
if (@intCast(i32, current_scale) - @intCast(i32, arg) > @intCast(i32, scale_min)) {
return current_scale - @intCast(u8, arg);
} else return scale_min;
}
test "scale reset" {
expect(scale_reset(42) == scale_default);
expect(scale_reset(42, 1) == scale_default);
}
test "scale up" {
expect(scale_up(42) == 43);
expect(scale_up(scale_max) == scale_max);
expect(scale_up(42, 1) == 43);
expect(scale_up(scale_max - 4, 42) == scale_max);
}
test "scale down" {
expect(scale_down(42) == 41);
expect(scale_down(scale_min) == scale_min);
expect(scale_down(42, 1) == 41);
expect(scale_down(scale_min + 2, 42) == scale_min);
}

View File

@ -4,11 +4,11 @@
// MIT licensed. The MIT license requires this copyright notice to be
// included in all copies and substantial portions of the software.
//! Act on level using selection.
const conf = @import("conf.zig");
const Parameter = @import("parameter.zig");
const Level = @import("level.zig");
/// Clear selection (deselect everything).
pub fn clear_selection(level: *Level, arg: conf.arg_type) void {
pub fn clear_selection(level: *Level, arg: Parameter.buffer_type) void {
var i: u32 = 0;
while (i < level.width * level.height) : (i += 1) {
level.selection[i] = false;
@ -16,7 +16,7 @@ pub fn clear_selection(level: *Level, arg: conf.arg_type) void {
}
/// Delete selected cells (set to 0).
pub fn delete(level: *Level, arg: conf.arg_type) void {
pub fn delete(level: *Level, arg: Parameter.buffer_type) void {
var i: u32 = 0;
while (i < level.width * level.height) : (i += 1) {
if (level.selection[i])
@ -25,7 +25,7 @@ pub fn delete(level: *Level, arg: conf.arg_type) void {
}
/// Replace selected cells with `arg`.
pub fn replace(level: *Level, arg: conf.arg_type) void {
pub fn replace(level: *Level, arg: Parameter.buffer_type) void {
const casted_arg = @intCast(Level.cell_type, arg);
var i: u32 = 0;
while (i < level.width * level.height) : (i += 1) {