lovatt.dev

March 10th, 2019
10/03/19

Elementary, My Dear Watson

In which we extract the Element class to its own module, and remove some updating logic from the GUI.


In our last thrilling installment we pulled out all of the table helpers that were hanging around in the GUI's core module, added some new ones, and used a bit of syntactic sugar to make them less cumbersome to work with.

This time around we're aiming for something a bit more ambitious - the Element class. All of the GUI elements are created with Element as their blueprint and, while it doesn't do a whole lot on its own, it provides fallbacks for all of the methods the GUI might ask an element to run. For instance, if the user tries to type while a Slider is focused, and the Slider doesn't have an ontype method, it can use a dummy function provided by Element to avoid errors.

Copy/Paste Wins Again

Just like last time, we're fortunate to be working with code that isn't too tangled with everything else. Don't worry, we'll get there - taking care of the easy stuff first will make things a bit tidier once we need to assess the harder parts, and by moving code into separate modules we'll be able to spot unwanted dependencies without even trying. Seriously - they'll be right there at the top of the file because the refactored module won't run without them.

Anyway.

The process is pretty straightforward:

  • Move all of the GUI.Element.___ functions to a new module and rename them to Element.___.
  • Import the table wrapper we created last time, T, and use that to create the Element class so all future elements can take advantage of our cleverness.
  • At this point all of the GUI elements will have broken, so we should point them all in the right direction: local Menubox = require("gui.element"):new()
  • We also need to make sure that Element is being returned by the module.

While We're Here

One mistake I made very early in this project was to put the GUI directly in charge of updating the elements. On each loop, it goes through each element, sees if the keyboard and mouse state have changed, figures out what the user did and calls the appropriate method.

It works, certainly, but it creates a landmine for anyone wanting to change either the GUI or the elements. Why? Because they're coupled together - the GUI needs to have enough information about each element to make these decisions, so the elements can't be significantly altered without also altering the GUI.

Imagine a CEO checking in with the cashiers in their stores and telling them what to do. That's clearly not a CEO's job - there are normally a few layers of head cashiers, managers, and regional managers in between. If the CEO's job is directly affected by one cashier's behavior, that's not good.

Alright, well... what if we let the elements do all of that? What if the GUI just tells each element "Here's what the user did, see if it affected you"? It's still not ideal - the GUI still shouldn't need to talk to the elements - but it would be a step in the right direction.

Keep Me Updated

As intimidating as it might sound, this change is going to continue our unbroken streak of painless refactorings.

At the moment:

  • On each loop, the GUI iterates through every element in GUI.elms and passes it to GUI.Update().
  • The GUI is declared globally, so its state is available to every module in the app.
  • Aside from the element and the GUI state, GUI.Update refers to GUI.isInside(), a function that determines if a given set of coordinates are inside a given element. It, too, would make more sense in the Element class.

This means that, aside from renaming a few things, we won't really have to do anything to make Update and isInside methods of Element. Hooray!

While we're at it, we can bring along a couple of seldom-used helper functions that fit our refactoring criteria perfectly - GUI.center centers one element inside another, and GUI.debug just prints all of an element's properties to the console.

calculating reasons for these functions to remain in Core.lua

    calculation complete

        reasons found: 0

That settles that. Another quick round of Copy/Paste/Rename (sounds like a sci-fi version of F***/Marry/Kill) and we're all done! Well, until I save this post and start the next one.


Next time: A new feature disguised as refactoring, or a refactoring that secretly desires to do something grander with its life? You be the judge.