Compare commits

...

4 Commits

7 changed files with 90 additions and 51 deletions

View File

@ -20,8 +20,8 @@ https://builds.sr.ht/~kikoodx/kble
## Manual compilation
Build requirements: [Zig](https://ziglang.org/download/) master branch,
[raylib](https://www.raylib.com).
For Arch users, `zig-dev-bin` and `zig-git` are available in the AUR.
[raylib](https://www.raylib.com). For Arch users, I made
[`kble-git`](https://aur.archlinux.org/packages/kble-git/) available in the AUR.
```sh
$ # Clone this repository
$ git clone https://git.sr.ht/~kikoodx/kble && cd kble
@ -79,14 +79,16 @@ Modes:
* `<return>`: normal mode, default
* `i`: free selection mode
* `v`: rectangle selection mode
* `I`: free unselection mode
* `V`: rectangle unselection mode
* `c`: camera mode [not implemented]
# Mouse control
Right click and left click are the same, except right click reset
selection before taking effect.
* Click: free selection.
* Shift + Click: rectangle selection.
* Click while selecting: end selection.
# Default mouse control
* Right click: free selection.
* Shift + left click: rectangle selection.
* Right click: libre unselection.
* Shift + right click: rectangle unselection.
* Left or right click while un·selecting: end selection.
# License
Copyright (c) 2021 KikooDX

View File

@ -143,6 +143,14 @@ pub const ActionsDef = .{
.category = ActionCat.mode,
.next_mode = Mode.rectangle,
},
.mode_unselect = Action{
.category = ActionCat.mode,
.next_mode = Mode.unselect,
},
.mode_unrectangle = Action{
.category = ActionCat.mode,
.next_mode = Mode.unrectangle,
},
.mode_camera = Action{
.category = ActionCat.mode,
.next_mode = Mode.camera,

View File

@ -21,6 +21,15 @@ fn col(r: u8, g: u8, b: u8) ray.Color {
pub const mouse_enabled: bool = true;
pub const mouse_left_btn: c_int = 0;
pub const mouse_right_btn: c_int = 1;
// Set to true for better graphical tablet experience.
// * false: click to enter, draw, click to exit.
// * true: click to enter, draw, release to exit.
pub const mouse_graphic_tablet: bool = false;
pub const default_grid_size = .{
.width = 16,
.height = 16,
};
pub const theme = .{
.background = ray.BLACK,
@ -28,6 +37,8 @@ pub const theme = .{
.normal = ray.GRAY,
.select = ray.BLUE,
.select_rect = ray.SKYBLUE,
.unselect = ray.RED,
.unrectangle = ray.PINK,
.camera = ray.PURPLE,
},
};

View File

@ -19,16 +19,21 @@ pub fn cursor(scale: scaling.scale_type, offset: Vec2, pos: Vec2, mode: Mode) vo
.normal => conf.theme.mode.normal,
.select => conf.theme.mode.select,
.rectangle => conf.theme.mode.select_rect,
.unselect => conf.theme.mode.unselect,
.unrectangle => conf.theme.mode.unrectangle,
.camera => conf.theme.mode.camera,
};
ray.DrawRectangleLines(x, y, scale, scale, color);
}
/// Draw rectangle selection preview.
pub fn rectangle_selection(scale: scaling.scale_type, offset: Vec2, rect: Rect) void {
pub fn rectangle_selection(scale: scaling.scale_type, offset: Vec2, rect: Rect, sel: bool) void {
const x = (rect.left_x - offset.x) * scale;
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, conf.theme.mode.select_rect);
ray.DrawRectangleLines(x, y, w, h, if (sel)
conf.theme.mode.select_rect
else
conf.theme.mode.unrectangle);
}

View File

@ -24,6 +24,7 @@ const ActionCat = actions.ActionCat;
const Mode = @import("modes.zig").Mode;
const char_range = 255;
const input_buffer_len = 255;
pub fn main() void {
const level_path: [*:0]const u8 = if (std.os.argv.len > 1)
@ -48,7 +49,8 @@ pub fn main() void {
// Try to load level, is doesn't exist create it.
var level: Level = Level.init_read(allocator, level_path) catch create: {
break :create Level.init(allocator, 16, 16) catch unreachable;
break :create Level.init(allocator, conf.default_grid_size.width, conf.default_grid_size.height) catch
unreachable;
};
defer level.deinit(allocator);
@ -109,6 +111,8 @@ pub fn main() void {
default_bindings['\n'] = &ActionsDef.mode_normal;
default_bindings['i'] = &ActionsDef.mode_select;
default_bindings['v'] = &ActionsDef.mode_rectangle;
default_bindings['I'] = &ActionsDef.mode_unselect;
default_bindings['V'] = &ActionsDef.mode_unrectangle;
default_bindings['c'] = &ActionsDef.mode_camera;
// Map user bindings.
comptime {
@ -120,7 +124,6 @@ pub fn main() void {
}
// Create input buffer.
const input_buffer_len = 255;
var input_buffer: [input_buffer_len]u32 = undefined;
comptime {
var i: u8 = 0;
@ -132,28 +135,18 @@ pub fn main() void {
while (!ray.WindowShouldClose()) {
{
// TODO: apply DRY
// Get keyboard input.
var key = ray.GetCharPressed();
// Check if more characters have been pressed.
while (key != 0) {
// Add key to buffer.
input_buffer[input_cursor] = @intCast(u32, key);
input_cursor += 1;
// Avoid writing out of memory.
if (input_cursor >= input_buffer_len)
input_cursor = input_buffer_len - 1;
add_key_to_buffer(&input_buffer, &input_cursor, key);
key = ray.GetCharPressed();
}
// Check for special keys, not detected by GetCharPressed.
if (ray.IsKeyDown(ray.KEY_ENTER)) {
input_buffer[input_cursor] = '\n';
input_cursor += 1;
// Avoid writing out of memory.
if (input_cursor >= input_buffer_len)
input_cursor = input_buffer_len - 1;
}
if (ray.IsKeyPressed(ray.KEY_ENTER))
add_key_to_buffer(&input_buffer, &input_cursor, '\n');
if (ray.IsKeyPressed(ray.KEY_TAB))
add_key_to_buffer(&input_buffer, &input_cursor, '\t');
}
// Process buffer content.
@ -167,7 +160,7 @@ pub fn main() void {
// TODO: move everything from this to a function (for config and macros support).
const action: actions.Action = bindings[key].*;
const apply_selection: bool = (mode == Mode.select);
const apply_selection: bool = (mode == .select);
switch (action.category) {
.none => std.log.info("No action bound to {}.", .{key}),
@ -192,13 +185,13 @@ pub fn main() void {
},
.mode => {
// Rectangle selection!
if (mode == Mode.rectangle) {
if (mode == .rectangle or mode == .unrectangle) {
const selection = Rect.init_from_vec2(cursor, cursor_before);
level.select_rect(selection, true);
level.select_rect(selection, mode == .rectangle);
}
cursor_before = cursor; // Save position before selection.
mode = action.next_mode;
if (mode == Mode.select) // Select first tile.
if (mode == .select) // Select first tile.
level.select_cell(cursor, true);
},
}
@ -217,17 +210,21 @@ pub fn main() void {
}
const left_click: bool = ray.IsMouseButtonPressed(conf.mouse_left_btn);
const left_release: bool = ray.IsMouseButtonReleased(conf.mouse_left_btn);
const right_click: bool = ray.IsMouseButtonPressed(conf.mouse_right_btn);
const right_release: bool = ray.IsMouseButtonReleased(conf.mouse_right_btn);
const click: bool = left_click or right_click;
const release: bool = left_release or right_release;
const end_sel_event = if (conf.mouse_graphic_tablet) release else click;
// If click, get out of current mode and apply changes if necessary.
if (click and mouse.mode != Mouse.MouseMode.idle) {
defer mouse.mode = Mouse.MouseMode.wait;
// When end selection event, get out of current mode and apply changes if necessary.
if (end_sel_event and mouse.mode != Mouse.MouseMode.idle) {
defer mouse.mode = .wait;
// Select area.
if (mouse.mode == Mouse.MouseMode.rect_sel) {
if (mouse.mode == .rect_sel or mouse.mode == .unrect_sel) {
const selection = Rect.init_from_vec2(mouse.start_pos, mouse.pos);
level.select_rect(selection, true);
level.select_rect(selection, mouse.mode == .rect_sel);
}
}
@ -240,22 +237,23 @@ pub fn main() void {
ray.IsKeyDown(ray.KEY_RIGHT_SHIFT);
if (click) { // Switch mode.
cursor = mouse.pos;
// Reset selection on right click.
if (right_click) {
verbs.clear_selection(&level, 1);
}
// Set position.
mouse.start_pos = mouse.pos;
mouse.mode = if (mod_rect_sel)
// If right click then un<mode>
mouse.mode = if (right_click and mod_rect_sel)
Mouse.MouseMode.unrect_sel
else if (right_click and !mod_rect_sel)
Mouse.MouseMode.unsel
else if (mod_rect_sel)
Mouse.MouseMode.rect_sel
else
Mouse.MouseMode.sel;
}
},
// Select stuff under the cursor.
.sel => {
.sel, .unsel => {
cursor = mouse.pos;
level.select_cell(mouse.pos, true);
level.select_cell(mouse.pos, mouse.mode == .sel);
},
else => {},
}
@ -268,9 +266,17 @@ pub fn main() void {
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));
if (mode == Mode.rectangle or mode == Mode.unrectangle)
draw.rectangle_selection(scale, camera, Rect.init_from_vec2(cursor, cursor_before), mode == .rectangle);
if (conf.mouse_enabled)
mouse.draw(scale, camera);
}
}
fn add_key_to_buffer(input_buffer: *[input_buffer_len]u32, input_cursor: *u8, key: c_int) void {
input_buffer[input_cursor.*] = @intCast(u32, key);
input_cursor.* += 1;
// Avoid writing out of memory.
if (input_cursor.* >= input_buffer_len)
input_cursor.* = input_buffer_len - 1;
}

View File

@ -8,5 +8,7 @@ pub const Mode = enum {
normal, // move cursor and do stuff
select, // same, but select while doing so
rectangle, // same, but select as a rectangle
unselect, // remove from selection instead of adding to it
unrectangle, // same as above for rectangle
camera, // move camera instead
};

View File

@ -20,6 +20,8 @@ pub const MouseMode = enum {
idle,
sel,
rect_sel,
unsel,
unrect_sel,
};
mode: MouseMode, // State.
@ -38,16 +40,19 @@ pub fn init() Self {
/// Draw cursor or preview rectangle selection depending on mode.
pub fn draw(self: *Self, scale: scaling.scale_type, offset: Vec2) void {
switch (self.mode) {
MouseMode.sel => {
// Cell size cursor.
.idle, .wait, .sel, .unsel => {
const x = (self.pos.x - offset.y) * scale;
const y = (self.pos.y - offset.y) * scale;
ray.DrawRectangleLines(x, y, scale, scale, conf.theme.mode.select);
ray.DrawRectangleLines(x, y, scale, scale, switch (self.mode) {
.sel => conf.theme.mode.select,
.unsel => conf.theme.mode.unselect,
else => conf.theme.mode.normal,
});
},
MouseMode.rect_sel => {
.rect_sel, .unrect_sel => {
const rect = Rect.init_from_vec2(self.pos, self.start_pos);
draw_fn.rectangle_selection(scale, offset, rect);
draw_fn.rectangle_selection(scale, offset, rect, self.mode == .rect_sel);
},
else => {},
}
}