These macros allow any panel that wants to save its state to do so in a relatively simple way. It is not as nice as a fully automated system, but it was something I could get working in a few hours. It seems to have held up well over the course of the project, and to the best of my knowledge it is still being used to store all editor state.
Q: | Why is selected_hash not a member of Lister_Panel? Lister_Panel::update deletes it every frame, so if you have two panels you’ll be in trouble. |
As hinted by the TODO above it, the reason for this is actually because I wanted to (at some point) move the hash out into the selection system itself, since lots of panels do lookups on the selection and they were all getting pushed through a very slow O(n) path. So if there were going to be multiple Lister panels, I would finish that work, since that is the superior option and pays additional dividends. There is no need for each panel to store their own hash, since the selection is universal and it would be exactly the same in each.
As to your suggestion that I would be “in trouble” with multiple panels, I do not see why. Multiple panels would just result in a bunch of redundant work done by the second panel for no reason, but it wouldn’t actually yield erroneous results. The only cause for trouble would be if the two panels were running on separate threads at the same time, but The Witness’s editor is not multithreaded in that way, so that is not a concern here.
Q: | There is a magic system to do the layout in a scrollable section, but you have to specify the number of elements in a row manually. Why? |
The Witness’s GUI is relatively bare-bones, and does not do deferred hit-testing. Thus the width and height of buttons must be known at the time the button is drawn, so that it can be hit-tested immediately and the results returned to the caller. Scrollable sections present no problems for this, because scrolling is simply a y offset that is applied to the buttons as they are drawn. Multi-button rows, on the other hand, do present a problem. If the user didn’t pass in the number of buttons that were going to be in the row, only after completing all the button calls for that row would the UI system know how big each one should be. At that point it would be too late to return hit-test information to the caller.
Of course, one could work around this limitation by having an EndRow() function that returns some kind of code indicating which of the buttons in the row was hit, but since it is trivial to just pass the button count, this seemed like an unnecessary complication beyond the nicer format of being able to individually if() each button call to handle its operation.
Q: | How do you handle button/editbox/whatever id persistence from frame to frame? Is it handled at all or is the id just the index of the button in the panel? |
Unless I am misremembering, there are no ids in the Witness editor GUI at all. It is a fully immediate-mode GUI. This means that button processing is not delayed to a second pass, it is processed immediately on the pass where the button is defined. IMGUI systems that require ids are usually doing things that are fancier than what the Witness’s editor GUI is doing, and thus need ids to correspond UI elements between passes. Since the Witness editor GUI doesn’t do anything fancy, it can get away with not having ids at all.
Q: | Do you have a length/complexity/whatever threshold for breaking out a sub-function not for “ semantic compression,” but for making it easier to scan the parent function? |
Not really. In general, it is just “to taste”. Typically I try to keep code together in one function if it is executed in order and is not reused. This makes it clear to everyone reading the code that, well, it is executed in order and it not reused! But sometimes, if a function gets excessively large and becomes hard to understand, I will break it up into smaller functions, even though those functions will never be called by anyone else. This is primarily to leverage the cognitive convenience of substituting, say, several hundred lines of code with just a descriptive function call like “ReticulateSplines()”. This lets someone reading the code know that splines are reticulated there, and they can understand that more easily than trying to pour through the several hundred lines and look for comments to that effect.
Q: | I noticed that in passes_filter, the LISTER_FILTER_MODE_POSITIVE and LISTER_FILTER_MODE_NEGATIVE cases are almost the same. differing only by “==” versus “!=”. What do you think about collapsing the POSITIVE into the NEGATIVE case? |
For quick reference, the original code looked like this: