Build native OpenGL Applications with HTML, CSS, and JavaScript
Project description
███╗ ███╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗
████╗ █████║██╔═══██╗██╔══██╗██╔══██╗██║ ██║
██╔████╔██║██║ ██║██████╔╝██████╔╝███████║
██║╚██╔╝██║██║ ██║██╔══██╗██╔═══╝ ██╔══██║
██║ ╚═╝ ██║╚██████╔╝██║ ██║██║ ██║ ██║
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
Build native OpenGL Applications with HTML, CSS, and JavaScript.
No browser. No Electron. No WebView. Just a lightweight native binary.
What is Morph?
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
Why Morph?
| 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 | ✗ | ✓ | ✓ |
Quick Start
1. Install
pip install levizr-morph
morph doctor # verify system dependencies
2. Create a project
morph init my-app
cd my-app
3. Start dev mode
morph dev
A native window opens. Edit src/App.mx — the window updates instantly without restarting.
4. Ship
morph build
# builds native binary
How It Works
Morph 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++.
Current State (v0.0.6 — Early Development)
✅ Working
| 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 |
🚧 In Progress
| 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 |
Features
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
Project Structure
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": []
}
System Requirements
| 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.
Roadmap
v0.1.0 (Next Up)
- 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
Future
- 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)
Contributing
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 doctor
The 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.
License
APACHE — see LICENSE.
Built with C++ and Python · Rendered with OpenGL · No browser required
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file levizr_morph-0.0.6.tar.gz.
File metadata
- Download URL: levizr_morph-0.0.6.tar.gz
- Upload date:
- Size: 224.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e3a55e5bd233b1faaf615647f463e8edb508dc28d5e7f8bb1596be1ec09d752c
|
|
| MD5 |
d1a023afabedaf5bf9fd4603f796c3a5
|
|
| BLAKE2b-256 |
b91738384a1e84dcfa5f4156e55131872e734b5265f94f9990aaa97f678e185a
|
Provenance
The following attestation bundles were made for levizr_morph-0.0.6.tar.gz:
Publisher:
python-publish.yml on Levizr/morph
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
levizr_morph-0.0.6.tar.gz -
Subject digest:
e3a55e5bd233b1faaf615647f463e8edb508dc28d5e7f8bb1596be1ec09d752c - Sigstore transparency entry: 1667787580
- Sigstore integration time:
-
Permalink:
Levizr/morph@eaa74b16a0413f87197c59b8b17a1b3864b69338 -
Branch / Tag:
refs/tags/0.0.6 - Owner: https://github.com/Levizr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@eaa74b16a0413f87197c59b8b17a1b3864b69338 -
Trigger Event:
release
-
Statement type:
File details
Details for the file levizr_morph-0.0.6-py3-none-any.whl.
File metadata
- Download URL: levizr_morph-0.0.6-py3-none-any.whl
- Upload date:
- Size: 256.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d210957b56f0d81442e0a161da685e7571d78f80d56a1667e16d458bc3e1141f
|
|
| MD5 |
3aa19f72d5283af7c5c4eda9c0913d55
|
|
| BLAKE2b-256 |
34984aae140bbfcc3c97131f5de403b59b32b7a784e939d95f259a4ae4e28cd6
|
Provenance
The following attestation bundles were made for levizr_morph-0.0.6-py3-none-any.whl:
Publisher:
python-publish.yml on Levizr/morph
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
levizr_morph-0.0.6-py3-none-any.whl -
Subject digest:
d210957b56f0d81442e0a161da685e7571d78f80d56a1667e16d458bc3e1141f - Sigstore transparency entry: 1667787649
- Sigstore integration time:
-
Permalink:
Levizr/morph@eaa74b16a0413f87197c59b8b17a1b3864b69338 -
Branch / Tag:
refs/tags/0.0.6 - Owner: https://github.com/Levizr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@eaa74b16a0413f87197c59b8b17a1b3864b69338 -
Trigger Event:
release
-
Statement type: