Source code: https://github.com/MrSmith33/voxelman/blob/master/source/voxelman/gui
Core widget components (https://github.com/MrSmith33/voxelman/blob/master/source/voxelman/gui/components.d):
- WidgetTransform - main component of any widget. Stores parent reference, relative position, absolute position, size, min size, measured size, and extra flags
- WidgetStyle - color and border color
- WidgetName - string of widget name
- WidgetType - string of widget type (for debugging)
- WidgetContainer - children widgets
- WidgetIsFocusable - if set becomes
focusedWidget
on pointer press.focusedWidget
will receive key and char events. - WidgetEvents - stores handlers for any event for the widget. Typical events:
- hidden - You can attach this flag to hide widget from rendering & input
Extra widget parts (https://github.com/MrSmith33/voxelman/blob/master/source/voxelman/gui/widgets.d):
- ButtonState
- ChildrenStash
- ConditionData
- DraggableSettings
- DropDownData
- IconData
- ImageData
- LinearLayoutSettings
- ListData
- ScrollableData
- SingleLayoutSettings
- TextData
- UserCheckHandler
- UserClickHandler - stores handlers for button click events
- WidgetIndex
- WidgetReference
- TextEditorViewportData
- TextEditorLineNumbersData
events:
- GuiUpdateEvent
- DrawEvent
- PointerPressEvent
- PointerReleaseEvent
- PointerClickEvent
- PointerDoubleClickEvent
- PointerMoveEvent
- ScrollEvent
- DragEvent
- DragBeginEvent
- DragEndEvent
- CharEnterEvent
- KeyPressEvent
- KeyReleaseEvent
- PointerEnterEvent
- PointerLeaveEvent
- FocusGainEvent
- FocusLoseEvent
- MeasureEvent
- LayoutEvent
- GroupSelectionEvent
gui state: https://github.com/MrSmith33/voxelman/blob/master/source/voxelman/gui/guicontext.d#L21
struct GuiState
{
WidgetId draggedWidget; /// Will receive onDrag events
WidgetId focusedWidget; /// Will receive all key events if input is not grabbed by other widget
WidgetId hoveredWidget; /// Widget over which pointer is located
WidgetId inputOwnerWidget; /// If set, this widget will receive all pointer movement events
WidgetId lastClickedWidget; /// Used for double-click checking. Is set before click event distribution
WidgetId pressedWidget;
ivec2 canvasSize;
ivec2 prevPointerPos = ivec2(int.max, int.max);
ivec2 pointerPressPos = ivec2(int.max, int.max);
ivec2 curPointerPos;
/// filled with curPointerPos - draggedWidget.absPos at the moment of press
ivec2 draggedWidgetOffset;
/// Icon is reset after widget leave event and before widget enter event.
/// If widget wants to change icon, it must set cursorIcon in PointerEnterEvent handler.
CursorIcon cursorIcon;
string delegate() getClipboard;
void delegate(string) setClipboard;
}
gui context https://github.com/MrSmith33/voxelman/blob/master/source/voxelman/gui/guicontext.d#L74
gui has multiple root widgets that cover the whole canvas. This way I can layer stuff. There is special overlay layer for storing context menus, drop-down lists, tooltips.
All window events are passed to gui context and then redistributed to specific widgets depending on gui state and cursor position.
Widgets are created with a function that creates entity, attaches all the needed components, sub-widgets, and links event handlers.
Widgets can be created with builder style API:
// how frame widget is created
FrameParts createFrame(WidgetProxy parent) // parent will contain new frame
{
// create new widget as a child of parent
WidgetProxy frame = parent.createChild(WidgetType("Frame"))
.setVLayout(0, padding4(0)) // set vertical layout for header and container
.addBackground(color_clouds) // set background color
.consumeMouse;
auto header = frame.createChild(WidgetType("Header"))
.addBackground(color_white)
.hexpand; // make header expand horizontally
auto container = frame.createChild(WidgetType("Container")).hvexpand; // make container fill the rest of the frame
// return widget parts. Caller will then add children to the container part.
return FrameParts(frame, header, container);
}
WidgetProxy
is wrapper for WidgetId
+ GuiContext
, so we can propagate those through builder functions.
Layout is done by providing measure
and layout
event handlers.
Using ecs allows to add/remove components dynamically at runtime, changing widget behavior on demand.