2021-01-29 12:32:38 +01:00
|
|
|
// 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.
|
2021-02-25 22:53:02 +01:00
|
|
|
const ray = @import("raylib.zig");
|
2021-01-26 17:44:40 +01:00
|
|
|
const std = @import("std");
|
2021-01-28 16:12:41 +01:00
|
|
|
const maxInt = std.math.maxInt;
|
2021-02-01 00:30:28 +01:00
|
|
|
const log = std.log.info;
|
2021-01-26 17:44:40 +01:00
|
|
|
|
2021-02-01 00:30:28 +01:00
|
|
|
const conf = @import("conf.zig");
|
2021-01-26 17:44:40 +01:00
|
|
|
const Level = @import("level.zig");
|
|
|
|
const Vec2 = @import("vec2.zig");
|
2021-02-01 00:30:28 +01:00
|
|
|
const Rect = @import("rect.zig");
|
|
|
|
const Mouse = @import("mouse.zig");
|
2021-02-02 14:21:12 +01:00
|
|
|
const draw = @import("draw.zig");
|
2021-02-25 17:13:12 +01:00
|
|
|
const Parameter = @import("parameter.zig");
|
2021-01-27 17:23:45 +01:00
|
|
|
const movement = @import("movement.zig");
|
2021-02-02 14:49:50 +01:00
|
|
|
const verbs = @import("verbs.zig");
|
2021-01-30 16:43:55 +01:00
|
|
|
const scaling = @import("scaling.zig");
|
2021-01-30 16:13:06 +01:00
|
|
|
const actions = @import("actions.zig");
|
|
|
|
const ActionsDef = actions.ActionsDef;
|
|
|
|
const ActionCat = actions.ActionCat;
|
2021-02-02 14:21:12 +01:00
|
|
|
const Mode = @import("modes.zig").Mode;
|
2021-01-30 16:13:06 +01:00
|
|
|
|
|
|
|
const char_range = 255;
|
2021-02-26 01:03:41 +01:00
|
|
|
const input_buffer_len = 255;
|
2021-01-24 19:22:48 +01:00
|
|
|
|
2021-02-02 14:21:12 +01:00
|
|
|
pub fn main() void {
|
2021-02-24 23:11:06 +01:00
|
|
|
const level_path: [*:0]const u8 = if (std.os.argv.len > 1)
|
|
|
|
std.os.argv[1]
|
|
|
|
else nopath: {
|
|
|
|
std.log.notice("No path provided, defaults to \"level.kble\".", .{});
|
|
|
|
break :nopath "level.kble";
|
|
|
|
};
|
|
|
|
|
2021-01-26 13:10:16 +01:00
|
|
|
// Create allocator
|
2021-01-26 23:38:41 +01:00
|
|
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
|
|
defer arena.deinit();
|
|
|
|
const allocator = &arena.allocator;
|
2021-01-24 19:22:48 +01:00
|
|
|
|
2021-01-26 17:44:40 +01:00
|
|
|
// Create window.
|
2021-01-24 19:22:48 +01:00
|
|
|
ray.SetConfigFlags(ray.FLAG_WINDOW_RESIZABLE);
|
2021-01-26 13:10:16 +01:00
|
|
|
ray.InitWindow(640, 480, "KBLE");
|
2021-01-24 19:22:48 +01:00
|
|
|
defer ray.CloseWindow();
|
|
|
|
|
2021-01-26 17:44:40 +01:00
|
|
|
// Limit FPS for performance.
|
2021-01-24 19:22:48 +01:00
|
|
|
ray.SetTargetFPS(60);
|
|
|
|
|
2021-02-24 23:11:06 +01:00
|
|
|
// Try to load level, is doesn't exist create it.
|
|
|
|
var level: Level = Level.init_read(allocator, level_path) catch create: {
|
2021-02-26 11:33:19 +01:00
|
|
|
break :create Level.init(allocator, conf.default_grid_size.width, conf.default_grid_size.height) catch
|
|
|
|
unreachable;
|
2021-02-24 23:11:06 +01:00
|
|
|
};
|
2021-01-26 13:10:16 +01:00
|
|
|
defer level.deinit(allocator);
|
|
|
|
|
2021-01-27 14:12:44 +01:00
|
|
|
// Create camera.
|
2021-02-02 14:21:12 +01:00
|
|
|
// TODO: Can't move camera, implement camera mode.
|
2021-02-02 14:49:50 +01:00
|
|
|
var camera: Vec2 = comptime Vec2.init(0, 0);
|
2021-01-26 17:44:40 +01:00
|
|
|
|
2021-01-30 16:43:55 +01:00
|
|
|
// Init scale, used by drawing code.
|
|
|
|
var scale: scaling.scale_type = scaling.scale_default;
|
2021-01-28 16:12:41 +01:00
|
|
|
|
2021-02-01 00:30:28 +01:00
|
|
|
// Create mouse.
|
2021-02-02 14:49:50 +01:00
|
|
|
var mouse: Mouse = comptime Mouse.init();
|
2021-02-01 00:30:28 +01:00
|
|
|
|
2021-01-28 13:46:02 +01:00
|
|
|
// Create cursor.
|
2021-02-02 14:49:50 +01:00
|
|
|
var cursor: Vec2 = comptime Vec2.init(0, 0);
|
|
|
|
|
|
|
|
// Used by rectangle selection mode.
|
|
|
|
var cursor_before: Vec2 = comptime Vec2.init(0, 0);
|
2021-01-28 13:46:02 +01:00
|
|
|
|
2021-02-02 14:21:12 +01:00
|
|
|
// Set default mode.
|
|
|
|
var mode: Mode = Mode.normal;
|
|
|
|
|
2021-02-25 17:13:12 +01:00
|
|
|
// Parameter buffer.
|
|
|
|
var parameter: Parameter = Parameter{};
|
|
|
|
|
2021-01-30 16:13:06 +01:00
|
|
|
// Create binding "table".
|
|
|
|
var bindings = [_]*const actions.Action{&actions.ActionsDef.none} ** char_range;
|
2021-02-25 23:31:23 +01:00
|
|
|
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;
|
2021-02-26 13:12:29 +01:00
|
|
|
default_bindings['I'] = &ActionsDef.mode_unselect;
|
|
|
|
default_bindings['V'] = &ActionsDef.mode_unrectangle;
|
2021-02-25 23:31:23 +01:00
|
|
|
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)];
|
|
|
|
}
|
2021-02-25 17:13:12 +01:00
|
|
|
}
|
|
|
|
}
|
2021-01-30 16:13:06 +01:00
|
|
|
|
2021-01-30 16:43:55 +01:00
|
|
|
// Create input buffer.
|
2021-01-27 14:12:44 +01:00
|
|
|
var input_buffer: [input_buffer_len]u32 = undefined;
|
|
|
|
comptime {
|
2021-01-27 19:26:12 +01:00
|
|
|
var i: u8 = 0;
|
2021-01-27 14:12:44 +01:00
|
|
|
while (i < input_buffer_len) : (i += 1) {
|
|
|
|
input_buffer[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
2021-01-27 19:26:12 +01:00
|
|
|
var input_cursor: u8 = 0;
|
2021-01-27 14:12:44 +01:00
|
|
|
|
2021-01-26 13:10:16 +01:00
|
|
|
while (!ray.WindowShouldClose()) {
|
2021-01-30 16:13:06 +01:00
|
|
|
{
|
|
|
|
// Get keyboard input.
|
|
|
|
var key = ray.GetCharPressed();
|
|
|
|
// Check if more characters have been pressed.
|
|
|
|
while (key != 0) {
|
2021-02-26 01:03:41 +01:00
|
|
|
add_key_to_buffer(&input_buffer, &input_cursor, key);
|
2021-01-30 16:13:06 +01:00
|
|
|
key = ray.GetCharPressed();
|
|
|
|
}
|
2021-02-02 14:21:12 +01:00
|
|
|
// Check for special keys, not detected by GetCharPressed.
|
2021-02-26 01:03:41 +01:00
|
|
|
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');
|
2021-01-27 14:12:44 +01:00
|
|
|
}
|
|
|
|
|
2021-01-27 17:23:45 +01:00
|
|
|
// Process buffer content.
|
|
|
|
// Read the buffer backwards. This is placeholder logic.
|
|
|
|
while (input_cursor > 0) {
|
|
|
|
input_cursor -= 1;
|
2021-02-25 17:13:12 +01:00
|
|
|
const key: u8 = if (input_buffer[input_cursor] <= char_range)
|
|
|
|
@intCast(u8, input_buffer[input_cursor])
|
2021-01-30 16:13:06 +01:00
|
|
|
else
|
|
|
|
0;
|
|
|
|
|
2021-02-04 00:55:57 +01:00
|
|
|
// TODO: move everything from this to a function (for config and macros support).
|
2021-01-30 16:13:06 +01:00
|
|
|
const action: actions.Action = bindings[key].*;
|
2021-02-26 13:12:29 +01:00
|
|
|
const apply_selection: bool = (mode == .select);
|
2021-01-30 16:13:06 +01:00
|
|
|
|
|
|
|
switch (action.category) {
|
2021-01-30 23:59:25 +01:00
|
|
|
.none => std.log.info("No action bound to {}.", .{key}),
|
2021-02-25 17:13:12 +01:00
|
|
|
.parameter => {
|
|
|
|
action.function_parameter(¶meter, key);
|
|
|
|
},
|
2021-01-30 16:13:06 +01:00
|
|
|
.movement => {
|
|
|
|
const selection_update: movement.SelectionUpdate =
|
2021-02-25 17:13:12 +01:00
|
|
|
action.function_move(&cursor, parameter.pop(1));
|
2021-02-02 14:21:12 +01:00
|
|
|
if (apply_selection and selection_update.active) {
|
2021-01-30 16:13:06 +01:00
|
|
|
level.apply_selection_update(selection_update);
|
|
|
|
}
|
2021-01-28 16:12:41 +01:00
|
|
|
},
|
2021-01-30 16:13:06 +01:00
|
|
|
.verb => {
|
2021-02-25 17:13:12 +01:00
|
|
|
action.function_verb(&level, parameter.pop(1));
|
2021-01-30 16:13:06 +01:00
|
|
|
},
|
|
|
|
.scale => {
|
2021-02-25 17:13:12 +01:00
|
|
|
scale = action.function_scale(scale, parameter.pop(1));
|
2021-01-30 16:13:06 +01:00
|
|
|
},
|
2021-02-24 15:48:32 +01:00
|
|
|
.file => {
|
|
|
|
action.function_file(&level, allocator, level_path);
|
|
|
|
},
|
2021-02-02 14:21:12 +01:00
|
|
|
.mode => {
|
2021-02-02 14:49:50 +01:00
|
|
|
// Rectangle selection!
|
2021-02-26 13:12:29 +01:00
|
|
|
if (mode == .rectangle or mode == .unrectangle) {
|
2021-02-02 14:49:50 +01:00
|
|
|
const selection = Rect.init_from_vec2(cursor, cursor_before);
|
2021-02-26 13:12:29 +01:00
|
|
|
level.select_rect(selection, mode == .rectangle);
|
2021-02-02 14:49:50 +01:00
|
|
|
}
|
|
|
|
cursor_before = cursor; // Save position before selection.
|
2021-02-02 14:21:12 +01:00
|
|
|
mode = action.next_mode;
|
2021-02-26 13:12:29 +01:00
|
|
|
if (mode == .select) // Select first tile.
|
2021-02-04 00:55:57 +01:00
|
|
|
level.select_cell(cursor, true);
|
2021-02-02 14:21:12 +01:00
|
|
|
},
|
2021-01-29 18:06:41 +01:00
|
|
|
}
|
2021-02-04 00:55:57 +01:00
|
|
|
// TODO end
|
2021-01-27 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
2021-02-01 00:30:28 +01:00
|
|
|
// Mouse operations.
|
|
|
|
if (conf.mouse_enabled) {
|
|
|
|
// Update position.
|
|
|
|
{
|
|
|
|
// Set mouse scaling.
|
|
|
|
ray.SetMouseScale(1.0 / @intToFloat(f32, scale), 1.0 / @intToFloat(f32, scale));
|
|
|
|
|
2021-02-02 18:38:47 +01:00
|
|
|
mouse.pos.x = @intCast(Vec2.int_type, ray.GetMouseX()) + camera.x;
|
|
|
|
mouse.pos.y = @intCast(Vec2.int_type, ray.GetMouseY()) + camera.y;
|
2021-02-01 00:30:28 +01:00
|
|
|
}
|
|
|
|
|
2021-02-01 10:37:58 +01:00
|
|
|
const left_click: bool = ray.IsMouseButtonPressed(conf.mouse_left_btn);
|
2021-02-26 10:30:22 +01:00
|
|
|
const left_release: bool = ray.IsMouseButtonReleased(conf.mouse_left_btn);
|
2021-02-01 10:37:58 +01:00
|
|
|
const right_click: bool = ray.IsMouseButtonPressed(conf.mouse_right_btn);
|
2021-02-26 10:30:22 +01:00
|
|
|
const right_release: bool = ray.IsMouseButtonReleased(conf.mouse_right_btn);
|
2021-02-01 10:37:58 +01:00
|
|
|
const click: bool = left_click or right_click;
|
2021-02-26 10:30:22 +01:00
|
|
|
const release: bool = left_release or right_release;
|
|
|
|
const end_sel_event = if (conf.mouse_graphic_tablet) release else click;
|
2021-02-01 10:37:58 +01:00
|
|
|
|
2021-02-26 10:30:22 +01:00
|
|
|
// When end selection event, get out of current mode and apply changes if necessary.
|
|
|
|
if (end_sel_event and mouse.mode != Mouse.MouseMode.idle) {
|
2021-02-26 13:12:29 +01:00
|
|
|
defer mouse.mode = .wait;
|
2021-02-01 10:37:58 +01:00
|
|
|
|
|
|
|
// Select area.
|
2021-02-26 13:12:29 +01:00
|
|
|
if (mouse.mode == .rect_sel or mouse.mode == .unrect_sel) {
|
2021-02-01 10:37:58 +01:00
|
|
|
const selection = Rect.init_from_vec2(mouse.start_pos, mouse.pos);
|
2021-02-26 13:12:29 +01:00
|
|
|
level.select_rect(selection, mouse.mode == .rect_sel);
|
2021-02-01 10:37:58 +01:00
|
|
|
}
|
|
|
|
}
|
2021-02-01 00:30:28 +01:00
|
|
|
|
|
|
|
// State machine.
|
|
|
|
switch (mouse.mode) {
|
2021-02-01 10:37:58 +01:00
|
|
|
.wait => mouse.mode = Mouse.MouseMode.idle,
|
2021-02-01 00:30:28 +01:00
|
|
|
// See if can switch mode.
|
|
|
|
.idle => {
|
|
|
|
const mod_rect_sel: bool = ray.IsKeyDown(ray.KEY_LEFT_SHIFT) or
|
|
|
|
ray.IsKeyDown(ray.KEY_RIGHT_SHIFT);
|
2021-02-01 10:37:58 +01:00
|
|
|
if (click) { // Switch mode.
|
2021-02-01 00:30:28 +01:00
|
|
|
cursor = mouse.pos;
|
2021-02-01 10:37:58 +01:00
|
|
|
// Set position.
|
2021-02-01 00:30:28 +01:00
|
|
|
mouse.start_pos = mouse.pos;
|
2021-02-26 13:12:29 +01:00
|
|
|
// 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)
|
2021-02-01 00:30:28 +01:00
|
|
|
Mouse.MouseMode.rect_sel
|
|
|
|
else
|
|
|
|
Mouse.MouseMode.sel;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
// Select stuff under the cursor.
|
2021-02-26 13:12:29 +01:00
|
|
|
.sel, .unsel => {
|
2021-02-01 00:30:28 +01:00
|
|
|
cursor = mouse.pos;
|
2021-02-26 13:12:29 +01:00
|
|
|
level.select_cell(mouse.pos, mouse.mode == .sel);
|
2021-02-01 00:30:28 +01:00
|
|
|
},
|
2021-02-01 11:16:44 +01:00
|
|
|
else => {},
|
2021-02-01 00:30:28 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-24 19:22:48 +01:00
|
|
|
ray.BeginDrawing();
|
|
|
|
defer ray.EndDrawing();
|
|
|
|
|
2021-02-25 22:53:02 +01:00
|
|
|
ray.ClearBackground(conf.theme.background);
|
2021-01-28 16:12:41 +01:00
|
|
|
level.draw(scale, camera);
|
|
|
|
level.draw_selection(scale, camera);
|
2021-02-02 14:21:12 +01:00
|
|
|
draw.cursor(scale, camera, cursor, mode);
|
2021-02-26 13:12:29 +01:00
|
|
|
if (mode == Mode.rectangle or mode == Mode.unrectangle)
|
|
|
|
draw.rectangle_selection(scale, camera, Rect.init_from_vec2(cursor, cursor_before), mode == .rectangle);
|
2021-02-25 22:53:02 +01:00
|
|
|
if (conf.mouse_enabled)
|
|
|
|
mouse.draw(scale, camera);
|
2021-01-24 19:22:48 +01:00
|
|
|
}
|
|
|
|
}
|
2021-02-26 01:03:41 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|