crystal-qt6
crystal-qt6
crystal-qt6 is a Qt6 bindings project for Crystal with an explicit, incrementally growing API for desktop applications.
This repository currently provides:
- a native Qt6 bridge compiled from C++
- an idiomatic Crystal wrapper for common widget tasks
- reusable object, timer, signal, and event-hook primitives for richer bindings
- runnable examples
- a spec suite that exercises the wrapper end to end
Status
This is still a focused subset of Qt6 rather than a full binding, but it is no longer just an initial foundation. The 0.7.0 surface covers custom widgets, a reduced desktop application shell, application metadata and stylesheet polish, undo/redo stacks and shared undo groups, widget-owned actions and shortcuts, standard dialog galleries, font dialogs, deeper raster/SVG/PDF rendering and export, configured image writing, richer QPainterPath editing, raw QImage byte-buffer workflows, image and pixmap transforms, richer EventWidget input hooks, clipboard access, QMimeData, richer text/document editing, broader model/view and table support, validators and completers, application-service utilities, common desktop controls, maintained integration examples, and the lifecycle fixes needed to run that surface more reliably across macOS and Linux.
Requirements
- Crystal 1.11+
- Qt6 Widgets, Qt6 Svg, and Qt6 Svg Widgets development packages available through
pkg-config - macOS with Homebrew Qt6 works out of the box when
pkg-configcan resolveQt6Widgets,Qt6Svg, andQt6SvgWidgets - Linux distributions using GCC's standard C++ runtime, such as Fedora with
qt6-qtbase-devel, are supported
The Crystal FFI layer links against the platform's default C++ runtime:
- macOS:
libc++ - Linux:
libstdc++
Installation
Add this shard to your application's shard.yml:
dependencies:
qt6:
github: djberg96/crystal-qt6
Then require the library from Crystal code:
require "qt6"
Build
The native Qt shim builds automatically the first time you compile, run, or spec the shard. You can still build it explicitly:
make native
If you need to disable the automatic build, set QT6CR_SKIP_BUILD=1 before invoking Crystal and build the shim yourself with make native.
Run the full test suite:
make spec
Run the GUI-oriented spec suite with the same platform defaults CI uses:
make gui-spec
Run the GUI specs with Crystal's preview multithreaded runtime and two workers:
make gui-spec-mt
Run the examples:
make example-hello
make example-counter
make example-shell
make example-slice
make example-showcase
make example-events
make example-render
make example-svg
make example-inspector
make example-modelview
make example-services
make example-dialogs
Direct Crystal commands also work without a separate native build step:
crystal run examples/hello_world.cr
crystal spec
Example highlights:
examples/hello_world.cr: smallest possible window with a label and buttonexamples/counter.cr: simple stateful widget wiring with button callbacksexamples/editor_shell.cr:QMainWindow, menus, actions, action groups, shortcuts, toolbars, docks, convenience dialog helpers, and form/grid layout compositionexamples/editor_vertical_slice.cr: one maintained “real editor slice” with aQMainWindowshell, two docks, a liveEventWidgetcanvas, pan/zoom input, model/view layer management, and PNG exportexamples/desktop_editor_showcase.cr: broader integration showcase with docks, model/view layer browsing, text/document editing, clipboardQMimeData, device-backed image loading, drag/drop notes, custom preview rendering, and PNG exportexamples/event_monitor.cr:QTimerplusEventWidgetresize, paint, mouse, wheel, and key hooksexamples/rendering_stack.cr: offscreen rendering plus file-backed and in-memory SVG import/export, named-element SVG rasterization, and PDF export withQImage,QPixmap,QSvgGenerator,QSvgRenderer,QPdfWriter,QPainter,QPainterPath, andQTransformexamples/svg_widget_renderer.cr: embeddedQSvgWidgetdisplay, in-memoryload_data, borrowedQSvgWidget#rendereraccess, named-element preview rendering, and widget grab exportexamples/inspector_workbench.cr: inspector-style editor UI withQScrollArea,QTabWidget,QSplitter,QGroupBox, radio buttons, sliders, spin boxes, and a liveEventWidgetcanvasexamples/model_view_workbench.cr: customAbstractListModel, proxy sorting/filtering with regex filters, shared selection models, proxy headers, and delegate-backed editor commit hooksexamples/application_services_showcase.cr: application metadata, stylesheets, window icons,QImageReader, clipboardQMimeData, drop receiving,QEventLoop,QProgressDialog, andQSplashScreenexamples/dialog_gallery.cr: standard dialog gallery forQMessageBox,QFileDialog,QColorDialog,QFontDialog,QInputDialog, andQProgressDialog
Build the long-form LaTeX guide:
make docs-book
The guide source lives under docs/book/ and includes screenshot placeholders for publishable dialog and example documentation.
Continuous Integration
GitHub Actions runs the native build, GUI spec suite, multithreaded GUI spec suite, and example compilation on both macOS and Linux via .github/workflows/ci.yml.
The shared make gui-spec target runs scripts/run_gui_specs.sh, which uses xvfb with Qt's xcb platform plugin on headless Linux and QT_QPA_PLATFORM=offscreen on macOS. That keeps local GUI-spec runs and CI on the same platform-selection path. The make gui-spec-mt target runs the same suite with CRYSTAL_WORKERS=2 and -Dpreview_mt so queued GUI callbacks are checked under Crystal's preview multithreaded runtime. The runner also quiets known Qt platform/font chatter while preserving other output and the spec exit status.
API Overview
require "qt6"
app = Qt6.application
count = 0
window = Qt6.window("Counter", 320, 140) do |widget|
widget.vbox do |column|
value = Qt6::Label.new(count.to_s)
button = Qt6::PushButton.new("Increment")
button.on_clicked do
count += 1
value.text = count.to_s
end
column << value
column << button
end
end
window.show
app.run
Current Binding Surface
Qt6.applicationfor creating or reusing a singleQApplicationQt6.windowfor quick top-level window setupQt6::QIconplus file-backed and theme-backed icon loading, application and widget window-icon/style-sheet helpers for desktop polishQt6::QObjectas the common wrapper base for owned Qt objectsQt6::Signalfor Crystal-side callback compositionQt6::QTimerandQt6::QEventLoopfor timeout-driven work and nested local event loopsQt6.clipboardandQt6::Clipboardfor process-wide clipboard text, image, and pixmap accessQt6::Color,Qt6::PointF,Qt6::Size, andQt6::RectFfor common value typesQt6::QImage,Qt6::QImageReader,Qt6::QImageWriter,Qt6::QPixmap,Qt6::QSvgGenerator,Qt6::QSvgRenderer,Qt6::QSvgWidget,Qt6::QPdfWriter,Qt6::QPainter,Qt6::QPainterPath,Qt6::QTransform,Qt6::QPen,Qt6::QBrush,Qt6::QFont,Qt6::QFontMetrics,Qt6::QFontMetricsF, andQt6::ImageFormatfor raster, SVG, and PDF rendering, including file-backed raster loading, probed image-reader metadata, configured image writing, file-backed and in-memory SVG loading,QSvgWidget#renderer, image/pixmap scaling and transforms, pixel inversion, and richer painter-path inspectionQt6::Widgetfor generic widgets and top-level windows, including widget-owned action registration for shortcut handlingQt6::MainWindow,Qt6::Dialog, andQt6::DockWidgetfor desktop application shellsQt6::MessageBox,Qt6::FileDialog,Qt6::ColorDialog,Qt6::FontDialog,Qt6::InputDialog,Qt6::ProgressDialog, andQt6::SplashScreenfor standard dialogs and shell polish widgets- convenience helpers such as
MessageBox.information,MessageBox.question,ColorDialog.get_color, andInputDialog.get_text/get_int/get_double/get_item Qt6::InputDialogInputModeplus message-box and file-dialog enums for dialog configurationQt6::MenuBar,Qt6::Menu,Qt6::ToolBar,Qt6::StatusBar,Qt6::Action, andQt6::ActionGroupfor shell compositionQt6::KeySequenceandQActionshortcuts for keyboard-driven commandsQt6::UndoStack,Qt6::UndoCommand, andQt6::UndoGroupfor application-level undo/redo history, clean-state tracking, macros, generated actions, and shared undo/redo routing across multiple document stacksQt6::EventWidgetfor custom widget event hooksQt6::Labelfor text and pixmap display, including alignment, word wrapping, and scaled contentsQt6::PushButtonfor push buttons and click callbacksQt6::LineEdit,Qt6::CheckBox,Qt6::RadioButton,Qt6::ComboBox,Qt6::Slider,Qt6::SpinBox,Qt6::DoubleSpinBox, andQt6::GroupBoxfor common form controls, including slider press/release hooks and opt-in click-to-position behaviorQt6::ListWidget,Qt6::ListWidgetItem,Qt6::TreeWidget, andQt6::TreeWidgetItemfor item-based list and tree panels, including item flags, check state, role data, reorder support, and list item change hooksQt6::ModelIndex,Qt6::AbstractItemModel,Qt6::AbstractListModel,Qt6::AbstractTreeModel,Qt6::StandardItem,Qt6::StandardItemModel,Qt6::SortFilterProxyModel,Qt6::StyledItemDelegate,Qt6::ListView, andQt6::TreeViewfor a broader model/view layer with roles, mutable callback-backed list/tree models, row insert/remove/move notifications, proxy sorting/filtering with regex and recursive tree filtering, delegate-based display formatting, drag/drop MIME payloads, and view-side drag/drop configuration- richer text and table polish through
QTextDocument#find,QTextCursornull/replace helpers, editor-side plain-text/HTML insertion helpers, header section-size access, table sort/resize-to-contents helpers, andTableWidget#on_item_double_clicked Qt6::AbstractItemViewandQt6::AbstractScrollAreafor shared item-view and scroll-surface infrastructure across list/tree/table widgets, model views, text editors, and scroll areas, including viewport access, index hit-testing, visual-rectangle lookup, and overwrite-mode drop configurationQt6::TabWidget,Qt6::ScrollArea,Qt6::Splitter, andQt6::Orientationfor editor-style panel and container compositionQt6::VBoxLayout,Qt6::HBoxLayout,Qt6::FormLayout, andQt6::GridLayoutfor layout composition, including box-layout stretch helpers
Testing Strategy
The specs cover:
- process shutdown behavior on macOS so teardown stays free of the
QThreadStorageexit warning - standard dialog configuration for
QMessageBox,QFileDialog,QColorDialog,QFontDialog, andQInputDialog - convenience helper flows for message, color, font, and input dialogs
- layout composition through vertical, horizontal, form, and grid layouts
- raster, SVG, and PDF rendering with images, pixmaps, painter paths, transforms, SVG generators, SVG renderers, SVG widgets, and PDF writers
QObjectdestruction signals,QTimertimeout delivery, andQEventLoopnested-loop behavior- geometry accessors and custom widget paint, resize, mouse, wheel, and key event hooks
- reduced application-shell wiring for actions, menus, toolbars, dialogs, docks, status bars, and common controls
- undo/redo stacks, command callbacks, clean-state tracking, macros, and generated undo/redo actions
- widget lifecycle and visibility state
- title and text round trips through the native bridge
- layout composition
- button click callbacks routed from Qt into Crystal blocks
That gives the project an executable contract for both the C++ shim and the Crystal wrapper.
Lifecycle Notes
Qt objects must be created and destroyed on the GUI thread. The bindings therefore avoid GC finalizers for Qt teardown and instead perform deterministic cleanup during process exit through Qt6.shutdown.
Applications may still use Crystal fibers or worker threads for ordinary computation and blocking I/O, including when compiled with -Dpreview_mt and run with CRYSTAL_WORKERS greater than 1. Keep that work away from Qt objects, then use Application#invoke_later to apply the result back on the Qt event loop.
You can call Qt6.shutdown yourself if you want an explicit shutdown point, but ordinary applications can rely on the built-in exit hook.
Contributors
- Anton Maminov (@mamantoha) for contributions, review, and API suggestions that helped expand and refine the bindings.
Project Layout
src/qt6.cr: public Crystal APIsrc/qt6/native.cr: FFI declarationsext/qt6cr/include/qt6cr.h: C ABI for the shimext/qt6cr/src/qt6cr.cpp: Qt6 bridge implementationspec/qt6_spec.cr: end-to-end specsexamples/: sample applicationsdocs/design.md: architecture notesdocs/book/: LaTeX guide source for longer-form, screenshot-oriented documentationdocs/crystal-qt6-roadmap.md: phased roadmap for growingcrystal-qt6toward larger application ports
Extending The Bindings
The next logical additions are:
- broader abstract-model bridges beyond flat list models, especially for custom tree models
- any remaining higher-level drag/drop polish the maintained editor slice exposes, such as richer proxy-model workflows or explicit drag objects if needed
- remaining rendering/document helpers driven by real downstream needs
- any additional image-reader, clipboard, or print helpers that the maintained editor slice exposes as real needs
- a roadmap refresh whenever a major phase closes so the documented plan stays aligned with the shipped surface
crystal-qt6
- 0
- 0
- 0
- 0
- 0
- about 12 hours ago
- April 28, 2026
MIT License
Tue, 28 Apr 2026 00:22:10 GMT