The Operation Was a Success!
Input for LINK has been completely overhauled, cutting over 200 lines of code and making LINK much more stable and organized.
Interacting with notes was once a tangled mess of if statements, with 5-6 different conditions each, spread throughout the code base and all subtly dependent on one another.
It was a nightmare.
Now, it looks like this:
I was able to use the existing code with very few changes. The most difficult part being, figuring out the proper behavior of the various different type of mouse clicks which I did through simple logging.
I did go over some of this last week, so be sure to check that out if you’re interested, but there was a few differences between what I theorized I could do and what I did in practice.
Step 1: What Are We’re Dealing With?
Mouse collision detection was already in place and completely self-contained so I only needed to grab the information that I already found.
The two pieces of information we get here is whether or not we’re hovering over a note object and if we’re hovering over a resize handle.
Step 2: What is the Mouse Doing?
As mentioned before, I need to know a few different states a mouse button can be in: “hover”, “press”, “double press”, “down”, “activate”, and “release”.
I might go into more detail about this later, but for now, here’s the relevant source code from LINK:
--object_hovered was set by collision detection earlier
local mouse_object = object_hovered
--set mouse state as reported by the engine
local mouse_msg = "hover"
local imc = input.mouse_click
if imc then
mouse_msg = imc == "pressed" and "press" or "down"
end
local last = input_state.mouse_msg
local active_hold = input_state.active_hold
local active_resize = input_state.active_resize
local active = active_hold == mouse_object and active_hold
local double_click = timers.double_click and input_state.double
--Is mouse within range of original click location?
if double_click then
--current mouse location based on camera offset
local ima = input.mouse.absolute
local mp_x = ima.x - camera.dx
local mp_y = ima.y - camera.dy
--original click position
local isdx, isdy = input_state.double_x, input_state.double_y
--only check if values are different
if mp_x ~= isdx or mp_y ~= isdy then
--the standard distance formula
local dist = distance(
input_state.double_x, input_state.double_y,
mp_x, mp_y
)
if dist > click_radius then
timers:stop("double_click")
double_click = false
end
end
end
if mouse_msg == "press" then
active = mouse_object
active_hold = mouse_object
--resize_object & resize_side from collision detection earlier
active_resize = resize_object > 0 and resize_side
if not timers.double_click else
--first click, set position, enable timer
local ima = input.mouse.absolute
local mp_x = ima.x - camera.dx
local mp_y = ima.y - camera.dy
double_click = mouse_object
input_state.double_x = mp_x
input_state.double_y = mp_y
timers:set("double_click", theme.double_click_speed)
else
--timer still active, so double click is a go!
if double_click == mouse_object then
mouse_msg = "double"
end
--stop timer so another press doesn't trigger another
--double click
timers:stop("double_click")
double_click = false
end
elseif mouse_msg == "down" then
--do nothing
elseif
mouse_msg == "hover" and
(last == "down" or last == "press" or last == "double")
then
mouse_msg = active and "activate" or "release"
elseif mouse_msg == "hover" then
active = false
active_hold = false
active_resize = false
end
-- save in input_state table for later use
input_state.mouse_msg = mouse_msg
input_state.active = active
input_state.active_hold = active_hold
input_state.active_resize = active_resize
input_state.double = double_click
Step 3: What Code Should Run Next?
Once I figure out the mouse state, I run it though the following code:
local istate = input_state.mouse_msg
if istate then
------ ![ INITIALIZE ]! ------
if istate == "hover" then
-- ![ HOVER ]! ------
elseif istate == "press" or istate == "double" then
-- ![ PRESS or DOUBLE CLICK ]!
end
if istate == "press" then
-- ![ PRESS ]! -------
elseif istate == "double" then
-- ![ DOUBLE CLICK ]! -------
elseif istate == "down" then
-- ![ DOWN ]! -------
elseif istate == "activate" then
-- ![ ACTIVATE ]! -------
istate = "release" --do release next
end
if istate == "release" then
-- ![ RELEASE ]! -------
end
------ ![ FINALIZE ]! -------
end
Originally, I thought there would be multiple different blocks of the above code, however, once I started the refactor I realized I was only revamping one part of the entire input system.
Editing notes and the simple dialogs that prompt you when reloading or quitting with unsaved changes are completely separate from this code so I let them be, for now.
Step 4: Update the Program
This is the best part. The entire reason I went through the trouble.
Once we’re done with step 2, I only have to look at one particular condition and execute the code if it is set.
For example, if I double click on an empty area I’ll set
input_state.new_note = true
in the previous step.
In this step I run the following:
if input_state.new_note then
input_state.new_note = false
exit_edit_mode() --stop editing a previous note
add_new_note(input_state.grab_x, input_state.grab_y)
end
That’s it! No more if this or that or whether a shaman determined it would rain by interpreting the way sand fell onto a piece of paper and only if the moon is in the waxing phase nonsense from before.
For the most part, the underlying logic didn’t change at all.
In fact, in many cases, the code could be simplified quite a bit because I handled mouse movement in Step 3 as well.
And that’s how I shed over 200 lines of code during this refactor.
Fixing the Line Command
The refactor finished much sooner than I had originally anticipated so I took a look at the issues with the “line” command.
Most of them I was able to fix, the remaining issues stem from problems I’ll be tackling soon.
Admittedly, I could have done this before the input system refactor, but the added breathing room I created made it much easier to approach.
In the end, I did what I originally tried (using two notes to represent each node) but I reference the other node by it’s item table (which is consistent within the program’s runtime).
On save, the primary node is updated with the secondary nodes location as part of its note text while the secondary node is discarded. On load, it creates the second node.
Because of using a stand alone note for the secondary node, all existing logic works with no changes necessary.
That was the elegance I was looking for this whole time, but faced with the mess of logic I had been dealing with, it seemed impossible to get working.
What’s Next?
The program is in a much, much more stable state than it’s ever been but there are a few issues that need to be taken care of.
I want to deal with object hierarchy next, which should go a long way in solving my z-ordering issues and will be necessary for the pending UI upgrade.
As I mentioned last time, I already have a proof-of-concept for object hierarchy so it’s just a matter of porting it over to LINK.
I’ll spend the following week working out how I’m going to tackle this problem but with the holidays right around the corner, I doubt I’ll have much to show for it.
Also, there’s a game jam in progress I’d like to submit something to so that’ll slow me down as well.
Files
Get LINK!
LINK!
Notes on an endless landscape
Status | On hold |
Category | Tool |
Author | hovershrimp |
Tags | Creative, Game Design, Management, Minimalist, planning, productivity |
More posts
- Finding the CoreMay 20, 2022
- Let's Get Wild at the LINK! JAMboreeMar 22, 2022
- Open Sourcing LINK! version 0.8 Alpha 1Jan 30, 2022
- Something Big is HappeningJan 04, 2022
- Lift and Separate, User InputDec 10, 2021
- Refactoring is Better with MetatablesDec 03, 2021
- Images Were a MistakeNov 29, 2021
Leave a comment
Log in with itch.io to leave a comment.