Skip to main content

Widget

The Widget API is a retained-mode UI library (Button, Label, Image, Border, CheckBox, Slider, Chart, DockPanel and more). Create widgets from a Screen, wire events, let the library handle hit-testing and repainting. Widget and Canvas are independent; Canvas is pure pixel drawing you manage yourself, Widget is a managed scene tree.

All widget updates must happen on the UI thread; the library runs single-threaded so a background thread cannot touch widgets directly (use UiTimer for periodic updates).

Touch input​

TouchBridge.Attach() registers touch input with the UI, but it does not read the touch chip. Widgets respond to touch only when your touch driver's events are passed to TouchBridge.Feed(x, y, pressed). Without that, the code runs and the screen draws, but buttons never raise Clicked. The Touch Screen page shows how to connect a touch driver to TouchBridge.

Getting started​

using System.Device.Gpio;
using System.Device.I2c;
using GHIElectronics.Endpoint.Core;
using GHIElectronics.Endpoint.Devices.Display;
using GHIElectronics.Endpoint.Devices.Touch;
using GHIElectronics.Endpoint.UI;

using var display = DisplayController.GetDefault();
display.Enable();

UI.Initialize();
TouchBridge.Attach();

// Touch: FT5xx6 class from the Touch Screen page, on I2C5, reset PF2, interrupt PB11
I2cController.Initialize(EPDomino24.I2c.I2c5);
var i2c = I2cDevice.Create(new I2cConnectionSettings(EPDomino24.I2c.I2c5, 0x38));
using var gpio = new GpioController(new EPDomino24.Gpio.Driver());
using var touch = new FT5xx6(i2c, gpio, resetPin: EPDomino24.Gpio.Pin.PF2, irqPin: EPDomino24.Gpio.Pin.PB11);
touch.OnChangedEvent += (sender, e) => {
if (e.Points.Length > 0)
TouchBridge.Feed(e.Points[0].X, e.Points[0].Y, pressed: e.EventType != TouchEventType.Released);
else
TouchBridge.Feed(0, 0, pressed: false);
};

var screen = Screen.Active;
screen.BackgroundColor = 0x101828;

var hello = new Label(screen) {
Text = "Hello Endpoint",
Color = 0xFFFFFF,
FontSize = 24,
X = 20, Y = 20,
};

var button = new Button(screen) {
Text = "Click me",
X = 20, Y = 80,
Width = 160, Height = 48,
};
button.Clicked += (s, e) => hello.Text = "Clicked!";

UI.Run();

Call UI.Stop() from any thread to exit the loop and let UI.Run() return.

Widgets​

Full reference in the UI API. Common widgets:

WidgetPurpose
LabelStatic or dynamic text.
ButtonClickable button with optional icon.
ImageBitmap display (raw, PNG, JPEG via the on-board decoder).
BorderContainer with optional rounded corners, used for cards and panels.
CheckBox / RadioButtonBoolean / single-choice input.
TextBoxSingle or multi-line text input with virtual keyboard.
SliderAnalog value picker.
ProgressBar / GaugeProgress and value visualization.
ChartLine and bar charts.
DockPanel / Stack / GridLayout containers.

Running widgets on an external display​

UI.Initialize() with no arguments drives the on-board panel. A second overload binds the Endpoint UI to any output (SPI / I2C screens, HDMI bridge, file, network socket) by giving it the dimensions, pixel format, and a flush callback that writes the rendered pixel buffer to your device. Same Widgets, same event loop; only the destination changes.

Verified end-to-end with the HiLetgo ILI9341 240x320 SPI TFT wired to mikroBUS #2. The driver sits between the UI flush callback and SPI (same driver as the Canvas example):

:::note Wiring an SPI display, gotchas

  • Chip select is driven by a GPIO, not by the SPI controller's hardware NSS. The driver opens the pin you pass and toggles it around each transaction. On .NET, SpiConnectionSettings needs chipSelectLine: 0 explicitly; Microsoft's Linux SPI stack does not accept -1.
  • Data / Command pin: ILI9341 distinguishes commands from pixel data by a separate GPIO (D/C). Low = command byte, high = data byte.
  • Reset pin: an active-low pulse brings the chip out of reset reliably at startup.
  • Pixel format on the wire is 16 bpp big-endian, but the UI pipeline renders RGB565 little-endian. The driver byte-swaps in Flush.
  • Red / Blue swap: some HiLetgo and eBay modules have the physical color filter wired as BGR, so yellow renders as cyan and red as blue. Set SwapRedBlue = true for those modules.
  • Touch is not wired in this example; add a touch chip driver and call TouchBridge.Feed(x, y, pressed) to make widgets clickable. :::
using System.Device.Gpio;
using System.Device.Spi;

using GHIElectronics.Endpoint.Core;
using GHIElectronics.Endpoint.Drivers.HiLetgo.ILI9341;
using GHIElectronics.Endpoint.UI;
using GHIElectronics.Endpoint.Drawing;

// Open SPI + driver.
SpiController.Initialize(EPDomino24.Spi.Spi1, bufferSize: 4096);
using var spi = SpiDevice.Create(ILI9341Controller.GetConnectionSettings(EPDomino24.Spi.Spi1));

using var gpio = new GpioController(new EPDomino24.Gpio.Driver());
using var tft = new ILI9341Controller(
spi: spi,
cs: EPDomino24.Gpio.Pin.PA14,
dc: EPDomino24.Gpio.Pin.PF14,
reset: EPDomino24.Gpio.Pin.PF4,
gpio: gpio);
tft.SwapRedBlue = true; // set for modules where yellow shows as cyan
tft.Enable();

// Bind the Endpoint UI to the ILI9341 via a flush callback.
UI.Initialize(tft.Width, tft.Height, PixelFormat.Rgb565, buffer => tft.Flush(buffer));

var screen = Screen.Active;
screen.BackgroundColor = 0x101828;
new Label(screen) {
X = 0, Y = 20, Width = tft.Width, Height = 30,
FontSize = 20, Color = 0xFFFFFF,
TextAlignment = TextAlignment.Center,
Text = "Hello from ILI9341",
};
UI.Run();