███╗ ███╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗
████╗ █████║██╔═══██╗██╔══██╗██╔══██╗██║ ██║
██╔████╔██║██║ ██║██████╔╝██████╔╝███████║
██║╚██╔╝██║██║ ██║██╔══██╗██╔═══╝ ██╔══██║
██║ ╚═╝ ██║╚██████╔╝██║ ██║██║ ██║ ██║
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
Build native OpenGL Applications with HTML, CSS, and JavaScript.
No browser. No Electron. No WebView. Just a lightweight native binary.
Morph is a UI framework that compiles .mx files (JSX-like syntax with CSS and JavaScript) directly into native OpenGL binaries. You write familiar web syntax — Morph produces a lean, native binary with zero browser overhead.
<!-- src/App.mx -->
<morph-window title="My App" width="800" height="600">
<div style="height: 48px; background: #1a1a2e;">
<button morph-open="settings">Settings</button>
</div>
<h1 style="color: #e0e0e0;">Hello from Morph</h1>
</morph-window>morph dev # live window, hot reload via Unix socket
morph build # optimized native binary| Electron | Qt | Morph | |
|---|---|---|---|
| Write UI in | HTML/CSS/JS | C++ / QML | HTML/CSS/JS |
| Runtime | Chromium (~150MB) | Qt libs | Zero |
| Binary size | ~80MB+ | ~20MB+ | <1MB |
| Native OpenGL access | ✗ | ✓ | ✓ |
| Hot reload | ✓ | ✗ | ✓ |
| Custom C++ nodes | ✗ | ✓ | ✓ |
1. Install
pip install levizr-morph
morph doctor # verify system dependencies2. Create a project
morph init my-app
cd my-app3. Start dev mode
morph devA native window opens. Edit src/App.mx — the window updates instantly without restarting.
4. Ship
morph build
# builds native binaryMorph is a compiler, not an interpreter. Your source files never ship — only the compiled binary does.
src/App.mx ──► MorphParser ──► JSXWalker ──► IRBuilder ──► LayoutEngine ──► IRSerializer
│
┌────────────────────────────────┴──────────────┐
▼ ▼
[Dev: IPC Socket] [Build: C++ Codegen]
morph_devrt binary node_emitter → g++ → binary
Python handles the entire toolchain — .mx parsing via tree-sitter, IR building, layout math, and Jinja2-based C++ code generation. C++ handles the runtime — OpenGL rendering, window management, and event handling. The final binary contains zero Python and zero Node.
In dev mode, the pipeline produces an IR dict that is sent over a Unix socket to a pre-compiled renderer (morph_devrt) on every file save. The window never closes — only the node tree swaps. In build mode, the same IR dict drives Jinja2 C++ code generation, producing a standalone binary via g++.
| Component | Status |
|---|---|
.mx file parsing — tree-sitter-based JSX, imports, props |
Complete |
| CSS parsing — local files, remote URLs, MD5-cached | Complete |
| Tailwind CSS — 500 common utility classes + arbitrary values | Complete |
| IRBuilder — walked AST → IR with inline CSS, Tailwind, color/unit conversion | Complete |
CLI — init (interactive wizard), dev, build, pkg, doctor, cache |
Complete |
Config — morph.config.json load/save |
Complete |
IR data models — IRNode, IRWindow, IRPage, IRViewport, IRStyle, IREvent |
Complete |
| IR serializer/deserializer — JSON-safe dict for dev socket | Complete |
| Layout engine — box model (margin, padding), vertical stacking, gap, flexbox | Complete |
| Dev file watcher — watchdog-based with debounce | Complete |
| Unix socket IPC — sends IR to dev runtime | Complete |
| C++ node emitter — IR → C++ instantiation code from Jinja2 templates | Complete |
| Build compiler — g++ invocation with conditional FreeType/GLFW/OpenGL flags | Complete |
| Package registry client — fetch, install, manifest parsing | Complete |
| OpenGL 3.3 batch renderer — instanced VAO/VBO/IBO, uniform color + rounded rect SDF | Complete |
| FreeType text rendering — per-size glyph atlas, batch text with kerning | Complete |
| Rounded rectangles — SDF-based border-radius in fragment shader | Complete |
Font weight support — bold/normal font selection, font-weight CSS property |
Complete |
Style inheritance — color, font-size, font-weight, text-align cascade from parent |
Complete |
Transparent backgrounds — only render when background-color set |
Complete |
Event system — onClick, MouseDown, MouseUp, MouseMove events |
Complete |
| Scrollbar — browser-like drag, wheel, track-click, nested containers | Complete |
| Viewport culling — skip off-screen children in draw + event dispatch | Complete |
Feature-based compilation — #define MORPH_FEATURE_* guards, linker GC |
Complete |
Flexbox — justify-content, align-items, two-pass layout, content-based sizing |
Complete |
text-align — left, center, right with correct container-relative centering |
Complete |
max-width — layout constraint for responsive sizing |
Complete |
Margin/padding side properties — margin-top, padding-left, etc. |
Complete |
Cursor support — cursor: pointer (hand), cursor: text (I-beam) |
Complete |
morph_devrt binary — dev mode C++ renderer, Unix socket IPC, hot reload |
Complete |
Border rendering — border-width, border-color, border-style with SDF shader on all elements (div, button, img); border ring batch on top of everything |
Complete |
Image rendering — stb_image-backed: PNG, JPEG, WebP, GIF, BMP, TGA, PSD, HDR, PNM, PIC; per-texture batching, border-radius clipping |
Complete |
Box-sizing — content-box and border-box CSS property |
Complete |
| Dev mode auto-build — CMake integration, automatic binary rebuild on missing | Complete |
Dirty incremental rendering — layout/paint dirty flag propagation via markDirty(), incremental layoutIfNeeded(), selective recordPaintTree(), cached m_displayList replay via executeDisplayList(); compile-time MORPH_FEATURE_DIRTY_RENDERING gate, auto-enabled for dynamic features (scroll, hover, events, cursor) |
Complete |
| Window config hot reload — title update on save, node tree swap without restart | Complete |
| DevTools panel — F12 toggle, element inspect (F2/click), box-model overlay (margin/border/padding/content), element info panel | Complete |
| Nested border-radius clipping — stencil-based (GL_INCR) so child clips properly intersect ancestor masks | Complete |
Runtime margin: auto — dynamic horizontal centering re-resolved on window resize |
Complete |
CSS :hover pseudo-class — class-based hover rules from CSS files; resolved at runtime with snap-style swap or smooth transition; supports color, background-color, margin, padding, border, border-radius, font-size, gap, justify-content, align-items, width, height |
Complete |
CSS transition — transition-duration / transition-timing-function properties and transition shorthand; per-element config; interpolation of all numeric/color properties with easing (linear, ease-in, ease-out, ease-in-out); string/display properties snap instantly |
Complete |
Body default padding: 8px — replaced UA default margin: 8px so body background fills edge-to-edge without white gaps; no backward-compat baggage (Morph is not a browser); trivially overridable via CSS |
Complete |
Window clear color from body background — glClearColor set to body's background-color each frame; fallback to white when body is transparent; eliminates color mismatch glitch during resize |
Complete |
| Dev source hash — CMake rebuild triggered when shared runtime files (core/, render/, widgets/, style/) change | Complete |
| Wayland/X11 fallback error — clear message when GLFW window creation fails | Complete |
| Component | Status | Notes |
|---|---|---|
| Style resolver — CSS cascade, specificity, selector matching | Stub | Only inline + Tailwind work via IRBuilder |
| JS interpreter — JS event handler → C++ lambdas | Stub | Only console.log works |
Flexbox flex-wrap — row overflow wrapping |
Partial | Content-based sizing works, wrap still in progress |
position: relative/fixed — offset positioning, sticky |
Stub | Not yet implemented |
CSS Properties (resolved from inline styles, CSS rules, and Tailwind classes)
width,height,max-widthmargin,padding+ individual side properties (margin-top,padding-left, etc.)background-color,color(hex, rgb, named)border-radius,border-width,border-color,border-stylebox-sizing—content-box,border-boxdisplay: flex,flex-direction,justify-content,align-items,flex-wrap,gapposition,left,right,top,bottomoverflow,cursorfont-size(px, %, em, bare numbers),font-weight,text-aligncolor,font-size,font-weight,text-aligncascade from parent to children<body>default changed frommargin: 8px→padding: 8px— body background fills window edge-to-edge; no white gaps. Override with any CSS rule.
Pseudo-classes
:hover— class-based rules from CSS files (e.g..btn:hover); style struct snap-copy on mouse enter/leave with correct restoration; all CSS properties above supported (color, background, margin, padding, border, border-radius, font-size, gap, flex alignment, width, height):hoverwith transitions —transition-duration,transition-timing-functionCSS properties;transitionshorthand (all 0.3s ease-in-out); interpolates all numeric/color properties (bgColor, color, margin, padding, border, radius, fontSize, gap, width/height, etc.); strings (display, position, flex-direction) snap instantly; easing: linear, ease-in, ease-out, ease-in-out
HTML Elements
div,span,h1–h6,p,button<img>— supports PNG, JPEG, WebP, GIF, BMP, TGA, PSD, HDR, PNM, PIC via stb_image; intrinsic aspect ratio;width/heightattributes;border-radiusclipping<morph-window>— declares a native window
C++ Runtime
- OpenGL 3.3 core profile batch renderer (instanced VAO/VBO/IBO)
- Rounded rectangles via SDF fragment shader (radius auto-clamped,
border-radius> 100px → 100px) - Border rendering —
border-width,border-color,border-stylevia SDF shader on all elements (div,button,img, etc.); border ring batch (m_borderBatch) flushed last, on top of fills, text, and images - Stencil-based border-radius clipping for images and child overflow (uses
GL_INCRso nested clips properly intersect) box-sizing: content-box/border-boxlayout modes- FreeType text rendering with per-size glyph atlas and word-wrap
- Font weight support (bold / normal with
DejaVuSans-Bold.ttf) - Style inheritance cascade (
color,font-size,font-weight,text-align) - Transparent backgrounds by default
onClick,MouseDown,MouseUp,MouseMoveevent dispatch- Scrollbar with drag, wheel, track-click; nested scroll containers
- Viewport culling for draw + events
- Feature-based dead code elimination
- Image rendering — stb_image-backed texture loading (PNG/JPEG/WebP/GIF/BMP/TGA/PSD/HDR/PNM/PIC); per-texture-ID batched draw calls (
m_imageBatches: unordered_map<GLuint, vector<ImageInstance>>);border-radiusstencil clipping on images - Runtime
margin: auto— sentinel-1.0fin style +marginAuto[4]flags, re-resolved dynamically on window resize cursor: pointerandcursor: textvia GLFW standard cursors:hoverpseudo-class — mouse enter/leave detection indispatchEvent(); style struct snap-copy to/fromhoverStyle;m_baseStylesnapshot for correct restoration; optional smooth transitions viaHoverTransitionsystem- CSS transitions —
m_transitionDuration/m_transitionEasingper-node config;onHover()starts interpolation between current style and target;interpolateStyles()lerps all numeric/color/position properties, snaps strings instantly;updateHoverTransition(dt)runs each frame fromupdate() - Window clear color set from body
background-coloreach frame (falls back to white); eliminates color mismatch glitch on resize - Dirty incremental rendering —
DirtyFlagenum (LayoutDirty, StyleDirty, PaintDirty, ScrollDirty, SubtreeDirty);markDirty()propagates flags up the tree;layoutIfNeeded()skips clean nodes;recordPaintTree()only re-records display lists for paint-dirty nodes;executeDisplayList()replays cachedm_displayListfor all nodes every frame;MORPH_FEATURE_DIRTY_RENDERINGcompile-time gate; auto-enabled when scroll, hover, events, or cursor features are present
DevTools (morph_devrt only)
F12— Toggle DevTools panelF2or click "Inspect Element" — Toggle inspect mode- Two tabs: Elements (inspect + node info) and Rendering (pipeline diagnostics)
- Box-model overlay: margin (orange), border (yellow), padding (green), content (blue)
- Element info panel: tag name, size, position, margin, padding, display, overflow, box-sizing, color (hex swatch), background (hex swatch), font size, font weight, text align
- Rendering tab: frame counter, total nodes, layout count / skipped / percentage, repainted count / cache hit rate, layout/paint savings percentages (color-coded green/red)
- Hot reload preserves DevTools state
my-app/
├── src/
│ ├── App.mx ← entry point (JSX + CSS + JS)
│ └── components/ ← reusable .mx components
├── cpp/ ← optional custom C++ nodes
│ └── my_widget.h
├── assets/ ← fonts, textures, etc.
├── morph.config.json ← project config + dependencies
└── dist/
└── app ← compiled binary (gitignored)
morph.config.json:
{
"name": "my-app",
"entry": "src/App.mx",
"window": {
"width": 1024,
"height": 768,
"title": "My App"
},
"dependencies": {},
"cpp_sources": []
}| Linux | macOS | Windows | |
|---|---|---|---|
| Python | 3.10+ | 3.10+ | 3.10+ |
| Compiler | g++ 11+ | clang++ 13+ | MSVC / MinGW |
| OpenGL | 3.3+ | 3.3+ | 3.3+ |
| GLFW | apt install libglfw3-dev |
brew install glfw |
bundled |
Run morph doctor after installing to verify your environment.
- CSS style resolver — Selector matching, cascade, specificity
- JS interpreter — Full JS expression handling (CallExpression, BinaryExpression)
-
flex-wrap— Row overflow wrapping -
position: relative/fixed— Offset and viewport-relative positioning -
margincollapse — Collapsing vertical margins between siblings
- Multi-window & navigation system
-
<morph-viewport>embedded OpenGL canvas - Custom C++ node integration
- morph-icons (first-party package)
- morph-animate
- Windows support
- VSCode extension (syntax highlighting for
.mxfiles)
Morph is in early development. Contributions, ideas, and feedback are very welcome.
git clone https://github.com/levizr/morph
cd morph
pip install -e ".[dev]"
morph doctorThe most impactful areas right now are the CSS style resolver, morph_devrt binary, and JS interpreter. See the Current State section for a full breakdown.
Open an issue before starting on large features so we can align on design.
APACHE — see LICENSE.
Built with C++ and Python · Rendered with OpenGL · No browser required