Skip to content

Instantly share code, notes, and snippets.

@MrSmith33
Last active August 22, 2021 11:29
Show Gist options
  • Save MrSmith33/f210a7cbd79f6c5446a93dd044ec371e to your computer and use it in GitHub Desktop.
Save MrSmith33/f210a7cbd79f6c5446a93dd044ec371e to your computer and use it in GitHub Desktop.

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.

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