Skip to content

Conversation

@sdzx-1
Copy link
Owner

@sdzx-1 sdzx-1 commented Aug 6, 2025

Some interesting improvements, just some exploration!

Peek 2025-08-06 23-51

@sdzx-1 sdzx-1 force-pushed the improve_two_stage_select branch from f084f9a to f5002f3 Compare August 6, 2025 22:37
@sdzx-1 sdzx-1 force-pushed the improve_two_stage_select branch from f5002f3 to 001d091 Compare August 6, 2025 23:04
@sdzx-1
Copy link
Owner Author

sdzx-1 commented Aug 6, 2025

@milogreg I'm not sure if this is a good idea, I'd like to hear your opinion.

Modified the two-stage selection to allow the building window to be always visible. When a building is selected, it enters the Tmp state, which allows us to move the mouse out of the building window and then enter the Select Cell state. If the mouse enters the building window in Select Cell state, it will return to the Select Building state.

@sdzx-1 sdzx-1 requested a review from milogreg August 6, 2025 23:59
@sdzx-1 sdzx-1 marked this pull request as ready for review August 6, 2025 23:59
@milogreg
Copy link
Collaborator

milogreg commented Aug 8, 2025

This seems like a somewhat fragile strategy, and there are a few issues that I've run into when testing. These aren't necessarily all bugs, but they are areas where the UI feels incomplete:

  • You can't zoom or pan the map if you are in the building window selection state.
  • If you are in the state where you have selected a building in the building window (core.Tmp({37},{32})), you can't press escape to deselect it.
  • If you are placing a building and are in the select.Inside((function 'Example'),{33},{36}) state (green outline), then move your cursor into the building window while holding alt (panning the map), you will not go into the building window selection state (select.Select((function 'Example'),{11},{19})).

You could definitely patch these issues, but I'd still be wary of going down this path.

I've thought a bit about how GUI apps in general (not just ray-game) could be built with Polystate, and have struggled with this kind of scenario where you need two separate components (map view and building selection) active at the same time.

In my mind, weaving complex dependencies between states to solve problems like this kind of defeats the purpose of abstraction, as it requires so much re-wiring every time requirements change.

Practically speaking, you may just want to make the building window be its own menu like before, and commit to not having multi-component rendering in ray-game for simplicity's sake.

However, if you do want to explore the possibility of more complex multi-component GUIs built with Polystate, I'd propose creating a new library: PolyGUI. This library would solve the problem of multi-component rendering, and would generally simplify/standardize the conventions required to build a Polystate GUI app.

PolyGUI could start out relatively lightweight, providing the following framework for interactions and rendering:

  • A Component is a state machine that represents the state of a GUI component.
  • To instantiate a Component, you must call its initialization function, which determines the values in its context and its starting state. This initialization function can have parameters which affect the created Component.
  • States within a Component can provide zero or more handler functions from a pre-defined set of handler functions. Some of these handler functions would return a transition, and some wouldn't return a transition (like render).
  • States within a Component don't provide the regular Polystate handler function, as they provide transition logic on a per-event basis. So, if you had a component that could transition when clicked, it would provide a clickHandler function that would hold the click transition logic. If that same component could transition any time a frame passes (like if its state depended on animation timing), it would also provide a frameHandler function that would hold the frame transition logic.
  • States within a Component can provide a children handler which returns a pointer to a tuple of child Components. These child Components would be stored in the parent Component's context. This allows Components to dynamically change their list of child Components based on their state, while still having a comptime-known component graph (analyzing the return type of children lets you know the child Component types at comptime).
  • The component runner constructs a Component's tree (graph of the component and its children) at comptime and calls the state handlers of the Component and its children. It starts at the bottom of the component tree (components with no children) and works its way up, calling the applicable handler if it exists in the component's current state.
  • An entire app would exist within one parent Component, so the entire app is ran with one component runner.
  • The component runner wouldn't contain the event-producing logic itself, it would just accept a comptime user-provided event receiver which defines the events that can be produced. Then, the user could plug in their event producer to the component runner to feed it events.
  • PolyGUI could provide some premade event producer/receiver pairs that wrap the user input / frame transition logic for raylib or other libraries.

@sdzx-1
Copy link
Owner Author

sdzx-1 commented Aug 8, 2025

Thanks for your reply! I also thought this PR was too complicated and was still weighing whether it was worth it.

First I will explain some things to you:

You can't zoom or pan the map if you are in the building window selection state.

Two-stage selection requires you to select a building first. While you can zoom the viewport in the building selection window, you cannot manipulate the map. It would be more intuitive to restrict your mouse cursor to the build window.

If you are in the state where you have selected a building in the building window (core.Tmp({37},{32})), you can't press escape to deselect it.

Yes, Tmp has no status returned, this should be easy to add.

If you are placing a building and are in the select.Inside((function 'Example'),{33},{36}) state (green outline), then move your cursor into the building window while holding alt (panning the map), you will not go into the building window selection state (select.Select((function 'Example'),{11},{19})).

The main reason here is that Select, Inside, and Hover were originally designed without the concept of regions. Therefore, the Inside state does not check whether it is still in the correct region. I only check the region in Select.

A good solution is to introduce the concept of region into Select, and the select instance needs to provide a function to determine what is the correct region.


Here are some of my opinions, which may not be correct, you can correct me:

I think what you mean is: building fully dynamic components, similar to traditional GUIs.

However, I would say that building this pr using the traditional method is not necessarily simpler than the current implementation. This is because you have to deal with the occlusion relationship between the building selection window and the cell selection window. This will affect the rendering and selection events of the cell selection window. And there must also be something like Tmp to handle the transition between these two choices.

Static construction makes everything depend on the type and becomes very intuitive.

@sdzx-1 sdzx-1 marked this pull request as draft August 8, 2025 02:55
@milogreg
Copy link
Collaborator

milogreg commented Aug 8, 2025

I think what you mean is: building fully dynamic components, similar to traditional GUIs.

However, I would say that building this pr using the traditional method is not necessarily simpler than the current implementation. This is because you have to deal with the occlusion relationship between the building selection window and the cell selection window. This will affect the rendering and selection events of the cell selection window. And there must also be something like Tmp to handle the transition between these two choices.

The difference between the component-based system I propose and the existing system is not whether it is static or dynamic. In fact, the component-based system is fully static and type-safe too!

The area that the component-based system would help in is exactly what you mentioned: the occlusion relationship between the building selection window and the cell selection window. This is because of the component hierarchy, which would naturally define the 'layers' of the GUI, and route events (like clicking and mouse movement) through the layers, starting at the top.

Now, my description of the component-based system didn't cover things like bounding boxes (which would allow for mouseLeaveHandler and mouseEnterHandler), but boundingBox could be easily added on as another optional function that components may provide, like render.

In essence, the goals of the component-based system are:

  • Modularize rendering and interaction logic into smaller chunks, reducing the amount of context they need to consider.
  • Provide a central utility that orchestrates the rendering and event passing for complex nested (layered) UI structures.
  • Allow an otherwise impossible number of UI permutations to be representable by states. For example, if you had 10 sub-menus open, each with 10 tabs, it would take 10^10 states to represent this UI with a single state machine. So, you'd need to store the tab positions in Context instead of as states. But, with multiple components that each have a state machine, it would just take 10 * 10 total states spread across 10 state machines, so you wouldn't need to rely on Context. The component-based system vs the existing system is analogous to NFA vs DFA: every NFA could be represented as a DFA, but the number of states may be untenable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants