crystal-qt6

Crystal bindings for QT6

crystal-qt6

CI

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.10.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, system tray support, 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, deeper shell/panel polish widgets, maintained integration examples, and the lifecycle fixes needed to run that surface more reliably across macOS and Linux. It also now includes a substantial Graphics View stack built around scenes, items, scene-local widgets and layouts, transforms, effects, and scene events, alongside richer shared widget infrastructure such as broader QApplication and style support, broader dialog and layout coverage, and the initial gesture stack built around QGesture, QGestureEvent, and callback-backed QGestureRecognizer.

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-config can resolve Qt6Widgets, Qt6Svg, and Qt6SvgWidgets
  • 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

On Linux and macOS, make spec uses the same Qt platform defaults as CI so popup- and window-oriented specs do not inherit a Wayland session or other desktop-specific backend quirks by accident.

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

When you invoke crystal spec directly, Qt uses your current desktop environment unless you set QT_QPA_PLATFORM yourself.

Example highlights:

  • examples/hello_world.cr: smallest possible window with a label and button
  • examples/counter.cr: simple stateful widget wiring with button callbacks
  • examples/editor_shell.cr: QMainWindow, menus, actions, action groups, shortcuts, toolbars, docks, convenience dialog helpers, and form/grid layout composition
  • examples/editor_vertical_slice.cr: one maintained “real editor slice” with a QMainWindow shell, two docks, a live EventWidget canvas, pan/zoom input, model/view layer management, and PNG export
  • examples/desktop_editor_showcase.cr: broader integration showcase with docks, model/view layer browsing, text/document editing, clipboard QMimeData, device-backed image loading, drag/drop notes, custom preview rendering, and PNG export
  • examples/event_monitor.cr: QTimer plus EventWidget resize, paint, mouse, wheel, and key hooks
  • examples/rendering_stack.cr: offscreen rendering plus file-backed and in-memory SVG import/export, named-element SVG rasterization, and PDF export with QImage, QPixmap, QSvgGenerator, QSvgRenderer, QPdfWriter, QPainter, QPainterPath, and QTransform
  • examples/svg_widget_renderer.cr: embedded QSvgWidget display, in-memory load_data, borrowed QSvgWidget#renderer access, named-element preview rendering, and widget grab export
  • examples/inspector_workbench.cr: inspector-style editor UI with QScrollArea, QTabWidget, QSplitter, QGroupBox, radio buttons, sliders, spin boxes, and a live EventWidget canvas
  • examples/model_view_workbench.cr: custom AbstractListModel, proxy sorting/filtering with regex filters, shared selection models, proxy headers, and delegate-backed editor commit hooks
  • examples/application_services_showcase.cr: application metadata, stylesheets, window icons, QImageReader, clipboard QMimeData, drop receiving, QEventLoop, QProgressDialog, and QSplashScreen
  • examples/dialog_gallery.cr: standard dialog gallery for QMessageBox, QFileDialog, QColorDialog, QFontDialog, QInputDialog, and QProgressDialog

Build the long-form LaTeX guide:

make docs-book

The guide source lives under docs/book/ and includes screenshot-oriented chapters for dialogs, graphics-view scenes, and larger 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.application for creating or reusing a single QApplication, with broader application-wide timing, focus, drag-threshold, and style helpers
  • Qt6::Style and Qt6::CommonStyle for application- and widget-level style selection plus standard-palette access
  • Qt6.window for quick top-level window setup
  • Qt6::QIcon plus file-backed and theme-backed icon loading, application and widget window-icon/style-sheet helpers for desktop polish
  • Qt6::QObject as the common wrapper base for owned Qt objects
  • Qt6::Signal for Crystal-side callback composition
  • Qt6::QTimer and Qt6::QEventLoop for timeout-driven work and nested local event loops
  • Qt6.clipboard and Qt6::Clipboard for process-wide clipboard text, image, and pixmap access
  • Qt6::Color, Qt6::Point, Qt6::PointF, Qt6::LineF, Qt6::Size, and Qt6::RectF for common value types
  • Qt6::QImage, Qt6::QImageReader, Qt6::QImageWriter, Qt6::QPixmap, Qt6::QBitmap, Qt6::QSvgGenerator, Qt6::QSvgRenderer, Qt6::QSvgWidget, Qt6::QPdfWriter, Qt6::QPainter, Qt6::QPainterPath, Qt6::QPainterPathStroker, Qt6::QTransform, Qt6::QPolygon, Qt6::QPolygonF, Qt6::QPen, Qt6::QBrush, Qt6::QPalette, Qt6::QLinearGradient, Qt6::QConicalGradient, Qt6::QRadialGradient, Qt6::QRegion, Qt6::GraphicsItem, Qt6::AbstractGraphicsShapeItem, Qt6::GraphicsEffect, Qt6::GraphicsBlurEffect, Qt6::GraphicsColorizeEffect, Qt6::GraphicsDropShadowEffect, Qt6::GraphicsOpacityEffect, Qt6::QFont, Qt6::QFontMetrics, Qt6::QFontMetricsF, and Qt6::ImageFormat for 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/bitmap scaling and transforms, pixel inversion, richer painter-path inspection including fill-rule control, integer and floating-point polygon geometry, direct integer-polygon path and painter support including convex-polygon drawing, brush-style and texture accessors, linear/conical/radial gradient brushes with stop inspection, spread, coordinate-mode, and radial focal-point controls, theme-aware application and widget palette colors, painter clip-region support, rounded/arc/polyline primitives, mutable and polygon-backed region algebra, bitmap-backed region masks, region-scoped widget invalidation, minimal graphics-item pen/brush/opaque-area support, and widget-attached blur/colorize/drop-shadow/opacity effects
  • Qt6::Widget for generic widgets and top-level windows, including widget-owned action registration for shortcut handling, widget mask regions, and attached graphics effects
  • Qt6::MainWindow, Qt6::Dialog, and Qt6::DockWidget for desktop application shells, including dock child/title-bar widget access and floating/toggle-view helpers
  • Qt6::MessageBox, Qt6::FileDialog, Qt6::ColorDialog, Qt6::FontDialog, Qt6::InputDialog, Qt6::ProgressDialog, Qt6::ErrorMessage, Qt6::Wizard, Qt6::WizardPage, Qt6::DialogButtonBox, Qt6::Frame, Qt6::FocusFrame, Qt6::RubberBand, Qt6::SplashScreen, Qt6::LcdNumber, and Qt6::SizeGrip for standard dialogs and shell polish widgets, including queued error-message display, process-wide ErrorMessage.qt_handler access when you explicitly need Qt's shared singleton, page-based wizard flows with option/style/pixmap controls plus shared field access, page-to-wizard lookup, and custom/help/page lifecycle callbacks, dialog-button lookup, centered/vertical button-box layout control, frame width/style polish, focus-frame target wiring, rubber-band selection overlays, aligned splash-screen status messages, LCD overflow/display polish, and resize-grip sizing
  • Qt6::MdiArea and Qt6::MdiSubWindow for multi-document workspaces, including tabbed-view controls, activation callbacks, background brushes, subwindow keyboard-step/options access, and active-window navigation helpers
  • convenience helpers such as MessageBox.information, MessageBox.question, ColorDialog.get_color, and InputDialog.get_text / get_multi_line_text / get_int / get_double / get_item
  • Qt6::InputDialogInputMode, Qt6::InputDialogOption, and message-box/file-dialog enums for dialog configuration
  • Qt6::MenuBar, Qt6::Menu, Qt6::ToolBar, Qt6::StatusBar, Qt6::Action, and Qt6::ActionGroup for shell composition
  • Qt6::KeySequence, Qt6::Shortcut, and QAction shortcuts for keyboard-driven commands, including standalone widget shortcuts with context, auto-repeat, and activation callbacks
  • Qt6::UndoStack, Qt6::UndoCommand, and Qt6::UndoGroup for application-level undo/redo history, clean-state tracking, macros, generated actions, and shared undo/redo routing across multiple document stacks
  • Qt6::EventWidget for custom widget event hooks
  • Qt6::Gesture, Qt6::GestureEvent, and Qt6::GestureRecognizer for gesture-aware input flows, including gesture-event inspection, acceptance control, widget gesture registration, and callback-backed custom recognizers
  • Qt6::Label for text and pixmap display, including alignment, word wrapping, and scaled contents
  • Qt6::ToolBox for stacked inspector-style pages with insertion, current-page access, page labels, enabled-state control, and current-index callbacks
  • Qt6::AbstractButton, Qt6::PushButton, and Qt6::ToolButton for button-style commands, including shared shortcut/down-state/auto-repeat/auto-exclusive/button-group helpers, press/release and checked-click callbacks, animate/toggle support, menus, default-button/flat styling, default actions, and auto-raise styling
  • Qt6::LineEdit, Qt6::CheckBox, Qt6::RadioButton, Qt6::ComboBox, Qt6::Slider, Qt6::SpinBox, Qt6::DoubleSpinBox, Qt6::GroupBox, and Qt6::ButtonGroup for common form controls, including checkbox tri-state/check-state helpers, radio-button auto-exclusive/click helpers, slider press/release hooks, opt-in click-to-position behavior, grouped-button id/checked-button management, and group-box title alignment/flat styling
  • Qt6::ListWidget, Qt6::ListWidgetItem, Qt6::TreeWidget, and Qt6::TreeWidgetItem for item-based list and tree panels, including item flags, check state, role data, reorder support, and list item change hooks
  • Qt6::ModelIndex, Qt6::AbstractItemModel, Qt6::AbstractListModel, Qt6::AbstractTreeModel, Qt6::StandardItem, Qt6::StandardItemModel, Qt6::SortFilterProxyModel, Qt6::ItemDelegate, Qt6::StyledItemDelegate, Qt6::QItemEditorCreatorBase, Qt6::QItemEditorCreator, Qt6::QItemEditorFactory, Qt6::FileIconProvider, Qt6::FileSystemModel, Qt6::ListView, Qt6::TreeView, and Qt6::ColumnView for a broader model/view layer with roles, mutable callback-backed list/tree models, live filesystem browsing, Finder-style column navigation, file icons plus permission/modified-time metadata, rename helpers and rename callbacks, preview-update callbacks, row insert/remove/move notifications, proxy sorting/filtering with regex and recursive tree filtering, delegate-based display formatting, delegate/editor-factory lifecycle hooks, drag/drop MIME payloads, and shared view-side helpers like scroll_to, select_all, and drag/drop configuration
  • richer text and table polish through QTextDocument#find, QTextCursor null/replace helpers, editor-side plain-text/HTML insertion helpers, QKeySequenceEdit shortcut capture, standalone QShortcut wiring, wizard-page mandatory field registration and shared field lookups, QDataWidgetMapper form binding, completer wrap/visibility controls, header section-size/interaction access, table sort/resize-to-contents helpers, and TableWidget#on_item_double_clicked
  • Qt6::AbstractItemView and Qt6::AbstractScrollArea for 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 configuration
  • Qt6::TabWidget, Qt6::TabBar, Qt6::ScrollArea, Qt6::Splitter, Qt6::StackedWidget, and Qt6::Orientation for editor-style panel and container composition, including page lookup, current-page selection, removal helpers, tab insertion/enabled-state/presentation controls, scroll-area child alignment, and splitter sizing/pane-polish controls
  • Qt6::BoxLayout, Qt6::VBoxLayout, Qt6::HBoxLayout, Qt6::FormLayout, and Qt6::GridLayout for layout composition, including shared box-layout direction, spacing, insertion, and stretch helpers plus richer form-layout row, visibility, and take-row support

Testing Strategy

The specs cover:

  • process shutdown behavior on macOS so teardown stays free of the QThreadStorage exit warning
  • standard dialog configuration for QMessageBox, QFileDialog, QColorDialog, QFontDialog, and QInputDialog
  • 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, clip regions, transforms, SVG generators, SVG renderers, SVG widgets, and PDF writers
  • QObject destruction signals, QTimer timeout delivery, and QEventLoop nested-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.

Some Qt APIs expose process-global helpers rather than ordinary per-instance widgets. In particular, ErrorMessage.qt_handler returns Qt's shared error dialog singleton, so it is best reserved for explicit process-wide reporting rather than routine dialog usage.

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 API
  • src/qt6/native.cr: FFI declarations
  • ext/qt6cr/include/qt6cr.h: C ABI for the shim
  • ext/qt6cr/src/qt6cr.cpp: Qt6 bridge implementation
  • spec/qt6_spec.cr: end-to-end specs
  • examples/: sample applications
  • docs/design.md: architecture notes
  • docs/book/: LaTeX guide source for longer-form, screenshot-oriented documentation
  • docs/crystal-qt6-roadmap.md: phased roadmap for growing crystal-qt6 toward larger application ports

Extending The Bindings

The next logical additions are:

  1. broader abstract-model bridges beyond flat list models, especially for custom tree models
  2. any remaining higher-level drag/drop polish the maintained editor slice exposes, such as richer proxy-model workflows or explicit drag objects if needed
  3. remaining rendering/document helpers driven by real downstream needs
  4. any additional image-reader, clipboard, or print helpers that the maintained editor slice exposes as real needs
  5. a roadmap refresh whenever a major phase closes so the documented plan stays aligned with the shipped surface
Repository

crystal-qt6

Owner
Statistic
  • 14
  • 2
  • 0
  • 1
  • 0
  • 36 minutes ago
  • April 8, 2026
License

MIT License

Links
Synced at

Tue, 19 May 2026 18:06:07 GMT

Languages