I was writing a GUI diagram drawing program to showcase my language called "kc"Code examples will also be written in this language. You should treat it as pseudocode. Expect Python syntax with Haskell's type system, naming scheme and keywords. . I let the code structure evolve naturally over time before the final refactor. After doing that, I realized I've already written such a thing. It was for my hand gesture screen control program made for my Bachelor's.
This pattern is useful, because it provides clear structure for writing GUI programs. The downside is that the theoretical version only allows one action at a time (although it's easy to hack it in depending on whatever requirements there are.)
The typical code structure looks like this:
input-action = None while ... input-action <= handle-input(input-action, mouse-location, ...whatever) ... draw-input-action-stuff(input-action) ...
Each action should start, update and end inside handle-input() .
Variable input-actionMaybe InputAction stores the current (or the absence of) input action (mouse over to see its type). "Input action" is whatever action is currently happening on the screen: eg. moving a diagram blog, resizing it, creating a new one, etc.
Let's first check inside handle-input() :
handle-input (input Maybe InputAction, ...) case input None ... Just(MkRect(begin-rect)) ... Just(MkArrow(begin-arrow)) ... ...
You can probably imagine, that None means that is currently being performed. Here, we can "start" input actions. We can also match on specific actions like MkRect and MkArrow among others. The begin-rect and begin-arrow are what's stored inside this InputAction ADThttps://en.wikipedia.org/wiki/Algebraic_data_type constructor.
In None we handle "action starts". Both singular actions (Ctrl+Z-ing) and longer actions (dragging, etc.) are represented here.
# These actions do not modify input-action and are executed once. elif Raycuck.is-key-pressed(KeyZ) undo(full) elif Raycuck.is-key-pressed(KeyR) redo(full) ... # These actions modify the input-action. elif Raycuck.is-mouse-button-pressed(MouseLeftButton) and \ state try-last-rect() maybe(...) return Just(MoveRect(state last-rect()&, mp)) elif Raycuck.is-mouse-button-pressed(MouseLeftButton) return Just(MkRect(mp)) ...
In the Just(...) cases, we either continue or end the actions. Notice in MkRect action, that if we release the mouse button, the action stops and we add this new block to the diagram. Otherwise, it keeps going.
Just(MkRect(begin-rect)) if Raycuck.is-mouse-button-up(MouseLeftButton) r = mk-rect(begin-rect, mp) (&state&.rects) BoundedList.add(r) full add-action(...) # add undo-redo for this action. return None return Just(MkRect(begin-rect))
input-actionMaybe InputAction also has the necessary information to display things to the GUI. Remember the blue selection rectangle when you left click and drag the mouse in Michaelsoft Bindows? This would be represented by input-actionMaybe InputAction .
Here's an example:
draw-input-stuff (input Maybe InputAction, mp Vec2) -> Unit case input Just(MkRect(v1)) Raycuck.draw-rectangle-rec(mk-rect(v1, mp) as-rectangle(), BLUE)
that's it.
InputAction MkRect Vec2 MoveRect Rect Vec2 ResizeRect Rect GrabbedCorner Vec2 SelectActionListElem # currently unused! MkArrow Vec2
miau miau