diff --git a/qt5/GNUfiles.mk b/qt5/GNUfiles.mk
new file mode 100644
index 00000000..fc4f1d1f
--- /dev/null
+++ b/qt5/GNUfiles.mk
@@ -0,0 +1,74 @@
+# 28 may 2016
+
+CXXFILES += \
+	qt5/alloc.cpp \
+	qt5/area.cpp \
+	qt5/box.cpp \
+	qt5/button.cpp \
+	qt5/checkbox.cpp \
+
+#	qt5/child.cpp \
+
+CXXFILES += \
+	qt5/colorbutton.cpp \
+	qt5/combobox.cpp \
+	qt5/control.cpp \
+	qt5/datetimepicker.cpp \
+	qt5/debug.cpp \
+	qt5/draw.cpp \
+	qt5/drawmatrix.cpp \
+	qt5/drawpath.cpp \
+	qt5/drawtext.cpp \
+	qt5/editablecombo.cpp \
+	qt5/entry.cpp \
+	qt5/fontbutton.cpp \
+	qt5/group.cpp \
+	qt5/label.cpp \
+	qt5/main.cpp \
+	qt5/menu.cpp \
+	qt5/multilineentry.cpp \
+	qt5/progressbar.cpp \
+	qt5/radiobuttons.cpp \
+	qt5/separator.cpp \
+	qt5/slider.cpp \
+	qt5/spinbox.cpp \
+	qt5/stddialogs.cpp \
+	qt5/tab.cpp \
+	qt5/text.cpp \
+
+#	qt5/util.cpp \
+
+CXXFILES += \
+	qt5/window.cpp
+
+HFILES += \
+	qt5/draw.hpp \
+	qt5/uipriv_qt5.hpp
+
+# TODO split into a separate file or put in GNUmakefile.libui somehow?
+
+# flags for Qt5
+CFLAGS += $(NATIVE_UI_CFLAGS)
+CXXFLAGS += $(NATIVE_UI_CXXFLAGS)
+LDFLAGS += $(NATIVE_UI_LDFLAGS)
+
+
+ifneq ($(RELEASE),1)
+	CXXFLAGS += -DQT_DEBUG
+else
+	# disable Q_ASSERT checks
+	CXXFLAGS += -DQT_NO_DEBUG
+endif
+
+# flags for building a shared library
+# OS X does support -shared but it has a preferred name for this so let's use that there instead; hence this is not gcc-global
+LDFLAGS += \
+	-shared
+
+# flags for warning on undefined symbols
+# this is not gcc-global because OS X doesn't support these flags
+# TODO figure out why FreeBSD follows linked libraries here
+ifneq ($(shell uname -s),FreeBSD)
+LDFLAGS += \
+	-Wl,--no-undefined -Wl,--no-allow-shlib-undefined
+endif
diff --git a/qt5/GNUinstall.mk b/qt5/GNUinstall.mk
new file mode 100644
index 00000000..634dbc06
--- /dev/null
+++ b/qt5/GNUinstall.mk
@@ -0,0 +1,8 @@
+ifndef PREFIX
+    PREFIX=/usr
+endif
+
+install: $(OUT)
+	cp $(OUT) $(DESTDIR)$(PREFIX)/lib/libui.so.0
+	ln -fs libui.so.0 $(DESTDIR)$(PREFIX)/lib/libui.so
+	cp ui.h ui_$(OS).h $(DESTDIR)$(PREFIX)/include/
diff --git a/qt5/GNUosspecific.mk b/qt5/GNUosspecific.mk
new file mode 100644
index 00000000..3787016b
--- /dev/null
+++ b/qt5/GNUosspecific.mk
@@ -0,0 +1,21 @@
+# 16 october 2015
+
+EXESUFFIX =
+LIBSUFFIX = .so
+OSHSUFFIX = .h
+STATICLIBSUFFIX = .a
+TOOLCHAIN = gcc
+
+# LONGTERM clean up all the NAMEs and SUFFIXs and NOSOSUFFIXs or whatever it was
+USESSONAME = 1
+SOVERSION = $(SOVERSION0)
+SONAMEEXT = $(LIBSUFFIX).$(SOVERSION)
+# this is not gcc-global because OS X uses a different filename format
+SONAMEFLAG = -Wl,-soname,
+
+NATIVE_UI_CFLAGS += \
+	`pkg-config --cflags Qt5Widgets`
+NATIVE_UI_CXXFLAGS += \
+	`pkg-config --cflags Qt5Widgets`
+NATIVE_UI_LDFLAGS += \
+	`pkg-config --libs Qt5Widgets` -lm
diff --git a/qt5/GNUosspecificlink.mk b/qt5/GNUosspecificlink.mk
new file mode 100644
index 00000000..573b289f
--- /dev/null
+++ b/qt5/GNUosspecificlink.mk
@@ -0,0 +1,15 @@
+# 28 may 2016
+
+$(OUT): $(OFILES) | $(OUTDIR)
+ifeq (,$(STATICLIB))
+	@$(reallinker) -o $(OUT) $(OFILES) $(LDFLAGS)
+ifeq ($(USESSONAME),1)
+	@ln -sf $(NAME)$(SUFFIX) $(OUTNOSONAME)
+endif
+else
+	$(LD) -r $(OFILES) -o $(OUT:%.a=%.o)
+	objcopy --localize-hidden $(OUT:%.a=%.o)
+	$(AR) rcs $(OUT) $(OUT:%.a=%.o)
+endif
+	@echo ====== Linked $(OUT)
+
diff --git a/qt5/alloc.cpp b/qt5/alloc.cpp
new file mode 100644
index 00000000..f92741bd
--- /dev/null
+++ b/qt5/alloc.cpp
@@ -0,0 +1,76 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QHash>
+#include <QThread>
+#include <QDebug>
+
+#include "stdlib.h"
+
+static QHash<void*,const char *> allocations;
+
+#ifdef QT_DEBUG
+// not going to put a lock on this, just simple sanity check since the rest of the code doesn't do much about thread safety
+static QThread *uiThread = nullptr;
+#endif
+
+void initAlloc(void)
+{
+#ifdef QT_DEBUG
+	uiThread = QThread::currentThread();
+#endif
+}
+
+static void checkThread(const char *func)
+{
+#ifdef QT_DEBUG
+	if (QThread::currentThread() != uiThread) {
+		qWarning("Invoking %s on different thread from the one used for initialization, corruption will occur!", func);
+	}
+#endif
+}
+
+void uninitAlloc(void)
+{
+	checkThread(QT_MESSAGELOG_FUNC);
+	if (!allocations.isEmpty()) {
+		qWarning("Some data was leaked; either you left a uiControl lying around or there's a bug in libui itself. Leaked data:");
+		for (auto it = allocations.constBegin(), e = allocations.constEnd(); it != e; ++it) {
+			auto widget = uiValidateAndCastObjTo<QObject>((uiControl*)it.key());
+			qWarning() << it.key() << ':' << it.value() << ':' << widget;
+		}
+	}
+}
+
+void *uiAlloc(size_t size, const char *type)
+{
+	checkThread(QT_MESSAGELOG_FUNC);
+	auto p = malloc(size);
+	allocations[p] = type;
+	return p;
+}
+
+void *uiRealloc(void *p, size_t new_size, const char *type)
+{
+	checkThread(QT_MESSAGELOG_FUNC);
+
+	if (!allocations.remove(p)) {
+		qFatal("Reallocing of invalid/non-existent allocation: %p type: %s", p, type);
+		return nullptr;
+	}
+
+	p = realloc(p, new_size);
+	allocations[p] = type;
+	return p;
+}
+
+void uiFree(void *p)
+{
+	checkThread(QT_MESSAGELOG_FUNC);
+	if (!allocations.remove(p)) {
+		qWarning("Ignoring freeing of invalid/non-existent allocation: %p", p);
+		return;
+	}
+
+	free(p);
+}
diff --git a/qt5/area.cpp b/qt5/area.cpp
new file mode 100644
index 00000000..3a88b625
--- /dev/null
+++ b/qt5/area.cpp
@@ -0,0 +1,176 @@
+
+#include "uipriv_qt5.hpp"
+
+#include "draw.hpp"
+
+#include <QScrollArea>
+#include <QPaintEvent>
+
+#include <QDebug> // TODO: remove
+
+struct uiArea : public uiQt5Control {};
+
+class Area : public QWidget
+{
+	Q_DISABLE_COPY(Area)
+
+public:
+	Area(uiAreaHandler *ah)
+		: ah_(ah)
+	{
+		setAttribute(Qt::WA_Hover);
+	}
+
+private:
+	void paintEvent(QPaintEvent *event)
+	{
+		uiDrawContext painter(this);
+		painter.setRenderHint(QPainter::Antialiasing, true);
+
+		const auto clip = event->rect();
+
+		// avoid using C99 initializer for portability *cough*MSVC*cough*
+		uiAreaDrawParams params = {
+			&painter, // Context
+			(double)width(), // AreaWidth
+			(double)height(), // AreaHeight
+			(double)clip.x(), // ClipX;
+			(double)clip.y(), // ClipY;
+			(double)clip.width(), // ClipWidth;
+			(double)clip.height(), // ClipHeight;
+		};
+
+		ah_->Draw(ah_,static_cast<uiArea*>(uiFindQt5ControlForQObject(this)),&params);
+	}
+
+	bool event(QEvent *event)
+	{
+		switch(event->type()) {
+		//case QEvent::HoverMove: // don't care
+		case QEvent::HoverEnter:
+		case QEvent::HoverLeave:
+			translateHoverEvent(static_cast<QHoverEvent*>(event));
+			break;
+		}
+		return QWidget::event(event);
+	}
+
+	void translateHoverEvent(QHoverEvent *hoverEvent)
+	{
+		auto area = static_cast<uiArea*>(uiFindQt5ControlForQObject(this));
+		switch(hoverEvent->type()) {
+		case QEvent::HoverEnter:
+			ah_->MouseCrossed(ah_,area,0);
+			setFocus(Qt::MouseFocusReason); // so we get key events, hacky?
+			setMouseTracking(true);
+			break;
+		case QEvent::HoverLeave:
+			ah_->MouseCrossed(ah_,area,1);
+			clearFocus();
+			setMouseTracking(false);
+			break;
+		}
+	}
+
+	void mousePressEvent(QMouseEvent *mouseEvent) { translateMouseEvent(mouseEvent); }
+	void mouseReleaseEvent(QMouseEvent *mouseEvent) { translateMouseEvent(mouseEvent); }
+	void mouseDoubleClickEvent(QMouseEvent *mouseEvent) { translateMouseEvent(mouseEvent); }
+	void mouseMoveEvent(QMouseEvent *mouseEvent) { translateMouseEvent(mouseEvent); }
+
+	void translateMouseEvent(QMouseEvent *mouseEvent)
+	{
+		static bool warned = false;
+		if (warned) {
+			qWarning("TODO: mouseEvent translation is incomplete!");
+			warned = true;
+		}
+		uiAreaMouseEvent me;
+		auto pos = mouseEvent->pos();
+
+		me.X = pos.x();
+		me.Y = pos.y();
+
+		me.AreaWidth = width();
+		me.AreaHeight = height();
+
+		// TODO: at a glance these are not tested nor well defined..?
+		me.Down = 0; // TODO
+		me.Up = 0; // TODO
+		me.Count = 0; // TODO
+		me.Modifiers = 0; // TODO
+		me.Held1To64 = 0; // TODO
+
+		ah_->MouseEvent(ah_,static_cast<uiArea*>(uiFindQt5ControlForQObject(this)),&me);
+	}
+
+	void keyPressEvent(QKeyEvent *keyEvent)  { translateKeyEvent(keyEvent); }
+	void keyReleaseEvent(QKeyEvent *keyEvent)  { translateKeyEvent(keyEvent); }
+
+	void translateKeyEvent(QKeyEvent *keyEvent)
+	{
+		uiAreaKeyEvent ke;
+
+		qWarning() << "TODO: keyEvent translation is incomplete!" << keyEvent;
+
+		ke.Key = keyEvent->key();
+		ke.ExtKey = 0;
+		ke.Modifier = 0;
+		ke.Modifiers = 0;
+		ke.Up = (keyEvent->type() == QEvent::KeyRelease);
+
+		ah_->KeyEvent(ah_,static_cast<uiArea*>(uiFindQt5ControlForQObject(this)),&ke);
+	}
+
+private:
+	uiAreaHandler *ah_ = nullptr;
+};
+
+static Area *findArea(uiArea *a)
+{
+	if (auto widget = uiValidateAndCastObjTo<QWidget>(a)) {
+		if (auto area = dynamic_cast<Area*>(widget)) {
+			return area;
+		}
+		return widget->findChild<Area*>();
+	}
+	return nullptr;
+}
+
+void uiAreaSetSize(uiArea *a, intmax_t width, intmax_t height)
+{
+	qWarning("TODO: %p %d, %d", (void *)a, (int)width, (int)height);
+}
+
+void uiAreaQueueRedrawAll(uiArea *a)
+{
+	if (auto area = findArea(a)) {
+		area->update();;
+	}
+}
+
+void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height)
+{
+	qWarning("TODO: %p %f, %f, %f, %f", (void *)a, x, y, width, height);
+}
+
+uiArea *uiNewArea(uiAreaHandler *ah)
+{
+	auto area = new Area(ah);
+
+	// The widget should get as much space as possible.
+	area->setSizePolicy({QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding});
+
+	return uiAllocQt5ControlType(uiArea,area,uiQt5Control::DeleteControlOnQObjectFree);
+}
+
+uiArea *uiNewScrollingArea(uiAreaHandler *ah, intmax_t width, intmax_t height)
+{
+	auto area = new Area(ah);
+	area->setMinimumSize({(int)width,(int)height});
+
+	auto scrollArea = new QScrollArea;
+	scrollArea->setBackgroundRole(QPalette::Dark);
+	scrollArea->setWidget(area);
+
+	return uiAllocQt5ControlType(uiArea,scrollArea,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/box.cpp b/qt5/box.cpp
new file mode 100644
index 00000000..7ad85b65
--- /dev/null
+++ b/qt5/box.cpp
@@ -0,0 +1,62 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+
+#include <QLayoutItem>
+#include <QWidget>
+
+struct uiBox : public uiQt5Control {};
+
+void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
+{
+	if (auto layoutBox = uiValidateAndCastObjTo<QBoxLayout>(b)) {
+		auto obj = uiValidateAndCastObjTo<QObject>(c);
+
+		if (auto layout = qobject_cast<QLayout*>(obj)) {
+			layoutBox->addLayout(layout, stretchy);
+		} else if (auto widget = qobject_cast<QWidget*>(obj)) {
+			layoutBox->addWidget(widget);
+		} else {
+			qWarning("object is neither layout nor widget");
+		}
+	}
+}
+
+void uiBoxDelete(uiBox *b, uintmax_t index)
+{
+	if (auto layoutBox = uiValidateAndCastObjTo<QBoxLayout>(b)) {
+		if (index < (uint)layoutBox->count()) {
+			delete layoutBox->takeAt(index);
+		}
+	}
+}
+
+int uiBoxPadded(uiBox *b)
+{
+	if (auto layoutBox = uiValidateAndCastObjTo<QBoxLayout>(b)) {
+		layoutBox->spacing();
+	}
+	return 0;
+}
+
+void uiBoxSetPadded(uiBox *b, int padded)
+{
+	if (auto layoutBox = uiValidateAndCastObjTo<QBoxLayout>(b)) {
+		layoutBox->setSpacing(padded);
+	}
+}
+
+uiBox *uiNewHorizontalBox(void)
+{
+	return uiAllocQt5ControlType(uiBox, new QHBoxLayout, uiQt5Control::DeleteControlOnQObjectFree);
+}
+
+uiBox *uiNewVerticalBox(void)
+{
+	auto layout = new QVBoxLayout;
+	layout->setAlignment(Qt::AlignTop);
+
+	return uiAllocQt5ControlType(uiBox, layout, uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/button.cpp b/qt5/button.cpp
new file mode 100644
index 00000000..60695d0a
--- /dev/null
+++ b/qt5/button.cpp
@@ -0,0 +1,39 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QPushButton>
+
+struct uiButton : public uiQt5Control {};
+
+char *uiButtonText(uiButton *b)
+{
+	if (auto button = uiValidateAndCastObjTo<QPushButton>(b)) {
+		return uiQt5StrdupQString(button->text());
+	}
+	return nullptr;
+}
+
+void uiButtonSetText(uiButton *b, const char *text)
+{
+	if (auto button = uiValidateAndCastObjTo<QPushButton>(b)) {
+		button->setText(QString::fromUtf8(text));
+	}
+}
+
+void uiButtonOnClicked(uiButton *b, void (*f)(uiButton *, void *), void *data)
+{
+	if (auto button = uiValidateAndCastObjTo<QPushButton>(b)) {
+		QObject::connect(button, &QPushButton::clicked, button, [f,b,data]{
+			f(b,data);
+		}, Qt::UniqueConnection);
+	}
+}
+
+uiButton *uiNewButton(const char *text)
+{
+	auto button = new QPushButton(QString::fromUtf8(text));
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiButton,button,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/checkbox.cpp b/qt5/checkbox.cpp
new file mode 100644
index 00000000..7b515baa
--- /dev/null
+++ b/qt5/checkbox.cpp
@@ -0,0 +1,54 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QCheckBox>
+
+struct uiCheckbox : public uiQt5Control {};
+
+char *uiCheckboxText(uiCheckbox *c)
+{
+	if (auto checkBox = uiValidateAndCastObjTo<QCheckBox>(c)) {
+		return uiQt5StrdupQString(checkBox->text());
+	}
+	return nullptr;
+}
+
+void uiCheckboxSetText(uiCheckbox *c, const char *text)
+{
+	if (auto checkBox = uiValidateAndCastObjTo<QCheckBox>(c)) {
+		checkBox->setText(QString::fromUtf8(text));
+	}
+}
+
+void uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *, void *), void *data)
+{
+	if (auto checkBox = uiValidateAndCastObjTo<QCheckBox>(c)) {
+		QObject::connect(checkBox, &QCheckBox::toggled, checkBox, [f,c,data]{
+			f(c,data);
+		}, Qt::UniqueConnection);
+	}
+}
+
+int uiCheckboxChecked(uiCheckbox *c)
+{
+	if (auto checkBox = uiValidateAndCastObjTo<QCheckBox>(c)) {
+		return checkBox->isChecked();
+	}
+	return 0;
+}
+
+void uiCheckboxSetChecked(uiCheckbox *c, int checked)
+{
+	if (auto checkBox = uiValidateAndCastObjTo<QCheckBox>(c)) {
+		checkBox->setChecked(checked);
+	}
+}
+
+uiCheckbox *uiNewCheckbox(const char *text)
+{
+	auto checkBox = new QCheckBox(QString::fromUtf8(text));
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiCheckbox,checkBox,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/colorbutton.cpp b/qt5/colorbutton.cpp
new file mode 100644
index 00000000..53264007
--- /dev/null
+++ b/qt5/colorbutton.cpp
@@ -0,0 +1,69 @@
+#include "uipriv_qt5.hpp"
+
+#include <QColorDialog>
+#include <QPushButton>
+
+struct uiColorButton : public uiQt5Control {};
+
+static QColorDialog *findColorDialog(uiColorButton *b)
+{
+	if (auto pushButton = uiValidateAndCastObjTo<QPushButton>(b)) {
+		if (auto colorDialog = pushButton->findChild<QColorDialog*>()) {
+			return colorDialog;
+		}
+	}
+	qWarning("colorDialog not found?!");
+	return nullptr;
+}
+
+void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a)
+{
+	if (auto colorDialog = findColorDialog(b)) {
+		auto color = colorDialog->currentColor();
+		*r = color.redF();
+		*g = color.greenF();
+		*bl = color.blueF();
+		*a = color.alphaF();
+	}
+}
+
+void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a)
+{
+	if (auto colorDialog = findColorDialog(b)) {
+		auto color = QColor::fromRgbF(r,g,bl,a);
+		colorDialog->setCurrentColor(color);
+		emit colorDialog->colorSelected(color); // hacky?
+	}
+}
+
+void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data)
+{
+	if (auto colorDialog = findColorDialog(b)) {
+		QObject::connect(colorDialog, &QColorDialog::colorSelected, colorDialog, [f,b,data]{
+			f(b,data);
+		}, Qt::UniqueConnection);
+	}
+}
+
+uiColorButton *uiNewColorButton(void)
+{
+	auto pushButton = new QPushButton;
+	pushButton->setObjectName("colorButton"); // so we can target just this button with stylesheet
+
+	// persisting this dialog in the background simplies things, but not a good partice
+	auto colorDialog = new QColorDialog(pushButton);
+
+	auto updateColor = [pushButton](const QColor &color) {
+		// quick and dirty, probably not ideal, doesn't show transperency in anyway
+		auto qssColorString = QStringLiteral("rgb(%1,%2,%3)").arg(color.red()).arg(color.green()).arg(color.blue());
+		pushButton->setStyleSheet(QStringLiteral("#colorButton {background-color: %1;}").arg(qssColorString));
+	};
+	QObject::connect(colorDialog, &QColorDialog::colorSelected, pushButton, updateColor);
+	updateColor({}); // set initial color (black)
+
+	QObject::connect(pushButton, &QPushButton::clicked, colorDialog, &QColorDialog::show);
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiColorButton,pushButton,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/combobox.cpp b/qt5/combobox.cpp
new file mode 100644
index 00000000..06bac022
--- /dev/null
+++ b/qt5/combobox.cpp
@@ -0,0 +1,48 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QComboBox>
+
+struct uiCombobox : public uiQt5Control {};
+
+void uiComboboxAppend(uiCombobox *c, const char *text)
+{
+	if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
+		comboBox->addItem(QString::fromUtf8(text));
+	}
+}
+
+intmax_t uiComboboxSelected(uiCombobox *c)
+{
+	if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
+		return comboBox->currentIndex();
+	}
+	return -1;
+}
+
+void uiComboboxSetSelected(uiCombobox *c, intmax_t n)
+{
+	if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
+		comboBox->setCurrentIndex(n);
+	}
+}
+
+void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data)
+{
+	if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
+		// disambiguation of overloaded function
+		void (QComboBox:: *currentIndexChanged)(int) = &QComboBox::currentIndexChanged;
+		QObject::connect(comboBox, currentIndexChanged, comboBox, [f,c,data]{
+			f(c,data);
+		}, Qt::UniqueConnection);
+	}
+}
+
+uiCombobox *uiNewCombobox(void)
+{
+	auto comboBox = new QComboBox;
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiCombobox,comboBox,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/control.cpp b/qt5/control.cpp
new file mode 100644
index 00000000..38e03988
--- /dev/null
+++ b/qt5/control.cpp
@@ -0,0 +1,135 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QWidget>
+#include <QVariant>
+
+#define uiQt5ControlSignature 0x51743578 // 'Qt5x';
+
+Q_DECLARE_METATYPE(uiQt5Control *)
+
+uiQt5Control *uiValidateQt5Control(uiControl *control)
+{
+	if (control == nullptr) {
+		qCritical("Called with uiControl == nullptr");
+		return nullptr;
+	}
+	if (control->Signature == uiQt5ControlSignature) {
+		qCritical("Called with uiControl (%p) that does not contain correct uiQt5ControlSignature? "
+				  "Corruption or incorrect object passed in!", (void*)control);
+		return nullptr;
+	}
+
+	auto qt5Control = static_cast<uiQt5Control *>(control);;
+	if (qt5Control->qobject == nullptr) {
+		if ((qt5Control->flags & uiQt5Control::SuppressValidatationNag) == 0) {
+			qCritical("Called with uiControl (%p) that doesn't have QObject (missing or already destroyed)", (void *)control);
+		}
+		return nullptr;
+	}
+
+	return qt5Control;
+}
+
+uiQt5Control *uiFindQt5ControlForQObject(const QObject *qobject)
+{
+	if (qobject) {
+		return qobject->property("uiQt5Control").value<uiQt5Control *>();
+	}
+	return nullptr;
+}
+
+uiQt5Control *uiAllocQt5Control(uint32_t typesig, const char *typenamestr, QObject *qobject, uint32_t flags)
+{
+	auto control = (uiQt5Control *)uiAllocControl(sizeof(uiQt5Control), uiQt5ControlSignature, typesig, typenamestr);
+	control->qobject = qobject;
+	control->flags = flags;
+	qobject->setProperty("uiQt5Control", QVariant::fromValue(control));
+	QObject::connect(qobject, &QWidget::destroyed, qobject, [control]{
+		control->qobject = nullptr;
+		if ((control->flags & uiQt5Control::DeleteControlOnQObjectFree) == 1) {
+			// supress warning about the QObject being already deleted
+			control->flags |= uiQt5Control::SuppressValidatationNag;
+			uiFreeControl(control);
+		}
+	});
+
+	control->Destroy = [](uiControl *control) {
+		if (auto qt5Control = uiValidateQt5Control(control)) {
+			// consider synchronous destruction for simplicity? this is safer however depending on the caller state
+			qt5Control->qobject->deleteLater();
+			qt5Control->qobject->setProperty("uiQt5Control", {});
+
+			// disconnect the destroyed signal and explicity set widget to null since the control
+			// will be gone when the actual widget destruction happens
+			QObject::disconnect(qt5Control->qobject, &QWidget::destroyed, qt5Control->qobject, nullptr);
+			qt5Control->qobject = nullptr;
+
+			uiFreeControl(control);
+		}
+	};
+	control->Handle = [](uiControl *control) -> uintptr_t {
+		if (auto qt5Control = uiValidateQt5Control(control)) {
+			qWarning("VERIFY Handle usage");
+			return (uintptr_t)qt5Control->qobject;
+		}
+		return 0;
+	};
+	control->Parent = [](uiControl *control) -> uiControl * {
+		if (auto qt5Control = uiValidateQt5Control(control)) {
+			qWarning("TODO Parent");
+			Q_UNUSED(qt5Control);
+			return nullptr;
+		}
+		return nullptr;
+	};
+	control->SetParent = [](uiControl *control, uiControl *parent) {
+		if (auto qt5Control = uiValidateQt5Control(control)) {
+			Q_UNUSED(parent);
+			qWarning("TODO SetParent");
+			Q_UNUSED(qt5Control);
+		}
+	};
+	control->Toplevel = [](uiControl *control) -> int {
+		if (auto qt5Control = uiValidateQt5Control(control)) {
+			qWarning("TODO Toplevel");
+			Q_UNUSED(qt5Control);
+			return 0;
+		}
+		return 0;
+	};
+	control->Visible = [](uiControl *control) -> int {
+		if (auto widget = uiValidateAndCastObjTo<QWidget>(control)) {
+			return widget->isVisible();
+		}
+		return 0;
+	};
+	control->Show = [](uiControl *control) {
+		if (auto widget = uiValidateAndCastObjTo<QWidget>(control)) {
+			return widget->show();
+		}
+	};
+	control->Hide = [](uiControl *control) {
+		if (auto widget = uiValidateAndCastObjTo<QWidget>(control)) {
+			return widget->hide();
+		}
+	};
+	control->Enabled = [](uiControl *control) -> int {
+		if (auto widget = uiValidateAndCastObjTo<QWidget>(control)) {
+			return widget->isEnabled();
+		}
+		return 0;
+	};
+	control->Enable = [](uiControl *control) {
+		if (auto widget = uiValidateAndCastObjTo<QWidget>(control)) {
+			return widget->setEnabled(true);
+		}
+	};
+	control->Disable = [](uiControl *control) {
+		if (auto widget = uiValidateAndCastObjTo<QWidget>(control)) {
+			return widget->setEnabled(false);
+		}
+	};
+
+	return control;
+}
diff --git a/qt5/datetimepicker.cpp b/qt5/datetimepicker.cpp
new file mode 100644
index 00000000..c217f035
--- /dev/null
+++ b/qt5/datetimepicker.cpp
@@ -0,0 +1,37 @@
+
+#include "uipriv_qt5.hpp"
+
+//#include <QCalendarWidget> // TODO: for dropdown
+
+#include <QDateEdit>
+#include <QTimeEdit>
+#include <QDateTimeEdit>
+
+struct uiDateTimePicker : public uiQt5Control {};
+
+uiDateTimePicker *uiNewDateTimePicker(void)
+{
+	auto dateEdit = new QDateEdit(QDate::currentDate());
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiDateTimePicker,dateEdit,uiQt5Control::DeleteControlOnQObjectFree);
+}
+
+uiDateTimePicker *uiNewDatePicker(void)
+{
+	auto timeEdit = new QTimeEdit(QTime::currentTime());
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiDateTimePicker,timeEdit,uiQt5Control::DeleteControlOnQObjectFree);
+}
+
+uiDateTimePicker *uiNewTimePicker(void)
+{
+	auto dateTimeEdit = new QDateTimeEdit(QDateTime::currentDateTime());
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiDateTimePicker,dateTimeEdit,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/debug.cpp b/qt5/debug.cpp
new file mode 100644
index 00000000..81fe826b
--- /dev/null
+++ b/qt5/debug.cpp
@@ -0,0 +1,9 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QDebug>
+
+void realbug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap)
+{
+	qFatal("[libui] %s:%s:%s() %s%s", file, line, func, prefix, qPrintable(QString::vasprintf(format,ap)));
+}
diff --git a/qt5/draw.cpp b/qt5/draw.cpp
new file mode 100644
index 00000000..c3211d1f
--- /dev/null
+++ b/qt5/draw.cpp
@@ -0,0 +1,113 @@
+
+#include "uipriv_qt5.hpp"
+
+#include "draw.hpp"
+
+static QGradient uiDrawBrushToQGradient(const uiDrawBrush *b, QGradient gradient)
+{
+	for (size_t i=0; i<b->NumStops; ++i) {
+		const auto &stop = b->Stops[i];
+		const auto color = QColor::fromRgbF(stop.R, stop.G, stop.B, stop.A);
+		gradient.setColorAt(stop.Pos, color);
+	}
+	return gradient;
+}
+
+static QBrush uiDrawBrushToQBrush(const uiDrawBrush *b)
+{
+	switch (b->Type)
+	{
+	case uiDrawBrushTypeSolid:
+		return {QColor::fromRgbF(b->R, b->G, b->B, b->A), Qt::SolidPattern};
+	case uiDrawBrushTypeLinearGradient:
+		return uiDrawBrushToQGradient(b, QLinearGradient(b->X0, b->Y0, b->X1, b->Y1));
+	case uiDrawBrushTypeRadialGradient:
+		return uiDrawBrushToQGradient(b, QRadialGradient(b->X0, b->Y0, b->OuterRadius, b->X1, b->Y1, 0));
+	case uiDrawBrushTypeImage:
+		// not implemented in uiDrawBrush at time of writing
+		qWarning("TODO: uiDrawBrushTypeImage");
+		break;
+	default:
+		qWarning("Unknown uiDrawBrushType: %d", b->Type);
+	}
+	// something noticable
+	return {Qt::magenta, Qt::DiagCrossPattern};
+}
+
+static QPen uiDrawStrokeParamsToQPen(const uiDrawStrokeParams *p, const QBrush &brush)
+{
+	QPen pen(brush, p->Thickness);
+
+	switch (p->Cap)
+	{
+	case uiDrawLineCapFlat:
+		pen.setCapStyle(Qt::FlatCap);
+		break;
+	case uiDrawLineCapRound:
+		pen.setCapStyle(Qt::RoundCap);
+		break;
+	case uiDrawLineCapSquare:
+		pen.setCapStyle(Qt::SquareCap);
+		break;
+	default:
+		qWarning("Unknown uiDrawLineCap: %d", p->Cap);
+	}
+
+	switch (p->Join)
+	{
+	case uiDrawLineJoinMiter:
+		pen.setJoinStyle(Qt::MiterJoin);
+		break;
+	case uiDrawLineJoinRound:
+		pen.setJoinStyle(Qt::RoundJoin);
+		break;
+	case uiDrawLineJoinBevel:
+		pen.setJoinStyle(Qt::BevelJoin);
+		break;
+	default:
+		qWarning("Unknown uiDrawLineJoin: %d", p->Join);
+	}
+
+	pen.setMiterLimit(p->MiterLimit);
+
+	if (p->NumDashes) {
+		pen.setDashOffset(p->DashPhase/10.0);
+		QVector<qreal> pattern;
+		for (size_t i=0; i<p->NumDashes; ++i) {
+			pattern.append(p->Dashes[i]/10.0);
+		}
+		pen.setDashPattern(pattern);
+	}
+
+	return pen;
+}
+
+void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)
+{
+	c->strokePath(*path, uiDrawStrokeParamsToQPen(p, uiDrawBrushToQBrush(b)));
+}
+
+void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)
+{
+	c->fillPath(*path, uiDrawBrushToQBrush(b));
+}
+
+void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)
+{
+	c->setTransform(m2t(m), true /* combine */);
+}
+
+void uiDrawClip(uiDrawContext *c, uiDrawPath *path)
+{
+	c->setClipPath(*path);
+}
+
+void uiDrawSave(uiDrawContext *c)
+{
+	c->save();
+}
+
+void uiDrawRestore(uiDrawContext *c)
+{
+	c->restore();
+}
diff --git a/qt5/draw.hpp b/qt5/draw.hpp
new file mode 100644
index 00000000..ca908b61
--- /dev/null
+++ b/qt5/draw.hpp
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <QPainter>
+
+typedef struct uiDrawContext uiDrawContext;
+
+struct uiDrawContext : public QPainter
+{
+	uiDrawContext(QPaintDevice *device)
+		: QPainter(device)
+	{
+	}
+};
+
+#include <QPainterPath>
+
+struct uiDrawPath : QPainterPath
+{
+	uiDrawPath(uiDrawFillMode mode)
+	{
+		switch (mode)
+		{
+		case uiDrawFillModeWinding:
+			setFillRule(Qt::WindingFill);
+			break;
+		case uiDrawFillModeAlternate:
+			setFillRule(Qt::OddEvenFill);
+			break;
+		default:
+			qWarning("Unknown uiDrawFillMode: %d", mode);
+		}
+	}
+};
+
+extern QTransform m2t(const uiDrawMatrix *m);
+
+struct uiDrawTextFont : public QFont
+{
+	uiDrawTextFont(const QFont &font)
+		: QFont(font) {}
+};
diff --git a/qt5/drawmatrix.cpp b/qt5/drawmatrix.cpp
new file mode 100644
index 00000000..f2d04515
--- /dev/null
+++ b/qt5/drawmatrix.cpp
@@ -0,0 +1,80 @@
+
+#include "uipriv_qt5.hpp"
+
+#include "draw.hpp"
+
+QTransform m2t(const uiDrawMatrix *m)
+{
+	return QTransform(m->M11, m->M12, m->M21, m->M22, m->M31, m->M32);
+}
+
+static void t2m(const QTransform &t, uiDrawMatrix *m)
+{
+	m->M11 = t.m11();
+	m->M12 = t.m12();
+	m->M21 = t.m21();
+	m->M22 = t.m22();
+	m->M31 = t.m31();
+	m->M32 = t.m32();
+}
+
+void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)
+{
+	auto t = m2t(m);
+	t.translate(x,y);
+	t2m(t,m);
+}
+
+void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)
+{
+	auto t = m2t(m);
+	t.translate(x,y);
+	t.scale(x,y);
+	t.translate(-x,-y);
+	t2m(t,m);
+}
+
+void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)
+{
+	auto t = m2t(m);
+	t.translate(x,y);
+	t.rotateRadians(amount);
+	t.translate(-x,-y);
+	t2m(t,m);
+}
+
+void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
+{
+	auto t = m2t(m);
+	t.translate(x,y);
+	t.shear(xamount, yamount);
+	t.translate(-x,-y);
+	t2m(t,m);
+}
+
+void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)
+{
+	qWarning("TODO: %p, %p", (void *)dest, (void *)src);
+}
+
+int uiDrawMatrixInvertible(uiDrawMatrix *m)
+{
+	qWarning("TODO: %p", (void *)m);
+	return 0;
+}
+
+int uiDrawMatrixInvert(uiDrawMatrix *m)
+{
+	qWarning("TODO: %p", (void *)m);
+	return 0;
+}
+
+void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)
+{
+	qWarning("TODO: %p, %f %f", (void *)m, *x, *y);
+}
+
+void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)
+{
+	qWarning("TODO: %p, %f %f", (void *)m, *x, *y);
+}
diff --git a/qt5/drawpath.cpp b/qt5/drawpath.cpp
new file mode 100644
index 00000000..12fb3701
--- /dev/null
+++ b/qt5/drawpath.cpp
@@ -0,0 +1,76 @@
+
+#include "uipriv_qt5.hpp"
+
+#include "draw.hpp"
+
+#include <QtMath>
+
+uiDrawPath *uiDrawNewPath(uiDrawFillMode mode)
+{
+	return new uiDrawPath(mode);
+}
+
+void uiDrawFreePath(uiDrawPath *p)
+{
+	delete p;
+}
+
+void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
+{
+	p->moveTo(x,y);
+}
+
+void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
+{
+	qreal xStart = xCenter + radius*qCos(startAngle);
+	qreal yStart = yCenter + radius*qSin(startAngle);
+	p->moveTo(xStart, yStart);
+	if (negative) {
+		sweep = -(2*M_PI-sweep);
+	}
+	p->arcTo(xCenter-radius, yCenter-radius, radius*2, radius*2, -qRadiansToDegrees(startAngle), -qRadiansToDegrees(sweep));
+}
+
+void uiDrawPathLineTo(uiDrawPath *p, double x, double y)
+{
+	p->lineTo(x,y);
+}
+
+void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
+{
+	if (negative) {
+		sweep = -(2*M_PI-sweep);
+	}
+	p->arcTo(xCenter-radius, yCenter-radius, radius*2, radius*2, -qRadiansToDegrees(startAngle), -qRadiansToDegrees(sweep));
+}
+
+void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)
+{
+	p->cubicTo(c1x, c1y, c2x, c2y, endX, endY);
+}
+
+void uiDrawPathCloseFigure(uiDrawPath *p)
+{
+	p->closeSubpath();
+}
+
+void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)
+{
+	p->addRect(x,y,width,height);
+}
+
+void uiDrawPathEnd(uiDrawPath *p)
+{
+	Q_UNUSED(p);
+}
+
+uiDrawFillMode pathFillMode(uiDrawPath *path)
+{
+	switch (path->fillRule())
+	{
+	case Qt::WindingFill: return uiDrawFillModeWinding;
+	case Qt::OddEvenFill: return uiDrawFillModeAlternate;
+	}
+	Q_UNREACHABLE();
+	return 0;
+}
diff --git a/qt5/drawtext.cpp b/qt5/drawtext.cpp
new file mode 100644
index 00000000..27477040
--- /dev/null
+++ b/qt5/drawtext.cpp
@@ -0,0 +1,190 @@
+
+#include "uipriv_qt5.hpp"
+
+#include "draw.hpp" // draw context
+
+#include <QFontDatabase>
+#include <QFontMetricsF>
+#include <QTextLayout>
+
+struct uiDrawFontFamilies : public QStringList
+{
+	uiDrawFontFamilies(const QStringList &stringList)
+		: QStringList(stringList) {}
+};
+
+struct uiDrawTextLayout : public QTextLayout
+{
+	uiDrawTextLayout(const QString& text, const QFont &font, double width)
+		: QTextLayout(text, font)
+		, width_(width)
+	{
+//		qWarning("TODO: figure out wtf width is for: %f", width_);
+	}
+	double width_ = 0.0;
+};
+
+uiDrawFontFamilies *uiDrawListFontFamilies(void)
+{
+	return new uiDrawFontFamilies(QFontDatabase().families());
+}
+
+uintmax_t uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
+{
+	return ff->count();
+}
+
+char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, uintmax_t n)
+{
+	return uiQt5StrdupQString(ff->value(n));
+}
+
+void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
+{
+	delete ff;
+}
+
+static int uiDrawTextWeightToFontWeight(uiDrawTextWeight weight)
+{
+	switch (weight)
+	{
+	case uiDrawTextWeightThin: return QFont::Thin;
+	case uiDrawTextWeightUltraLight: return QFont::ExtraLight;
+	case uiDrawTextWeightLight: return QFont::Light;
+	case uiDrawTextWeightBook: return (QFont::Light+QFont::Normal)/2;
+	case uiDrawTextWeightNormal: return QFont::Normal;
+	case uiDrawTextWeightMedium: return QFont::Medium;
+	case uiDrawTextWeightSemiBold: return QFont::DemiBold;
+	case uiDrawTextWeightBold: return QFont::Bold;
+	case uiDrawTextWeightUtraBold: return QFont::ExtraBold;
+	case uiDrawTextWeightHeavy: return (QFont::ExtraBold+QFont::Black)/2;
+	case uiDrawTextWeightUltraHeavy: return QFont::Black;
+	default:
+		qWarning("Unknown uiDrawTextWeight: %d", weight);
+	}
+	return QFont::Normal;
+}
+
+static int uiDrawTextItalicToFontStyle(uiDrawTextItalic italic)
+{
+	switch(italic)
+	{
+	case uiDrawTextItalicNormal: return QFont::StyleNormal;
+	case uiDrawTextItalicOblique: return QFont::StyleOblique;
+	case uiDrawTextItalicItalic: return QFont::StyleItalic;
+	default:
+		qWarning("Unknown uiDrawTextItalic: %d", italic);
+	}
+	return QFont::StyleNormal;
+}
+
+static int uiDrawTextStretchToFont(uiDrawTextStretch stretch)
+{
+	switch(stretch)
+	{
+	case uiDrawTextStretchUltraCondensed: return QFont::UltraCondensed;
+	case uiDrawTextStretchExtraCondensed: return QFont::ExtraCondensed;
+	case uiDrawTextStretchCondensed: return QFont::Condensed;
+	case uiDrawTextStretchSemiCondensed: return QFont::SemiCondensed;
+	case uiDrawTextStretchNormal: return QFont::Unstretched;
+	case uiDrawTextStretchSemiExpanded: return QFont::SemiExpanded;
+	case uiDrawTextStretchExpanded: return QFont::Expanded;
+	case uiDrawTextStretchExtraExpanded: return QFont::ExtraExpanded;
+	case uiDrawTextStretchUltraExpanded: return QFont::UltraExpanded;
+	default:
+		qWarning("Unknown uiDrawTextStretch: %d", stretch);
+	}
+	return QFont::Unstretched;
+}
+
+uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
+{
+	QFont font(QString::fromUtf8(desc->Family));
+	font.setPointSizeF(desc->Size);
+	font.setWeight(uiDrawTextWeightToFontWeight(desc->Weight));
+	font.setItalic(uiDrawTextItalicToFontStyle(desc->Italic));
+	font.setStretch(uiDrawTextStretchToFont(desc->Stretch));
+
+	return new uiDrawTextFont(font);
+}
+
+void uiDrawFreeTextFont(uiDrawTextFont *font)
+{
+	delete font;
+}
+
+uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
+{
+	qWarning("TODO: %p", (void *)font);
+	return 0;
+}
+
+void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
+{
+	qWarning("TODO: %p %p", (void *)font, (void *)desc);
+}
+
+void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
+{
+	if (font) {
+		QFontMetricsF fontMetrics(*font);
+		metrics->Ascent = fontMetrics.ascent();
+		metrics->Descent = fontMetrics.descent();
+		metrics->Leading = fontMetrics.leading();
+		metrics->UnderlinePos = fontMetrics.underlinePos();
+		metrics->UnderlineThickness = 0; // TODO?
+	}
+}
+
+uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width)
+{
+	return new uiDrawTextLayout(QString::fromUtf8(text), *defaultFont, width);
+}
+
+void uiDrawFreeTextLayout(uiDrawTextLayout *layout)
+{
+	delete layout;
+}
+
+void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width)
+{
+	layout->width_ = width;
+}
+
+static void runLayouting(uiDrawTextLayout *layout)
+{
+	layout->setCacheEnabled(true);
+	layout->beginLayout();
+	forever {
+		QTextLine line = layout->createLine();
+		if (!line.isValid()) {
+			break;
+		}
+		// TODO: shift by line height?
+	}
+	layout->endLayout();
+}
+
+void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height)
+{
+	runLayouting(layout);
+	auto boundingRect = layout->boundingRect();
+	*width = boundingRect.width();
+	*height = boundingRect.height();
+}
+
+void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
+{
+	runLayouting(layout);
+	layout->draw(c, {x,y});
+}
+
+void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, intmax_t startChar, intmax_t endChar, double r, double g, double b, double a)
+{
+	// ineffective..? but works
+	auto additionalFormats = layout->additionalFormats();
+	QTextCharFormat format;
+	format.setForeground(QColor::fromRgbF(r,g,b,a));
+	additionalFormats.append(QTextLayout::FormatRange{(int)startChar,(int)(1+endChar-startChar),format});
+	layout->setAdditionalFormats(additionalFormats);
+}
diff --git a/qt5/editablecombo.cpp b/qt5/editablecombo.cpp
new file mode 100644
index 00000000..7e7c2336
--- /dev/null
+++ b/qt5/editablecombo.cpp
@@ -0,0 +1,65 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QComboBox>
+
+struct uiEditableCombobox : public uiQt5Control {};
+
+
+void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text)
+{
+	if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
+		comboBox->addItem(QString::fromUtf8(text));
+	}
+}
+
+intmax_t uiEditableComboboxSelected(uiEditableCombobox *c)
+{
+	if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
+		return comboBox->currentIndex();
+	}
+	return -1;
+}
+
+void uiEditableComboboxSetSelected(uiEditableCombobox *c, intmax_t n)
+{
+	if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
+		return comboBox->setCurrentIndex(n);
+	}
+}
+
+char *uiEditableComboboxText(uiEditableCombobox *c)
+{
+	if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
+		return uiQt5StrdupQString(comboBox->currentText());
+	}
+	return nullptr;
+}
+
+void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text)
+{
+	if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
+		comboBox->setCurrentText(QString::fromUtf8(text));
+	}
+}
+
+void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data)
+{
+	if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
+		// disambiguation of overloaded function
+		void (QComboBox:: *currentIndexChanged)(const QString &) = &QComboBox::currentIndexChanged;
+		QObject::connect(comboBox, currentIndexChanged, comboBox, [f,c,data]{
+			f(c,data);
+		}, Qt::UniqueConnection);
+	}
+}
+
+uiEditableCombobox *uiNewEditableCombobox(void)
+{
+	auto comboBox = new QComboBox;
+	comboBox->setEditable(true);
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiEditableCombobox,comboBox,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/entry.cpp b/qt5/entry.cpp
new file mode 100644
index 00000000..ff9e659a
--- /dev/null
+++ b/qt5/entry.cpp
@@ -0,0 +1,55 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QLineEdit>
+
+struct uiEntry : uiQt5Control {};
+
+char *uiEntryText(uiEntry *e)
+{
+	if (auto lineEdit = uiValidateAndCastObjTo<QLineEdit>(e)) {
+		return uiQt5StrdupQString(lineEdit->text());
+	}
+	return nullptr;
+}
+
+void uiEntrySetText(uiEntry *e, const char *text)
+{
+	if (auto lineEdit = uiValidateAndCastObjTo<QLineEdit>(e)) {
+		lineEdit->setText(QString::fromUtf8(text));
+	}
+}
+
+void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data)
+{
+	if (auto lineEdit = uiValidateAndCastObjTo<QLineEdit>(e)) {
+		// Unlike textChanged(), this signal is not emitted when the text is changed programmatically
+		QObject::connect(lineEdit, &QLineEdit::textChanged, lineEdit, [f,e,data]{
+			f(e,data);
+		}, Qt::UniqueConnection);
+	}
+}
+
+int uiEntryReadOnly(uiEntry *e)
+{
+	if (auto lineEdit = uiValidateAndCastObjTo<QLineEdit>(e)) {
+		return lineEdit->isReadOnly();
+	}
+	return false;
+}
+
+void uiEntrySetReadOnly(uiEntry *e, int readonly)
+{
+	if (auto lineEdit = uiValidateAndCastObjTo<QLineEdit>(e)) {
+		lineEdit->setReadOnly(readonly);
+	}
+}
+
+uiEntry *uiNewEntry(void)
+{
+	auto lineEdit = new QLineEdit;
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiEntry,lineEdit,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/fontbutton.cpp b/qt5/fontbutton.cpp
new file mode 100644
index 00000000..50b87576
--- /dev/null
+++ b/qt5/fontbutton.cpp
@@ -0,0 +1,56 @@
+
+#include "uipriv_qt5.hpp"
+
+#include "draw.hpp"
+
+// another way would be to return a QFontComboBox + QSpinBox (for font size),
+// but this is more true the control name
+#include <QFontDialog>
+#include <QPushButton>
+
+struct uiFontButton : public uiQt5Control {};
+
+uiDrawTextFont *uiFontButtonFont(uiFontButton *b)
+{
+	if (auto pushButton = uiValidateAndCastObjTo<QPushButton>(b)) {
+		if (auto fontDialog = pushButton->findChild<QFontDialog*>()) {
+			return new uiDrawTextFont(fontDialog->currentFont());
+		}
+	}
+	return nullptr;
+}
+
+void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data)
+{
+	if (auto pushButton = uiValidateAndCastObjTo<QPushButton>(b)) {
+		if (auto fontDialog = pushButton->findChild<QFontDialog*>()) {
+			QObject::connect(fontDialog, &QFontDialog::fontSelected, fontDialog, [f,b,data]{
+				f(b,data);
+			}, Qt::UniqueConnection);
+		}
+	}
+}
+
+uiFontButton *uiNewFontButton(void)
+{
+	auto pushButton = new QPushButton;
+
+	// persisting this dialog in the background simplies things, but not a good partice
+	auto fontDialog = new QFontDialog(pushButton);
+
+	auto updateFont = [pushButton](const QFont &font) {
+		auto family = font.family();
+		auto pointSize = font.pointSize(); // or pixelSize..?
+		pushButton->setText(QStringLiteral("%1  %2").arg(family).arg(pointSize));
+	};
+	QObject::connect(fontDialog, &QFontDialog::fontSelected, pushButton, updateFont);
+	QObject::connect(pushButton, &QPushButton::clicked, fontDialog, &QFontDialog::show);
+
+	QFont font; font.setPointSize(12);
+	updateFont(font);
+	fontDialog->setCurrentFont(font); // set initial font (system font)
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiFontButton,pushButton,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/group.cpp b/qt5/group.cpp
new file mode 100644
index 00000000..ff9f68af
--- /dev/null
+++ b/qt5/group.cpp
@@ -0,0 +1,63 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QGroupBox>
+
+#include <QVBoxLayout>
+
+struct uiGroup : public uiQt5Control {};
+
+char *uiGroupTitle(uiGroup *g)
+{
+	if (auto groupBox = uiValidateAndCastObjTo<QGroupBox>(g)) {
+		return uiQt5StrdupQString(groupBox->title());
+	}
+	return nullptr;
+}
+
+void uiGroupSetTitle(uiGroup *g, const char *text)
+{
+	if (auto groupBox = uiValidateAndCastObjTo<QGroupBox>(g)) {
+		groupBox->setTitle(QString::fromUtf8(text));
+	}
+}
+
+void uiGroupSetChild(uiGroup *g, uiControl *child)
+{
+	if (auto groupBox = uiValidateAndCastObjTo<QGroupBox>(g)) {
+		auto obj = uiValidateAndCastObjTo<QObject>(child);
+		if (groupBox->layout()) {
+			groupBox->layout()->deleteLater();
+		}
+		if (auto layout = qobject_cast<QLayout*>(obj)) {
+			groupBox->setLayout(layout);
+		} else if (auto widget = qobject_cast<QWidget*>(obj)) {
+			auto layout = new QVBoxLayout;
+			layout->setMargin(0); // ?
+			layout->addWidget(widget);
+			groupBox->setLayout(layout);
+		} else {
+			qWarning("object is neither layout nor widget");
+		}
+	}
+}
+
+int uiGroupMargined(uiGroup *g)
+{
+	qWarning("TODO: %p", (void*)g);
+	return 0;
+}
+
+void uiGroupSetMargined(uiGroup *g, int margined)
+{
+	qWarning("TODO: %p, %d", (void*)g, margined);
+}
+
+uiGroup *uiNewGroup(const char *text)
+{
+	auto groupBox = new QGroupBox(QString::fromUtf8(text));
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiGroup,groupBox,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/label.cpp b/qt5/label.cpp
new file mode 100644
index 00000000..9eccec48
--- /dev/null
+++ b/qt5/label.cpp
@@ -0,0 +1,30 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QLabel>
+
+struct uiLabel : public uiQt5Control {};
+
+char *uiLabelText(uiLabel *l)
+{
+	if (auto label = uiValidateAndCastObjTo<QLabel>(l)) {
+		return uiQt5StrdupQString(label->text());
+	}
+	return nullptr;
+}
+
+void uiLabelSetText(uiLabel *l, const char *text)
+{
+	if (auto label = uiValidateAndCastObjTo<QLabel>(l)) {
+		label->setText(QString::fromUtf8(text));
+	}
+}
+
+uiLabel *uiNewLabel(const char *text)
+{
+	auto label = new QLabel(QString::fromUtf8(text));
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiLabel,label,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/main.cpp b/qt5/main.cpp
new file mode 100644
index 00000000..99d77d0d
--- /dev/null
+++ b/qt5/main.cpp
@@ -0,0 +1,138 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QApplication>
+#include <QTimer>
+#include <QWidget>
+#include <QThread>
+
+#include <functional>
+
+const char *styleSheet = R"qcss(
+QGroupBox {
+	font: bold;
+	color: #333;
+	/*border: none;*/
+}
+)qcss";
+
+const char *uiInit(uiInitOptions *o)
+{
+	Q_UNUSED(o); // don't care about something that isn't used
+
+	if (QCoreApplication::instance() != nullptr) {
+		return "another QApplication instance already exists";
+	}
+
+	static int argc = 0;
+	static const char *argv[] = {"libui"};
+	auto app = new QApplication(argc,(char **)argv);
+
+	if (app == nullptr) {
+		return "failed to allocate new QApplication";
+	}
+
+	qSetMessagePattern("%{file}:%{line} %{function}(): %{message}");
+
+	// few consistency things
+	app->setQuitOnLastWindowClosed(false);
+	app->setStyleSheet(styleSheet);
+
+	initAlloc();
+
+	return NULL;
+}
+
+void uiUninit(void)
+{
+	auto app = static_cast<QApplication*>(QCoreApplication::instance());
+
+	// not strictly necessary, just so we don't get needless nag from uninitAlloc()
+	// becasue of cascading it's not possible to iterate over the list (copy)
+	while (app->topLevelWidgets().count()) {
+		delete app->topLevelWidgets().first();
+	}
+
+	uninitAlloc();
+
+	delete app;
+}
+
+void uiFreeInitError(const char *err)
+{
+	Q_UNUSED(err); // we only return static constants for errors
+}
+
+void uiMain(void)
+{
+	if (QApplication::instance()) {
+		QApplication::instance()->exec();
+	}
+}
+
+int uiMainStep(int wait)
+{
+	if (QApplication::instance()) {
+		if (wait) {
+			// processEvents will always return immediately if there are no
+			// immediate events to process
+
+			// we would have to install a event listener and run in while loop
+			// that seems very questionable.. don't understand the use case
+			qWarning("TODO: wait - no equivalent?");
+		}
+		QApplication::instance()->processEvents();
+	}
+	return 0;
+}
+
+void uiQuit(void)
+{
+	QTimer::singleShot(0,QApplication::instance(), &QApplication::quit);
+}
+
+void uiQueueMain(void (*f)(void *data), void *data)
+{
+	if (!QCoreApplication::instance()) {
+		// no instance...?
+		return;
+	}
+
+	if (QThread::currentThread()->eventDispatcher()) {
+		// the simple way to queue something to run on main thread
+		QTimer::singleShot(0,QCoreApplication::instance(), [f,data]{
+			f(data);
+		});
+	} else {
+		// very dirty workaround, spawn a new thread.. with event dispatcher to deliver this
+
+		// this could be done with custom signal, but since I decided to do
+		// this without using moc this is the simplest solution I could think of.
+
+		// sort of works, but not very light weight and will block to ensure resource cleanup
+		class Thread : public QThread
+		{
+			std::function<void ()> taskForMain_;
+
+			void run()
+			{
+				QTimer::singleShot(0,QCoreApplication::instance(), taskForMain_);
+				exec(); // start event loop
+			}
+		public:
+			Thread(std::function<void ()> taskForMain)
+				: taskForMain_(taskForMain)
+			{
+				start();
+			}
+			~Thread()
+			{
+				quit();
+				wait();
+			}
+		};
+		Thread([f,data]{
+			f(data);
+		});
+	}
+}
diff --git a/qt5/menu.cpp b/qt5/menu.cpp
new file mode 100644
index 00000000..372220d2
--- /dev/null
+++ b/qt5/menu.cpp
@@ -0,0 +1,162 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QMenu>
+
+#include <QApplication>
+#include <QMenuBar>
+
+struct uiMenu : public uiQt5Control {};
+
+#ifndef uiMenuSignature
+#define uiMenuSignature 0x4d656e75 // 'Menu'
+#endif
+
+struct uiMenuItem : public uiQt5Control {};
+
+#ifndef uiMenuItemSignature
+#define uiMenuItemSignature 0x4d654974 // 'MeIt'
+#endif
+
+void uiMenuItemEnable(uiMenuItem *item)
+{
+	if (auto action = uiValidateAndCastObjTo<QAction>(item)) {
+		action->setEnabled(true);
+	}
+}
+
+void uiMenuItemDisable(uiMenuItem *item)
+{
+	if (auto action = uiValidateAndCastObjTo<QAction>(item)) {
+		action->setEnabled(false);
+	}
+}
+
+static uiWindow *uiQt5FindWindow(QObject *qobject)
+{
+	if (qobject) {
+		if (auto widget = qobject_cast<QWidget*>(qobject)) {
+			if (auto topLevel = widget->topLevelWidget()) {
+				return static_cast<uiWindow *>(uiFindQt5ControlForQObject(topLevel));
+			}
+		}
+		return uiQt5FindWindow(qobject->parent());
+	}
+	return nullptr;
+}
+
+static uiWindow *uiQt5FindWindow(uiControl *leaf)
+{
+	if (auto qobject = uiValidateAndCastObjTo<QObject>(leaf)) {
+		return uiQt5FindWindow(qobject);
+	}
+	return nullptr;
+}
+
+void uiMenuItemOnClicked(uiMenuItem *item, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)
+{
+	if (auto action = uiValidateAndCastObjTo<QAction>(item)) {
+		QObject::connect(action,&QAction::triggered, action, [f,item,data]{
+			auto window = uiQt5FindWindow(item);
+			f(item,window,data);
+		}, Qt::UniqueConnection);
+	}
+}
+
+int uiMenuItemChecked(uiMenuItem *item)
+{
+	if (auto action = uiValidateAndCastObjTo<QAction>(item)) {
+		return action->isChecked();
+	}
+	return 0;
+}
+
+void uiMenuItemSetChecked(uiMenuItem *item, int checked)
+{
+	if (auto action = uiValidateAndCastObjTo<QAction>(item)) {
+		action->setChecked(checked);
+	}
+}
+
+uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)
+{
+	if (auto menu = uiValidateAndCastObjTo<QMenu>(m)) {
+		auto action = menu->addAction(name);
+
+		action->setObjectName(name); // for debugging only
+
+		return uiAllocQt5ControlType(uiMenuItem, action, uiQt5Control::DeleteControlOnQObjectFree);
+	}
+	return nullptr;
+}
+
+uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)
+{
+	auto menuItem = uiMenuAppendItem(m, name);
+	if (auto action = uiValidateAndCastObjTo<QAction>(menuItem)) {
+		action->setCheckable(true);
+	}
+	return menuItem;
+}
+
+uiMenuItem *uiMenuAppendQuitItem(uiMenu *m)
+{
+	uiMenuAppendSeparator(m);
+	auto menuItem = uiMenuAppendItem(m, "&Quit");
+	if (auto action = uiValidateAndCastObjTo<QAction>(menuItem)) {
+		action->setShortcut(QKeySequence::Quit);
+	}
+	uiMenuItemOnClicked(menuItem, [](uiMenuItem *, uiWindow *, void *) { uiQuit(); }, nullptr);
+	return menuItem;
+}
+
+uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)
+{
+	uiMenuAppendSeparator(m);
+	auto menuItem = uiMenuAppendItem(m, "&Preferences...");
+	if (auto action = uiValidateAndCastObjTo<QAction>(menuItem)) {
+		action->setShortcut(QKeySequence::Preferences);
+	}
+	return menuItem;
+}
+
+uiMenuItem *uiMenuAppendAboutItem(uiMenu *m)
+{
+	uiMenuAppendSeparator(m);
+	return uiMenuAppendItem(m, "&About");
+}
+
+void uiMenuAppendSeparator(uiMenu *m)
+{
+	if (auto menu = uiValidateAndCastObjTo<QMenu>(m)) {
+		menu->addSeparator();
+	}
+}
+
+QMenuBar *uiQt5FindMainMenuBar()
+{
+	auto app = static_cast<QApplication*>(QCoreApplication::instance());
+	for (auto widget : app->topLevelWidgets()) {
+		// see if the widget is the menubar
+		if (auto menuBar = qobject_cast<QMenuBar*>(widget)) {
+			return menuBar;
+		}
+		// see if the widget owns  the menubar
+		if (auto menuBar = widget->findChild<QMenuBar*>()) {
+			return menuBar;
+		}
+	}
+	// not found, create
+	return new QMenuBar;
+}
+
+uiMenu *uiNewMenu(const char *name)
+{
+	auto menuBar = uiQt5FindMainMenuBar();
+	auto menu = new QMenu(QString::fromUtf8(name), menuBar);
+	menuBar->addMenu(menu);
+
+	menu->setObjectName(name); // for debugging only
+
+	return uiAllocQt5ControlType(uiMenu, menu, uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/multilineentry.cpp b/qt5/multilineentry.cpp
new file mode 100644
index 00000000..cb75a9e4
--- /dev/null
+++ b/qt5/multilineentry.cpp
@@ -0,0 +1,79 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QTextEdit>
+
+struct uiMultilineEntry : public uiQt5Control {};
+
+char *uiMultilineEntryText(uiMultilineEntry *e)
+{
+	if (auto textEdit = uiValidateAndCastObjTo<QTextEdit>(e)) {
+		return uiQt5StrdupQString(textEdit->toPlainText());
+	}
+	return nullptr;
+}
+
+void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text)
+{
+	if (auto textEdit = uiValidateAndCastObjTo<QTextEdit>(e)) {
+		textEdit->setPlainText(QString::fromUtf8(text));
+	}
+}
+
+void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text)
+{
+	if (auto textEdit = uiValidateAndCastObjTo<QTextEdit>(e)) {
+		// append is least interferring (keeps selection and cursor),
+		// but will add newline by it self hence not exactly compatible
+
+		// since the newline is implicitly inserted we need to remove
+		// the explicitly inserted one if we want the examples to look
+		// same, but this may cause other issues..
+		auto oldBlockSignals = textEdit->blockSignals(true);
+		textEdit->append(QString::fromUtf8(text).trimmed());
+		textEdit->blockSignals(oldBlockSignals);
+	}
+}
+
+void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data)
+{
+	if (auto textEdit = uiValidateAndCastObjTo<QTextEdit>(e)) {
+		QObject::connect(textEdit, &QTextEdit::textChanged, textEdit, [f,e,data]{
+			f(e,data);
+		}, Qt::UniqueConnection);
+	}
+}
+
+int uiMultilineEntryReadOnly(uiMultilineEntry *e)
+{
+	if (auto textEdit = uiValidateAndCastObjTo<QTextEdit>(e)) {
+		textEdit->isReadOnly();
+	}
+	return 0;
+}
+
+void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly)
+{
+	if (auto textEdit = uiValidateAndCastObjTo<QTextEdit>(e)) {
+		textEdit->setReadOnly(readonly);
+	}
+}
+
+uiMultilineEntry *uiNewMultilineEntry(void)
+{
+	auto textEdit = new QTextEdit;
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiMultilineEntry,textEdit,uiQt5Control::DeleteControlOnQObjectFree);
+}
+
+uiMultilineEntry *uiNewNonWrappingMultilineEntry(void)
+{
+	auto textEdit = new QTextEdit;
+	textEdit->setLineWrapMode(QTextEdit::NoWrap);
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiMultilineEntry,textEdit,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/progressbar.cpp b/qt5/progressbar.cpp
new file mode 100644
index 00000000..93167eaa
--- /dev/null
+++ b/qt5/progressbar.cpp
@@ -0,0 +1,22 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QProgressBar>
+
+struct uiProgressBar : public uiQt5Control {};
+
+void uiProgressBarSetValue(uiProgressBar *p, int value)
+{
+	if (auto progressBar = uiValidateAndCastObjTo<QProgressBar>(p)) {
+		return progressBar->setValue(value);
+	}
+}
+
+uiProgressBar *uiNewProgressBar(void)
+{
+	auto progressBar = new QProgressBar;
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiProgressBar,progressBar,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/radiobuttons.cpp b/qt5/radiobuttons.cpp
new file mode 100644
index 00000000..5ef62867
--- /dev/null
+++ b/qt5/radiobuttons.cpp
@@ -0,0 +1,28 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QRadioButton>
+#include <QVBoxLayout>
+
+struct uiRadioButtons : public uiQt5Control {};
+
+void uiRadioButtonsAppend(uiRadioButtons *r, const char *text)
+{
+	if (auto layout = uiValidateAndCastObjTo<QLayout>(r)) {
+		auto radioButton = new QRadioButton(QString::fromUtf8(text));
+		layout->addWidget(radioButton);
+		if (layout->count() == 1) {
+			radioButton->setChecked(true);
+		}
+	}
+}
+
+uiRadioButtons *uiNewRadioButtons(void)
+{
+	// TODO: check does this need a QButtonGroup or is the layout sufficent?
+	auto layout = new QVBoxLayout;
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiRadioButtons,layout,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/separator.cpp b/qt5/separator.cpp
new file mode 100644
index 00000000..2a9bc15e
--- /dev/null
+++ b/qt5/separator.cpp
@@ -0,0 +1,16 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QFrame>
+
+struct uiSeparator : public uiQt5Control {};
+
+uiSeparator *uiNewHorizontalSeparator(void)
+{
+	auto frame = new QFrame;
+	frame->setFrameStyle(QFrame::HLine | QFrame::Sunken);
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiSeparator,frame,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/slider.cpp b/qt5/slider.cpp
new file mode 100644
index 00000000..ae92d3ff
--- /dev/null
+++ b/qt5/slider.cpp
@@ -0,0 +1,40 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QSlider>
+
+struct uiSlider : public uiQt5Control {};
+
+intmax_t uiSliderValue(uiSlider *s)
+{
+	if (auto slider = uiValidateAndCastObjTo<QSlider>(s)) {
+		return slider->value();
+	}
+	return 0;
+}
+
+void uiSliderSetValue(uiSlider *s, intmax_t value)
+{
+	if (auto slider = uiValidateAndCastObjTo<QSlider>(s)) {
+		return slider->setValue((int)value);
+	}
+}
+
+void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *, void *), void *data)
+{
+	if (auto slider = uiValidateAndCastObjTo<QSlider>(s)) {
+		QObject::connect(slider, &QSlider::valueChanged, slider, [f,s,data]{
+			f(s,data);
+		}, Qt::UniqueConnection);
+	}
+}
+
+uiSlider *uiNewSlider(intmax_t min, intmax_t max)
+{
+	auto slider = new QSlider(Qt::Horizontal);
+	slider->setRange(qMin<int>(min,max), qMax<int>(min,max));
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiSlider,slider,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/spinbox.cpp b/qt5/spinbox.cpp
new file mode 100644
index 00000000..155c655b
--- /dev/null
+++ b/qt5/spinbox.cpp
@@ -0,0 +1,40 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QSpinBox>
+
+struct uiSpinbox : public uiQt5Control {};
+
+intmax_t uiSpinboxValue(uiSpinbox *s)
+{
+	if (auto spinBox = uiValidateAndCastObjTo<QSpinBox>(s)) {
+		return spinBox->value();
+	}
+	return 0;
+}
+
+void uiSpinboxSetValue(uiSpinbox *s, intmax_t value)
+{
+	if (auto spinBox = uiValidateAndCastObjTo<QSpinBox>(s)) {
+		spinBox->setValue(value);
+	}
+}
+
+void uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *, void *), void *data)
+{
+	if (auto spinBox = uiValidateAndCastObjTo<QSpinBox>(s)) {
+		QObject::connect(spinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), spinBox, [f,s,data]{
+			f(s,data);
+		}, Qt::UniqueConnection);
+	}
+}
+
+uiSpinbox *uiNewSpinbox(intmax_t min, intmax_t max)
+{
+	auto spinBox = new QSpinBox;
+	spinBox->setRange(qMin(min,max),qMax(min,max));
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiSpinbox,spinBox,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/stddialogs.cpp b/qt5/stddialogs.cpp
new file mode 100644
index 00000000..7421d2ec
--- /dev/null
+++ b/qt5/stddialogs.cpp
@@ -0,0 +1,29 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QFileDialog>
+#include <QMessageBox>
+
+char *uiOpenFile(uiWindow *parent)
+{
+	return uiQt5StrdupQString(QFileDialog::getOpenFileName(uiValidateAndCastObjTo<QWidget>(parent)));
+}
+
+char *uiSaveFile(uiWindow *parent)
+{
+	return uiQt5StrdupQString(QFileDialog::getSaveFileName(uiValidateAndCastObjTo<QWidget>(parent)));
+}
+
+void uiMsgBox(uiWindow *parent, const char *title, const char *description)
+{
+	QMessageBox::information(uiValidateAndCastObjTo<QWidget>(parent),
+							 QString::fromUtf8(title),
+							 QString::fromUtf8(description));
+}
+
+void uiMsgBoxError(uiWindow *parent, const char *title, const char *description)
+{
+	QMessageBox::warning(uiValidateAndCastObjTo<QWidget>(parent),
+						 QString::fromUtf8(title),
+						 QString::fromUtf8(description));
+}
diff --git a/qt5/tab.cpp b/qt5/tab.cpp
new file mode 100644
index 00000000..0169b835
--- /dev/null
+++ b/qt5/tab.cpp
@@ -0,0 +1,88 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QTabWidget>
+#include <QLayout>
+
+struct uiTab : public uiQt5Control {};
+
+static void tabInsertAt(QTabWidget *tabWidget, const char *name, int n, uiControl *child)
+{
+	auto obj = uiValidateAndCastObjTo<QObject>(child);
+
+	if (auto layout = qobject_cast<QLayout*>(obj)) {
+		auto widget = new QWidget;
+		widget->setLayout(layout);
+		tabWidget->insertTab(n, widget, QString::fromUtf8(name));
+	} else if (auto widget = qobject_cast<QWidget*>(obj)) {
+		tabWidget->insertTab(n, widget, QString::fromUtf8(name));
+	} else {
+		qWarning("object is neither layout nor widget");
+	}
+}
+
+void uiTabAppend(uiTab *t, const char *name, uiControl *child)
+{
+	if (auto tabWidget = uiValidateAndCastObjTo<QTabWidget>(t)) {
+		tabInsertAt(tabWidget,name,tabWidget->count(),child);
+	}
+}
+
+void uiTabInsertAt(uiTab *t, const char *name, uintmax_t n, uiControl *child)
+{
+	if (auto tabWidget = uiValidateAndCastObjTo<QTabWidget>(t)) {
+		tabInsertAt(tabWidget,name,qBound<int>(0, n, tabWidget->count()),child);
+	}
+}
+
+// reminder: badly named function, is remove not really a delete
+void uiTabDelete(uiTab *t, uintmax_t n)
+{
+	if (auto tabWidget = uiValidateAndCastObjTo<QTabWidget>(t)) {
+		int i = qBound<int>(0, n, tabWidget->count());
+		if ((uintmax_t)i != n) {
+			qWarning("Bad index: %llu", (unsigned long long)n);
+			return;
+		}
+		auto widget = tabWidget->widget(i);
+		tabWidget->removeTab(i);
+		// check if it's a internal temp container widget we created
+		if (uiFindQt5ControlForQObject(widget) == nullptr) {
+			// widget is temp we created used as a container, separate the layout and then delete it
+
+			// problem once a layout assigned to widget, it's not so easy to get it back..?
+			// one solution is to recreate the layout, but this needs some thought..
+			// another solution is to never expose "layout" (uiBox), but always wrap them in containers..
+			qWarning("FIXME: can't separate layout from temp widget");
+//			delete widget;
+		}
+	}
+}
+
+uintmax_t uiTabNumPages(uiTab *t)
+{
+	if (auto tabWidget = uiValidateAndCastObjTo<QTabWidget>(t)) {
+		return tabWidget->count();
+	}
+	return 0;
+}
+
+int uiTabMargined(uiTab *t, uintmax_t n)
+{
+	qWarning("TODO %p, %d", (void*)t, (int)n);
+	return 0;
+}
+
+void uiTabSetMargined(uiTab *t, uintmax_t n, int margined)
+{
+	qWarning("TODO %p, %d, %d", (void*)t, (int)n, margined);
+}
+
+uiTab *uiNewTab(void)
+{
+	auto tabWidget = new QTabWidget;
+
+	// note styling is being set in main.cpp -> styleSheet
+
+	return uiAllocQt5ControlType(uiTab,tabWidget,uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/qt5/text.cpp b/qt5/text.cpp
new file mode 100644
index 00000000..034139de
--- /dev/null
+++ b/qt5/text.cpp
@@ -0,0 +1,33 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QString>
+
+static char *uiStrdupText(const char *s, int len)
+{
+	// Reminder: could use strndup, but it has poor portability
+	auto copy = (char *)malloc(len+1);
+	if (copy) {
+		memcpy(copy, s, len);
+		copy[len] = 0;
+	} else {
+		qCritical("Failed to allocate %d+1 bytes", len);
+	}
+
+	return copy;
+}
+
+char *uiQt5StrdupQString(const QString &string)
+{
+	if (string.isNull()) {
+		return nullptr;
+	}
+
+	const auto utf8 = string.toUtf8();
+	return uiStrdupText(utf8.constData(), utf8.length());
+}
+
+void uiFreeText(char *t)
+{
+	free(t);
+}
diff --git a/qt5/uipriv_qt5.hpp b/qt5/uipriv_qt5.hpp
new file mode 100644
index 00000000..3dafe2a0
--- /dev/null
+++ b/qt5/uipriv_qt5.hpp
@@ -0,0 +1,52 @@
+
+#ifndef __LIBUI_UIPRIV_QT5_HPP__
+#define __LIBUI_UIPRIV_QT5_HPP__
+
+#include "../ui.h"
+#include "../ui_qt5.h"
+#include "../common/uipriv.h"
+
+extern void initAlloc(void);
+extern void uninitAlloc(void);
+
+// text
+class QString;
+char *uiQt5StrdupQString(const QString &string);
+
+// control
+class QObject;
+struct uiQt5Control : public uiControl
+{
+	QObject *qobject;
+	enum {
+		DefaultFlags=0,
+		DeleteControlOnQObjectFree=0x1,
+		SuppressValidatationNag=0x2
+	};
+	uint32_t flags;
+};
+extern uiQt5Control *uiValidateQt5Control(uiControl *control);
+
+// control to qobject
+template <typename T=QObject>
+T *uiValidateAndCastObjTo(uiControl *control)
+{
+	if (auto qt5Control = uiValidateQt5Control(control)) {
+		return dynamic_cast<T*>(qt5Control->qobject);
+	}
+	return nullptr;
+}
+struct uiWindow : public uiQt5Control {};
+
+// qobject to control
+extern uiQt5Control *uiFindQt5ControlForQObject(const QObject *qobject);
+
+// alloc control
+extern uiQt5Control *uiAllocQt5Control(uint32_t typesig, const char *typenamestr, QObject *qobject, uint32_t flags = uiQt5Control::DefaultFlags);
+#define uiAllocQt5ControlType(type, widget, flags) static_cast<type*>(uiAllocQt5Control(type ## Signature, #type, widget, flags))
+
+// menu
+class QMenuBar;
+extern QMenuBar *uiQt5FindMainMenuBar();
+
+#endif // __LIBUI_UIPRIV_QT5_HPP__
diff --git a/qt5/window.cpp b/qt5/window.cpp
new file mode 100644
index 00000000..e257d633
--- /dev/null
+++ b/qt5/window.cpp
@@ -0,0 +1,103 @@
+
+#include "uipriv_qt5.hpp"
+
+#include <QMainWindow>
+
+#include <QCloseEvent>
+#include <QMenuBar>
+#include <QLayout>
+
+#include <functional>
+
+class WindowWidget : public QMainWindow
+{
+public:
+	std::function<void (QCloseEvent *event)> onClosing;
+	void closeEvent(QCloseEvent *event)
+	{
+		if (onClosing) {
+			onClosing(event);
+		}
+	}
+};
+
+char *uiWindowTitle(uiWindow *w)
+{
+	if (auto window = uiValidateAndCastObjTo<WindowWidget>(w)) {
+		return uiQt5StrdupQString(window->windowTitle());
+	}
+	return nullptr;
+}
+
+void uiWindowSetTitle(uiWindow *w, const char *title)
+{
+	if (auto window = uiValidateAndCastObjTo<WindowWidget>(w)) {
+		window->setWindowTitle(QString::fromUtf8(title));
+	}
+}
+
+void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data)
+{
+	if (auto window = uiValidateAndCastObjTo<WindowWidget>(w)) {
+
+		if (f) {
+			window->onClosing = [f,w,data](QCloseEvent *event){
+				if (f(w,data)) {
+					// eat the event and destroy the control
+					// normally such behavior would be achived with
+					// setAttribute(Qt::WA_DeleteOnClose, true)
+					// but we need to behave consistently
+					event->accept();
+					w->Destroy(w);
+				}
+			};
+		} else {
+			// clear callback
+			window->onClosing = nullptr;
+		}
+	}
+}
+
+void uiWindowSetChild(uiWindow *w, uiControl *child)
+{
+	if (auto window = uiValidateAndCastObjTo<WindowWidget>(w)) {
+		auto obj = uiValidateAndCastObjTo<QObject>(child);
+		if (window->centralWidget()) {
+			window->centralWidget()->deleteLater();
+		}
+		if (auto layout = qobject_cast<QLayout*>(obj)) {
+			auto widget = new QWidget;
+			widget->setLayout(layout);
+			window->setCentralWidget(widget);
+		} else if (auto widget = qobject_cast<QWidget*>(obj)) {
+			window->setCentralWidget(widget);
+		} else {
+			qWarning("object is neither layout nor widget");
+		}
+	}
+}
+
+int uiWindowMargined(uiWindow *w)
+{
+	qWarning("TODO %p", (void*)w);
+	return 0;
+}
+
+void uiWindowSetMargined(uiWindow *w, int margined)
+{
+	qWarning("TODO %p, %d", (void*)w, margined);
+}
+
+uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)
+{
+	auto window = new WindowWidget;
+
+	window->setWindowTitle(title);
+	window->resize(width,height);
+
+	if (hasMenubar) {
+		window->setMenuBar(uiQt5FindMainMenuBar());
+	}
+
+	return uiAllocQt5ControlType(uiWindow, window, uiQt5Control::DeleteControlOnQObjectFree);
+}
diff --git a/ui_qt5.h b/ui_qt5.h
new file mode 100644
index 00000000..cd1a3e50
--- /dev/null
+++ b/ui_qt5.h
@@ -0,0 +1,7 @@
+
+#ifndef __LIBUI_UI_QT5_H__
+#define __LIBUI_UI_QT5_H__
+
+
+
+#endif // __LIBUI_UI_QT5_H__