Repository: slint-ui/slint Branch: master Commit: 7b2b2eb3de2c Files: 3030 Total size: 18.0 MB Directory structure: gitextract_o08rqpvx/ ├── .cargo/ │ └── config.toml ├── .clang-format ├── .clippy.toml ├── .dockerignore ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── 1-bug-report.yaml │ │ ├── 2-feature-request.yaml │ │ ├── 3-tracking-issue.md │ │ ├── 4-blank.md │ │ └── config.yml │ ├── actions/ │ │ ├── codesign/ │ │ │ └── action.yaml │ │ ├── install-linux-dependencies/ │ │ │ └── action.yaml │ │ ├── install-skia-dependencies/ │ │ │ └── action.yaml │ │ └── setup-rust/ │ │ └── action.yaml │ ├── ci_path_filters.yaml │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── autofix.yaml │ ├── bevy_examples.yaml │ ├── build_and_test_reusable.yaml │ ├── build_docs.yaml │ ├── ci.yaml │ ├── cpp_package.yaml │ ├── crater.yaml │ ├── embedded_build.yaml │ ├── issue_triage.yaml │ ├── material.yaml │ ├── material_gallery.yaml │ ├── material_wasm_gallery.yaml │ ├── nightly_snapshot.yaml │ ├── nightly_tests.yaml │ ├── node_test_reusable.yaml │ ├── publish_npm_package.yaml │ ├── python_test_reusable.yaml │ ├── schedule_nightly_snapshot.yaml │ ├── servo_example.yaml │ ├── slint_tool_binary.yaml │ ├── spellcheck.yaml │ ├── torizon_demos.yaml │ ├── translations.yaml │ ├── tree_sitter.yaml │ ├── upgrade_version.yaml │ ├── upload_esp_idf_component.yaml │ ├── upload_pypi.yaml │ ├── upload_pypi_briefcase.yaml │ ├── upload_pypi_slint_compiler.yaml │ ├── wasm_demos.yaml │ └── wasm_editor_and_interpreter.yaml ├── .gitignore ├── .mailmap ├── .mise/ │ ├── config.toml │ ├── tasks/ │ │ ├── fix/ │ │ │ └── text/ │ │ │ └── trailing_spaces │ │ └── lint/ │ │ └── legal/ │ │ └── reuse │ └── tasks.toml ├── .npmrc ├── .taplo.toml ├── .vscode/ │ ├── .gitignore │ └── extensions.json ├── AGENTS.md ├── CHANGELOG.md ├── CMakeLists.txt ├── CMakePresets.json ├── CONTRIBUTING.md ├── Cargo.toml ├── Cross.toml ├── FAQ.md ├── LICENSE.md ├── LICENSES/ │ ├── Apache-2.0.txt │ ├── CC-BY-2.0.txt │ ├── CC-BY-4.0.txt │ ├── CC-BY-ND-4.0.txt │ ├── CC-BY-SA-3.0.txt │ ├── CC-BY-SA-4.0.txt │ ├── CC-PDDC.txt │ ├── GPL-3.0-only.txt │ ├── LicenseRef-DejaVu-Font.txt │ ├── LicenseRef-Slint-Royalty-free-2.0.md │ ├── LicenseRef-Slint-Software-3.0.md │ ├── LicenseRef-qskinny.txt │ ├── MIT.txt │ ├── OFL-1.1-RFN.txt │ ├── OFL-1.1.txt │ └── Unlicense.txt ├── README.md ├── REUSE.toml ├── SECURITY.md ├── about.toml ├── api/ │ ├── cpp/ │ │ ├── CMakeLists.txt │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── cbindgen.rs │ │ ├── cmake/ │ │ │ ├── SlintConfig.cmake.in │ │ │ └── SlintMacro.cmake │ │ ├── docs/ │ │ │ ├── _static/ │ │ │ │ └── theme_tweak.css │ │ │ ├── _templates/ │ │ │ │ └── base.html │ │ │ ├── cmake.md │ │ │ ├── cmake_reference.md │ │ │ ├── conf.py │ │ │ ├── generated_code.md │ │ │ ├── genindex.rst │ │ │ ├── getting_started.md │ │ │ ├── index.rst │ │ │ ├── live_preview.md │ │ │ ├── mcu/ │ │ │ │ ├── esp-idf/ │ │ │ │ │ └── troubleshoot.md │ │ │ │ ├── esp_idf.md │ │ │ │ ├── generic.md │ │ │ │ ├── intro.md │ │ │ │ ├── stm32/ │ │ │ │ │ └── generic.md │ │ │ │ └── stm32.md │ │ │ ├── overview.md │ │ │ ├── pyproject.toml │ │ │ ├── thirdparty.hbs │ │ │ └── types.md │ │ ├── esp-idf/ │ │ │ └── slint/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── cmake/ │ │ │ │ └── FindSlint.cmake │ │ │ ├── esp-println.x │ │ │ ├── idf_component.yml │ │ │ ├── include/ │ │ │ │ └── slint-esp.h │ │ │ └── src/ │ │ │ └── slint-esp.cpp │ │ ├── include/ │ │ │ ├── slint-interpreter.h │ │ │ ├── slint-platform.h │ │ │ ├── slint-stm32.h │ │ │ ├── slint-testing.h │ │ │ ├── slint.h │ │ │ ├── slint_brush.h │ │ │ ├── slint_callbacks.h │ │ │ ├── slint_color.h │ │ │ ├── slint_config.h │ │ │ ├── slint_image.h │ │ │ ├── slint_interpreter.h │ │ │ ├── slint_item_tree.h │ │ │ ├── slint_live_preview.h │ │ │ ├── slint_models.h │ │ │ ├── slint_pathdata.h │ │ │ ├── slint_point.h │ │ │ ├── slint_properties.h │ │ │ ├── slint_sharedvector.h │ │ │ ├── slint_size.h │ │ │ ├── slint_string.h │ │ │ ├── slint_tests_helpers.h │ │ │ ├── slint_timer.h │ │ │ ├── slint_window.h │ │ │ └── vtable.h │ │ ├── lib.rs │ │ ├── platform.rs │ │ └── tests/ │ │ ├── CMakeLists.txt │ │ ├── datastructures.cpp │ │ ├── eventloop.cpp │ │ ├── interpreter.cpp │ │ ├── libraries/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── app-window.slint │ │ │ └── main.cpp │ │ ├── models.cpp │ │ ├── multiple-includes/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── another-window.slint │ │ │ ├── app-window.slint │ │ │ ├── logic.cpp │ │ │ ├── logic.h │ │ │ └── main.cpp │ │ ├── platform_eventloop.cpp │ │ ├── properties.cpp │ │ ├── test.slint │ │ ├── testing.cpp │ │ ├── translator.cpp │ │ └── window.cpp │ ├── node/ │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── .yarnrc.yml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── __test__/ │ │ │ ├── api.spec.mts │ │ │ ├── compiler.spec.mts │ │ │ ├── eventloop.spec.mts │ │ │ ├── globals.spec.mts │ │ │ ├── helpers/ │ │ │ │ └── utils.ts │ │ │ ├── js_value_conversion.spec.mts │ │ │ ├── models.spec.mts │ │ │ ├── resources/ │ │ │ │ ├── error.slint │ │ │ │ ├── test-constructor.slint │ │ │ │ ├── test-enum.slint │ │ │ │ ├── test-fileloader.slint │ │ │ │ ├── test-struct.slint │ │ │ │ └── test.slint │ │ │ ├── tsconfig.json │ │ │ ├── types.spec.mts │ │ │ └── window.spec.mts │ │ ├── binaries.json │ │ ├── biome.json │ │ ├── build-on-demand.mjs │ │ ├── build.rs │ │ ├── cover.md │ │ ├── package.json │ │ ├── rust/ │ │ │ ├── interpreter/ │ │ │ │ ├── component_compiler.rs │ │ │ │ ├── component_definition.rs │ │ │ │ ├── component_instance.rs │ │ │ │ ├── diagnostic.rs │ │ │ │ ├── value.rs │ │ │ │ └── window.rs │ │ │ ├── interpreter.rs │ │ │ ├── lib.rs │ │ │ ├── types/ │ │ │ │ ├── brush.rs │ │ │ │ ├── image_data.rs │ │ │ │ ├── model.rs │ │ │ │ ├── point.rs │ │ │ │ └── size.rs │ │ │ └── types.rs │ │ ├── thirdparty.hbs │ │ ├── tsconfig.json │ │ ├── typescript/ │ │ │ ├── index.ts │ │ │ └── models.ts │ │ └── vitest.config.ts │ ├── python/ │ │ ├── briefcase/ │ │ │ ├── README.md │ │ │ ├── pyproject.toml │ │ │ └── src/ │ │ │ └── briefcasex_slint/ │ │ │ └── __init__.py │ │ └── slint/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── api_match.rs │ │ ├── async_adapter.rs │ │ ├── brush.rs │ │ ├── build.rs │ │ ├── build_docs.py │ │ ├── errors.rs │ │ ├── image.rs │ │ ├── interpreter.rs │ │ ├── language.rs │ │ ├── lib.rs │ │ ├── models.rs │ │ ├── pyproject.toml │ │ ├── slint/ │ │ │ ├── __init__.py │ │ │ ├── language.py │ │ │ ├── loop.py │ │ │ ├── models.py │ │ │ ├── py.typed │ │ │ └── slint.pyi │ │ ├── stub-gen/ │ │ │ └── main.rs │ │ ├── tests/ │ │ │ ├── api-match.slint │ │ │ ├── test-file-error.slint │ │ │ ├── test-load-file.slint │ │ │ ├── test_api_match.py │ │ │ ├── test_async.py │ │ │ ├── test_brush.py │ │ │ ├── test_callback_decorators.py │ │ │ ├── test_compiler.py │ │ │ ├── test_enums.py │ │ │ ├── test_gc.py │ │ │ ├── test_image.py │ │ │ ├── test_instance.py │ │ │ ├── test_invoke_from_event_loop.py │ │ │ ├── test_load_file.py │ │ │ ├── test_loader.py │ │ │ ├── test_loop.py │ │ │ ├── test_models.py │ │ │ ├── test_named_tuples.py │ │ │ ├── test_timers.py │ │ │ └── test_translations.py │ │ ├── thirdparty.hbs │ │ ├── timer.rs │ │ └── value.rs │ ├── rs/ │ │ ├── build/ │ │ │ ├── Cargo.toml │ │ │ └── lib.rs │ │ ├── macros/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── lib.rs │ │ └── slint/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── android.rs │ │ ├── compile_fail_tests.rs │ │ ├── docs.rs │ │ ├── lib.rs │ │ ├── mcu.md │ │ ├── private_unstable_api.rs │ │ ├── tests/ │ │ │ ├── invalid_rust_attr.slint │ │ │ ├── partial_renderer.rs │ │ │ ├── popups.rs │ │ │ ├── show_strongref.rs │ │ │ ├── simple_macro.rs │ │ │ ├── spawn_local.rs │ │ │ ├── text_layout_cache.rs │ │ │ ├── tokio.rs │ │ │ └── tokio_block_in_place.rs │ │ └── type-mappings.md │ └── wasm-interpreter/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── biome.json ├── cmake/ │ ├── FindOpenGLES2.cmake │ └── FindOpenGLES3.cmake ├── cspell.json ├── demos/ │ ├── CMakeLists.txt │ ├── README.md │ ├── energy-monitor/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── index.html │ │ ├── ios-project.yml │ │ ├── src/ │ │ │ ├── controllers/ │ │ │ │ ├── header.rs │ │ │ │ └── weather.rs │ │ │ ├── lib.rs │ │ │ └── main.rs │ │ └── ui/ │ │ ├── big_main.slint │ │ ├── blocks/ │ │ │ ├── blocks.slint │ │ │ ├── header.slint │ │ │ ├── kiosk_overlay.slint │ │ │ └── mobile_header.slint │ │ ├── components/ │ │ │ ├── menu_background.slint │ │ │ └── state_layer.slint │ │ ├── desktop_window.slint │ │ ├── images.slint │ │ ├── mcu_window.slint │ │ ├── mid_main.slint │ │ ├── mobile_main.slint │ │ ├── pages/ │ │ │ ├── about.slint │ │ │ ├── balance.slint │ │ │ ├── dashboard.slint │ │ │ ├── dashboard_mobile.slint │ │ │ ├── menu_page/ │ │ │ │ ├── menu_overview.slint │ │ │ │ ├── menu_page.slint │ │ │ │ └── settings.slint │ │ │ ├── overview.slint │ │ │ ├── page.slint │ │ │ ├── pages.slint │ │ │ ├── usage.slint │ │ │ └── weather.slint │ │ ├── small_main.slint │ │ ├── theme.slint │ │ └── widgets/ │ │ ├── balance_chart.slint │ │ ├── bar_chart.slint │ │ ├── bar_tiles.slint │ │ ├── chart_axis.slint │ │ ├── chart_pattern.slint │ │ ├── check_box.slint │ │ ├── float_button.slint │ │ ├── group_box.slint │ │ ├── icon_button.slint │ │ ├── item.slint │ │ ├── list_view.slint │ │ ├── menu.slint │ │ ├── menu_button.slint │ │ ├── navigation.slint │ │ ├── page_scroll_view.slint │ │ ├── pagination.slint │ │ ├── radio_button.slint │ │ ├── scroll_view.slint │ │ ├── switch.slint │ │ ├── tab_widget.slint │ │ ├── tile.slint │ │ ├── value_display.slint │ │ └── widgets.slint │ ├── home-automation/ │ │ ├── README.md │ │ ├── node/ │ │ │ ├── biome.json │ │ │ ├── main.js │ │ │ └── package.json │ │ ├── rust/ │ │ │ ├── Cargo.toml │ │ │ ├── index.html │ │ │ ├── ios-project.yml │ │ │ ├── lib.rs │ │ │ └── main.rs │ │ ├── ui/ │ │ │ ├── api.slint │ │ │ ├── appState.slint │ │ │ ├── common.slint │ │ │ ├── components/ │ │ │ │ ├── alarm.slint │ │ │ │ ├── appliance.slint │ │ │ │ ├── camera.slint │ │ │ │ ├── control.slint │ │ │ │ ├── dial/ │ │ │ │ │ ├── dial.slint │ │ │ │ │ └── dialLights.slint │ │ │ │ ├── dishwasher.slint │ │ │ │ ├── general/ │ │ │ │ │ ├── doors.slint │ │ │ │ │ ├── fancySlider.slint │ │ │ │ │ ├── haText.slint │ │ │ │ │ └── innerShadowRectangle.slint │ │ │ │ ├── graph.slint │ │ │ │ ├── hvac.slint │ │ │ │ ├── info.slint │ │ │ │ ├── lamp.slint │ │ │ │ ├── mainView/ │ │ │ │ │ ├── fullScreenView.slint │ │ │ │ │ ├── fullScreenWidgetLoader.slint │ │ │ │ │ ├── fullScreenWidgetLoaderSW.slint │ │ │ │ │ ├── mainScreen.slint │ │ │ │ │ ├── sidebar.slint │ │ │ │ │ ├── viewButton.slint │ │ │ │ │ ├── widgetLoader.slint │ │ │ │ │ └── widgetLoaderSoftwareRenderer.slint │ │ │ │ ├── microwave.slint │ │ │ │ ├── musicPlayer.slint │ │ │ │ ├── overhead.slint │ │ │ │ ├── powerInfo.slint │ │ │ │ ├── timeInfo.slint │ │ │ │ └── weatherInfo.slint │ │ │ ├── demo-debug.slint │ │ │ ├── demo-sw-renderer.slint │ │ │ ├── demo.slint │ │ │ └── fonts/ │ │ │ ├── Poppins-Bold.ttf.license │ │ │ ├── Poppins-Light.ttf.license │ │ │ ├── Poppins-Medium.ttf.license │ │ │ ├── Poppins-Regular.ttf.license │ │ │ └── Poppins-Thin.ttf.license │ │ └── zephyr/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── VERSION │ │ └── main.cpp │ ├── printerdemo/ │ │ ├── README.md │ │ ├── cpp/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ └── main.cpp │ │ ├── cpp_interpreted/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ └── main.cpp │ │ ├── lang/ │ │ │ └── fr/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── printerdemo.mo │ │ │ └── printerdemo.po │ │ ├── node/ │ │ │ ├── README │ │ │ ├── main.js │ │ │ └── package.json │ │ ├── printerdemo.pot │ │ ├── python/ │ │ │ ├── README.md │ │ │ ├── main.py │ │ │ └── pyproject.toml │ │ ├── rust/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── index.html │ │ │ ├── lib.rs │ │ │ └── main.rs │ │ ├── ui/ │ │ │ ├── common.slint │ │ │ ├── components/ │ │ │ │ ├── button.slint │ │ │ │ ├── drop_down_menu.slint │ │ │ │ ├── headers.slint │ │ │ │ ├── icon_button.slint │ │ │ │ ├── icon_text_button.slint │ │ │ │ ├── popup_menu.slint │ │ │ │ ├── sidebar.slint │ │ │ │ ├── spinbox.slint │ │ │ │ └── text_button.slint │ │ │ ├── fonts/ │ │ │ │ ├── Inter-24pt-Medium.ttf.license │ │ │ │ ├── Inter-24pt-Regular.ttf.license │ │ │ │ └── convert.sh │ │ │ ├── images/ │ │ │ │ ├── cat.jpg.license │ │ │ │ ├── dog.jpg.license │ │ │ │ ├── elephant.jpg.license │ │ │ │ └── snake.jpg.license │ │ │ ├── pages/ │ │ │ │ ├── copy_page.slint │ │ │ │ ├── home_page.slint │ │ │ │ ├── ink_page.slint │ │ │ │ ├── page.slint │ │ │ │ ├── pages.slint │ │ │ │ ├── print_page.slint │ │ │ │ ├── printer_queue.slint │ │ │ │ ├── scan_page.slint │ │ │ │ ├── settings_page.slint │ │ │ │ └── usb_page.slint │ │ │ └── printerdemo.slint │ │ └── zephyr/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── README_NXP.md │ │ ├── VERSION │ │ └── main.cpp │ ├── printerdemo_mcu/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── bench.rs │ │ ├── build.rs │ │ ├── esp-idf/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── idf_component.yml │ │ │ │ └── main.cpp │ │ │ ├── partitions.csv │ │ │ ├── rust-toolchain.toml │ │ │ └── sdkconfig.defaults │ │ ├── main.rs │ │ └── ui/ │ │ ├── common.slint │ │ ├── components/ │ │ │ ├── CheckBox.slint │ │ │ ├── ComboBox.slint │ │ │ ├── MiniPopup.slint │ │ │ ├── Page.slint │ │ │ ├── PushButton.slint │ │ │ ├── ShadowBox.slint │ │ │ ├── SideBar.slint │ │ │ ├── SpinBox.slint │ │ │ └── components.slint │ │ ├── pages/ │ │ │ ├── copy_page.slint │ │ │ ├── home_page.slint │ │ │ ├── ink_page.slint │ │ │ ├── pages.slint │ │ │ ├── printer_queue.slint │ │ │ ├── scan_page.slint │ │ │ └── settings_page.slint │ │ └── printerdemo.slint │ ├── usecases/ │ │ ├── cpp/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ └── main.cpp │ │ ├── esp-idf/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── idf_component.yml │ │ │ │ └── main.cpp │ │ │ ├── rust-toolchain.toml │ │ │ └── sdkconfig.defaults │ │ ├── lang/ │ │ │ └── de/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── usecases.mo │ │ │ └── usecases.po │ │ ├── rust/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── index.html │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── main.rs │ │ ├── ui/ │ │ │ ├── app.slint │ │ │ ├── assets.slint │ │ │ ├── icons.slint │ │ │ ├── views/ │ │ │ │ ├── dashboard_view.slint │ │ │ │ ├── header_view.slint │ │ │ │ ├── mail_view.slint │ │ │ │ └── main_view.slint │ │ │ ├── views.slint │ │ │ ├── virtual_keyboard.slint │ │ │ ├── widgets/ │ │ │ │ ├── bar_chart.slint │ │ │ │ ├── card_list_view.slint │ │ │ │ ├── container.slint │ │ │ │ ├── dialog.slint │ │ │ │ ├── extended_line_edit.slint │ │ │ │ ├── icon.slint │ │ │ │ ├── icon_button.slint │ │ │ │ ├── navigation_list_view.slint │ │ │ │ ├── segmented.slint │ │ │ │ ├── styling.slint │ │ │ │ ├── tile.slint │ │ │ │ ├── title_text.slint │ │ │ │ └── value_display.slint │ │ │ └── widgets.slint │ │ └── update_translations.sh │ ├── weather-demo/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── index.html │ │ ├── src/ │ │ │ ├── app_main.rs │ │ │ ├── lib.rs │ │ │ ├── main.rs │ │ │ └── weather/ │ │ │ ├── dummyweather.json │ │ │ ├── dummyweathercontroller.rs │ │ │ ├── mod.rs │ │ │ ├── openweathercontroller.rs │ │ │ ├── utils.rs │ │ │ ├── weathercontroller.rs │ │ │ └── weatherdisplaycontroller.rs │ │ └── ui/ │ │ ├── about-box.slint │ │ ├── city_weather.slint │ │ ├── city_weather_tile.slint │ │ ├── controls/ │ │ │ ├── busy-layer.slint │ │ │ ├── generic.slint │ │ │ ├── stackview.slint │ │ │ └── weather.slint │ │ ├── expanded_city_weather_tile.slint │ │ ├── forecast_with_graph.slint │ │ ├── location_datatypes.slint │ │ ├── location_search.slint │ │ ├── main.slint │ │ ├── page-base.slint │ │ ├── style/ │ │ │ └── styles.slint │ │ ├── ui_utils.slint │ │ └── weather_datatypes.slint │ └── zephyr-common/ │ ├── boards/ │ │ ├── mimxrt1170_evk_mimxrt1176_cm7.conf │ │ ├── native_sim_64.conf │ │ └── native_sim_64.overlay │ ├── prj.conf │ ├── slint-zephyr.cpp │ ├── slint-zephyr.h │ └── west.yaml ├── docker/ │ ├── Dockerfile.aarch64-unknown-linux-gnu │ ├── Dockerfile.armv7-unknown-linux-gnueabihf │ ├── Dockerfile.cpp-image │ ├── Dockerfile.riscv64gc-unknown-linux-gnu │ ├── Dockerfile.torizon-demos │ └── Dockerfile.x86_64-unknown-linux-gnu ├── docs/ │ ├── astro/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── astro.config.mjs │ │ ├── biome.json │ │ ├── ec.config.mjs │ │ ├── package.json │ │ ├── playwright.config.ts │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ └── editor/ │ │ │ │ ├── codemirror.js │ │ │ │ ├── language-facets.js │ │ │ │ ├── package.json │ │ │ │ └── rollup.config.js │ │ │ ├── content/ │ │ │ │ ├── code/ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── Cargo.toml │ │ │ │ │ ├── app-window.slint │ │ │ │ │ ├── main_game_logic.cpp │ │ │ │ │ ├── main_game_logic.js │ │ │ │ │ ├── main_game_logic.py │ │ │ │ │ ├── main_game_logic_in_rust.rs │ │ │ │ │ ├── main_initial.cpp │ │ │ │ │ ├── main_initial.js │ │ │ │ │ ├── main_initial.py │ │ │ │ │ ├── main_initial.rs │ │ │ │ │ ├── main_memory_tile.rs │ │ │ │ │ ├── main_multiple_tiles.rs │ │ │ │ │ ├── main_polishing_the_tile.rs │ │ │ │ │ ├── main_tiles_from_cpp.cpp │ │ │ │ │ ├── main_tiles_from_js.js │ │ │ │ │ ├── main_tiles_from_python.py │ │ │ │ │ ├── main_tiles_from_rust.rs │ │ │ │ │ ├── memory.slint │ │ │ │ │ ├── memory_game_logic.slint │ │ │ │ │ ├── memory_tile.slint │ │ │ │ │ ├── memory_tiles_from_cpp.slint │ │ │ │ │ └── package.json │ │ │ │ ├── collections/ │ │ │ │ │ └── std-widgets/ │ │ │ │ │ ├── Date.md │ │ │ │ │ └── Time.md │ │ │ │ └── docs/ │ │ │ │ ├── guide/ │ │ │ │ │ ├── backends-and-renderers/ │ │ │ │ │ │ ├── backend_linuxkms.md │ │ │ │ │ │ ├── backend_qt.mdx │ │ │ │ │ │ ├── backend_winit.md │ │ │ │ │ │ └── backends_and_renderers.mdx │ │ │ │ │ ├── development/ │ │ │ │ │ │ ├── best-practices.mdx │ │ │ │ │ │ ├── custom-controls.mdx │ │ │ │ │ │ ├── debugging_techniques.mdx │ │ │ │ │ │ ├── focus.mdx │ │ │ │ │ │ ├── fonts.md │ │ │ │ │ │ ├── third-party-libraries.mdx │ │ │ │ │ │ └── translations.mdx │ │ │ │ │ ├── language/ │ │ │ │ │ │ ├── coding/ │ │ │ │ │ │ │ ├── animation.mdx │ │ │ │ │ │ │ ├── expressions-and-statements.mdx │ │ │ │ │ │ │ ├── file.mdx │ │ │ │ │ │ │ ├── functions-and-callbacks.mdx │ │ │ │ │ │ │ ├── globals.mdx │ │ │ │ │ │ │ ├── name-resolution.mdx │ │ │ │ │ │ │ ├── positioning-and-layouts.mdx │ │ │ │ │ │ │ ├── properties.mdx │ │ │ │ │ │ │ ├── repetition-and-data-models.mdx │ │ │ │ │ │ │ ├── states.mdx │ │ │ │ │ │ │ └── structs-and-enums.mdx │ │ │ │ │ │ └── concepts/ │ │ │ │ │ │ ├── reactivity-vs-react.mdx │ │ │ │ │ │ ├── reactivity.mdx │ │ │ │ │ │ └── slint-language.mdx │ │ │ │ │ ├── platforms/ │ │ │ │ │ │ ├── desktop.mdx │ │ │ │ │ │ ├── embedded.mdx │ │ │ │ │ │ ├── mobile/ │ │ │ │ │ │ │ ├── android.mdx │ │ │ │ │ │ │ ├── general.mdx │ │ │ │ │ │ │ └── ios.mdx │ │ │ │ │ │ ├── other.mdx │ │ │ │ │ │ └── web.mdx │ │ │ │ │ └── tooling/ │ │ │ │ │ ├── figma-inspector.mdx │ │ │ │ │ ├── helix.mdx │ │ │ │ │ ├── jetbrains-ide.mdx │ │ │ │ │ ├── kate.mdx │ │ │ │ │ ├── manual-setup.mdx │ │ │ │ │ ├── neo-vim.mdx │ │ │ │ │ ├── qt-creator.mdx │ │ │ │ │ ├── sublime-text.mdx │ │ │ │ │ ├── vscode.mdx │ │ │ │ │ └── zed.mdx │ │ │ │ ├── index.mdx │ │ │ │ ├── language-integrations/ │ │ │ │ │ └── index.mdx │ │ │ │ ├── reference/ │ │ │ │ │ ├── colors-and-brushes.mdx │ │ │ │ │ ├── common.mdx │ │ │ │ │ ├── elements/ │ │ │ │ │ │ ├── image.mdx │ │ │ │ │ │ ├── path.mdx │ │ │ │ │ │ ├── rectangle.mdx │ │ │ │ │ │ ├── styled-text.mdx │ │ │ │ │ │ └── text.mdx │ │ │ │ │ ├── gestures/ │ │ │ │ │ │ ├── flickable.mdx │ │ │ │ │ │ ├── pinchgesturehandler.mdx │ │ │ │ │ │ ├── swipegesturehandler.mdx │ │ │ │ │ │ └── toucharea.mdx │ │ │ │ │ ├── global-functions/ │ │ │ │ │ │ ├── builtinfunctions.mdx │ │ │ │ │ │ └── math.mdx │ │ │ │ │ ├── global-namespaces/ │ │ │ │ │ │ └── platform.mdx │ │ │ │ │ ├── keyboard-input/ │ │ │ │ │ │ ├── focusscope.mdx │ │ │ │ │ │ ├── overview.mdx │ │ │ │ │ │ ├── textinput.mdx │ │ │ │ │ │ └── textinputinterface.mdx │ │ │ │ │ ├── layouts/ │ │ │ │ │ │ ├── flexboxlayout.mdx │ │ │ │ │ │ ├── gridlayout.mdx │ │ │ │ │ │ ├── horizontallayout.mdx │ │ │ │ │ │ ├── overview.mdx │ │ │ │ │ │ └── verticallayout.mdx │ │ │ │ │ ├── overview.mdx │ │ │ │ │ ├── primitive-types.mdx │ │ │ │ │ ├── std-widgets/ │ │ │ │ │ │ ├── basic-widgets/ │ │ │ │ │ │ │ ├── button.mdx │ │ │ │ │ │ │ ├── checkbox.mdx │ │ │ │ │ │ │ ├── combobox.mdx │ │ │ │ │ │ │ ├── progressindicator.mdx │ │ │ │ │ │ │ ├── slider.mdx │ │ │ │ │ │ │ ├── spinbox.mdx │ │ │ │ │ │ │ ├── spinner.mdx │ │ │ │ │ │ │ ├── standardbutton.mdx │ │ │ │ │ │ │ └── switch.mdx │ │ │ │ │ │ ├── globals/ │ │ │ │ │ │ │ ├── palette.mdx │ │ │ │ │ │ │ └── stylemetrics.mdx │ │ │ │ │ │ ├── layouts/ │ │ │ │ │ │ │ ├── gridbox.mdx │ │ │ │ │ │ │ ├── groupbox.mdx │ │ │ │ │ │ │ ├── horizontalbox.mdx │ │ │ │ │ │ │ └── verticalbox.mdx │ │ │ │ │ │ ├── misc/ │ │ │ │ │ │ │ ├── aboutslint.mdx │ │ │ │ │ │ │ ├── datepicker.mdx │ │ │ │ │ │ │ └── timepicker.mdx │ │ │ │ │ │ ├── overview.mdx │ │ │ │ │ │ ├── style.mdx │ │ │ │ │ │ └── views/ │ │ │ │ │ │ ├── lineedit.mdx │ │ │ │ │ │ ├── listview.mdx │ │ │ │ │ │ ├── scrollview.mdx │ │ │ │ │ │ ├── standardlistview.mdx │ │ │ │ │ │ ├── standardtableview.mdx │ │ │ │ │ │ ├── tabwidget.mdx │ │ │ │ │ │ └── textedit.mdx │ │ │ │ │ ├── timer.mdx │ │ │ │ │ └── window/ │ │ │ │ │ ├── contextmenuarea.mdx │ │ │ │ │ ├── dialog.mdx │ │ │ │ │ ├── menubar.mdx │ │ │ │ │ ├── popupwindow.mdx │ │ │ │ │ └── window.mdx │ │ │ │ └── tutorial/ │ │ │ │ ├── conclusion.md │ │ │ │ ├── creating_the_tiles.mdx │ │ │ │ ├── from_one_to_multiple_tiles.mdx │ │ │ │ ├── game_logic.mdx │ │ │ │ ├── getting_started.mdx │ │ │ │ ├── ideas_for_the_reader.mdx │ │ │ │ ├── memory_tile.mdx │ │ │ │ ├── polishing_the_tile.mdx │ │ │ │ ├── quickstart.mdx │ │ │ │ └── running_in_a_browser.mdx │ │ │ ├── content.config.ts │ │ │ ├── styles/ │ │ │ │ ├── custom.css │ │ │ │ └── theme.css │ │ │ └── utils/ │ │ │ ├── slint-docs-highlight.html │ │ │ └── slint-docs-preview.html │ │ ├── tests/ │ │ │ ├── link-test.spec.ts │ │ │ └── smoke-test.spec.ts │ │ ├── tsconfig.json │ │ └── writing-style-guide.md │ ├── building.md │ ├── common/ │ │ ├── biome.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── Banner.astro │ │ │ │ ├── CodeSnippetMD.astro │ │ │ │ ├── Footer.astro │ │ │ │ ├── FourCardGrid.astro │ │ │ │ ├── Header.astro │ │ │ │ ├── IconLinkCard.astro │ │ │ │ ├── LangRefLink.astro │ │ │ │ ├── Link.astro │ │ │ │ ├── ReleaseLink.astro │ │ │ │ ├── SlintProperty.astro │ │ │ │ ├── ThemeSelect.astro │ │ │ │ ├── ThreeCardGrid.astro │ │ │ │ ├── Type.astro │ │ │ │ ├── VersionBanner.astro │ │ │ │ └── VersionSelector.astro │ │ │ └── utils/ │ │ │ ├── site-config.ts │ │ │ ├── slint.tmLanguage.json │ │ │ └── utils.ts │ │ └── tsconfig.json │ ├── development/ │ │ ├── animation-internals.md │ │ ├── compiler-internals.md │ │ ├── custom-renderer.md │ │ ├── ffi-language-bindings.md │ │ ├── input-event-system.md │ │ ├── item-tree.md │ │ ├── layout-system.md │ │ ├── lsp-architecture.md │ │ ├── model-repeater-system.md │ │ ├── property-binding-deep-dive.md │ │ ├── text-layout.md │ │ ├── type-system.md │ │ └── window-backend-integration.md │ ├── development.md │ ├── embedded-tutorials.md │ ├── install_qt.md │ ├── internal/ │ │ ├── processes.md │ │ ├── release.md │ │ └── triage.md │ ├── nightly-release-notes.md │ ├── readme.md │ ├── release-artifacts.md │ ├── release-notes.md │ ├── site/ │ │ └── index.html │ ├── testing.md │ └── torizon.md ├── editors/ │ ├── README.md │ ├── kate/ │ │ └── slint.ksyntaxhighlighter.xml │ ├── sublime/ │ │ ├── LSP.sublime-settings │ │ ├── Slint.sublime-syntax │ │ └── Slint.tmPreferences │ ├── tree-sitter-slint/ │ │ ├── .gitignore │ │ ├── CONTRIBUTING.md │ │ ├── README.md │ │ ├── corpus/ │ │ │ ├── empty.txt │ │ │ └── lookup.txt │ │ ├── grammar.js │ │ ├── test-to-corpus.py │ │ └── tree-sitter.json │ ├── vscode/ │ │ ├── .gitignore │ │ ├── .vscode/ │ │ │ ├── extensions.json │ │ │ ├── launch.json │ │ │ ├── settings.json │ │ │ └── tasks.json │ │ ├── .vscodeignore │ │ ├── README.md │ │ ├── biome.json │ │ ├── esbuild.js │ │ ├── language-configuration.json │ │ ├── package.json │ │ ├── slint.injection.json │ │ ├── slint.markdown-injection.json │ │ ├── snippets/ │ │ │ └── slint.json │ │ ├── src/ │ │ │ ├── browser-lsp-worker.ts │ │ │ ├── browser.ts │ │ │ ├── common.ts │ │ │ ├── extension.ts │ │ │ ├── lsp_commands.ts │ │ │ ├── quick_picks.ts │ │ │ ├── snippets.ts │ │ │ ├── telemetry.ts │ │ │ ├── tsconfig.json │ │ │ └── wasm_preview.ts │ │ ├── static/ │ │ │ └── walkthroughs/ │ │ │ └── welcome/ │ │ │ ├── create_slint_file.md │ │ │ ├── get_help.md │ │ │ └── show_preview.png.license │ │ ├── telemetry.json │ │ ├── tests/ │ │ │ └── grammar/ │ │ │ ├── basic.slint │ │ │ ├── expressions.slint │ │ │ ├── invalid.slint │ │ │ ├── printerdemo.slint │ │ │ ├── snippets.slint │ │ │ ├── states.slint │ │ │ └── strings.slint │ │ ├── tsconfig.default.json │ │ └── tsconfig.json │ └── zed/ │ ├── Cargo.toml │ ├── README.md │ ├── extension.toml │ ├── languages/ │ │ └── slint/ │ │ ├── brackets.scm │ │ ├── config.toml │ │ ├── folds.scm │ │ ├── highlights.scm │ │ ├── indents.scm │ │ ├── injections.scm │ │ └── locals.scm │ └── src/ │ └── slint.rs ├── examples/ │ ├── 7guis/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── booker.rs │ │ ├── booker.slint │ │ ├── cells.rs │ │ ├── cells.slint │ │ ├── circledraw.rs │ │ ├── circledraw.slint │ │ ├── counter.slint │ │ ├── crud.rs │ │ ├── crud.slint │ │ ├── tempconv.slint │ │ └── timer.slint │ ├── CMakeLists.txt │ ├── README.md │ ├── async-io/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── main.py │ │ ├── main.rs │ │ ├── pyproject.toml │ │ └── stockticker.slint │ ├── bash/ │ │ ├── README.md │ │ ├── laptop.svg.license │ │ ├── simple_input.sh │ │ ├── sysinfo.slint │ │ ├── sysinfo_linux.sh │ │ └── sysinfo_macos.sh │ ├── bevy/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── bevy-hosts-slint/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── assets/ │ │ │ │ └── cow.gltf │ │ │ └── main.rs │ │ ├── bevy-hosts-slint-gpu/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── main.rs │ │ └── slint-hosts-bevy/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── main.rs │ │ ├── slint_bevy_adapter.rs │ │ └── web_asset.rs │ ├── carousel/ │ │ ├── README.md │ │ ├── cpp/ │ │ │ ├── CMakeLists.txt │ │ │ └── main.cpp │ │ ├── esp-idf/ │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── main.cpp │ │ │ ├── rust-toolchain.toml │ │ │ └── s3-box/ │ │ │ ├── CMakeLists.txt │ │ │ ├── main/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ └── idf_component.yml │ │ │ ├── partitions.csv │ │ │ └── sdkconfig.defaults │ │ ├── node/ │ │ │ ├── README │ │ │ ├── main.js │ │ │ └── package.json │ │ ├── rust/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── index.html │ │ │ └── main.rs │ │ └── ui/ │ │ ├── card.slint │ │ ├── carousel.slint │ │ ├── carousel_demo.slint │ │ ├── fonts/ │ │ │ ├── Roboto-Bold.ttf.license │ │ │ └── Roboto-Regular.ttf.license │ │ ├── label.slint │ │ ├── svg/ │ │ │ ├── home_black.svg.license │ │ │ ├── info_black.svg.license │ │ │ └── settings_black.svg.license │ │ ├── theme.slint │ │ └── title_label.slint │ ├── cpp/ │ │ ├── README.md │ │ ├── platform_native/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── app-window.slint │ │ │ ├── appview.cpp │ │ │ ├── appview.h │ │ │ ├── main.cpp │ │ │ └── windowadapter_win.h │ │ ├── platform_qt/ │ │ │ ├── CMakeLists.txt │ │ │ ├── README.md │ │ │ ├── app-window.slint │ │ │ └── main.cpp │ │ └── qt_viewer/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── interface.ui │ │ └── qt_viewer.cpp │ ├── dial/ │ │ ├── README.md │ │ └── dial.slint │ ├── fancy-switches/ │ │ ├── DarkModeSwitch.slint │ │ ├── README.md │ │ ├── SunMoonSwitch.slint │ │ └── demo.slint │ ├── fancy_demo/ │ │ ├── README.md │ │ └── main.slint │ ├── ffmpeg/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── player/ │ │ │ ├── audio.rs │ │ │ └── video.rs │ │ ├── player.rs │ │ └── scene.slint │ ├── gallery/ │ │ ├── CMakeLists.txt │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── gallery.pot │ │ ├── gallery.slint │ │ ├── index.html │ │ ├── lang/ │ │ │ ├── de/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── gallery.mo │ │ │ │ └── gallery.po │ │ │ ├── fr/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── gallery.mo │ │ │ │ └── gallery.po │ │ │ └── ja/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── gallery.mo │ │ │ └── gallery.po │ │ ├── main.cpp │ │ ├── main.rs │ │ ├── thumbsup.png.license │ │ ├── ui/ │ │ │ ├── gallery_settings.slint │ │ │ ├── pages/ │ │ │ │ ├── about_page.slint │ │ │ │ ├── controls_page.slint │ │ │ │ ├── easings_page.slint │ │ │ │ ├── list_view_page.slint │ │ │ │ ├── page.slint │ │ │ │ ├── pages.slint │ │ │ │ ├── styled_text_page.slint │ │ │ │ ├── table_view_page.slint │ │ │ │ └── text_edit_page.slint │ │ │ └── side_bar.slint │ │ └── update_translations.sh │ ├── gstreamer-player/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── main.rs │ │ ├── scene.slint │ │ └── slint_video_sink/ │ │ ├── egl_integration.rs │ │ ├── mod.rs │ │ └── software_rendering.rs │ ├── imagefilter/ │ │ ├── README.md │ │ ├── assets/ │ │ │ └── cat.jpg.license │ │ ├── node/ │ │ │ ├── .gitignore │ │ │ ├── README │ │ │ ├── main.ts │ │ │ ├── package.json │ │ │ └── tsconfig.json │ │ ├── rust/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── index.html │ │ │ └── main.rs │ │ └── ui/ │ │ └── main.slint │ ├── iot-dashboard/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── dashboard.cpp │ │ ├── dashboard.h │ │ ├── iot-dashboard.slint │ │ ├── main.cpp │ │ └── main.slint │ ├── layouts/ │ │ ├── flexbox-interactive.slint │ │ ├── grid-with-model-in-rows.slint │ │ ├── grid-with-nested-for.slint │ │ ├── grid-with-repeated-rows.slint │ │ ├── vector-as-grid.slint │ │ └── vertical-layout-with-model.slint │ ├── maps/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── main.rs │ ├── mcu-board-support/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── embassy.rs │ │ ├── esope_sld_c_w_s3/ │ │ │ ├── cargo-config.toml │ │ │ └── esope_sld_c_w_s3.rs │ │ ├── esp32_s3_box_3/ │ │ │ ├── cargo-config.toml │ │ │ └── esp32_s3_box_3.rs │ │ ├── esp32_s3_lcd_ev_board/ │ │ │ ├── cargo-config.toml │ │ │ └── esp32_s3_lcd_ev_board.rs │ │ ├── lib.rs │ │ ├── m5stack_cores3/ │ │ │ ├── cargo-config.toml │ │ │ └── m5stack_cores3.rs │ │ ├── pico2_st7789/ │ │ │ ├── board_config.toml │ │ │ ├── memory.x │ │ │ ├── pico2_st7789.rs │ │ │ └── rp_pico2.rs │ │ ├── pico2_touch_lcd_2_8/ │ │ │ ├── board_config.toml │ │ │ ├── memory.x │ │ │ └── pico2_touch_lcd_2_8.rs │ │ ├── pico_st7789/ │ │ │ ├── board_config.toml │ │ │ ├── memory.x │ │ │ └── pico_st7789.rs │ │ ├── profiler.rs │ │ ├── stm32h735g/ │ │ │ ├── board_config.toml │ │ │ ├── memory.x │ │ │ └── stm32h735g.rs │ │ ├── stm32u5g9j_dk2/ │ │ │ ├── board_config.toml │ │ │ ├── hspi.rs │ │ │ ├── memory.x │ │ │ └── stm32u5g9j_dk2.rs │ │ └── waveshare_esp32_s3_touch_amoled_1_8/ │ │ ├── cargo-config.toml │ │ └── waveshare_esp32_s3_touch_amoled_1_8.rs │ ├── mcu-embassy/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── .gitignore │ │ ├── .vscode/ │ │ │ └── settings.json │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── slint_generated/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── build.rs │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── src/ │ │ │ ├── bin/ │ │ │ │ ├── ui_mcu.rs │ │ │ │ └── ui_simulator.rs │ │ │ ├── controller.rs │ │ │ ├── lib.rs │ │ │ ├── mcu/ │ │ │ │ ├── double_buffer.rs │ │ │ │ ├── hardware.rs │ │ │ │ ├── mod.rs │ │ │ │ └── rcc_setup.rs │ │ │ ├── simulator/ │ │ │ │ ├── hardware.rs │ │ │ │ └── mod.rs │ │ │ └── slint_backend.rs │ │ └── ui/ │ │ ├── common.slint │ │ └── main.slint │ ├── memory/ │ │ ├── CMakeLists.txt │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── icons/ │ │ │ └── README.md │ │ ├── index.html │ │ ├── main.js │ │ ├── main.py │ │ ├── main.rs │ │ ├── memory.cpp │ │ ├── memory.slint │ │ ├── package.json │ │ └── pyproject.toml │ ├── native-gestures/ │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── native-gestures.slint │ │ └── src/ │ │ ├── lib.rs │ │ └── main.rs │ ├── opengl_texture/ │ │ ├── CMakeLists.txt │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── main.cpp │ │ ├── main.rs │ │ └── scene.slint │ ├── opengl_underlay/ │ │ ├── CMakeLists.txt │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── index.html │ │ ├── main.cpp │ │ ├── main.rs │ │ └── scene.slint │ ├── orbit-animation/ │ │ ├── README.md │ │ ├── demo.slint │ │ └── orbiter.slint │ ├── plotter/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── index.html │ │ ├── main.rs │ │ ├── plotter.slint │ │ └── wasm_backend.rs │ ├── repeater/ │ │ ├── README.md │ │ └── demo.slint │ ├── safe-ui/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── core/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── pixels/ │ │ │ │ ├── bgra8888.rs │ │ │ │ └── mod.rs │ │ │ ├── platform.rs │ │ │ └── slint-safeui-platform-interface.h │ │ ├── simulator/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ └── src/ │ │ │ ├── desktop_platform.rs │ │ │ └── main.rs │ │ └── ui/ │ │ └── app-window.slint │ ├── servo/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ ├── main.rs │ │ │ └── webview/ │ │ │ ├── adapter.rs │ │ │ ├── delegate.rs │ │ │ ├── events_utils/ │ │ │ │ ├── key_event_util.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── pointer_event_util.rs │ │ │ │ └── url_event_util.rs │ │ │ ├── mod.rs │ │ │ ├── rendering_context/ │ │ │ │ ├── gpu_rendering_context.rs │ │ │ │ ├── metal/ │ │ │ │ │ ├── metal.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── texture_importer.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── servo_rendering_adapter.rs │ │ │ │ └── surfman_context.rs │ │ │ ├── waker.rs │ │ │ ├── webview.rs │ │ │ ├── webview.slint │ │ │ └── webview_events.rs │ │ └── ui/ │ │ └── app.slint │ ├── slide_puzzle/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── berlin.jpg.license │ │ ├── build.rs │ │ ├── index.html │ │ ├── main.rs │ │ ├── plaster-font/ │ │ │ └── Plaster-Regular.ttf.license │ │ └── slide_puzzle.slint │ ├── speedometer/ │ │ ├── 01 Digitall.ttf.license │ │ ├── README.md │ │ ├── demo.slint │ │ └── rust/ │ │ ├── Cargo.toml │ │ ├── index.html │ │ ├── lib.rs │ │ └── main.rs │ ├── sprite-sheet/ │ │ ├── README.md │ │ ├── SpriteSheet.slint │ │ └── demo.slint │ ├── todo/ │ │ ├── README.md │ │ ├── cpp/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── app.cpp │ │ │ ├── app.h │ │ │ ├── main.cpp │ │ │ └── tests/ │ │ │ └── test_todo_basic.cpp │ │ ├── node/ │ │ │ ├── README │ │ │ ├── main.js │ │ │ └── package.json │ │ ├── rust/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── index.html │ │ │ ├── lib.rs │ │ │ └── main.rs │ │ └── ui/ │ │ └── todo.slint │ ├── todo-mvc/ │ │ ├── README.md │ │ ├── rust/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── index.html │ │ │ └── src/ │ │ │ ├── callback.rs │ │ │ ├── lib.rs │ │ │ ├── main.rs │ │ │ ├── mvc/ │ │ │ │ ├── controllers/ │ │ │ │ │ ├── create_task_controller.rs │ │ │ │ │ └── task_list_controller.rs │ │ │ │ ├── controllers.rs │ │ │ │ ├── models/ │ │ │ │ │ ├── date_model.rs │ │ │ │ │ ├── task_model.rs │ │ │ │ │ └── time_model.rs │ │ │ │ ├── models.rs │ │ │ │ ├── repositories/ │ │ │ │ │ ├── mock_date_time_repository.rs │ │ │ │ │ ├── mock_task_repository.rs │ │ │ │ │ ├── traits/ │ │ │ │ │ │ ├── date_time_repository.rs │ │ │ │ │ │ └── task_repository.rs │ │ │ │ │ └── traits.rs │ │ │ │ └── repositories.rs │ │ │ ├── mvc.rs │ │ │ ├── ui/ │ │ │ │ ├── create_task_adapter.rs │ │ │ │ ├── navigation_adapter.rs │ │ │ │ └── task_list_adapter.rs │ │ │ └── ui.rs │ │ └── ui/ │ │ ├── index.slint │ │ ├── views/ │ │ │ ├── create_task_view.slint │ │ │ └── task_list_view.slint │ │ └── widgets/ │ │ ├── action_button.slint │ │ ├── focus_touch_area.slint │ │ ├── icon_button.slint │ │ ├── selection_list_view.slint │ │ ├── state_layer.slint │ │ ├── styling.slint │ │ └── text_button.slint │ ├── uefi-demo/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── demo.slint │ │ └── main.rs │ ├── virtual_keyboard/ │ │ ├── README.md │ │ ├── cpp/ │ │ │ ├── CMakeLists.txt │ │ │ └── main.cpp │ │ ├── rust/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ └── main.rs │ │ └── ui/ │ │ ├── icons.slint │ │ ├── main_window.slint │ │ └── virtual_keyboard.slint │ └── wgpu_texture/ │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── lib.rs │ ├── main.rs │ ├── scene.slint │ └── shader.wgsl ├── flake.nix ├── helper_crates/ │ ├── const-field-offset/ │ │ ├── CHANGELOG.md │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── macro/ │ │ │ ├── Cargo.toml │ │ │ └── macro.rs │ │ ├── src/ │ │ │ └── lib.rs │ │ └── tests/ │ │ └── test_field_offset.rs │ └── vtable/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── macro/ │ │ ├── Cargo.toml │ │ └── macro.rs │ ├── src/ │ │ ├── compile_fail_tests.rs │ │ ├── lib.rs │ │ └── vrc.rs │ └── tests/ │ ├── test_vrc.rs │ └── test_vtable.rs ├── internal/ │ ├── backends/ │ │ ├── android-activity/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── androidwindowadapter.rs │ │ │ ├── build.rs │ │ │ ├── java/ │ │ │ │ └── SlintAndroidJavaHelper.java │ │ │ ├── javahelper.rs │ │ │ └── lib.rs │ │ ├── linuxkms/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── calloop_backend/ │ │ │ │ └── input.rs │ │ │ ├── calloop_backend.rs │ │ │ ├── display/ │ │ │ │ ├── gbmdisplay.rs │ │ │ │ ├── swdisplay/ │ │ │ │ │ ├── dumbbuffer.rs │ │ │ │ │ └── linuxfb.rs │ │ │ │ ├── swdisplay.rs │ │ │ │ └── vulkandisplay.rs │ │ │ ├── display.rs │ │ │ ├── drmoutput.rs │ │ │ ├── fullscreenwindowadapter.rs │ │ │ ├── lib.rs │ │ │ ├── noop_backend.rs │ │ │ └── renderer/ │ │ │ ├── femtovg.rs │ │ │ ├── skia.rs │ │ │ └── sw.rs │ │ ├── qt/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── accessible_generated.rs │ │ │ ├── build.rs │ │ │ ├── key_generated.rs │ │ │ ├── lib.rs │ │ │ ├── qt_accessible.rs │ │ │ ├── qt_widgets/ │ │ │ │ ├── button.rs │ │ │ │ ├── checkbox.rs │ │ │ │ ├── combobox.rs │ │ │ │ ├── groupbox.rs │ │ │ │ ├── lineedit.rs │ │ │ │ ├── listviewitem.rs │ │ │ │ ├── palette.rs │ │ │ │ ├── progress_indicator.rs │ │ │ │ ├── scrollview.rs │ │ │ │ ├── slider.rs │ │ │ │ ├── spinbox.rs │ │ │ │ ├── stylemetrics.rs │ │ │ │ ├── tableheadersection.rs │ │ │ │ └── tabwidget.rs │ │ │ ├── qt_widgets.rs │ │ │ └── qt_window.rs │ │ ├── selector/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── api.rs │ │ │ ├── build.rs │ │ │ └── lib.rs │ │ ├── testing/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── build.rs │ │ │ ├── ffi.rs │ │ │ ├── internal_tests.rs │ │ │ ├── lib.rs │ │ │ ├── search_api.rs │ │ │ ├── slint_systest.proto │ │ │ ├── systest.rs │ │ │ ├── testing_backend.rs │ │ │ └── tests/ │ │ │ ├── click.rs │ │ │ └── layout_kind.rs │ │ └── winit/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── accesskit.rs │ │ ├── build.rs │ │ ├── clipboard.rs │ │ ├── drag_resize_window.rs │ │ ├── event_loop.rs │ │ ├── frame_throttle.rs │ │ ├── ios/ │ │ │ ├── keyboard_animator.rs │ │ │ └── virtual_keyboard.rs │ │ ├── ios.rs │ │ ├── lib.rs │ │ ├── muda.rs │ │ ├── renderer/ │ │ │ ├── femtovg/ │ │ │ │ └── glcontext.rs │ │ │ ├── femtovg.rs │ │ │ ├── skia.rs │ │ │ └── sw.rs │ │ ├── tests/ │ │ │ └── menubar_borrow.rs │ │ ├── wasm_input_helper.rs │ │ ├── winitwindowadapter.rs │ │ └── xdg_color_scheme.rs │ ├── common/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── builtin_structs.rs │ │ ├── color_parsing.rs │ │ ├── enums.rs │ │ ├── key_codes.rs │ │ ├── lib.rs │ │ ├── sharedfontique/ │ │ │ └── DejaVuSans.ttf.license │ │ ├── sharedfontique.rs │ │ └── styled_text.rs │ ├── compiler/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── benches/ │ │ │ └── semantic_analysis.rs │ │ ├── build.rs │ │ ├── builtin_macros.rs │ │ ├── builtins.slint │ │ ├── diagnostics.rs │ │ ├── embedded_resources.rs │ │ ├── expression_tree.rs │ │ ├── fileaccess.rs │ │ ├── generator/ │ │ │ ├── cpp.rs │ │ │ ├── cpp_live_preview.rs │ │ │ ├── python/ │ │ │ │ └── diff.rs │ │ │ ├── python.rs │ │ │ ├── rust.rs │ │ │ └── rust_live_preview.rs │ │ ├── generator.rs │ │ ├── langtype.rs │ │ ├── layout.rs │ │ ├── lexer.rs │ │ ├── lib.rs │ │ ├── literals.rs │ │ ├── llr/ │ │ │ ├── expression.rs │ │ │ ├── item_tree.rs │ │ │ ├── lower_expression.rs │ │ │ ├── lower_layout_expression.rs │ │ │ ├── lower_to_item_tree.rs │ │ │ ├── optim_passes/ │ │ │ │ ├── count_property_use.rs │ │ │ │ ├── inline_expressions.rs │ │ │ │ └── remove_unused.rs │ │ │ └── pretty_print.rs │ │ ├── llr.rs │ │ ├── load_builtins.rs │ │ ├── lookup.rs │ │ ├── namedreference.rs │ │ ├── object_tree/ │ │ │ └── interfaces.rs │ │ ├── object_tree.rs │ │ ├── parser/ │ │ │ ├── document.rs │ │ │ ├── element.rs │ │ │ ├── expressions.rs │ │ │ ├── statements.rs │ │ │ └── type.rs │ │ ├── parser-test-macro/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── lib.rs │ │ ├── parser.rs │ │ ├── passes/ │ │ │ ├── apply_default_properties_from_style.rs │ │ │ ├── binding_analysis.rs │ │ │ ├── border_radius.rs │ │ │ ├── check_expressions.rs │ │ │ ├── check_public_api.rs │ │ │ ├── clip.rs │ │ │ ├── collect_custom_fonts.rs │ │ │ ├── collect_globals.rs │ │ │ ├── collect_init_code.rs │ │ │ ├── collect_libraries.rs │ │ │ ├── collect_structs_and_enums.rs │ │ │ ├── collect_subcomponents.rs │ │ │ ├── compile_paths.rs │ │ │ ├── const_propagation.rs │ │ │ ├── deduplicate_property_read.rs │ │ │ ├── default_geometry.rs │ │ │ ├── deprecated_rotation_origin.rs │ │ │ ├── embed_glyphs.rs │ │ │ ├── embed_images.rs │ │ │ ├── flickable.rs │ │ │ ├── focus_handling.rs │ │ │ ├── generate_item_indices.rs │ │ │ ├── infer_aliases_types.rs │ │ │ ├── inject_debug_hooks.rs │ │ │ ├── inlining.rs │ │ │ ├── key_bindings.rs │ │ │ ├── lower_absolute_coordinates.rs │ │ │ ├── lower_accessibility.rs │ │ │ ├── lower_component_container.rs │ │ │ ├── lower_layout.rs │ │ │ ├── lower_menus.rs │ │ │ ├── lower_platform.rs │ │ │ ├── lower_popups.rs │ │ │ ├── lower_property_to_element.rs │ │ │ ├── lower_repeated_rows.rs │ │ │ ├── lower_shadows.rs │ │ │ ├── lower_states.rs │ │ │ ├── lower_tabwidget.rs │ │ │ ├── lower_text_input_interface.rs │ │ │ ├── lower_timers.rs │ │ │ ├── materialize_fake_properties.rs │ │ │ ├── move_declarations.rs │ │ │ ├── optimize_useless_rectangles.rs │ │ │ ├── purity_check.rs │ │ │ ├── remove_aliases.rs │ │ │ ├── remove_return.rs │ │ │ ├── remove_unused_properties.rs │ │ │ ├── repeater_component.rs │ │ │ ├── resolve_native_classes.rs │ │ │ ├── resolving/ │ │ │ │ └── remove_noop.rs │ │ │ ├── resolving.rs │ │ │ ├── unique_id.rs │ │ │ ├── visible.rs │ │ │ ├── windows.rs │ │ │ └── z_order.rs │ │ ├── passes.rs │ │ ├── pathutils.rs │ │ ├── tests/ │ │ │ ├── consistent_styles.rs │ │ │ ├── syntax/ │ │ │ │ ├── accessibility/ │ │ │ │ │ └── accessible_properties.slint │ │ │ │ ├── analysis/ │ │ │ │ │ ├── binding_loop1.slint │ │ │ │ │ ├── binding_loop2.slint │ │ │ │ │ ├── binding_loop_function.slint │ │ │ │ │ ├── binding_loop_image.slint │ │ │ │ │ ├── binding_loop_issue_772.slint │ │ │ │ │ ├── binding_loop_layout.slint │ │ │ │ │ ├── binding_loop_layout2.slint │ │ │ │ │ ├── binding_loop_layout3.slint │ │ │ │ │ ├── binding_loop_layout4.slint │ │ │ │ │ ├── binding_loop_layout_if.slint │ │ │ │ │ ├── binding_loop_mappointtowindow.slint │ │ │ │ │ ├── binding_loop_popupwindow.slint │ │ │ │ │ ├── binding_loop_self.slint │ │ │ │ │ ├── binding_loop_text.slint │ │ │ │ │ ├── binding_loop_window.slint │ │ │ │ │ └── binding_loop_window2.slint │ │ │ │ ├── basic/ │ │ │ │ │ ├── animate.slint │ │ │ │ │ ├── array.slint │ │ │ │ │ ├── assign.slint │ │ │ │ │ ├── bom-simple.slint │ │ │ │ │ ├── box_shadow.slint │ │ │ │ │ ├── children_placeholder.slint │ │ │ │ │ ├── clip.slint │ │ │ │ │ ├── comments.slint │ │ │ │ │ ├── conic-gradient.slint │ │ │ │ │ ├── dialog.slint │ │ │ │ │ ├── dialog2.slint │ │ │ │ │ ├── double_binding.slint │ │ │ │ │ ├── double_color.slint │ │ │ │ │ ├── duplicated_id.slint │ │ │ │ │ ├── easing.slint │ │ │ │ │ ├── easing_not_called.slint │ │ │ │ │ ├── empty.slint │ │ │ │ │ ├── enums.slint │ │ │ │ │ ├── for.slint │ │ │ │ │ ├── for_range.slint │ │ │ │ │ ├── gridlayout_binding_loop.slint │ │ │ │ │ ├── gridlayout_no_mixing.slint │ │ │ │ │ ├── image.slint │ │ │ │ │ ├── inline_component.slint │ │ │ │ │ ├── item_as_property.slint │ │ │ │ │ ├── layout.slint │ │ │ │ │ ├── let.slint │ │ │ │ │ ├── linear-gradient.slint │ │ │ │ │ ├── markdown.slint │ │ │ │ │ ├── markdown_interpolation.slint │ │ │ │ │ ├── object_in_binding.slint │ │ │ │ │ ├── opacity.slint │ │ │ │ │ ├── parse_error.slint │ │ │ │ │ ├── path.slint │ │ │ │ │ ├── path_commands.slint │ │ │ │ │ ├── path_for.slint │ │ │ │ │ ├── percent.slint │ │ │ │ │ ├── popup.slint │ │ │ │ │ ├── property_animation.slint │ │ │ │ │ ├── property_declaration.slint │ │ │ │ │ ├── property_declaration_in_component.slint │ │ │ │ │ ├── radial-gradient.slint │ │ │ │ │ ├── return.slint │ │ │ │ │ ├── rotation-origin.slint │ │ │ │ │ ├── rotation.slint │ │ │ │ │ ├── rust-attr.slint │ │ │ │ │ ├── self_assign.slint │ │ │ │ │ ├── signal.slint │ │ │ │ │ ├── states_transitions.slint │ │ │ │ │ ├── states_transitions2.slint │ │ │ │ │ ├── states_transitions3.slint │ │ │ │ │ ├── states_two_way.slint │ │ │ │ │ ├── sub_elements.slint │ │ │ │ │ ├── supersimple.slint │ │ │ │ │ ├── svg_path.slint │ │ │ │ │ ├── tr.slint │ │ │ │ │ ├── tr2.slint │ │ │ │ │ ├── type_declaration.slint │ │ │ │ │ └── unknown_item.slint │ │ │ │ ├── callbacks/ │ │ │ │ │ ├── init.slint │ │ │ │ │ └── property-changes.slint │ │ │ │ ├── elements/ │ │ │ │ │ ├── component_container.slint │ │ │ │ │ ├── contextmenu.slint │ │ │ │ │ ├── contextmenu2.slint │ │ │ │ │ ├── contextmenu3.slint │ │ │ │ │ ├── key_binding_duplicate.slint │ │ │ │ │ ├── key_binding_invalid.slint │ │ │ │ │ ├── key_binding_valid.slint │ │ │ │ │ ├── listview.slint │ │ │ │ │ ├── menubar-lookup.slint │ │ │ │ │ ├── menubar.slint │ │ │ │ │ ├── menubar2.slint │ │ │ │ │ ├── menubar3.slint │ │ │ │ │ ├── menubar4.slint │ │ │ │ │ ├── path.slint │ │ │ │ │ ├── popup.slint │ │ │ │ │ ├── popup2.slint │ │ │ │ │ ├── popup3.slint │ │ │ │ │ ├── sub-windows.slint │ │ │ │ │ ├── tabwidget.slint │ │ │ │ │ ├── tabwidget2.slint │ │ │ │ │ ├── tabwidget3.slint │ │ │ │ │ ├── text.slint │ │ │ │ │ ├── timer.slint │ │ │ │ │ └── timer2.slint │ │ │ │ ├── exports/ │ │ │ │ │ ├── export_cross_source_duplicates.slint │ │ │ │ │ ├── export_duplicates.slint │ │ │ │ │ ├── export_empty.slint │ │ │ │ │ ├── export_many_components.slint │ │ │ │ │ ├── export_non_window.slint │ │ │ │ │ ├── reexport2.slint │ │ │ │ │ ├── reexport_duplicate.slint │ │ │ │ │ ├── root_compo_export.slint │ │ │ │ │ ├── root_compo_export2.slint │ │ │ │ │ ├── root_compo_export3.slint │ │ │ │ │ ├── single_global_missing_export.slint │ │ │ │ │ └── unused_compo.slint │ │ │ │ ├── expressions/ │ │ │ │ │ ├── arithmetic_op.slint │ │ │ │ │ ├── at_keys_parser.slint │ │ │ │ │ ├── at_keys_resolving.slint │ │ │ │ │ ├── at_keys_valid.slint │ │ │ │ │ ├── clamp.slint │ │ │ │ │ ├── comparison_operator.slint │ │ │ │ │ ├── comparison_operator2.slint │ │ │ │ │ ├── condition_operator.slint │ │ │ │ │ ├── image-url.slint │ │ │ │ │ ├── image-url2.slint │ │ │ │ │ ├── math-macro.slint │ │ │ │ │ ├── minmax.slint │ │ │ │ │ ├── noops.slint │ │ │ │ │ ├── percent2.slint │ │ │ │ │ ├── strings.slint │ │ │ │ │ └── unary_op.slint │ │ │ │ ├── focus/ │ │ │ │ │ ├── focus_invalid.slint │ │ │ │ │ ├── focus_not_called.slint │ │ │ │ │ ├── focus_wrong_args.slint │ │ │ │ │ └── initial_focus_non_input.slint │ │ │ │ ├── functions/ │ │ │ │ │ ├── function_double_qualified.slint │ │ │ │ │ ├── functions.slint │ │ │ │ │ ├── functions_call.slint │ │ │ │ │ ├── functions_no_body.slint │ │ │ │ │ ├── functions_purity.slint │ │ │ │ │ └── functions_purity_recursive_5220.slint │ │ │ │ ├── fuzzing/ │ │ │ │ │ ├── 6512.slint │ │ │ │ │ ├── 6518.slint │ │ │ │ │ ├── 6519.slint │ │ │ │ │ ├── 6587.slint │ │ │ │ │ ├── 6588.slint │ │ │ │ │ ├── 6590.slint │ │ │ │ │ ├── 6632.slint │ │ │ │ │ ├── 6650.slint │ │ │ │ │ ├── 6979.slint │ │ │ │ │ └── 7095.slint │ │ │ │ ├── imports/ │ │ │ │ │ ├── bug_2719.slint │ │ │ │ │ ├── bug_3674_alias-from-broken-import.slint │ │ │ │ │ ├── cyclic_import.slint │ │ │ │ │ ├── error_in_import.slint │ │ │ │ │ ├── error_in_import2.slint │ │ │ │ │ ├── error_in_import3.slint │ │ │ │ │ ├── font.slint │ │ │ │ │ ├── import_builtin.slint │ │ │ │ │ ├── import_error2.slint │ │ │ │ │ ├── import_errors.slint │ │ │ │ │ ├── import_parse_error1.slint │ │ │ │ │ ├── import_parse_error2.slint │ │ │ │ │ ├── import_parse_error3.slint │ │ │ │ │ ├── import_parse_error4.slint │ │ │ │ │ ├── import_parse_error5.slint │ │ │ │ │ ├── import_parse_error6.slint │ │ │ │ │ ├── import_parse_error7.slint │ │ │ │ │ ├── import_style_base.slint │ │ │ │ │ ├── invalid_export.slint │ │ │ │ │ ├── just_import.slint │ │ │ │ │ ├── library.slint │ │ │ │ │ └── visibility_errors.slint │ │ │ │ ├── interfaces/ │ │ │ │ │ ├── callbacks.slint │ │ │ │ │ ├── global_interfaces.slint │ │ │ │ │ ├── interface_export.slint │ │ │ │ │ ├── interface_functions.slint │ │ │ │ │ ├── interface_implements.slint │ │ │ │ │ ├── interface_inheritance.slint │ │ │ │ │ ├── interface_invalid_content.slint │ │ │ │ │ ├── interface_properties.slint │ │ │ │ │ ├── interface_uses1.slint │ │ │ │ │ ├── interface_uses2.slint │ │ │ │ │ ├── interface_uses3.slint │ │ │ │ │ ├── interfaces.slint │ │ │ │ │ ├── interfaces_colon_equal.slint │ │ │ │ │ └── uses_errors.slint │ │ │ │ ├── layout/ │ │ │ │ │ ├── flexbox_layout_properties.slint │ │ │ │ │ ├── flexbox_stretch_warning.slint │ │ │ │ │ ├── grid_layout_properties.slint │ │ │ │ │ ├── if_in_grid_row.slint │ │ │ │ │ ├── min_max_conflict.slint │ │ │ │ │ ├── padding.slint │ │ │ │ │ ├── repeated_in_repeated_row.slint │ │ │ │ │ └── spacing.slint │ │ │ │ ├── lookup/ │ │ │ │ │ ├── absolute-position.slint │ │ │ │ │ ├── array_index.slint │ │ │ │ │ ├── callback_alias.slint │ │ │ │ │ ├── callback_alias2.slint │ │ │ │ │ ├── callback_alias3.slint │ │ │ │ │ ├── callback_alias_global.slint │ │ │ │ │ ├── callback_alias_issue4938.slint │ │ │ │ │ ├── callback_not_called.slint │ │ │ │ │ ├── callback_return.slint │ │ │ │ │ ├── color.slint │ │ │ │ │ ├── conversion.slint │ │ │ │ │ ├── dashes.slint │ │ │ │ │ ├── deprecated_property.slint │ │ │ │ │ ├── easing.slint │ │ │ │ │ ├── enum.slint │ │ │ │ │ ├── for_lookup.slint │ │ │ │ │ ├── global.slint │ │ │ │ │ ├── if.slint │ │ │ │ │ ├── issue_1461.slint │ │ │ │ │ ├── issue_5246_states_function.slint │ │ │ │ │ ├── name_reuse.slint │ │ │ │ │ ├── property.slint │ │ │ │ │ ├── recover_id_lookup.slint │ │ │ │ │ ├── signal_arg.slint │ │ │ │ │ ├── two_way_binding.slint │ │ │ │ │ ├── two_way_binding_infer.slint │ │ │ │ │ └── two_way_binding_model.slint │ │ │ │ ├── new_syntax/ │ │ │ │ │ ├── deprecated_syntax.slint │ │ │ │ │ ├── input_output.slint │ │ │ │ │ ├── input_output2.slint │ │ │ │ │ ├── new_component.slint │ │ │ │ │ ├── new_lookup.slint │ │ │ │ │ └── two_way_input_output.slint │ │ │ │ └── parse_error/ │ │ │ │ ├── children_placeholder0.slint │ │ │ │ ├── export0.slint │ │ │ │ ├── export1.slint │ │ │ │ ├── for0.slint │ │ │ │ ├── if0.slint │ │ │ │ ├── if1.slint │ │ │ │ ├── if2.slint │ │ │ │ ├── if3.slint │ │ │ │ ├── if4.slint │ │ │ │ ├── non_ascii.slint │ │ │ │ ├── property1.slint │ │ │ │ ├── property2.slint │ │ │ │ ├── state1.slint │ │ │ │ ├── state2.slint │ │ │ │ ├── state3.slint │ │ │ │ ├── struct.slint │ │ │ │ └── sub.slint │ │ │ ├── syntax_tests.rs │ │ │ └── typeloader/ │ │ │ ├── custom_style/ │ │ │ │ └── TestStyle/ │ │ │ │ └── std-widgets.slint │ │ │ ├── dependency_local.slint │ │ │ ├── dependency_test_main.slint │ │ │ ├── incpath/ │ │ │ │ ├── bug_2719_import.slint │ │ │ │ ├── bug_3674_alias_from_invalid_import.slint │ │ │ │ ├── dependency_from_incpath.slint │ │ │ │ ├── local_helper_type.slint │ │ │ │ ├── should_fail.slint │ │ │ │ ├── should_fail2.slint │ │ │ │ ├── should_fail3.slint │ │ │ │ └── should_fail4.slint │ │ │ ├── library/ │ │ │ │ ├── dependency_from_library.slint │ │ │ │ ├── lib.slint │ │ │ │ └── library_helper_type.slint │ │ │ ├── recursive_import1.slint │ │ │ ├── recursive_import2.slint │ │ │ └── some_rust_file.rs │ │ ├── translations.rs │ │ ├── typeloader.rs │ │ ├── typeregister.rs │ │ └── widgets/ │ │ ├── common/ │ │ │ ├── about-slint.slint │ │ │ ├── combobox-base.slint │ │ │ ├── datepicker_base.slint │ │ │ ├── internal-components.slint │ │ │ ├── layout.slint │ │ │ ├── lineedit-base.slint │ │ │ ├── listview.slint │ │ │ ├── menu-base.slint │ │ │ ├── menus.slint │ │ │ ├── slider-base.slint │ │ │ ├── spinbox-base.slint │ │ │ ├── spinner-base.slint │ │ │ ├── standardbutton.slint │ │ │ ├── tabwidget-base.slint │ │ │ ├── textedit-base.slint │ │ │ └── time-picker-base.slint │ │ ├── cosmic/ │ │ │ ├── button.slint │ │ │ ├── checkbox.slint │ │ │ ├── color-scheme.slint │ │ │ ├── combobox.slint │ │ │ ├── components.slint │ │ │ ├── datepicker.slint │ │ │ ├── groupbox.slint │ │ │ ├── lineedit.slint │ │ │ ├── menu.slint │ │ │ ├── progressindicator.slint │ │ │ ├── scrollview.slint │ │ │ ├── slider.slint │ │ │ ├── spinbox.slint │ │ │ ├── spinner.slint │ │ │ ├── std-widgets-impl.slint │ │ │ ├── std-widgets.slint │ │ │ ├── style-base.slint │ │ │ ├── styling.slint │ │ │ ├── switch.slint │ │ │ ├── tableview.slint │ │ │ ├── tabwidget.slint │ │ │ ├── textedit.slint │ │ │ └── time-picker.slint │ │ ├── cupertino/ │ │ │ ├── button.slint │ │ │ ├── checkbox.slint │ │ │ ├── color-scheme.slint │ │ │ ├── combobox.slint │ │ │ ├── components.slint │ │ │ ├── datepicker.slint │ │ │ ├── groupbox.slint │ │ │ ├── lineedit.slint │ │ │ ├── menu.slint │ │ │ ├── progressindicator.slint │ │ │ ├── scrollview.slint │ │ │ ├── slider.slint │ │ │ ├── spinbox.slint │ │ │ ├── spinner.slint │ │ │ ├── std-widgets-impl.slint │ │ │ ├── std-widgets.slint │ │ │ ├── style-base.slint │ │ │ ├── styling.slint │ │ │ ├── switch.slint │ │ │ ├── tableview.slint │ │ │ ├── tabwidget.slint │ │ │ ├── textedit.slint │ │ │ └── time-picker.slint │ │ ├── fluent/ │ │ │ ├── button.slint │ │ │ ├── checkbox.slint │ │ │ ├── color-scheme.slint │ │ │ ├── combobox.slint │ │ │ ├── components.slint │ │ │ ├── datepicker.slint │ │ │ ├── groupbox.slint │ │ │ ├── lineedit.slint │ │ │ ├── menu.slint │ │ │ ├── progressindicator.slint │ │ │ ├── scrollview.slint │ │ │ ├── slider.slint │ │ │ ├── spinbox.slint │ │ │ ├── spinner.slint │ │ │ ├── std-widgets-impl.slint │ │ │ ├── std-widgets.slint │ │ │ ├── style-base.slint │ │ │ ├── styling.slint │ │ │ ├── switch.slint │ │ │ ├── tableview.slint │ │ │ ├── tabwidget.slint │ │ │ ├── textedit.slint │ │ │ └── time-picker.slint │ │ ├── material/ │ │ │ ├── button.slint │ │ │ ├── checkbox.slint │ │ │ ├── color-scheme.slint │ │ │ ├── combobox.slint │ │ │ ├── components.slint │ │ │ ├── datepicker.slint │ │ │ ├── groupbox.slint │ │ │ ├── lineedit.slint │ │ │ ├── menu.slint │ │ │ ├── progressindicator.slint │ │ │ ├── scrollview.slint │ │ │ ├── slider.slint │ │ │ ├── spinbox.slint │ │ │ ├── spinner.slint │ │ │ ├── std-widgets-impl.slint │ │ │ ├── std-widgets.slint │ │ │ ├── style-base.slint │ │ │ ├── styling.slint │ │ │ ├── switch.slint │ │ │ ├── tableview.slint │ │ │ ├── tabwidget.slint │ │ │ ├── textedit.slint │ │ │ └── time-picker.slint │ │ └── qt/ │ │ ├── button.slint │ │ ├── checkbox.slint │ │ ├── combobox.slint │ │ ├── datepicker.slint │ │ ├── groupbox.slint │ │ ├── internal-scrollview.slint │ │ ├── lineedit.slint │ │ ├── menu.slint │ │ ├── progressindicator.slint │ │ ├── scrollview.slint │ │ ├── slider.slint │ │ ├── spinbox.slint │ │ ├── spinner.slint │ │ ├── std-widgets-impl.slint │ │ ├── std-widgets.slint │ │ ├── style-base.slint │ │ ├── styling.slint │ │ ├── switch.slint │ │ ├── tableview.slint │ │ ├── tabwidget.slint │ │ ├── textedit.slint │ │ └── time-picker.slint │ ├── core/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── accessibility.rs │ │ ├── animations/ │ │ │ └── physics_simulation.rs │ │ ├── animations.rs │ │ ├── api.rs │ │ ├── callbacks.rs │ │ ├── component_factory.rs │ │ ├── context.rs │ │ ├── date_time.rs │ │ ├── future.rs │ │ ├── gdb_pretty_printers.py │ │ ├── graphics/ │ │ │ ├── bitmapfont.rs │ │ │ ├── border_radius.rs │ │ │ ├── boxshadowcache.rs │ │ │ ├── brush.rs │ │ │ ├── color.rs │ │ │ ├── image/ │ │ │ │ ├── cache.rs │ │ │ │ ├── htmlimage.rs │ │ │ │ └── svg.rs │ │ │ ├── image.rs │ │ │ ├── path.rs │ │ │ ├── rendering_metrics_collector.rs │ │ │ ├── wgpu_27.rs │ │ │ └── wgpu_28.rs │ │ ├── graphics.rs │ │ ├── input.rs │ │ ├── item_focus.rs │ │ ├── item_rendering.rs │ │ ├── item_tree.rs │ │ ├── items/ │ │ │ ├── component_container.rs │ │ │ ├── drag_n_drop.rs │ │ │ ├── flickable.rs │ │ │ ├── image.rs │ │ │ ├── input_items.rs │ │ │ ├── path.rs │ │ │ └── text.rs │ │ ├── items.rs │ │ ├── layout.rs │ │ ├── lengths.rs │ │ ├── lib.rs │ │ ├── menus.rs │ │ ├── model/ │ │ │ ├── adapters.rs │ │ │ └── model_peer.rs │ │ ├── model.rs │ │ ├── partial_renderer.rs │ │ ├── platform.rs │ │ ├── properties/ │ │ │ ├── change_tracker.rs │ │ │ ├── ffi.rs │ │ │ └── properties_animations.rs │ │ ├── properties.rs │ │ ├── renderer.rs │ │ ├── rtti.rs │ │ ├── sharedvector.rs │ │ ├── slice.rs │ │ ├── string.rs │ │ ├── styled_text.rs │ │ ├── tests.rs │ │ ├── textlayout/ │ │ │ ├── fragments.rs │ │ │ ├── glyphclusters.rs │ │ │ ├── linebreak_simple.rs │ │ │ ├── linebreak_unicode.rs │ │ │ ├── linebreaker.rs │ │ │ ├── shaping.rs │ │ │ └── sharedparley.rs │ │ ├── textlayout.rs │ │ ├── timers.rs │ │ ├── translations.rs │ │ ├── unsafe_single_threaded.rs │ │ ├── window/ │ │ │ └── popup.rs │ │ └── window.rs │ ├── core-macros/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── lib.rs │ │ ├── link-data.json │ │ └── slint_doc.rs │ ├── interpreter/ │ │ ├── Cargo.toml │ │ ├── api.rs │ │ ├── dynamic_item_tree.rs │ │ ├── dynamic_type.rs │ │ ├── eval.rs │ │ ├── eval_layout.rs │ │ ├── ffi.rs │ │ ├── global_component.rs │ │ ├── highlight.rs │ │ ├── json.rs │ │ ├── lib.rs │ │ ├── live_preview.rs │ │ ├── tests.rs │ │ └── value_model.rs │ └── renderers/ │ ├── femtovg/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── font_cache.rs │ │ ├── images.rs │ │ ├── itemrenderer.rs │ │ ├── lib.rs │ │ ├── opengl.rs │ │ └── wgpu.rs │ ├── skia/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── cached_image.rs │ │ ├── d3d_surface.rs │ │ ├── font_cache.rs │ │ ├── itemrenderer.rs │ │ ├── lib.rs │ │ ├── metal_surface.rs │ │ ├── opengl_surface.rs │ │ ├── software_surface.rs │ │ ├── vulkan_surface.rs │ │ ├── wgpu_27_surface/ │ │ │ ├── dx12.rs │ │ │ ├── metal.rs │ │ │ └── vulkan.rs │ │ ├── wgpu_27_surface.rs │ │ ├── wgpu_28_surface/ │ │ │ ├── dx12.rs │ │ │ ├── metal.rs │ │ │ └── vulkan.rs │ │ └── wgpu_28_surface.rs │ └── software/ │ ├── Cargo.toml │ ├── README.md │ ├── draw_functions.rs │ ├── fixed.rs │ ├── fonts/ │ │ ├── pixelfont.rs │ │ ├── systemfonts.rs │ │ └── vectorfont.rs │ ├── fonts.rs │ ├── lib.rs │ ├── minimal_software_window.rs │ ├── path.rs │ ├── scene.rs │ └── target_pixel_buffer.rs ├── knip.json ├── logo/ │ ├── README.md │ └── slint-logo.icns ├── package.json ├── pnpm-workspace.yaml ├── rustfmt.toml ├── scripts/ │ ├── build_for_ios_with_cargo.bash │ ├── pre-commit-clippy.sh │ ├── prepare_binary_package.sh │ ├── prepare_vscode_nightly.sh │ ├── publish.sh │ └── run_clippy.sh ├── tests/ │ ├── cases/ │ │ ├── absolute_coords.slint │ │ ├── absolute_coords2.slint │ │ ├── accessibility/ │ │ │ ├── accessible_id.slint │ │ │ ├── actions.slint │ │ │ └── materialized.slint │ │ ├── bindings/ │ │ │ ├── animated_default_geometry.slint │ │ │ ├── change_sub_property.slint │ │ │ ├── change_sub_property2.slint │ │ │ ├── change_sub_property_indirection.slint │ │ │ ├── change_sub_property_indirection_issue2185.slint │ │ │ ├── issue_1026_opacity_alias.slint │ │ │ ├── issue_10704_two_way_if.slint │ │ │ ├── issue_345_opacity_animation.slint │ │ │ ├── issue_385_default_geom.slint │ │ │ ├── multiple_two_way.slint │ │ │ ├── override.slint │ │ │ ├── two_way_binding.slint │ │ │ ├── two_way_binding2.slint │ │ │ ├── two_way_binding_animation.slint │ │ │ ├── two_way_binding_extra.slint │ │ │ ├── two_way_binding_structs.slint │ │ │ ├── two_way_binding_structs2.slint │ │ │ ├── two_way_bug.slint │ │ │ ├── two_way_deep.slint │ │ │ ├── two_way_global.slint │ │ │ ├── two_way_model.slint │ │ │ ├── two_way_priority.slint │ │ │ ├── two_way_priority_default.slint │ │ │ └── two_way_simple.slint │ │ ├── callbacks/ │ │ │ ├── callback_alias.slint │ │ │ ├── callback_conn.slint │ │ │ ├── callback_name_conflicts.slint │ │ │ ├── functions.slint │ │ │ ├── handler.slint │ │ │ ├── handler_with_arg.slint │ │ │ ├── init.slint │ │ │ ├── init_access_base_compo.slint │ │ │ ├── init_layout.slint │ │ │ ├── init_listview.slint │ │ │ ├── init_popup.slint │ │ │ └── return_value.slint │ │ ├── children/ │ │ │ ├── children_in_clip.slint │ │ │ ├── children_in_row.slint │ │ │ ├── children_placeholder.slint │ │ │ ├── children_placeholder_5865.slint │ │ │ ├── children_placeholder_three_levels.slint │ │ │ ├── children_placeholder_two_levels.slint │ │ │ ├── timer_next_to_children.slint │ │ │ └── zorder.slint │ │ ├── conditional/ │ │ │ ├── cast.slint │ │ │ ├── expr.slint │ │ │ ├── stm.slint │ │ │ └── stm2.slint │ │ ├── crashes/ │ │ │ ├── access_elided_prop.slint │ │ │ ├── access_moved_propdecl.slint │ │ │ ├── alias_in_if.slint │ │ │ ├── empty_listview.slint │ │ │ ├── issue1113_popup_in_repeater.slint │ │ │ ├── issue1267_opacity_in_layout.slint │ │ │ ├── issue1271_set_in_if.slint │ │ │ ├── issue1327_inlined_state_property.slint │ │ │ ├── issue1659_combobox_in_tab.slint │ │ │ ├── issue173_uninitialized_globals.slint │ │ │ ├── issue3589_layout_click_deletes_item.slint │ │ │ ├── issue5259_popup_in_row.slint │ │ │ ├── issue6721_struct_convert.slint │ │ │ ├── issue9141_path_commands.slint │ │ │ ├── issue_1009_const_alias.slint │ │ │ ├── issue_10927_struct_field_in_for_if.slint │ │ │ ├── issue_1233_listview_const_shadow.slint │ │ │ ├── issue_2274_const_state_crash.slint │ │ │ ├── issue_4002_width_alias.slint │ │ │ ├── issue_422_enclosing_component.slint │ │ │ ├── issue_6984_cross_global.slint │ │ │ ├── issue_7316_changed_visibility.slint │ │ │ ├── issue_983_listview_const_width.slint │ │ │ ├── layout_deleted_item.slint │ │ │ ├── layout_if_in_component.slint │ │ │ ├── link_two_way_geometry_if.slint │ │ │ ├── optimized_item_shadow.slint │ │ │ ├── optimized_popup_parent.slint │ │ │ ├── shadow_fixed_width.slint │ │ │ ├── subsubcomponent.slint │ │ │ ├── subsubcomponent2.slint │ │ │ └── unused_callback_alias.slint │ │ ├── elements/ │ │ │ ├── component_container.slint │ │ │ ├── component_container_component.slint │ │ │ ├── component_container_init.slint │ │ │ ├── component_container_size.slint │ │ │ ├── contextmenu_delete.slint │ │ │ ├── contextmenu_for.slint │ │ │ ├── contextmenu_in_repeater.slint │ │ │ ├── deprecated_rotation-origin.slint │ │ │ ├── dialog.slint │ │ │ ├── dragarea_droparea.slint │ │ │ ├── element_ref_on_root_compo.slint │ │ │ ├── event_rotation.slint │ │ │ ├── event_scaling.slint │ │ │ ├── flickable.slint │ │ │ ├── flickable2.slint │ │ │ ├── flickable3.slint │ │ │ ├── flickable_focus.slint │ │ │ ├── flickable_in_flickable.slint │ │ │ ├── flickable_stay_in_bounds.slint │ │ │ ├── image.slint │ │ │ ├── image_geometry.slint │ │ │ ├── key_binding.slint │ │ │ ├── listview-millions.slint │ │ │ ├── listview.slint │ │ │ ├── listview_click_open.slint │ │ │ ├── listview_interactive_animation.slint │ │ │ ├── listview_interactive_animation_xy_animation.slint │ │ │ ├── menubar_condition.slint │ │ │ ├── menubar_empty.slint │ │ │ ├── menubar_for.slint │ │ │ ├── path.slint │ │ │ ├── path_fit.slint │ │ │ ├── pinchgesturehandler.slint │ │ │ ├── pinchgesturehandler_nested.slint │ │ │ ├── popupwindow.slint │ │ │ ├── popupwindow_close.slint │ │ │ ├── popupwindow_close_policy.slint │ │ │ ├── popupwindow_close_policy_inherited.slint │ │ │ ├── popupwindow_context.slint │ │ │ ├── popupwindow_cursor.slint │ │ │ ├── popupwindow_inherits.slint │ │ │ ├── popupwindow_nested.slint │ │ │ ├── popupwindow_nested_close-on-click.slint │ │ │ ├── popupwindow_open_twice.slint │ │ │ ├── scaling2.slint │ │ │ ├── styledtext.slint │ │ │ ├── swipegesturehandler.slint │ │ │ ├── swipegesturehandler2.slint │ │ │ ├── tabwidget.slint │ │ │ ├── timer.slint │ │ │ ├── timer_start_stop_restart.slint │ │ │ ├── togglebutton.slint │ │ │ ├── toucharea.slint │ │ │ └── toucharea_doubleclick.slint │ │ ├── examples/ │ │ │ ├── box_shadow_use.slint │ │ │ ├── color.slint │ │ │ ├── colored_image.slint │ │ │ ├── hello.slint │ │ │ ├── image_fit.slint │ │ │ ├── image_rendering.slint │ │ │ ├── key_press.slint │ │ │ ├── layer.slint │ │ │ ├── opacity.slint │ │ │ ├── path_fill_rule.slint │ │ │ ├── path_line_join.slint │ │ │ ├── path_viewbox.slint │ │ │ ├── plusminus.slint │ │ │ ├── rectangle_clip.slint │ │ │ ├── rotate.slint │ │ │ ├── sample_component.slint │ │ │ └── window_default_font.slint │ │ ├── exports/ │ │ │ ├── cpp_namespace.slint │ │ │ ├── export_root.slint │ │ │ ├── multiple_components.slint │ │ │ └── named_exports.slint │ │ ├── expr/ │ │ │ ├── abs.slint │ │ │ ├── acos.slint │ │ │ ├── animation_tick.slint │ │ │ ├── arithmetic.slint │ │ │ ├── asin.slint │ │ │ ├── atan.slint │ │ │ ├── atan2.slint │ │ │ ├── ceil.slint │ │ │ ├── clamp.slint │ │ │ ├── comparison.slint │ │ │ ├── cos.slint │ │ │ ├── debug.slint │ │ │ ├── exp.slint │ │ │ ├── floor.slint │ │ │ ├── let.slint │ │ │ ├── ln.slint │ │ │ ├── log.slint │ │ │ ├── math.slint │ │ │ ├── minmax.slint │ │ │ ├── mod.slint │ │ │ ├── pow.slint │ │ │ ├── return.slint │ │ │ ├── return2.slint │ │ │ ├── return3.slint │ │ │ ├── round.slint │ │ │ ├── sign.slint │ │ │ ├── sin.slint │ │ │ ├── sqrt.slint │ │ │ ├── string_concatenation.slint │ │ │ ├── string_template.slint │ │ │ ├── tan.slint │ │ │ ├── to-fixed.slint │ │ │ ├── to-precision.slint │ │ │ ├── tr.slint │ │ │ └── trigo.slint │ │ ├── focus/ │ │ │ ├── 7058_scrolled_clip.slint │ │ │ ├── clear_focus.slint │ │ │ ├── event_propagation.slint │ │ │ ├── event_propagation_2.slint │ │ │ ├── event_propagation_3.slint │ │ │ ├── focus_change.slint │ │ │ ├── focus_change_event.slint │ │ │ ├── focus_change_event_reason.slint │ │ │ ├── focus_change_subcompo.slint │ │ │ ├── focus_change_through_signal.slint │ │ │ ├── focus_gained_and_lost.slint │ │ │ ├── focus_policy.slint │ │ │ ├── forward.slint │ │ │ ├── hidden_programmatic.slint │ │ │ ├── initial_focus.slint │ │ │ ├── initial_focus_through_component.slint │ │ │ ├── initial_focus_through_layout.slint │ │ │ ├── keyboard_focus.slint │ │ │ ├── keyboard_focus2.slint │ │ │ ├── keyboard_focus_capture.slint │ │ │ ├── listview-hidden.slint │ │ │ ├── popupwindow_focus.slint │ │ │ ├── text-input-focused-parent-deleted.slint │ │ │ └── text-input-focused.slint │ │ ├── globals/ │ │ │ ├── alias_to_global.slint │ │ │ ├── global_accessor_api.slint │ │ │ ├── global_alias.slint │ │ │ ├── global_binding.slint │ │ │ ├── global_binding_const.slint │ │ │ ├── global_callback.slint │ │ │ ├── global_depends_on_global.slint │ │ │ └── issue_2064_two_way_default_value.slint │ │ ├── imports/ │ │ │ ├── duplicated_name.slint │ │ │ ├── exported_component.slint │ │ │ ├── external_globals.slint │ │ │ ├── external_globals_nameclash.slint │ │ │ ├── external_interfaces.slint │ │ │ ├── external_interfaces_as.slint │ │ │ ├── external_structs.slint │ │ │ ├── external_type.slint │ │ │ ├── import_multi.slint │ │ │ ├── just_import.slint │ │ │ ├── library.slint │ │ │ ├── reexport.slint │ │ │ └── reexport2.slint │ │ ├── input/ │ │ │ ├── clip_mouse.slint │ │ │ ├── scroll-event.slint │ │ │ └── visible_mouse.slint │ │ ├── interfaces/ │ │ │ ├── callbacks.slint │ │ │ ├── implements.slint │ │ │ ├── implements_defaults.slint │ │ │ ├── implements_inherits.slint │ │ │ └── uses.slint │ │ ├── issues/ │ │ │ ├── issue_10321_tab_focus_flickable.slint │ │ │ ├── issue_10544_model_gaps.slint │ │ │ ├── issue_10923_popupwindow-reshow.slint │ │ │ ├── issue_1837_percent_comparison.slint │ │ │ ├── issue_1846_visibility_in_for.slint │ │ │ ├── issue_2483_access_spacing.slint │ │ │ ├── issue_2598_listview_reuse_elem.slint │ │ │ ├── issue_2608_canon_img_path.slint │ │ │ ├── issue_2717_has-hover.slint │ │ │ ├── issue_2780_listview_crash.slint │ │ │ ├── issue_3107_if_optimized_rect.slint │ │ │ ├── issue_3318_window_size.slint │ │ │ ├── issue_3561_return_image.slint │ │ │ ├── issue_4072_optimized_alias.slint │ │ │ ├── issue_4163_flickable_parent_percent.slint │ │ │ ├── issue_4241_alias-const.slint │ │ │ ├── issue_4884_show_popup.slint │ │ │ ├── issue_4942_no_else_value.slint │ │ │ ├── issue_5146_init_animated.slint │ │ │ ├── issue_5260_init_inlined.slint │ │ │ ├── issue_5375_children_in_popup.slint │ │ │ ├── issue_5500_panics_uninitialized_animation.slint │ │ │ ├── issue_5883.slint │ │ │ ├── issue_5887_struct_f64-f32.slint │ │ │ ├── issue_6443_mouse-cursor.slint │ │ │ ├── issue_6616_optimized_property_read.slint │ │ │ ├── issue_6651_export_sort.slint │ │ │ ├── issue_7811_inline_stack_overflow.slint │ │ │ ├── issue_7848_timer_depends_model_data.slint │ │ │ ├── issue_7864_condition_return_void.slint │ │ │ ├── issue_8144_optimized-used.slint │ │ │ ├── issue_8710_popup_show_from_changed.slint │ │ │ ├── issue_9209_array_in_struct_conversion.slint │ │ │ ├── issue_9546.slint │ │ │ ├── issue_9722_popup_panic.slint │ │ │ ├── issue_9765-two-way-binding-to-point.slint │ │ │ ├── issue_9954_big-ifs.slint │ │ │ ├── member_fun_from_parent.slint │ │ │ └── name_conflicts.slint │ │ ├── layout/ │ │ │ ├── box_alignment.slint │ │ │ ├── box_percentages.slint │ │ │ ├── box_preferred_size.slint │ │ │ ├── default_fill.slint │ │ │ ├── empty_layout.slint │ │ │ ├── flexbox-inside-h-inside-v.slint │ │ │ ├── flexbox-preferred-width.slint │ │ │ ├── flexbox_align_content.slint │ │ │ ├── flexbox_align_items.slint │ │ │ ├── flexbox_alignment.slint │ │ │ ├── flexbox_column_multiple_columns.slint │ │ │ ├── flexbox_column_reverse.slint │ │ │ ├── flexbox_column_spacing_and_padding.slint │ │ │ ├── flexbox_column_varying_widths.slint │ │ │ ├── flexbox_in_vertical_layout.slint │ │ │ ├── flexbox_min_width.slint │ │ │ ├── flexbox_multiple_rows.slint │ │ │ ├── flexbox_oversized.slint │ │ │ ├── flexbox_repeater.slint │ │ │ ├── flexbox_repeater_column.slint │ │ │ ├── flexbox_repeater_mixed.slint │ │ │ ├── flexbox_row_reverse.slint │ │ │ ├── flexbox_runtime_direction.slint │ │ │ ├── flexbox_spacing_and_padding.slint │ │ │ ├── flexbox_varying_heights.slint │ │ │ ├── flickable_in_layout.slint │ │ │ ├── geometry_center_by_default.slint │ │ │ ├── grid2.slint │ │ │ ├── grid_conditional_row_colspan.slint │ │ │ ├── grid_empty_row.slint │ │ │ ├── grid_fixed_size.slint │ │ │ ├── grid_min_max.slint │ │ │ ├── grid_nested_for_irregular.slint │ │ │ ├── grid_padding.slint │ │ │ ├── grid_preferred_size.slint │ │ │ ├── grid_repeated_row_with_inner_if.slint │ │ │ ├── grid_simple.slint │ │ │ ├── grid_spacing.slint │ │ │ ├── grid_span.slint │ │ │ ├── grid_variable_row_col.slint │ │ │ ├── grid_with_custom_row.slint │ │ │ ├── grid_with_model_in_row.slint │ │ │ ├── grid_with_nested_for_in_row.slint │ │ │ ├── grid_with_repeated_rows.slint │ │ │ ├── grid_with_x_y_model.slint │ │ │ ├── grid_within_for.slint │ │ │ ├── height_for_width.slint │ │ │ ├── horizontal_for.slint │ │ │ ├── horizontal_layout_percentages.slint │ │ │ ├── horizontal_layout_percentages_multi_element.slint │ │ │ ├── horizontal_sizes.slint │ │ │ ├── issue6285_auto_explicit_restrictions.slint │ │ │ ├── issue8091_overriden_padding.slint │ │ │ ├── issue_1057_listview_subcomponent_height.slint │ │ │ ├── issue_1072_opacity_geometry.slint │ │ │ ├── issue_140.slint │ │ │ ├── issue_147_for_explicit_size.slint │ │ │ ├── issue_147_for_explicit_size_merge.slint │ │ │ ├── issue_149_nested_for.slint │ │ │ ├── issue_160_box_min_max.slint │ │ │ ├── issue_167_if_relayout.slint │ │ │ ├── issue_2537_visible_in_listview.slint │ │ │ ├── issue_407_for_layout_in_flickable.slint │ │ │ ├── issue_553_materialized_init.slint │ │ │ ├── issue_7761_animated_pc_size.slint │ │ │ ├── issue_783_constraint_from_children.slint │ │ │ ├── materialized_minmax.slint │ │ │ ├── nested_boxes.slint │ │ │ ├── nested_grid_1.slint │ │ │ ├── nested_grid_2.slint │ │ │ ├── nested_grid_minmax.slint │ │ │ ├── opacity_in_layout.slint │ │ │ ├── override_from_parent.slint │ │ │ ├── special_default_geometry.slint │ │ │ ├── text_no_wrap.slint │ │ │ ├── text_preferred_size.slint │ │ │ ├── vertical_if.slint │ │ │ ├── vertical_layout_percentages.slint │ │ │ ├── vertical_layout_percentages_multi_element.slint │ │ │ ├── window_fixed.slint │ │ │ └── window_preferred.slint │ │ ├── lookup/ │ │ │ ├── global_lookup.slint │ │ │ ├── id_lookup.slint │ │ │ ├── renamed_elements.slint │ │ │ └── rust_names.slint │ │ ├── models/ │ │ │ ├── array.slint │ │ │ ├── assign_equal_model.slint │ │ │ ├── delete_from_clicked.slint │ │ │ ├── dirty_model.slint │ │ │ ├── for.slint │ │ │ ├── if.slint │ │ │ ├── if_cond_property.slint │ │ │ ├── if_dirty.slint │ │ │ ├── if_lookup.slint │ │ │ ├── indirect_model_changes.slint │ │ │ ├── init_recursion.slint │ │ │ ├── issue_4961_model_index_property.slint │ │ │ ├── listview_model_change.slint │ │ │ ├── model.slint │ │ │ ├── model_in_struct.slint │ │ │ ├── negative_intmodel.slint │ │ │ ├── scrolled_model.slint │ │ │ ├── write_to_model.slint │ │ │ ├── write_to_model_listview.slint │ │ │ └── write_to_model_sub_component.slint │ │ ├── platform.slint │ │ ├── properties/ │ │ │ ├── animation_bindings_reactive.slint │ │ │ ├── animation_from_click.slint │ │ │ ├── animation_from_click_in_repeated_4741.slint │ │ │ ├── animation_merging.slint │ │ │ ├── animation_props_depends.slint │ │ │ ├── border_radius.slint │ │ │ ├── changes.slint │ │ │ ├── changes_alias.slint │ │ │ ├── changes_loop.slint │ │ │ ├── dashes.slint │ │ │ ├── delayed_transitions.slint │ │ │ ├── issue1237_states_visible.slint │ │ │ ├── issue5038_state_in_base.slint │ │ │ ├── property_animation.slint │ │ │ ├── property_animation_restart.slint │ │ │ ├── states.slint │ │ │ ├── states_with_animation.slint │ │ │ ├── transitions.slint │ │ │ └── transitions2.slint │ │ ├── simple.slint │ │ ├── subcomponents/ │ │ │ ├── nested_repeater.slint │ │ │ ├── no_children.slint │ │ │ └── repeaters.slint │ │ ├── test_infrastructure.slint │ │ ├── testing/ │ │ │ ├── dynamic_components.slint │ │ │ └── find_by_element_id_or_type.slint │ │ ├── text/ │ │ │ ├── componentcontainer_font_size_propagation.slint │ │ │ ├── control_keys_input.slint │ │ │ ├── cursor_move.slint │ │ │ ├── cursor_move_grapheme.slint │ │ │ ├── custom_font.slint │ │ │ ├── cut.slint │ │ │ ├── default_color.slint │ │ │ ├── font_size_propagation.slint │ │ │ ├── input_type.slint │ │ │ ├── input_type_decimal.slint │ │ │ ├── input_type_number.slint │ │ │ ├── key_repeat.slint │ │ │ ├── keyboard_modifiers.slint │ │ │ ├── metrics.slint │ │ │ ├── select_all.slint │ │ │ ├── select_double_click.slint │ │ │ ├── select_dribble_click.slint │ │ │ ├── surrogate_cursor.slint │ │ │ ├── text_change.slint │ │ │ ├── text_property_change.slint │ │ │ ├── textinput_functions.slint │ │ │ ├── textinput_keyevents.slint │ │ │ └── undo_redo.slint │ │ ├── translations/ │ │ │ ├── bundle.slint │ │ │ ├── bundle_no_context.slint │ │ │ ├── fr/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ └── bundle.po │ │ │ └── up/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── bundle.po │ │ │ └── bundle_no_context.po │ │ ├── types/ │ │ │ ├── angles.slint │ │ │ ├── array.slint │ │ │ ├── array_of_array.slint │ │ │ ├── bool.slint │ │ │ ├── brush.slint │ │ │ ├── color.slint │ │ │ ├── conic_gradient.slint │ │ │ ├── cubic-bezier.slint │ │ │ ├── duration.slint │ │ │ ├── easing.slint │ │ │ ├── enum_compare.slint │ │ │ ├── enums.slint │ │ │ ├── functions.slint │ │ │ ├── gradients.slint │ │ │ ├── int_conversion.slint │ │ │ ├── keys.slint │ │ │ ├── length.slint │ │ │ ├── nested_struct.slint │ │ │ ├── object.slint │ │ │ ├── percent.slint │ │ │ ├── relative_lengths.slint │ │ │ ├── rem.slint │ │ │ ├── resource.slint │ │ │ ├── string.slint │ │ │ ├── string_character_count.slint │ │ │ ├── string_to_float.slint │ │ │ ├── string_to_lowercase.slint │ │ │ ├── string_to_uppercase.slint │ │ │ ├── structs.slint │ │ │ ├── structs2.slint │ │ │ ├── structs_keyword.slint │ │ │ └── styled_text.slint │ │ └── widgets/ │ │ ├── about.slint │ │ ├── button.slint │ │ ├── checkbox.slint │ │ ├── combobox.slint │ │ ├── contextmenu.slint │ │ ├── datepicker.slint │ │ ├── groupbox.slint │ │ ├── lineedit.slint │ │ ├── listview.slint │ │ ├── menubar.slint │ │ ├── scroll_event_propagation.slint │ │ ├── scrollview.slint │ │ ├── slider_basic.slint │ │ ├── slider_default_value.slint │ │ ├── spinbox_basic.slint │ │ ├── spinbox_default_value.slint │ │ ├── switch.slint │ │ ├── tableview.slint │ │ ├── tabwidget.slint │ │ ├── textedit.slint │ │ └── timepicker.slint │ ├── doctests/ │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── main.rs │ ├── driver/ │ │ ├── cpp/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── cppdriver.rs │ │ │ └── main.rs │ │ ├── driverlib/ │ │ │ ├── Cargo.toml │ │ │ └── lib.rs │ │ ├── interpreter/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── interpreter.rs │ │ │ └── main.rs │ │ ├── nodejs/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── main.rs │ │ │ └── nodejs.rs │ │ ├── python/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── main.rs │ │ │ └── python.rs │ │ └── rust/ │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── main.rs │ │ ├── template.rs │ │ └── tests/ │ │ ├── 7guis.rs │ │ ├── accessibility.rs │ │ ├── bindings.rs │ │ ├── callbacks.rs │ │ ├── children.rs │ │ ├── conditional.rs │ │ ├── crashes.rs │ │ ├── elements.rs │ │ ├── examples.rs │ │ ├── exports.rs │ │ ├── expr.rs │ │ ├── focus.rs │ │ ├── globals.rs │ │ ├── imports.rs │ │ ├── input.rs │ │ ├── interfaces.rs │ │ ├── issues.rs │ │ ├── layout.rs │ │ ├── lookup.rs │ │ ├── models.rs │ │ ├── properties.rs │ │ ├── subcomponents.rs │ │ ├── testing.rs │ │ ├── text.rs │ │ ├── translations.rs │ │ ├── types.rs │ │ ├── widgets-cosmic.rs │ │ ├── widgets-cupertino.rs │ │ ├── widgets-fluent.rs │ │ ├── widgets-material.rs │ │ └── widgets-qt.rs │ ├── helper_components/ │ │ ├── export_globals.slint │ │ ├── export_interfaces.slint │ │ ├── export_structs.slint │ │ ├── issue_6651_implicit_export.slint │ │ ├── main_window.slint │ │ ├── re_export.slint │ │ ├── re_export2.slint │ │ ├── re_export_all.slint │ │ └── test_button.slint │ ├── manual/ │ │ ├── font-metrics.slint │ │ ├── module-builds/ │ │ │ ├── app/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── build.rs │ │ │ │ ├── src/ │ │ │ │ │ └── main.rs │ │ │ │ └── ui/ │ │ │ │ └── app-window.slint │ │ │ ├── blogica/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── build.rs │ │ │ │ ├── src/ │ │ │ │ │ └── lib.rs │ │ │ │ └── ui/ │ │ │ │ └── blogica.slint │ │ │ └── blogicb/ │ │ │ ├── Cargo.toml │ │ │ ├── build.rs │ │ │ ├── src/ │ │ │ │ └── lib.rs │ │ │ └── ui/ │ │ │ └── blogicb.slint │ │ ├── opacity_inheritance.slint │ │ ├── partial-rendering-circus.slint │ │ ├── path-stroke-cap.slint │ │ └── windowattributes/ │ │ ├── Cargo.toml │ │ └── main.rs │ ├── run_tests.sh │ └── screenshots/ │ ├── Cargo.toml │ ├── build.rs │ ├── cases/ │ │ ├── basic/ │ │ │ ├── border.slint │ │ │ ├── conic-gradients.slint │ │ │ ├── linear-gradients.slint │ │ │ ├── nested-window-item.slint │ │ │ ├── opacity-clip.slint │ │ │ ├── radial-gradients.slint │ │ │ ├── rgb.slint │ │ │ ├── text_features.slint │ │ │ └── translucent-background.slint │ │ ├── image/ │ │ │ ├── border-image-repeat.slint │ │ │ ├── border-image.slint │ │ │ ├── border-image2.slint │ │ │ ├── image-repeat.slint │ │ │ ├── images-alignment.slint │ │ │ ├── images-scale_factor.slint │ │ │ └── images.slint │ │ ├── path/ │ │ │ └── path.slint │ │ └── text/ │ │ ├── set-selection-offsets.slint │ │ ├── styled.slint │ │ ├── text-clipped.slint │ │ ├── text-elided.slint │ │ ├── text-input-selection.slint │ │ ├── text-input.slint │ │ └── text.slint │ ├── fonts/ │ │ ├── .gitignore │ │ ├── NotoSans-Bold.ttf.license │ │ ├── NotoSans-Italic.ttf.license │ │ ├── NotoSans-Light.ttf.license │ │ ├── NotoSans-Regular.ttf.license │ │ └── convert.sh │ ├── main.rs │ ├── skia.rs │ ├── software.rs │ └── testing.rs ├── tools/ │ ├── compiler/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── main.rs │ │ └── pyproject.toml │ ├── docsnapper/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── headless.rs │ │ └── main.rs │ ├── figma-inspector/ │ │ ├── .gitignore │ │ ├── PUBLISH.md │ │ ├── README.md │ │ ├── backend/ │ │ │ ├── code.ts │ │ │ ├── tsconfig.json │ │ │ └── utils/ │ │ │ ├── code-utils.ts │ │ │ ├── export-variables.ts │ │ │ └── property-parsing.ts │ │ ├── biome.json │ │ ├── figma.config.ts │ │ ├── index.html │ │ ├── package.json │ │ ├── public-zip/ │ │ │ └── readme.txt │ │ ├── shared/ │ │ │ └── universals.d.ts │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── DialogFrame.tsx │ │ │ │ └── snippet/ │ │ │ │ ├── CodeSnippet.tsx │ │ │ │ ├── dark-theme.json │ │ │ │ └── light-theme.json │ │ │ ├── globals.d.ts │ │ │ ├── index-react.tsx │ │ │ ├── main.css │ │ │ ├── main.tsx │ │ │ ├── utils/ │ │ │ │ ├── bolt-utils.ts │ │ │ │ ├── store.ts │ │ │ │ └── utils.ts │ │ │ └── vite-env.d.ts │ │ ├── tests/ │ │ │ ├── export-variables.test.ts │ │ │ ├── figma_output.json │ │ │ └── property-parsing.test.ts │ │ ├── tsconfig.json │ │ ├── vite.config.code.ts │ │ └── vite.config.ts │ ├── figma_import/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── figmatypes.rs │ │ ├── main.rs │ │ └── rendered.rs │ ├── lsp/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── common/ │ │ │ ├── component_catalog.rs │ │ │ ├── document_cache.rs │ │ │ ├── rename_component.rs │ │ │ ├── rename_element_id.rs │ │ │ ├── test.rs │ │ │ ├── text_edit.rs │ │ │ └── token_info.rs │ │ ├── common.rs │ │ ├── fmt/ │ │ │ ├── README.md │ │ │ ├── fmt.rs │ │ │ ├── tool.rs │ │ │ └── writer.rs │ │ ├── fmt.rs │ │ ├── language/ │ │ │ ├── completion.rs │ │ │ ├── formatting.rs │ │ │ ├── goto.rs │ │ │ ├── hover.rs │ │ │ ├── semantic_tokens.rs │ │ │ ├── signature_help.rs │ │ │ └── test.rs │ │ ├── language.rs │ │ ├── main.rs │ │ ├── preview/ │ │ │ ├── connector/ │ │ │ │ ├── native.rs │ │ │ │ └── wasm.rs │ │ │ ├── connector.rs │ │ │ ├── debug.rs │ │ │ ├── drop_location.rs │ │ │ ├── element_selection.rs │ │ │ ├── eval.rs │ │ │ ├── ext.rs │ │ │ ├── outline.rs │ │ │ ├── preview_data.rs │ │ │ ├── properties.rs │ │ │ ├── ui/ │ │ │ │ ├── brushes.rs │ │ │ │ ├── log_messages.rs │ │ │ │ ├── palette.rs │ │ │ │ ├── property_view.rs │ │ │ │ ├── recent_colors.rs │ │ │ │ └── search_model.rs │ │ │ ├── ui.rs │ │ │ └── undo_redo.rs │ │ ├── preview.rs │ │ ├── ui/ │ │ │ ├── api.slint │ │ │ ├── assets/ │ │ │ │ ├── Inter-VariableFont.ttf.license │ │ │ │ ├── SourceCodePro-Medium.ttf.license │ │ │ │ └── dial.svg.license │ │ │ ├── components/ │ │ │ │ ├── body-strong-text.slint │ │ │ │ ├── body-text.slint │ │ │ │ ├── console-panel.slint │ │ │ │ ├── diagnostics-overlay.slint │ │ │ │ ├── draggable-panel.slint │ │ │ │ ├── expandable-group.slint │ │ │ │ ├── expandable-listview.slint │ │ │ │ ├── group.slint │ │ │ │ ├── header-text.slint │ │ │ │ ├── icon-button.slint │ │ │ │ ├── layout-helpers.slint │ │ │ │ ├── out-of-date-box.slint │ │ │ │ ├── property-widgets.slint │ │ │ │ ├── resizer.slint │ │ │ │ ├── selection-popup.slint │ │ │ │ ├── spreadsheet-dialog.slint │ │ │ │ ├── spreadsheet.slint │ │ │ │ ├── state-layer.slint │ │ │ │ ├── status-line.slint │ │ │ │ ├── styling.slint │ │ │ │ └── widgets/ │ │ │ │ ├── basics.slint │ │ │ │ ├── boolean-widget.slint │ │ │ │ ├── brush-helpers.slint │ │ │ │ ├── code-widget.slint │ │ │ │ ├── color-basics.slint │ │ │ │ ├── enum-widget.slint │ │ │ │ ├── float-widget.slint │ │ │ │ ├── floating-brush-picker-widget.slint │ │ │ │ ├── floating-brush-sections/ │ │ │ │ │ ├── color-mode-and-apply.slint │ │ │ │ │ ├── css-color-ui.slint │ │ │ │ │ ├── gradient-ui.slint │ │ │ │ │ └── palettes.slint │ │ │ │ ├── gradient-basics.slint │ │ │ │ ├── inline-brush-widget.slint │ │ │ │ ├── integer-widget.slint │ │ │ │ ├── json-widget.slint │ │ │ │ ├── multi-value-widget.slint │ │ │ │ ├── string-widget.slint │ │ │ │ └── widget-helpers.slint │ │ │ ├── main.slint │ │ │ ├── views/ │ │ │ │ ├── header-view.slint │ │ │ │ ├── library-view.slint │ │ │ │ ├── outline-view.slint │ │ │ │ ├── preview-data-view.slint │ │ │ │ ├── preview-view.slint │ │ │ │ └── property-view.slint │ │ │ └── windowglobal.slint │ │ ├── util.rs │ │ └── wasm_main.rs │ ├── slintpad/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── biome.json │ │ ├── index.html │ │ ├── package.json │ │ ├── playwright.config.ts │ │ ├── preview.html │ │ ├── src/ │ │ │ ├── dialogs.ts │ │ │ ├── editor_widget.ts │ │ │ ├── github.ts │ │ │ ├── highlighting.ts │ │ │ ├── index.ts │ │ │ ├── lsp.ts │ │ │ ├── preview.ts │ │ │ ├── preview_widget.ts │ │ │ ├── proxy.ts │ │ │ ├── tsconfig.json │ │ │ ├── types.ts │ │ │ └── worker/ │ │ │ ├── lsp_worker.ts │ │ │ ├── monaco_worker.mjs │ │ │ ├── tsconfig.json │ │ │ └── types.ts │ │ ├── styles/ │ │ │ ├── colors.css │ │ │ ├── content.css │ │ │ └── index.css │ │ ├── tests/ │ │ │ └── smoke-test.spec.ts │ │ ├── tsconfig.default.json │ │ ├── tsconfig.json │ │ └── vite.config.mts │ ├── tr-extractor/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── main.rs │ ├── updater/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── experiments/ │ │ │ ├── geometry_changes.rs │ │ │ ├── input_output_properties.rs │ │ │ ├── lookup_changes.rs │ │ │ ├── new_component_declaration.rs │ │ │ ├── purity.rs │ │ │ └── transitions.rs │ │ ├── main.rs │ │ └── transforms/ │ │ └── renames.rs │ └── viewer/ │ ├── Cargo.toml │ ├── README.md │ └── main.rs ├── ui-libraries/ │ └── material/ │ ├── .gitignore │ ├── docs/ │ │ ├── MIGRATION.md │ │ ├── README.md │ │ ├── astro.config.ts │ │ ├── biome.json │ │ ├── ec.config.mjs │ │ ├── package.json │ │ ├── playwright.config.ts │ │ ├── public/ │ │ │ └── robots.txt │ │ ├── src/ │ │ │ ├── assets/ │ │ │ │ └── styles/ │ │ │ │ ├── custom.css │ │ │ │ ├── iconfont.css │ │ │ │ ├── tailwind.css │ │ │ │ └── theme.css │ │ │ ├── components/ │ │ │ │ ├── CustomStyles.astro │ │ │ │ ├── Favicons.astro │ │ │ │ ├── Header.astro │ │ │ │ ├── Logo.astro │ │ │ │ ├── common/ │ │ │ │ │ ├── ApplyColorMode.astro │ │ │ │ │ ├── BasicScripts.astro │ │ │ │ │ ├── CommonMeta.astro │ │ │ │ │ ├── Image.astro │ │ │ │ │ ├── Metadata.astro │ │ │ │ │ ├── SiteVerification.astro │ │ │ │ │ ├── ToggleMenu.astro │ │ │ │ │ └── ToggleTheme.astro │ │ │ │ ├── ui/ │ │ │ │ │ ├── Background.astro │ │ │ │ │ ├── Button.astro │ │ │ │ │ ├── Headline.astro │ │ │ │ │ ├── ItemGrid.astro │ │ │ │ │ ├── ItemGrid2.astro │ │ │ │ │ └── WidgetWrapper.astro │ │ │ │ └── widgets/ │ │ │ │ ├── Announcement.astro │ │ │ │ ├── CallToAction.astro │ │ │ │ ├── Features.astro │ │ │ │ ├── Features2.astro │ │ │ │ ├── Footer.astro │ │ │ │ ├── Header.astro │ │ │ │ ├── Hero.astro │ │ │ │ └── Hero2.astro │ │ │ ├── config.yaml │ │ │ ├── content/ │ │ │ │ ├── collections/ │ │ │ │ │ ├── enums/ │ │ │ │ │ │ ├── CheckState.md │ │ │ │ │ │ ├── FABStyle.md │ │ │ │ │ │ ├── LayoutAlignment.md │ │ │ │ │ │ └── ScrollBarPolicy.md │ │ │ │ │ └── structs/ │ │ │ │ │ ├── IconButtonItem.md │ │ │ │ │ ├── ListItem.md │ │ │ │ │ ├── MenuItem.md │ │ │ │ │ ├── NavigationGroup.md │ │ │ │ │ ├── NavigationItem.md │ │ │ │ │ ├── SegmentedItem.md │ │ │ │ │ └── Time.md │ │ │ │ ├── config.ts │ │ │ │ └── docs/ │ │ │ │ ├── components/ │ │ │ │ │ ├── AppBars/ │ │ │ │ │ │ ├── app_bar.mdx │ │ │ │ │ │ ├── bottom_app_bar.mdx │ │ │ │ │ │ ├── large_app_bar.mdx │ │ │ │ │ │ ├── medium_app_bar.mdx │ │ │ │ │ │ ├── navigation_bar.mdx │ │ │ │ │ │ ├── search_bar.mdx │ │ │ │ │ │ ├── small_app_bar.mdx │ │ │ │ │ │ └── tab_bar.mdx │ │ │ │ │ ├── Badges/ │ │ │ │ │ │ └── badge.mdx │ │ │ │ │ ├── Buttons/ │ │ │ │ │ │ ├── elevated_button.mdx │ │ │ │ │ │ ├── filled_button.mdx │ │ │ │ │ │ ├── filled_icon_button.mdx │ │ │ │ │ │ ├── floating_action_button.mdx │ │ │ │ │ │ ├── icon_button.mdx │ │ │ │ │ │ ├── outline_button.mdx │ │ │ │ │ │ ├── outline_icon_button.mdx │ │ │ │ │ │ ├── segmented_button.mdx │ │ │ │ │ │ ├── text_button.mdx │ │ │ │ │ │ ├── tonal_button.mdx │ │ │ │ │ │ └── tonal_icon_button.mdx │ │ │ │ │ ├── Cards/ │ │ │ │ │ │ ├── elevated_card.mdx │ │ │ │ │ │ ├── filled_card.mdx │ │ │ │ │ │ └── outlined_card.mdx │ │ │ │ │ ├── Checkboxes/ │ │ │ │ │ │ ├── check_box.mdx │ │ │ │ │ │ └── check_box_tile.mdx │ │ │ │ │ ├── Chips/ │ │ │ │ │ │ ├── action_chip.mdx │ │ │ │ │ │ ├── filter_chip.mdx │ │ │ │ │ │ └── input_chip.mdx │ │ │ │ │ ├── Dialogs/ │ │ │ │ │ │ ├── dialog.mdx │ │ │ │ │ │ └── fullscreen_dialog.mdx │ │ │ │ │ ├── Layouts/ │ │ │ │ │ │ ├── grid.mdx │ │ │ │ │ │ ├── horizontal.mdx │ │ │ │ │ │ └── vertical.mdx │ │ │ │ │ ├── Navigation/ │ │ │ │ │ │ ├── modal_navigation_drawer.mdx │ │ │ │ │ │ ├── navigation_drawer.mdx │ │ │ │ │ │ └── navigation_rail.mdx │ │ │ │ │ ├── Progress/ │ │ │ │ │ │ ├── circular_progress_indicator.mdx │ │ │ │ │ │ └── linear_progress_indicator.mdx │ │ │ │ │ ├── RadioButton/ │ │ │ │ │ │ ├── radio_button.mdx │ │ │ │ │ │ └── radio_button_tile.mdx │ │ │ │ │ ├── Sheets/ │ │ │ │ │ │ └── modal_bottom_sheet.mdx │ │ │ │ │ ├── avatar.mdx │ │ │ │ │ ├── date_picker.mdx │ │ │ │ │ ├── divider.mdx │ │ │ │ │ ├── drop_down_menu.mdx │ │ │ │ │ ├── list_tile.mdx │ │ │ │ │ ├── material_window.mdx │ │ │ │ │ ├── modal.mdx │ │ │ │ │ ├── modal_drawer.mdx │ │ │ │ │ ├── popup_menu.mdx │ │ │ │ │ ├── scroll_view.mdx │ │ │ │ │ ├── slider.mdx │ │ │ │ │ ├── snack_bar.mdx │ │ │ │ │ ├── switch.mdx │ │ │ │ │ ├── text_field.mdx │ │ │ │ │ ├── time_picker.mdx │ │ │ │ │ └── tooltip.mdx │ │ │ │ └── getting-started.mdx │ │ │ ├── content.config.ts │ │ │ ├── layouts/ │ │ │ │ ├── LandingLayout.astro │ │ │ │ ├── Layout.astro │ │ │ │ └── PageLayout.astro │ │ │ ├── misc/ │ │ │ │ └── Slint-tmLanguage.json │ │ │ ├── navigation.ts │ │ │ ├── pages/ │ │ │ │ ├── index.astro │ │ │ │ └── landing/ │ │ │ │ ├── click-through.astro │ │ │ │ └── product.astro │ │ │ └── utils/ │ │ │ ├── frontmatter.ts │ │ │ ├── iconNames.ts │ │ │ ├── images-optimization.ts │ │ │ ├── images.ts │ │ │ ├── link-data.json │ │ │ ├── permalinks.ts │ │ │ └── site-config.ts │ │ ├── tailwind.config.js │ │ ├── tests/ │ │ │ └── smoke-test.spec.ts │ │ ├── tsconfig.json │ │ ├── vendor/ │ │ │ └── integration/ │ │ │ ├── index.ts │ │ │ └── utils/ │ │ │ ├── configBuilder.ts │ │ │ └── loadConfig.ts │ │ ├── vscode.tailwind.json │ │ └── wrangler.toml │ ├── examples/ │ │ └── gallery/ │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── index.html │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ └── main.rs │ │ └── ui/ │ │ ├── components/ │ │ │ ├── component_card.slint │ │ │ ├── group.slint │ │ │ └── text_icon_button.slint │ │ ├── fonts/ │ │ │ └── Roboto-VariableFont.ttf.license │ │ ├── icons.slint │ │ ├── main.slint │ │ ├── main_window_adapter.slint │ │ ├── themes/ │ │ │ ├── material_green_theme.json │ │ │ ├── material_purple_theme.json │ │ │ ├── material_red_theme.json │ │ │ └── material_slint_theme.json │ │ └── views/ │ │ ├── actions_view.slint │ │ ├── components_view.slint │ │ ├── main_view.slint │ │ └── navigation_view.slint │ └── src/ │ ├── LICENSE.md │ ├── README.md │ ├── material.slint │ └── ui/ │ ├── components/ │ │ ├── app_bar.slint │ │ ├── badge.slint │ │ ├── base_button.slint │ │ ├── base_navigation.slint │ │ ├── bottom_app_bar.slint │ │ ├── bottom_sheet.slint │ │ ├── card.slint │ │ ├── check_box.slint │ │ ├── chip.slint │ │ ├── date_picker.slint │ │ ├── dialog.slint │ │ ├── divider.slint │ │ ├── drawer.slint │ │ ├── drop_down_menu.slint │ │ ├── elevated_button.slint │ │ ├── elevation.slint │ │ ├── extended_touch_area.slint │ │ ├── filled_button.slint │ │ ├── filled_icon_button.slint │ │ ├── floating_action_button.slint │ │ ├── grid.slint │ │ ├── horizontal.slint │ │ ├── icon.slint │ │ ├── icon_button.slint │ │ ├── list.slint │ │ ├── list_view.slint │ │ ├── material_text.slint │ │ ├── material_window.slint │ │ ├── menu.slint │ │ ├── modal.slint │ │ ├── navigation_bar.slint │ │ ├── navigation_drawer.slint │ │ ├── navigation_rail.slint │ │ ├── outline_button.slint │ │ ├── outline_icon_button.slint │ │ ├── progress_indicator.slint │ │ ├── radio_button.slint │ │ ├── scroll_view.slint │ │ ├── search_bar.slint │ │ ├── segmented_button.slint │ │ ├── slider.slint │ │ ├── snack_bar.slint │ │ ├── state_layer.slint │ │ ├── switch.slint │ │ ├── tab_bar.slint │ │ ├── text_button.slint │ │ ├── text_field.slint │ │ ├── time_picker.slint │ │ ├── tonal_button.slint │ │ ├── tonal_icon_button.slint │ │ ├── tooltip.slint │ │ └── vertical.slint │ ├── icons/ │ │ └── icons.slint │ ├── items/ │ │ ├── list_item.slint │ │ ├── menu_item.slint │ │ └── navigation_item.slint │ └── styling/ │ ├── material_animations.slint │ ├── material_palette.slint │ ├── material_schemes.slint │ ├── material_style_metrics.slint │ └── material_typography.slint └── xtask/ ├── Cargo.toml └── src/ ├── cppdocs.rs ├── license_headers_check.rs ├── main.rs ├── nodepackage.rs ├── reuse_compliance_check.rs └── slintdocs.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 [alias] xtask = "run --locked --package xtask --" [target.xtensa-esp32s3-none-elf] rustflags = [ "-C", "link-arg=-nostartfiles", # Without this flag, we get miscompilation of floating point operations that cause the clipping region to be totally wrong "-C", "target-feature=-fp", ] [target.xtensa-esp32s2-none-elf] rustflags = [ # Enable the atomic codegen option for Xtensa "-C", "target-feature=+s32c1i", ] [target.x86_64-pc-windows-msvc] # Increase default stack size to avoid running out of stack # space in debug builds. The size matches Linux's default. rustflags = ["-C", "link-arg=/STACK:8000000"] [target.aarch64-pc-windows-msvc] # Increase default stack size to avoid running out of stack # space in debug builds. The size matches Linux's default. rustflags = ["-C", "link-arg=/STACK:8000000"] ================================================ FILE: .clang-format ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 --- # Webkit style was loosely based on the Qt style BasedOnStyle: WebKit Standard: "c++20" # Column width is limited to 100 in accordance with Qt Coding Style. # https://wiki.qt.io/Qt_Coding_Style # Note that this may be changed at some point in the future. ColumnLimit: 100 # How much weight do extra characters after the line length limit have. # PenaltyExcessCharacter: 4 # Disable reflow of qdoc comments: indentation rules are different. # Translation comments and SDPX-License-Identifiers either. CommentPragmas: "^(!|:|\\s*[S]PDX-License-Identifier: )" # We want a space between the type and the star for pointer types. PointerBindsToType: false # We use template< without space. SpaceAfterTemplateKeyword: false # We want to break before the operators, but not before a '='. BreakBeforeBinaryOperators: NonAssignment # Braces are usually attached, but not after functions or class declarations. BreakBeforeBraces: Custom BraceWrapping: AfterClass: true AfterControlStatement: Never AfterEnum: false AfterFunction: true AfterNamespace: false AfterObjCDeclaration: false AfterStruct: true AfterUnion: false BeforeCatch: false BeforeElse: false IndentBraces: false # When constructor initializers do not fit on one line, put them each on a new line. ConstructorInitializerAllOnOneLineOrOnePerLine: true # Indent initializers by 4 spaces ConstructorInitializerIndentWidth: 4 # Indent width for line continuations. ContinuationIndentWidth: 8 # No indentation for namespaces. NamespaceIndentation: None # Allow indentation for preprocessing directives (if/ifdef/endif). https://reviews.llvm.org/rL312125 IndentPPDirectives: AfterHash # Horizontally align arguments after an open bracket. # The coding style does not specify the following, but this is what gives # results closest to the existing code. AlignAfterOpenBracket: "Align" AlwaysBreakTemplateDeclarations: "Yes" # Ideally we should also allow less short function in a single line, but # clang-format does not handle that. AllowShortFunctionsOnASingleLine: Inline # The coding style specifies some include order categories, but also tells to # separate categories with an empty line. It does not specify the order within # the categories. Since the SortInclude feature of clang-format does not # re-order includes separated by empty lines, the feature is not used. SortIncludes: "Never" # macros for which the opening brace stays attached. ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE, ] # Break constructor initializers before the colon and after the commas. BreakConstructorInitializers: BeforeColon ================================================ FILE: .clippy.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 type-complexity-threshold = 2500 too-many-arguments-threshold = 10 ================================================ FILE: .dockerignore ================================================ target .git ================================================ FILE: .gitattributes ================================================ *.rs diff=rust # These files are automatically checked by the build.rs # With linguist-generated we can mark them as generated so they don't show up in the Github diff. # https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github tests/driver/rust/tests/*.rs linguist-generated ================================================ FILE: .github/ISSUE_TEMPLATE/1-bug-report.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: 🐞 Bug Report description: Report a bug or unexpected behavior in Slint labels: ["need triaging"] type: "Bug" body: - type: markdown attributes: value: | Thank you for reporting a bug! 🐛 Please use this template to provide the information we need to investigate the issue. Before submitting, please search the [issue tracker](https://github.com/slint-ui/slint/issues) to ensure your bug hasn’t already been reported. If you have questions or need help with Slint, visit our ["Discussions" tab](https://github.com/slint-ui/slint/discussions). - type: textarea attributes: label: Bug Description description: | Clearly describe the issue, including: - What is the bug? - What behavior did you expect, and what happened instead? - Steps to reproduce the issue - Any error messages or logs, if available. validations: required: true - type: textarea attributes: label: Reproducible Code (if applicable) description: | If possible, include a minimal code snippet that reproduces the problem. This helps us debug faster! placeholder: | export component TestCase inherits Window { /* Your reproducible Slint code here */ } render: slint validations: required: false - type: textarea attributes: label: Environment Details description: | Provide information about your setup: - Slint version (mention if this is a regression from a previous release). - Operating system and its version (e.g., Windows 11, Ubuntu 22.04, Android, etc.). If Linux, what Desktop Environment and whether you are using Wayland or X11. - Programming language used (e.g., C++, Rust, JavaScript, Python, etc.). - Backend/renderer being used (e.g., Qt, Skia, FemtoVG, Software, etc.). value: | - Slint Version: - Platform/OS: - Programming Language: - Backend/Renderer: validations: required: true - type: textarea attributes: label: Product Impact description: | Tell us about your project: - What are you building with Slint? - How critical is this issue for your product (e.g., blocker, inconvenience)? This helps us prioritize the issue effectively. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/2-feature-request.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: ✨ Feature Request description: Suggest a new feature or improvement for Slint labels: ["need triaging"] type: "Feature" body: - type: markdown attributes: value: | Thank you for suggesting a feature or improvement! ✨ We value your ideas and feedback. However, before submitting a feature request here, please consider starting a discussion in the ["Discussions" tab](https://github.com/slint-ui/slint/discussions) to refine your idea and gather input from the community. Before submitting, please search the [issue tracker](https://github.com/slint-ui/slint/issues) to ensure your issue hasn’t already been reported. If you're confident about your suggestion and want to proceed, please fill out the form below. - type: textarea attributes: label: Feature Description description: | Please describe the feature or improvement you would like to see in Slint. Be as detailed as possible: - What problem does this feature solve, or what use case does it address? - How would this feature improve your workflow or product? Feel free to add code sample. You can format it in markdown within ```` ```slint ```` tags. validations: required: true - type: textarea attributes: label: Product Impact description: | Tell us about your project: - What are you building with Slint? - How would you make use of that feature? - How critical is this feature for your product (e.g., nice-to-have, must-have)? This helps us prioritize the issue effectively. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/3-tracking-issue.md ================================================ --- name: 🎯 Tracking Issue — ⚠️ Internal use only about: Track progress on a long-term goal (Internal use only) title: '🎯 Tracking: ' labels: 'roadmap' assignees: '' --- ## Goal [What are we trying to achieve?] ## See also - Outline: [link] (if applicable) ================================================ FILE: .github/ISSUE_TEMPLATE/4-blank.md ================================================ --- name: 🚧 Blank issue — ⚠️ Internal use only about: Issue without labels. (Only for Slint developers) title: '' labels: '' assignees: '' --- ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 blank_issues_enabled: false contact_links: - name: ❓ Ask a Question url: https://github.com/slint-ui/slint/discussions about: Have a question about Slint? Head over to the Discussions forum to ask questions, share knowledge, and connect with the community! ================================================ FILE: .github/actions/codesign/action.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 --- name: Apple Codesign Binary description: Sign the given binary with the developer certificate inputs: binary: description: "Path to binary" required: true default: "" certificate: description: "certificate secret" required: true certificate_password: description: "certificate password" required: true keychain_password: description: "keychain password to use" required: true developer_id: description: "developer id to use" required: true runs: using: composite steps: - name: Codesign binary shell: bash env: CERT: ${{ inputs.certificate }} CERT_PW: ${{ inputs.certificate_password }} KEYCHAIN_PW: ${{ inputs.keychain_password }} DEV_ID: ${{ inputs.developer_id }} run: | echo -n "$CERT" | base64 --decode -o certificate.p12 security create-keychain -p $KEYCHAIN_PW build.keychain security default-keychain -s build.keychain security unlock-keychain -p $KEYCHAIN_PW build.keychain security import certificate.p12 -k build.keychain -P $CERT_PW -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PW build.keychain /usr/bin/codesign --force -s $DEV_ID "${{ inputs.binary }}" -v ================================================ FILE: .github/actions/install-linux-dependencies/action.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # cSpell: ignore libxcb libxkbcommon xfixes --- name: Install Linux dependencies description: Set up Linux dependencies for Slint inputs: extra-packages: description: "Extra packages to install" required: false default: "" force-gcc-10: description: 'Force GCC version 10 (default: "no")' required: false default: "no" old-ubuntu: description: "This is running on an older version of Ubuntu" required: false default: "no" runs: using: composite steps: - name: Install Linux dependencies if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libxkbcommon-x11-dev libudev-dev libinput-dev libfontconfig-dev ${{ inputs.extra-packages }} shell: bash - name: Install Linux dependencies if: ${{ runner.os == 'Linux' && (inputs.old-ubuntu != 'true')}} run: | sudo apt-get update sudo apt-get install libseat-dev libsystemd-dev shell: bash - name: Install C++ compiler if: ${{ (runner.os == 'Linux') && (inputs.force-gcc-10 == 'true') }} run: | sudo apt-get install gcc-10 g++-10 echo "CC=gcc-10" >> $GITHUB_ENV echo "CXX=g++-10" >> $GITHUB_ENV shell: bash ================================================ FILE: .github/actions/install-skia-dependencies/action.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 --- name: Install Skia dependencies description: Set up dependencies needed to build Skia runs: using: composite steps: - name: Upgrade LLVM for Skia build on Windows if: runner.os == 'Windows' run: choco upgrade llvm --version "19.1.7" --allow-downgrade shell: bash # See https://github.com/ilammy/msvc-dev-cmd?tab=readme-ov-file#caveats - name: Remove GNU link.exe from GH actions if: runner.os == 'Windows' run: rm /usr/bin/link shell: bash ================================================ FILE: .github/actions/setup-rust/action.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # cSpell: ignore debuginfo rustflags swatinem --- name: Setup Rust support description: Set up Slint development environment inputs: toolchain: description: "Rust toolchain to use (default: stable)" required: false default: stable target: description: "Rust target to use" required: false default: "" components: description: "Rust components to install" required: false default: "" key: description: "Extra cache keying information" required: false default: "" cache: description: "Enable Rust caching (default: true)" required: false default: "true" save_if: description: "Condition to save the cache" required: false default: ${{ github.ref == 'refs/heads/master' }} runs: using: composite steps: - name: Disable debug info to avoid running out of disk space on Windows if: runner.os == 'Windows' run: | echo "RUSTFLAGS=-C debuginfo=0" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append shell: powershell # Skia source builds end up with long paths, exceeding the 260 character limit. Enable this git for windows # option to use newer Windows API (https://github.com/git-for-windows/git/blob/bc3743def76f487b6dbc18b1b2645ab081c14980/Documentation/config/core.txt#L679) - name: Enable long path support for git checkouts if: runner.os == 'Windows' run: | git config --system core.longpaths true shell: powershell - name: Move cargo home close to the target/$profile directory, so that relative paths from build to cargo source are without drive letter if: runner.os == 'Windows' run: | echo "CARGO_HOME=${{ runner.workspace }}\cargo" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append shell: powershell - name: Set GETTEXT_DIR environment variable or it will still be compiled from source. if: runner.os == 'macOS' run: | GETTEXT_PATH=$(brew --prefix gettext) echo "GETTEXT_DIR=${GETTEXT_PATH}" >> $GITHUB_ENV echo "Installed gettext at: ${GETTEXT_PATH}" shell: bash - name: Install Rust ${{ inputs.toolchain }} uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ inputs.toolchain }} components: ${{ inputs.components }} target: ${{ inputs.target }} - uses: Swatinem/rust-cache@v2 if: inputs.cache == 'true' with: key: ${{ inputs.key }}-1 save-if: ${{ inputs.save_if }} ================================================ FILE: .github/ci_path_filters.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 ci_config: &ci_config - '.github/**' - '.mise/**' # We count changes to Cargo.lock as a "CI config" level change, as we need to ensure that all CI jobs will work correctly with the updated lock file. # Otherwise, later job runs may run into issues that have nothing to do with their changes. - 'Cargo.lock' files_in_root: &files_in_root - '*!(/)' # changes in the root, such as cspell.json or pnpm-lock.yaml - '.*' js_config: &js_config - 'package.json' - 'package-lock.json' - 'pnpm-workspace.yaml' - '.npmrc' - 'pnpm-lock.yaml' slint: - 'api/**' - 'cmake/**' - 'demos/**' - 'docker/**' - 'docs/**' - 'editors/**' - 'helper_crates/**' - 'internal/**' - 'logo/**' - 'scripts/**' - 'tests/**' - 'tools/compiler/**' - 'tools/docsnapper/**' - 'tools/figma_import/**' - 'tools/lsp/**' - 'tools/slintpad/**' - 'tools/tr-extractor/**' - 'tools/updater/**' - 'tools/viewer/**' - 'xtask/**' - *files_in_root - *ci_config figma_inspector: - 'tools/figma-inspector/**' - *js_config - *ci_config material_components: - 'ui-libraries/material/**' internal: &internal # The API specific tests don't depends on renderer or backend, but they do depend on the Qt widgets and the testing backend - 'internal/!({renderers,backends}/**)' - 'internal/backends/qt/**' - 'internal/backends/testing/**' - 'helper_crates/**' - 'Cargo.toml' # the root Cargo.toml - '.cargo/**' - *ci_config api_cpp: - 'api/cpp/**' - 'tools/compiler/**' - *ci_config # The renderers are not part of the `internal` group, but are exposed in the C++ API - 'internal/renderers/{skia,software}/**' api_python: - 'api/python/**' - *ci_config api_node: - 'api/node/**' - *js_config - *ci_config api_rs: - 'api/rs/**' - 'Cargo.toml' - *ci_config tests: - 'tests/**' examples: # Some examples are tested separately and can be excluded from here - 'examples/!({servo,bevy,*mcu*,uefi-demo,}/**)' - 'demos/**' examples_mcu: - 'examples/*mcu*/**' - 'internal/renderers/software/**' vsce: - *internal - *js_config - 'api/rs/**' - 'editors/vscode/**' - 'tools/lsp/**' - 'docs/common/src/utils/slint.tmLanguage.json' slintpad: - *internal - 'tools/slintpad/**' - 'api/wasm-interpreter/**' - 'tools/lsp/**' - 'api/rs/**' - 'docs/common/src/utils/slint.tmLanguage.json' updater_test: - *internal - 'tests/cases/**' - 'tools/updater/**' servo_example: - 'examples/servo/**' - *ci_config bevy_examples: - 'examples/bevy/**' - *ci_config rust_files: - '**/*.rs' - '**/Cargo.toml' - 'Cargo.lock' - *ci_config ================================================ FILE: .github/dependabot.yml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: github-actions open-pull-requests-limit: 20 directory: / schedule: interval: weekly time: "01:00" groups: github-actions: patterns: - "*" # Group all Actions updates into a single larger pull request ignore: - dependency-name: "lukka/run-cmake" - package-ecosystem: "npm" # See documentation for possible values open-pull-requests-limit: 20 directory: "/" # Location of package manifests schedule: interval: "weekly" time: "01:00" ignore: - dependency-name: "@types/node" - dependency-name: "@types/vscode" - dependency-name: "@codingame/monaco-vscode-configuration-service-override" - dependency-name: "@codingame/monaco-vscode-files-service-override" - dependency-name: "@codingame/monaco-vscode-keybindings-service-override" - dependency-name: "@codingame/monaco-vscode-languages-service-override" - dependency-name: "@codingame/monaco-vscode-model-service-override" - dependency-name: "@codingame/monaco-vscode-storage-service-override" - dependency-name: "@codingame/monaco-vscode-api" - dependency-name: "@codingame/monaco-vscode-editor-api" - dependency-name: "@napi-rs/cli" - dependency-name: "monaco-editor-wrapper" - dependency-name: "monaco-languageclient" groups: npm-major-updates: update-types: ["major"] npm-minor-updates: update-types: ["minor"] npm-patch-updates: update-types: ["patch"] ================================================ FILE: .github/pull_request_template.md ================================================ ================================================ FILE: .github/workflows/autofix.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: autofix.ci # needed to securely identify the workflow on: pull_request: branches: [master, "feature/*", "pre-release/*"] push: branches: [master, "feature/*", "pre-release/*"] workflow_dispatch: merge_group: permissions: contents: read concurrency: group: ci-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: CARGO_INCREMENTAL: false GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: format_fix: runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v6 - uses: jdx/mise-action@v4 with: version: 2025.9.18 log_level: debug - name: Install rust dependencies run: rustup toolchain install stable-x86_64-unknown-linux-gnu --profile default - name: Run fixes run: mise run --force --jobs=1 'ci:autofix:fix' - name: Suggest format changes uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8 lint_typecheck: runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v6 - uses: jdx/mise-action@v4 with: version: 2025.9.18 log_level: debug - name: Install rust dependencies run: rustup toolchain install stable-x86_64-unknown-linux-gnu --profile default - name: Run lints run: mise run --force --jobs=1 'ci:autofix:lint' ci: needs: [format_fix, lint_typecheck] permissions: contents: read deployments: write pull-requests: read uses: ./.github/workflows/ci.yaml secrets: ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_API_TOKEN_2: ${{ secrets.CLOUDFLARE_API_TOKEN_2 }} CLOUDFLARE_ACCOUNT_ID_2: ${{ secrets.CLOUDFLARE_ACCOUNT_ID_2 }} READ_WRITE_PRIVATE_KEY: ${{ secrets.READ_WRITE_PRIVATE_KEY }} done: needs: [ci] name: done runs-on: ubuntu-latest steps: - run: echo "All checks passed." ================================================ FILE: .github/workflows/bevy_examples.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Bevy examples on: workflow_dispatch: inputs: update: description: 'Whether to run cargo update before testing' required: false type: boolean default: false save_if: description: 'Condition to save the cache' required: false type: boolean default: true workflow_call: inputs: update: description: 'Whether to run cargo update before testing' required: false type: boolean default: false save_if: description: 'Condition to save the cache' required: false type: boolean default: true jobs: bevy_examples: strategy: matrix: os: [ubuntu-22.04, macos-14, windows-2022] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: save_if: ${{ inputs.save_if }} - uses: ./.github/actions/install-linux-dependencies with: extra-packages: libwayland-dev libasound2-dev - uses: ./.github/actions/install-skia-dependencies - uses: ilammy/msvc-dev-cmd@v1 - name: Cargo update if: ${{ inputs.update }} run: cargo update working-directory: examples/bevy - name: Build Bevy Examples run: cargo build --locked working-directory: examples/bevy ================================================ FILE: .github/workflows/build_and_test_reusable.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Build and Test (Reusable) on: workflow_call: inputs: name: description: 'Name of the job' required: true type: string os: description: 'Operating system to run on' required: true type: string rust_version: description: 'Rust version to use' required: true type: string update: description: 'Whether to run cargo update before testing' required: false type: boolean default: false extra_args: description: 'Extra arguments to pass to cargo test' required: false type: string default: "" cache: description: 'Use rust-cache' required: false type: boolean default: true save_if: description: 'Condition to save the cache' required: false type: boolean default: true timeout_minutes: description: 'Timeout in minutes' required: false type: number default: 120 jobs: build_and_test: name: ${{ inputs.name }} timeout-minutes: ${{ inputs.timeout_minutes }} env: MACOSX_DEPLOYMENT_TARGET: "11.0" DYLD_FRAMEWORK_PATH: /Users/runner/work/slint/Qt/6.2.2/macos/lib QT_QPA_PLATFORM: offscreen RUSTFLAGS: -D warnings CARGO_PROFILE_DEV_DEBUG: 0 CARGO_INCREMENTAL: false RUST_BACKTRACE: 1 SLINT_EMIT_DEBUG_INFO: 1 runs-on: ${{ inputs.os }} steps: - name: Maximize build space if: runner.os == 'Linux' run: | df -h [ -d /usr/local/lib/android ] && sudo rm -rf /usr/local/lib/android [ -d /usr/share/dotnet ] && sudo rm -rf /usr/share/dotnet # For now, comment out a few of these deletion commands to speed up the runner startup # [ -d /opt/ghc ] && sudo rm -rf /opt/ghc # [ -d /opt/hostedtoolcache/CodeQL ] && sudo rm -rf /opt/hostedtoolcache/CodeQL # Pruning docker images could free up another 3GB # sudo docker image prune --all --force df -h - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies - uses: ./.github/actions/install-skia-dependencies - uses: ilammy/msvc-dev-cmd@v1 - uses: actions/setup-python@v6 with: python-version: '3.10' - name: Install uv uses: astral-sh/setup-uv@v7 - name: Install Qt if: runner.os != 'Windows' uses: jurplel/install-qt-action@v4 with: version: "6.2.2" setup-python: false cache: true - name: Install ffmpeg and alsa (Linux) if: runner.os == 'Linux' run: sudo apt-get install clang libavcodec-dev libavformat-dev libavutil-dev libavfilter-dev libavdevice-dev libasound2-dev pkg-config - name: Install gstreamer and libunwind (Linux) if: runner.os == 'Linux' run: sudo apt-get install libunwind-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good - name: Setup headless display if: runner.os != 'macOS' uses: pyvista/setup-headless-display-action@v4 - uses: ./.github/actions/setup-rust with: toolchain: ${{ inputs.rust_version }} key: x-v3 save_if: ${{ inputs.save_if }} cache: ${{ inputs.cache }} - name: Cargo update if: inputs.update # Ignore rust_version for stable/nightly to test with latest dependencies run: cargo update ${{ (inputs.rust_version == 'stable' || inputs.rust_version == 'nightly') && '--ignore-rust-version' || '' }} - name: Run tests run: cargo test --locked --verbose --all-features --workspace --timings ${{ inputs.extra_args }} --exclude slint-node --exclude pyslint --exclude test-driver-node --exclude slint-node --exclude test-driver-nodejs --exclude test-driver-cpp --exclude test-driver-python --exclude mcu-board-support --exclude mcu-embassy --exclude printerdemo_mcu --exclude uefi-demo --exclude slint-cpp --exclude slint-python -- --skip=_qt::t env: SLINT_CREATE_SCREENSHOTS: 1 shell: bash - name: Run tests (qt) run: cargo test --locked --verbose --all-features --workspace ${{ inputs.extra_args }} --exclude slint-node --exclude pyslint --exclude test-driver-node --exclude slint-node --exclude test-driver-nodejs --exclude test-driver-cpp --exclude test-driver-python --exclude mcu-board-support --exclude mcu-embassy --exclude printerdemo_mcu --exclude uefi-demo --exclude slint-cpp --exclude slint-python _qt -- --test-threads=1 shell: bash - name: live-preview for rust test env: SLINT_LIVE_PREVIEW: 1 run: cargo test --locked --verbose --all-features --features slint/live-preview -p test-driver-rust -- --skip=_qt::t shell: bash - name: Upload build timing report if: always() uses: actions/upload-artifact@v7 with: name: cargo-timings-${{ inputs.os }}-${{ inputs.rust_version }} path: target/cargo-timings/cargo-timing.html if-no-files-found: ignore - name: Archive screenshots after failed tests if: ${{ failure() }} uses: actions/upload-artifact@v7 with: name: screenshots-${{ inputs.os }} path: | tests/screenshots/references - name: Print Disk Usage if: always() run: df -h ================================================ FILE: .github/workflows/build_docs.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # Build various demo binaries, c++ packages and documentation and publish them on the website # Job 1 (rust-cpp-docs): Build Rust and C++ documentation # Job 2 (astro-docs-tests): Generate screenshots, build Astro docs and run tests # Job 3 (node-python-docs): Build Node and Python documentation # Job 4 (check-warnings): Validate docs for warnings name: Build docs on: workflow_call: secrets: READ_WRITE_PRIVATE_KEY: required: true inputs: release: type: string default: "false" required: false description: "Release? Enable options for building binaries for a release (i.e. apply a nightly tag, nightly version)" app-id: type: string required: true jobs: # Job 1: Build Rust and C++ documentation rust-cpp-docs: runs-on: ubuntu-24.04 env: # Allow deprecated warning because we are using nightly and some things might be deprecated in nightly # for which the stable alternative is not yet available. RUSTFLAGS: -D warnings -W deprecated --cfg slint_nightly_test RUSTDOCFLAGS: --html-in-header=/home/runner/work/slint/slint/docs/astro/src/utils/slint-docs-highlight.html -D warnings -W deprecated --cfg docsrs -Zunstable-options --generate-link-to-definition SLINT_NO_QT: 1 CARGO_INCREMENTAL: false RELEASE_INPUT: ${{ inputs.release }} steps: - uses: actions/checkout@v6 - name: Set up crate rustdoc link run: | rgb_version=`grep 'rgb = ' internal/core/Cargo.toml | sed 's/^.*"\(.*\)"/\1/'` echo "RUSTDOCFLAGS=$RUSTDOCFLAGS --extern-html-root-url rgb=https://docs.rs/rgb/$rgb_version/ --extern-html-root-url android_activity=https://docs.rs/android-activity/0.5/ --extern-html-root-url raw_window_handle=https://docs.rs/raw_window_handle/0.6 --extern-html-root-url winit=https://docs.rs/winit/0.30 --extern-html-root-url wgpu=https://docs.rs/wgpu/26 --extern-html-root-url input=https://docs.rs/input/0.9 --extern-html-root-url fontique=https://docs.rs/fontique/0.7" >> $GITHUB_ENV - uses: ./.github/actions/install-linux-dependencies - uses: ./.github/actions/setup-rust with: toolchain: nightly target: aarch64-linux-android - name: Install apt dependencies run: sudo apt-get install doxygen - uses: baptiste0928/cargo-install@v3 with: crate: cargo-about version: "=0.6.6" - name: Install uv uses: astral-sh/setup-uv@v7 - name: Build Cpp docs run: | CPPDOCS_EXTRA_FLAGS="" if [ "$RELEASE_INPUT" != "true" ]; then CPPDOCS_EXTRA_FLAGS="--experimental" fi cargo xtask cppdocs --show-warnings $CPPDOCS_EXTRA_FLAGS - name: "Rust docs" run: cargo doc -p slint -p slint-build -p slint-interpreter --no-deps --all-features - name: "Rust android-activity and i-slint-backend-winit" run: | cargo doc -p i-slint-backend-android-activity -p i-slint-backend-winit -p i-slint-backend-testing --no-deps --target aarch64-linux-android --features=i-slint-backend-android-activity/native-activity,i-slint-backend-android-activity/aa-06,i-slint-backend-winit/raw-window-handle-06,i-slint-backend-winit/renderer-femtovg cp -r target/aarch64-linux-android/doc/i_slint_backend_android_activity/ target/doc/ cp -r target/aarch64-linux-android/doc/i_slint_backend_winit/ target/doc/ cp -r target/aarch64-linux-android/doc/i_slint_backend_testing/ target/doc/ - name: "Prepare Rust and C++ docs for website directory structure" run: | mkdir -p artifact/docs mv target/doc artifact/docs/rust mv target/cppdocs/html artifact/docs/cpp - name: "Upload Rust/C++ Docs Artifacts" uses: actions/upload-artifact@v7 with: name: docs-rust-cpp path: artifact - name: "Check for docs warnings in internal crates" run: cargo doc --workspace --no-deps --all-features --exclude slint-node --exclude pyslint --exclude mcu-board-support --exclude mcu-embassy --exclude printerdemo_mcu --exclude carousel --exclude test-* --exclude plotter --exclude uefi-demo --exclude ffmpeg --exclude gstreamer-player --exclude slint-cpp --exclude slint-python # Job 2: Build Astro docs and run tests astro-docs-tests: runs-on: ubuntu-24.04 env: RUSTFLAGS: -D warnings -W deprecated RUSTDOCFLAGS: --html-in-header=/home/runner/work/slint/slint/docs/astro/src/utils/slint-docs-highlight.html -D warnings -W deprecated --cfg docsrs -Zunstable-options --generate-link-to-definition SLINT_NO_QT: 1 CARGO_INCREMENTAL: false RELEASE_INPUT: ${{ inputs.release }} steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 - uses: actions/setup-node@v6 with: node-version: 24 package-manager-cache: false - uses: ./.github/actions/install-linux-dependencies - name: Install MS fonts for comic sans needed by one of the screenshots in the docs run: | echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | sudo debconf-set-selections sudo apt-get install ttf-mscorefonts-installer -y - uses: ./.github/actions/setup-rust with: toolchain: stable target: aarch64-linux-android cache: false - name: "Generate Screenshots for Example Snippets" run: cargo run --release -p slint-docsnapper -- docs/astro/src/content --overwrite - name: "Install Node dependencies" run: pnpm i --frozen-lockfile - name: Extract Version from Cargo.toml id: version run: | version=$(awk '/^\[workspace.package\]/ {found=1} found && /^version = / {gsub(/version = |"/, "", $0); print $0; exit}' Cargo.toml) if [[ -z "$version" ]]; then echo "Version not found" exit 1 fi echo "VERSION=$version" >> $GITHUB_OUTPUT - name: Update URL for sitemap in site-config.ts run: | version="${{ steps.version.outputs.VERSION }}" if [ "$RELEASE_INPUT" != "true" ]; then base_url="https://snapshots.slint.dev" base_path="/master/docs/slint/" slint_download_version=nightly else base_url="https://releases.slint.dev" base_path="/$version/docs/slint/" slint_download_version=v$version fi sed -i "s|BASE_URL = \".*\"|BASE_URL = \"$base_url\"|" docs/common/src/utils/site-config.ts sed -i "s|BASE_PATH = \".*\"|BASE_PATH = \"$base_path\"|" docs/common/src/utils/site-config.ts sed -i "s|SLINT_DOWNLOAD_VERSION = \".*\"| SLINT_DOWNLOAD_VERSION = \"$slint_download_version\"|" docs/common/src/utils/site-config.ts - name: "Slint Language Documentation" run: cargo xtask slintdocs - name: Spellcheck working-directory: docs/astro run: pnpm spellcheck - name: Install Playwright working-directory: docs/astro run: pnpm exec playwright install --with-deps - name: Run Playwright tests working-directory: docs/astro run: pnpm exec playwright test - name: Publish Test Summary Results working-directory: docs/astro run: npx github-actions-ctrf playwright-report/ctrf-report.json - name: Upload test results if: always() uses: actions/upload-artifact@v7 with: name: playwright-test-report path: docs/astro/playwright-report/ retention-days: 30 - name: Debug - Verify dist exists before upload run: | echo "=== Checking docs/astro/dist ===" ls -la docs/astro/ || echo "docs/astro/ does not exist" ls -la docs/astro/dist/ || echo "docs/astro/dist/ does not exist" echo "File count in dist: $(find docs/astro/dist -type f 2>/dev/null | wc -l)" echo "First 10 files:" find docs/astro/dist -type f 2>/dev/null | head -10 - name: "Prepare docs for website structure" run: | mkdir -p artifact/docs mv docs/astro/dist artifact/docs/slint - name: "Upload Astro Docs Artifacts" uses: actions/upload-artifact@v7 with: name: docs-astro path: artifact # Job 3: Build Node and Python documentation node-python-docs: runs-on: ubuntu-24.04 env: RELEASE_INPUT: ${{ inputs.release }} steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 - uses: actions/setup-node@v6 with: node-version: 24 package-manager-cache: false - uses: ./.github/actions/install-linux-dependencies - uses: ./.github/actions/setup-rust with: toolchain: stable target: aarch64-linux-android cache: false - uses: baptiste0928/cargo-install@v3 with: crate: cargo-about version: "=0.6.6" - name: Install uv uses: astral-sh/setup-uv@v7 - name: "Install Node dependencies" run: pnpm i --frozen-lockfile - name: "Node docs" run: pnpm run docs working-directory: api/node - name: Setup headless display uses: pyvista/setup-headless-display-action@v4 - name: "Python docs" run: uv run build_docs.py working-directory: api/python/slint - name: "Prepare docs for website structure" run: | mkdir -p artifact/docs mv api/node/docs artifact/docs/node mv api/python/slint/docs artifact/docs/python - name: "Upload Python/Node Docs Artifacts" uses: actions/upload-artifact@v7 with: name: docs-node-python path: artifact ================================================ FILE: .github/workflows/ci.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: CI on: workflow_dispatch: workflow_call: secrets: CLOUDFLARE_API_TOKEN: required: true CLOUDFLARE_ACCOUNT_ID: required: true CLOUDFLARE_API_TOKEN_2: required: true CLOUDFLARE_ACCOUNT_ID_2: required: true ANDROID_KEYSTORE_PASSWORD: required: true ANDROID_KEYSTORE_BASE64: required: true READ_WRITE_PRIVATE_KEY: required: true env: MACOSX_DEPLOYMENT_TARGET: "11.0" SLINT_COMPILER_DENY_WARNINGS: 1 jobs: files-changed: runs-on: ubuntu-latest timeout-minutes: 1 permissions: pull-requests: read outputs: slint: ${{ steps.filter.outputs.slint }} figma_inspector : ${{ steps.filter.outputs.figma_inspector }} material_components : ${{ steps.filter.outputs.material_components }} internal : ${{ steps.filter.outputs.internal }} api_cpp : ${{ steps.filter.outputs.api_cpp }} api_python : ${{ steps.filter.outputs.api_python }} api_node : ${{ steps.filter.outputs.api_node }} api_rs : ${{ steps.filter.outputs.api_rs }} tests : ${{ steps.filter.outputs.tests }} examples : ${{ steps.filter.outputs.examples }} examples_mcu : ${{ steps.filter.outputs.examples_mcu }} vsce : ${{ steps.filter.outputs.vsce }} slintpad : ${{ steps.filter.outputs.slintpad }} updater_test : ${{ steps.filter.outputs.updater_test }} servo_example : ${{ steps.filter.outputs.servo_example }} bevy_examples : ${{ steps.filter.outputs.bevy_examples }} rust_files : ${{ steps.filter.outputs.rust_files }} steps: - uses: actions/checkout@v6 - uses: dorny/paths-filter@v4 id: filter with: token: ${{ github.token }} filters: .github/ci_path_filters.yaml build_and_test: needs: files-changed if: needs.files-changed.outputs.slint == 'true' || needs.files-changed.outputs.examples == 'true' strategy: matrix: include: - os: ubuntu-22.04 name: "Ubuntu 22.04 beta" rust_version: "beta" save_if: ${{ github.ref == 'refs/heads/master' }} extra_args: "--exclude plotter" - os: windows-2022 name: "Windows 2022 1.92" rust_version: "1.92" save_if: ${{ github.ref == 'refs/heads/master' }} extra_args: "--exclude ffmpeg --exclude gstreamer-player" - os: macos-14 name: "MacOS 14 stable" rust_version: "stable" save_if: ${{ github.ref == 'refs/heads/master' }} extra_args: "--exclude ffmpeg --exclude gstreamer-player" uses: ./.github/workflows/build_and_test_reusable.yaml with: os: ${{ matrix.os }} name: build_and_test ${{ matrix.name }} rust_version: ${{ matrix.rust_version }} extra_args: ${{ matrix.extra_args }} --exclude material-gallery save_if: ${{ matrix.save_if }} # For changes to Slint internals, do a quick test on Linux for Node.js only. node_test_linux: needs: files-changed if: needs.files-changed.outputs.internal == 'true' || needs.files-changed.outputs.api_node == 'true' || needs.files-changed.outputs.tests == 'true' uses: ./.github/workflows/node_test_reusable.yaml with: name: "Node.js Linux" os: "ubuntu-22.04" node_test_macos: needs: files-changed if: needs.files-changed.outputs.api_node == 'true' || needs.files-changed.outputs.tests == 'true' uses: ./.github/workflows/node_test_reusable.yaml with: name: "Node.js macOS" os: "macos-14" node_test_windows: needs: files-changed if: needs.files-changed.outputs.api_node == 'true' || needs.files-changed.outputs.tests == 'true' uses: ./.github/workflows/node_test_reusable.yaml with: name: "Node.js Windows" os: "windows-2022" # For changes to Slint internals, do a quick test on Linux for Python only. python_test_linux: needs: files-changed if: needs.files-changed.outputs.internal == 'true' || needs.files-changed.outputs.api_python == 'true' || needs.files-changed.outputs.tests == 'true' uses: ./.github/workflows/python_test_reusable.yaml with: name: "Python Linux" os: "ubuntu-22.04" python_test_macos: needs: files-changed if: needs.files-changed.outputs.api_python == 'true' || needs.files-changed.outputs.tests == 'true' uses: ./.github/workflows/python_test_reusable.yaml with: name: "Python macOS" os: "macos-14" python_test_windows: needs: files-changed if: needs.files-changed.outputs.api_python == 'true' || needs.files-changed.outputs.tests == 'true' uses: ./.github/workflows/python_test_reusable.yaml with: name: "Python Windows" os: "windows-2022" cpp_test_driver: needs: files-changed if: needs.files-changed.outputs.internal == 'true' || needs.files-changed.outputs.api_cpp == 'true' || needs.files-changed.outputs.tests == 'true' env: DYLD_FRAMEWORK_PATH: /Users/runner/work/slint/Qt/5.15.2/clang_64/lib QT_QPA_PLATFORM: offscreen RUSTFLAGS: -D warnings CARGO_INCREMENTAL: false RUST_BACKTRACE: 1 CARGO_PROFILE_DEV_DEBUG: 0 strategy: matrix: os: [ubuntu-22.04, macos-14, windows-2022] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies with: force-gcc-10: true - name: Install Qt if: runner.os == 'Linux' uses: jurplel/install-qt-action@v4 with: version: "5.15.2" cache: true - name: Set default style if: runner.os != 'Windows' run: echo "SLINT_STYLE=native" >> $GITHUB_ENV - name: Set default style if: runner.os == 'Windows' run: | echo "SLINT_STYLE=fluent" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append echo "SLINT_NO_QT=1" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - uses: ./.github/actions/setup-rust - name: Run tests run: cargo test --verbose -p test-driver-cpp --features slint-cpp/backend-qt cpp_cmake: needs: files-changed if: needs.files-changed.outputs.internal == 'true' || needs.files-changed.outputs.api_cpp == 'true' || needs.files-changed.outputs.examples == 'true' env: DYLD_FRAMEWORK_PATH: /Users/runner/work/slint/Qt/6.5.1/clang_64/lib QT_QPA_PLATFORM: offscreen CARGO_INCREMENTAL: false RUST_BACKTRACE: 1 CARGO_PROFILE_DEV_DEBUG: 0 SLINT_EMIT_DEBUG_INFO: 1 strategy: matrix: include: - os: macos-14 rust_version: "1.88" - os: windows-2022 rust_version: "nightly" - os: ubuntu-22.04 rust_version: "stable" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies with: force-gcc-10: true - name: Install Qt uses: jurplel/install-qt-action@v4 with: version: 6.5.1 cache: true - uses: ./.github/actions/setup-rust - uses: ilammy/msvc-dev-cmd@v1 - name: Select MSVC (windows) if: matrix.os == 'windows-2022' run: | echo "CC=cl.exe" >> $GITHUB_ENV echo "CXX=cl.exe" >> $GITHUB_ENV - name: Enable test coverage for resource embedding in C++ when building examples if: matrix.os == 'ubuntu-22.04' run: | echo "SLINT_EMBED_RESOURCES=true" >> $GITHUB_ENV - name: C++ Build uses: lukka/run-cmake@v3.4 with: cmakeListsOrSettingsJson: CMakeListsTxtAdvanced cmakeListsTxtPath: CMakeLists.txt cmakeAppendedArgs: "-DSLINT_BUILD_TESTING=ON -DSLINT_BUILD_EXAMPLES=ON -DCMAKE_BUILD_TYPE=Debug -DSLINT_FEATURE_RENDERER_SKIA=ON -DSLINT_FEATURE_BACKEND_QT=ON -DSLINT_FEATURE_EXPERIMENTAL=ON -DSLINT_FEATURE_TESTING=ON" buildDirectory: ${{ runner.workspace }}/cppbuild buildWithCMakeArgs: "--config Debug" - name: ctest working-directory: ${{ runner.workspace }}/cppbuild run: ctest --verbose -C Debug - name: cpack working-directory: ${{ runner.workspace }}/cppbuild run: cmake --build . --config Debug --target package - name: "Create C++ packages artifact" uses: actions/upload-artifact@v7 with: name: cpp_bin-${{ matrix.os }} path: ${{ runner.workspace }}/cppbuild/Slint-cpp-* cpp_package_test: needs: [cpp_cmake] runs-on: ubuntu-22.04 env: QT_QPA_PLATFORM: offscreen CARGO_INCREMENTAL: false CARGO_PROFILE_DEV_DEBUG: 0 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies with: force-gcc-10: true - name: Install Qt (Ubuntu) uses: jurplel/install-qt-action@v4 with: version: 6.5.1 cache: true - uses: actions/download-artifact@v8 with: name: cpp_bin-ubuntu-22.04 path: cpp-package - name: unpack package run: | mkdir package tar xvf cpp-package/Slint-cpp-*.tar.gz -C package --strip-components=1 echo "CMAKE_PREFIX_PATH=`pwd`/package" >> $GITHUB_ENV # Build the examples with a config different than the package (which is debug) - name: Build examples uses: lukka/run-cmake@v3.4 with: cmakeListsOrSettingsJson: CMakeListsTxtAdvanced cmakeListsTxtPath: examples/CMakeLists.txt cmakeAppendedArgs: "-DCMAKE_BUILD_TYPE=Release -DSLINT_FEATURE_INTERPRETER=1 -DSLINT_FEATURE_BACKEND_QT=1" buildDirectory: ${{ runner.workspace }}/examples/build buildWithCMakeArgs: "--config Release" vsce_build_test: needs: files-changed if: needs.files-changed.outputs.vsce == 'true' runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 - uses: Swatinem/rust-cache@v2 with: key: "vsce_1" # increment this to bust the cache if needed - uses: ./.github/actions/install-linux-dependencies - uses: actions/setup-node@v6 with: node-version: 24 package-manager-cache: false - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - name: Fake slint-lsp build run: | mkdir -p target/debug echo 1 > target/debug/slint-lsp - name: Run pnpm install working-directory: editors/vscode run: pnpm install --frozen-lockfile - name: vscode prebuild working-directory: editors/vscode run: pnpm vscode:prepublish - name: Build VS Code extension working-directory: editors/vscode run: pnpm local-package - name: Typescript syntax check # has to run after the build as it depends on the wasm working-directory: editors/vscode run: pnpm type-check - name: Check tmGrammar working-directory: editors/vscode run: pnpm test_grammar # test to compile the mcu backend for the arm target (no_std) mcu: needs: files-changed if: needs.files-changed.outputs.internal == 'true' || needs.files-changed.outputs.examples_mcu == 'true' || needs.files-changed.outputs.api_rs == 'true' env: SLINT_FONT_SIZES: 8,11,10,12,13,14,15,16,18,20,22,24,32 RUSTFLAGS: --cfg slint_int_coord -D warnings CARGO_PROFILE_DEV_DEBUG: 0 CARGO_PROFILE_RELEASE_OPT_LEVEL: s runs-on: ubuntu-22.04 strategy: matrix: include: - feature: pico-st7789 target: thumbv6m-none-eabi - feature: pico2-st7789 target: thumbv8m.main-none-eabihf - feature: pico2-touch-lcd-2-8 target: thumbv8m.main-none-eabihf - feature: stm32h735g target: thumbv7em-none-eabihf steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: target: ${{matrix.target}} - name: Check run: cargo check --target=${{matrix.target}} -p printerdemo_mcu --no-default-features --features=mcu-board-support/${{matrix.feature}} --release # test to compile the mcu backend for the arm target (no_std) using embassy mcu-embassy: needs: files-changed if: needs.files-changed.outputs.internal == 'true' || needs.files-changed.outputs.examples_mcu == 'true' || needs.files-changed.outputs.api_rs == 'true' env: SLINT_FONT_SIZES: 8,11,10,12,13,14,15,16,18,20,22,24,32 RUSTFLAGS: --cfg slint_int_coord -D warnings CARGO_PROFILE_DEV_DEBUG: 0 CARGO_PROFILE_RELEASE_OPT_LEVEL: s runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: target: thumbv8m.main-none-eabihf - name: Check working-directory: examples/mcu-embassy run: cargo check --bin ui_mcu --target=thumbv8m.main-none-eabihf --no-default-features --features="mcu-embassy/mcu" --release # mcu_esp: # env: # RUSTFLAGS: -D warnings # runs-on: ubuntu-22.04 # steps: # - uses: actions/checkout@v6 # - uses: dtolnay/rust-toolchain@stable # - uses: esp-rs/xtensa-toolchain@v1.6 # with: # default: true # buildtargets: esp32 # ldproxy: false # # version pinned until new version of esp-hal with https://github.com/esp-rs/esp-hal/pull/2615/ # version: 1.82.0 # - uses: Swatinem/rust-cache@v2 # - name: S3Box # run: cargo +esp check -p printerdemo_mcu --target xtensa-esp32s3-none-elf --no-default-features --features=mcu-board-support/esp32-s3-box --config examples/mcu-board-support/esp32_s3_box/cargo-config.toml --release ffi_32bit_build: needs: files-changed if: needs.files-changed.outputs.internal == 'true' || needs.files-changed.outputs.api_cpp == 'true' runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: target: armv7-unknown-linux-gnueabihf - uses: baptiste0928/cargo-install@v3 with: crate: cross - name: Check run: cross check --target=armv7-unknown-linux-gnueabihf -p slint-cpp --no-default-features --features=testing,interpreter,std docs: needs: files-changed if: needs.files-changed.outputs.slint == 'true' uses: ./.github/workflows/build_docs.yaml secrets: READ_WRITE_PRIVATE_KEY: ${{ secrets.READ_WRITE_PRIVATE_KEY }} with: release: false app-id: ${{ vars.READ_WRITE_APP_ID }} slintpad: needs: files-changed if: needs.files-changed.outputs.slintpad == 'true' uses: ./.github/workflows/wasm_editor_and_interpreter.yaml wasm_demo: needs: files-changed if: needs.files-changed.outputs.internal == 'true' || needs.files-changed.outputs.examples == 'true' || needs.files-changed.outputs.api_rs == 'true' uses: ./.github/workflows/wasm_demos.yaml with: build_artifacts: false tree-sitter: needs: files-changed if: needs.files-changed.outputs.slint == 'true' uses: ./.github/workflows/tree_sitter.yaml with: latest: false tag: "v0.26.5" # Checkout a old version of the tests and example, then run the slint-updater on them # and check that it worked with the interpreter test. updater_test: needs: files-changed if: needs.files-changed.outputs.updater_test == 'true' env: SLINT_NO_QT: 1 CARGO_INCREMENTAL: false RUST_BACKTRACE: 1 CARGO_PROFILE_DEV_DEBUG: 0 strategy: matrix: from_version: ["0.3.0"] runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: ./.github/actions/install-linux-dependencies - uses: ./.github/actions/setup-rust - name: Checkout old version run: | git checkout v${{ matrix.from_version }} --no-overlay -- examples git checkout v${{ matrix.from_version }} --no-overlay -- tests/cases git checkout v${{ matrix.from_version }} --no-overlay -- tests/helper_components # Remove examples and demos from the workspace because they may no longer exist or their Cargo.toml might prevent the build of the updater sed -i "/examples/d" Cargo.toml sed -i "/demos/d" Cargo.toml - name: "Commit old checkout" run: | git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" git config --global user.name "${GITHUB_ACTOR}" git commit -a -m "REVERT TESTS TO v${{ matrix.from_version }}" - name: run the updater run: | cargo run -p slint-updater -- -i examples/*/*.slint cargo run -p slint-updater -- -i examples/*/*/*.slint cargo run -p slint-updater -- -i tests/cases/*.slint cargo run -p slint-updater -- -i tests/cases/*/*.slint - name: Show the diff run: git diff - name: test # Skip a few tests that rely on private renamed properties. # Skip the tests which makes two way binding to output property (these are warning in previous version) # Skip the test that use impure functions in property bindings (this is also warning in previous version) # Skip the example that did not exist or that are broken # Skip the path layout related tests as the element has been removed # Skip the booker as it use api from the LineEdit that wasn"t meant to be used run: | cargo test -p test-driver-interpreter -- \ --skip test_interpreter_text_cursor_move \ --skip test_interpreter_text_cursor_move_grapheme \ --skip test_interpreter_text_cut \ --skip test_interpreter_text_select_all \ --skip test_interpreter_text_surrogate_cursor \ --skip test_interpreter_text_text_change \ --skip test_interpreter_crashes_layout_deleted_item \ --skip test_interpreter_focus_focus_change_subcompo \ --skip test_interpreter_focus_focus_change_through_signal \ --skip test_interpreter_globals_alias_to_global \ --skip test_interpreter_text_default_color \ --skip test_interpreter_crashes_issue1271_set_in_if \ --skip test_interpreter_models_assign_equal_model \ --skip example_carousel \ --skip example_fancy_demo \ --skip example_dial \ --skip example_fancy_switches \ --skip example_sprite_sheet \ --skip example_grid_model_rows \ --skip example_grid_with_repeated_rows \ --skip example_vector_as_grid \ --skip example_vlayout \ --skip example_flexbox_interactive \ --skip test_interpreter_elements_path_fit \ --skip test_interpreter_layout_path \ --skip test_interpreter_7guis_booker \ # Test that the formater don't introduce slint compilation error fmt_test: needs: files-changed if: needs.files-changed.outputs.slintpad == 'true' || needs.files-changed.outputs.tests == 'true' env: SLINT_NO_QT: 1 CARGO_INCREMENTAL: false RUST_BACKTRACE: 1 CARGO_PROFILE_DEV_DEBUG: 0 RUSTFLAGS: -D warnings runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies - uses: ./.github/actions/setup-rust - name: run the formatter run: | cargo run -p slint-lsp --no-default-features -- format -i tests/cases/**/*.slint cargo run -p slint-lsp --no-default-features -- format -i examples/**/*.slint - name: Show the diff run: git diff - name: Run the intepreter test to make sure that the test are passing after format run: cargo test -p test-driver-interpreter - name: "Commit changes" run: | git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" git config --global user.name "${GITHUB_ACTOR}" git commit -a -m "Run slint-lsp format" - name: Do another pass to check that it is idempotent run: | cargo run -p slint-lsp --no-default-features -- format -i tests/cases/**/*.slint cargo run -p slint-lsp --no-default-features -- format -i examples/**/*.slint git diff --exit-code esp-idf-quick: needs: files-changed if: needs.files-changed.outputs.internal == 'true' || needs.files-changed.outputs.api_cpp == 'true' || needs.files-changed.outputs.examples == 'true' runs-on: ubuntu-22.04 container: espressif/idf:release-v5.2 steps: - name: Fix up pydantic regression (https://github.com/espressif/idf-component-manager/issues/97#issuecomment-3380777944) run: | . ${IDF_PATH}/export.sh cd $IDF_PYTHON_ENV_PATH bin/pip install pydantic==2.11.10 - uses: actions/checkout@v6 - run: apt-get update && apt-get install -y libfontconfig1-dev - uses: dtolnay/rust-toolchain@stable - uses: esp-rs/xtensa-toolchain@v1.6 with: default: true buildtargets: esp32 ldproxy: false - uses: Swatinem/rust-cache@v2 - name: Build and Test Printer demo shell: bash working-directory: demos/printerdemo_mcu/esp-idf run: | . ${IDF_PATH}/export.sh idf.py build android: needs: files-changed if: needs.files-changed.outputs.internal == 'true' || needs.files-changed.outputs.api_rs == 'true' || needs.files-changed.outputs.examples == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies - name: Install Android API level 30 run: ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --install "platforms;android-30" - name: Cache cargo-apk id: cargo-apk-cache uses: actions/cache@v5 with: path: ~/.cargo/bin/cargo-apk key: cargo-apk-cache # Only build cargo-apk if not cached - uses: dtolnay/rust-toolchain@stable if: steps.cargo-apk-cache.outputs.cache-hit != 'true' - name: Install cargo-apk if: steps.cargo-apk-cache.outputs.cache-hit != 'true' run: cargo install cargo-apk - uses: ./.github/actions/setup-rust with: target: aarch64-linux-android - name: Build todo demo run: cargo apk build -p todo --target aarch64-linux-android --lib - name: Build energy-monitor example run: cargo apk build -p energy-monitor --target aarch64-linux-android --lib - name: Build printerdemo example run: cargo apk build -p printerdemo --target aarch64-linux-android --lib - name: Build usecases demo run: cargo apk build -p usecases --target aarch64-linux-android --lib test-figma-inspector: needs: files-changed if: needs.files-changed.outputs.figma_inspector == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: 24 package-manager-cache: false id: node-install - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 - name: Run pnpm install working-directory: tools/figma-inspector run: pnpm install --frozen-lockfile - name: Run build working-directory: tools/figma-inspector run: pnpm build - name: Run tests working-directory: tools/figma-inspector run: pnpm test - name: Type Check working-directory: tools/figma-inspector run: pnpm type-check - name: Build zip working-directory: tools/figma-inspector run: pnpm zip - name: Archive zip uses: actions/upload-artifact@v7 with: name: figma-plugin path: tools/figma-inspector/zip material-components: needs: files-changed if: needs.files-changed.outputs.material_components == 'true' permissions: contents: read deployments: write uses: ./.github/workflows/material.yaml secrets: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_API_TOKEN_2: ${{ secrets.CLOUDFLARE_API_TOKEN_2 }} CLOUDFLARE_ACCOUNT_ID_2: ${{ secrets.CLOUDFLARE_ACCOUNT_ID_2 }} ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} servo_example: needs: files-changed if: needs.files-changed.outputs.servo_example == 'true' uses: ./.github/workflows/servo_example.yaml bevy_examples: needs: files-changed if: needs.files-changed.outputs.bevy_examples == 'true' uses: ./.github/workflows/bevy_examples.yaml clippy: needs: files-changed if: needs.files-changed.outputs.rust_files == 'true' env: RUSTFLAGS: -D warnings CARGO_INCREMENTAL: false CARGO_PROFILE_DEV_DEBUG: 0 runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies - name: Install ffmpeg and alsa (Linux) run: sudo apt-get install clang libavcodec-dev libavformat-dev libavutil-dev libavfilter-dev libavdevice-dev libasound2-dev pkg-config - name: Install gstreamer and libunwind (Linux) run: sudo apt-get install libunwind-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good - uses: ./.github/actions/setup-rust - name: Run Clippy run: ./scripts/run_clippy.sh ================================================ FILE: .github/workflows/cpp_package.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Build the C++ binary package on: workflow_dispatch: workflow_call: inputs: extra_cmake_flags: type: string description: Extra CMake flags to pass to the build. default: "" env: MACOSX_DEPLOYMENT_TARGET: "11.0" # Keep in sync with features in nightly_snapshot.yaml, slint_tool_binary.yaml, api/node/Cargo.toml, and api/python/slint/Cargo.toml SLINT_BINARY_FEATURES: "-DSLINT_FEATURE_BACKEND_LINUXKMS_NOSEAT=ON -DSLINT_FEATURE_BACKEND_WINIT=ON -DSLINT_FEATURE_RENDERER_FEMTOVG=ON -DSLINT_FEATURE_RENDERER_SKIA=ON -DSLINT_FEATURE_RENDERER_SOFTWARE=ON -DSLINT_FEATURE_LIVE_PREVIEW=ON" SLINT_MCU_FEATURES: "-DSLINT_FEATURE_FREESTANDING=ON -DSLINT_FEATURE_RENDERER_SOFTWARE=ON" # env variable used by espup https://github.com/esp-rs/espup?tab=readme-ov-file#github-api GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: cmake_package_desktop: env: DYLD_FRAMEWORK_PATH: /Users/runner/work/slint/Qt/6.2.2/clang_64/lib QT_QPA_PLATFORM: offscreen CARGO_INCREMENTAL: false strategy: matrix: os: [ubuntu-22.04, macos-14, windows-2022] include: - os: ubuntu-22.04 package_suffix: linux - os: macos-14 package_suffix: macos-aarch64 - os: windows-2022 package_suffix: windows-x86_64 msvc_arch: x64 - os: windows-11-arm package_suffix: windows-aarch64 msvc_arch: arm64 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies with: old-ubuntu: true - name: Install Qt (Ubuntu) uses: jurplel/install-qt-action@v4 if: matrix.os == 'ubuntu-22.04' with: version: 6.2.2 cache: true - uses: ./.github/actions/setup-rust - uses: baptiste0928/cargo-install@v3 with: crate: cargo-about version: "=0.6.6" - name: Prepare licenses run: bash -x ../../scripts/prepare_binary_package.sh ${{ runner.workspace }}/cppbuild working-directory: api/cpp/ - uses: ilammy/msvc-dev-cmd@v1 with: arch: ${{ matrix.msvc_arch }} if: runner.os == 'Windows' - name: Select MSVC (windows) run: | echo "CC=cl.exe" >> $GITHUB_ENV echo "CXX=cl.exe" >> $GITHUB_ENV if: runner.os == 'Windows' - name: C++ Build uses: lukka/run-cmake@v3.4 with: cmakeListsOrSettingsJson: CMakeListsTxtAdvanced cmakeListsTxtPath: CMakeLists.txt cmakeAppendedArgs: "-DCMAKE_BUILD_TYPE=RelWithDebInfo ${{ env.SLINT_BINARY_FEATURES }} ${{ inputs.extra_cmake_flags }} -DSLINT_FEATURE_SDF_FONTS=ON" buildDirectory: ${{ runner.workspace }}/cppbuild buildWithCMakeArgs: "--config Release" - name: cpack working-directory: ${{ runner.workspace }}/cppbuild run: cmake --build . --config Release --target package - name: "Upload C++ packages" uses: actions/upload-artifact@v7 with: name: cpp_bin-${{ matrix.package_suffix }} path: ${{ runner.workspace }}/cppbuild/Slint-cpp-* cmake_package_mcu_arm: env: CARGO_INCREMENTAL: false strategy: matrix: target: [thumbv7em-none-eabihf] runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies with: old-ubuntu: true - uses: ./.github/actions/setup-rust with: target: ${{ matrix.target }} - name: Install GNU Arm Embedded Toolchain run: sudo apt-get update && sudo apt-get install gcc-arm-none-eabi - uses: baptiste0928/cargo-install@v3 with: crate: cargo-about version: "=0.6.6" - name: Prepare licenses run: bash -x ../../scripts/prepare_binary_package.sh ${{ runner.workspace }}/cppbuild working-directory: api/cpp/ - name: C++ Build uses: lukka/run-cmake@v3.4 with: cmakeListsOrSettingsJson: CMakeListsTxtAdvanced cmakeListsTxtPath: CMakeLists.txt cmakeAppendedArgs: "-GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=OFF -DRust_CARGO_TARGET=${{ matrix.target }} -DCMAKE_C_COMPILER=arm-none-eabi-gcc -DCMAKE_CXX_COMPILER=arm-none-eabi-g++ -DCMAKE_AR=arm-none-eabi-ar -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY -DSLINT_COMPILER=download ${{ env.SLINT_MCU_FEATURES }} ${{ inputs.extra_cmake_flags }}" buildDirectory: ${{ runner.workspace }}/cppbuild buildWithCMakeArgs: "--config Release" - name: cpack working-directory: ${{ runner.workspace }}/cppbuild run: cpack -G TGZ - name: "Upload C++ packages" uses: actions/upload-artifact@v7 with: name: cpp_mcu_bin-${{ matrix.target }} path: ${{ runner.workspace }}/cppbuild/Slint-cpp-* cmake_package_mcu_esp: env: CARGO_INCREMENTAL: false strategy: matrix: idf_target: [esp32, esp32s2, esp32s3, esp32p4] include: - idf_target: esp32 rust_target: xtensa-esp32-none-elf - idf_target: esp32s2 rust_target: xtensa-esp32s2-none-elf - idf_target: esp32s3 rust_target: xtensa-esp32s3-none-elf - idf_target: esp32p4 rust_target: riscv32imafc-esp-espidf runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies with: old-ubuntu: true - uses: dtolnay/rust-toolchain@stable - uses: cargo-bins/cargo-binstall@main - name: install espup run: | cargo binstall espup espup install rustup default esp - name: add esp toolchains to PATH if: runner.os != 'Windows' run: | source "$HOME/export-esp.sh" echo "$PATH" >> "$GITHUB_PATH" - uses: baptiste0928/cargo-install@v3 with: crate: cargo-about version: "=0.6.6" - name: Prepare licenses run: bash -x ../../scripts/prepare_binary_package.sh ${{ runner.workspace }}/cppbuild working-directory: api/cpp/ - name: C++ Build uses: lukka/run-cmake@v3.4 with: cmakeListsOrSettingsJson: CMakeListsTxtAdvanced cmakeListsTxtPath: CMakeLists.txt # NOTE: xtensa-esp-elf-gcc as compiler for the RISC-V targets is wrong, but the compiler argument here is only # used to ensure that SIZEOF_VOID_P is 4 in the generated cmake config file. Otherwise this build requires no # C compiler. cmakeAppendedArgs: "-GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DESP_PLATFORM=1 -DIDF_TARGET=${{ matrix.idf_target }} -DRust_CARGO_TARGET=${{ matrix.rust_target }} -DCMAKE_C_COMPILER=xtensa-esp-elf-gcc -DCMAKE_AR=xtensa-esp-elf-ar -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY -DSLINT_LIBRARY_CARGO_FLAGS='-Zbuild-std=core,alloc' -DSLINT_COMPILER=download ${{ env.SLINT_MCU_FEATURES }} ${{ inputs.extra_cmake_flags }}" buildDirectory: ${{ runner.workspace }}/cppbuild buildWithCMakeArgs: "--config Release" - name: cpack working-directory: ${{ runner.workspace }}/cppbuild run: cpack -G TGZ - name: "Upload C++ packages" uses: actions/upload-artifact@v7 with: name: cpp_mcu_bin-${{ matrix.idf_target }} path: ${{ runner.workspace }}/cppbuild/Slint-cpp-* cmake_package_arm_linux: env: CARGO_INCREMENTAL: false strategy: matrix: gcc_target: [aarch64-linux-gnu, arm-linux-gnueabihf] include: - gcc_target: aarch64-linux-gnu rust_target: aarch64-unknown-linux-gnu cmake_system_processor: arm64 - gcc_target: arm-linux-gnueabihf rust_target: armv7-unknown-linux-gnueabihf cmake_system_processor: armhf runs-on: ubuntu-22.04 container: ghcr.io/slint-ui/slint/${{ matrix.rust_target }}-cpp steps: - uses: actions/checkout@v6 - uses: baptiste0928/cargo-install@v3 with: crate: cargo-about version: "=0.6.6" - name: Prepare licenses run: bash -x ../../scripts/prepare_binary_package.sh ../../build working-directory: api/cpp/ - name: CMake configure run: cmake -B build -S . -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=${{ matrix.cmake_system_processor }} -DRust_CARGO_TARGET=${{ matrix.rust_target}} -DCMAKE_C_COMPILER=${{ matrix.gcc_target }}-gcc -DCMAKE_CXX_COMPILER=${{ matrix.gcc_target }}-g++ -DSLINT_COMPILER=download ${{ env.SLINT_BINARY_FEATURES }} ${{ inputs.extra_cmake_flags }} - name: CMake build run: cmake --build build --parallel - name: CMake package working-directory: build run: cpack -G TGZ - name: "Upload C++ packages" uses: actions/upload-artifact@v7 with: name: cpp_bin-${{ matrix.rust_target }} path: build/Slint-cpp-* cmake_slint_compiler: env: CARGO_INCREMENTAL: false strategy: matrix: os: [ubuntu-22.04, macos-14, windows-2022] include: - os: ubuntu-22.04 package_suffix: Linux-x86_64 cargo_target: x86_64-unknown-linux-gnu - os: ubuntu-22.04-arm package_suffix: Linux-aarch64 cargo_target: aarch64-unknown-linux-gnu - os: macos-14 package_suffix: Darwin-arm64 cargo_target: aarch64-apple-darwin - os: windows-2022 package_suffix: Windows-AMD64 msvc_arch: x64 cargo_target: x86_64-pc-windows-msvc - os: windows-11-arm package_suffix: Windows-ARM64 msvc_arch: arm64 cargo_target: aarch64-pc-windows-msvc runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies with: old-ubuntu: true - uses: ./.github/actions/setup-rust - uses: ilammy/msvc-dev-cmd@v1 with: arch: ${{ matrix.msvc_arch }} if: runner.os == 'Windows' - name: Select MSVC (windows) run: | echo "CC=cl.exe" >> $GITHUB_ENV echo "CXX=cl.exe" >> $GITHUB_ENV if: runner.os == 'Windows' # Build Slint-compiler with CMake so that CMakeLists.txt can configure C++ specific features - name: Slint Compiler Build uses: lukka/run-cmake@v3.4 with: cmakeListsOrSettingsJson: CMakeListsTxtAdvanced cmakeListsTxtPath: CMakeLists.txt cmakeAppendedArgs: "-GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DSLINT_BUILD_RUNTIME=OFF ${{ inputs.extra_cmake_flags }} -DSLINT_FEATURE_SDF_FONTS=ON -DRust_CARGO_TARGET=${{ matrix.cargo_target }}" buildDirectory: ${{ runner.workspace }}/cppbuild buildWithCMakeArgs: "--config Release" - name: cpack working-directory: ${{ runner.workspace }}/cppbuild run: cpack -G TGZ - name: Extract Slint Compiler binary working-directory: ${{ runner.workspace }}/cppbuild shell: bash run: cmake -E tar xvf Slint-cpp-*.tar.gz - name: Repackage working-directory: ${{ runner.workspace }}/cppbuild shell: bash run: | cd Slint-cpp-*/bin cmake -E tar czf ../../slint-compiler-${{ matrix.package_suffix }}.tar.gz slint-compiler* - name: "Upload Slint compiler package" uses: actions/upload-artifact@v7 with: name: slint-compiler-bin-${{ matrix.package_suffix }} path: ${{ runner.workspace }}/cppbuild/slint-compiler-*.tar.gz ================================================ FILE: .github/workflows/crater.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # This workflow checks out and build a bunch of crates that uses Slint, # with the current branch name: Crater on: workflow_dispatch: jobs: crater: env: SLINT_NO_QT: 1 CARGO_INCREMENTAL: false RUST_BACKTRACE: 1 strategy: fail-fast: false matrix: git_url: # from madewithslint - "https://github.com/slint-ui/cargo-ui" - "https://github.com/GaspardCulis/slint-tetris" #- "https://github.com/dngulin/gpcl" # depends on the winit backend - "https://codeberg.org/flovansl/co_sl" - "https://seed.radicle.garden/z3oxAZSLcyXgpa7fcvgtueF49jHpH.git" #vivi #- "https://github.com/ElevenJune/mastermind_Rust" # Not upgraded to Slint 1.0 #- "https://github.com/Project-Trains/launcher" # Not upgraded to Slint 1.0 - "https://github.com/bjorn/raccoin" - "https://gitlab.com/floers/ordinary" # Has `zip="*"` in its Cargo.toml - "https://github.com/parchlinux/parch-welcome/" #- "https://github.com/boclair/fractal-explorer" # RE-ENABLE WHEN https://github.com/arturoc/color-rs/pull/2 is released - "https://github.com/arunpkio/tymoz" - "https://github.com/Surrealism-All/surrealism-ui-template" - https://github.com/raykavin/market-prices-rust - https://github.com/pezfisk/OxideManager - https://github.com/m4rz3r0/foruster # from crates.io - "https://codeberg.org/drendog/frametrix" - "https://github.com/hzqd/mycalc" - "https://github.com/zilongyang/rust_slint_todos" - "https://github.com/gavinshox/chess-oxide" - "https://github.com/Obscurely/falion" #- "https://github.com/abhay-n-j/warp-gui-app" (wrong edition in Cargo.toml) - "https://github.com/dimas-fw/dimas" - "https://github.com/vova-max-png/slintcalc" - "https://github.com/moistpyro/land-calc" - "https://github.com/clay-6/baze64" - "https://github.com/Siiir/k_means-interactive/" - "https://codeberg.org/waydeer/layer-shika" - "https://github.com/limitcool/xm" #- "https://github.com/vexide/vexide-slint" # vexide somehow enable the error_in_core feature of snafu - "https://github.com/ChenhuiZhang/envsensor-demo" - "https://github.com/arunkumar-mourougappane/gjallarhorn-rs" - "https://github.com/3axap4ehko/litra" - "https://github.com/rezabani/modern-minesweeper" - "https://github.com/guimath/phog" - "https://github.com/mcbridejc/slint-evdev-input" - "https://github.com/heathcliff26/turbo-clicker" - "https://github.com/Decodetalkers/launchre" - "https://github.com/Decodetalkers/haruhishot" - "https://github.com/smalltext/trace-slint/" # - "https://github.com/danrauch/thermocam" # error: failed to run custom build command for `v4l2-sys-mit v0.2.0` - "https://github.com/saturn77/magnet" # - "https://github.com/zzq0097/mmm_rs" # could not compile `netease-cloud-music-api` (lib) - "https://github.com/CMahaff/lasim" # many stars #- "https://github.com/spades9/image-tools" # compile error - "https://github.com/studylessshape/calculator-by-rs" - "https://github.com/gleb-skobinsky/system-metrics" - "https://github.com/AccAutomaton/ECJTU-CAN-Helper" - "https://github.com/Vadoola/pv-unlocker" #- "https://github.com/zzoe/liando" # compile error in zstd-safe https://github.com/gyscos/zstd-rs/issues/270 - "https://github.com/Knowit-Objectnet/infoskjerm-trondheim" - "https://github.com/CaleGlisson/Zelda_pattern_rust" - "https://codeberg.org/dweiss96/rf2_league_mod_tool" - "https://github.com/Badless/slint-editor" - "https://github.com/IvanB101/hamming-huffman" - "https://github.com/izuku0550/Custom-Note-Builder" - "https://github.com/mrquantumoff/quadrant_lite" - "https://github.com/jpnurmi/jpos-weather" #- "https://github.com/osp-project/RizPackageTools" 404 - "https://github.com/xenein/CountMeDown-rs" - "https://github.com/JeremiasMeister/rust-heightmap-generator" - "https://github.com/joaofl/any-serve" - "https://github.com/Heng30/rssbox" - "https://github.com/Heng30/rssbox-android" - "https://github.com/Heng30/uibox" #- "https://github.com/Heng30/bitbox" #pinned slint-build version #- "https://github.com/Heng30/solana-wallet-sollet" # error in dependency (pyth-sdk-solana) - "https://github.com/nzhenev/vtbox" #- "https://github.com/hambooooo/hamboo-rs" #uses nightly rust # - "https://github.com/hambooooo/hamboo-embassy" #uses nightly rust #- "https://github.com/Kllngii/Wetterstation" # can't check submodules - "https://github.com/tredeneo/simple-inventary-management" #uses sqlx that needs database access # - "https://github.com/xcodes2cn/Xviewer" #copies slint - "https://github.com/olishmollie/radix" - "https://github.com/zivit/watchlist" - "https://github.com/FelgoSDK/RustyWeather" - "https://github.com/ThilinaTLM/docker_ui" - "https://github.com/PlanetTeamSpeakk/Thermostat" - "https://github.com/develcooking/Reelix" - "https://github.com/Albino-Client/AlbinoLauncher" - "https://github.com/radev1924/profit-first-income-calculator-desktop" - "https://github.com/JarrodDoyle/slint-fmeditor" - "https://github.com/planet0104/slint-noframe-window" #- "https://github.com/Dusk-Labs/kira" uses private API # - "https://github.com/TuEmb/can-viewer" # depends on a deleted repo # - "https://github.com/qhua948/anubis" # uses nightly rust - "https://github.com/Surrealism-All/slimk" - "https://github.com/BounceU/car_thang" - "https://github.com/sloganking/codevis-gui" - "https://github.com/CzNorbi/tic_tac_toe_ui" - "https://github.com/remy2019/smtbr_gui" - "https://github.com/mrandal/PDFer" - "https://github.com/dhasoft-rs/7Seg-Slint-Widget" # - "https://github.com/DraKraft/ozon-rs" # Error: the trait `ToSql` is not implemented for `usize` # - "https://github.com/SUP2Ak/Lur-Slint" # repo gone? # - "https://github.com/foxboxpdx/melonstt" # repo gone? - "https://github.com/sanyexieai/me_chat" #- "https://github.com/oreo3494/rockpaper.git" # error because of `rand` - "https://github.com/padangpanda/password-generator-desktop" - "https://github.com/RealAdarsh/slint-guess-game" - "https://github.com/DASPRiD/vrc-osc-manager" - "https://github.com/beac0n/ruroco" - "https://github.com/SergioRibera/Simplemoji" - "https://github.com/Ashintosh/NoPass" # - "https://github.com/Gremious/stickerbox" depends on unstable-wgpu-24 - "https://github.com/aenriii/rimgv" - "https://github.com/Brayan-724/amosd" - "https://github.com/kevinquillen/sysinfo" - "https://github.com/home-cooked-firmware/hcui" - "https://github.com/EinsPhoenix/MineSweeper-Rust" - "https://github.com/themkoi/Cosmic-Wanderer" - "https://github.com/xcrong/slint_data_processer" - "https://github.com/Newlifer/peditor" - "https://github.com/spineda2019/tRust" - "https://github.com/franckKyete/tools-desktop" - "https://github.com/zys60233/secret-box" - "https://github.com/xjy12345654/css_parser" - "https://github.com/zuyzz/app_flock" #- "https://github.com/interstellarfrog/OG3-Pack-Updater" #zip2.6 is yanked - "https://github.com/balicz3k/FileGuard" - "https://github.com/ravioni-encoder/ravioni" - "https://github.com/konmenel/tzimpouki" - "https://github.com/TAlgorhythmic/dystellar-launcher-rs" - "https://github.com/trappitsch/calculon" - "https://github.com/Jaysmito101/slint-nav" - "https://github.com/mq1/TinyWiiBackupManager" - "https://github.com/jacquetc/qleany" - https://github.com/taichi765/Tsukuyomi - https://github.com/Cheikh-Nakamoto/Generateur-d-architecture- - https://github.com/Spencer-0003/aletheia - https://github.com/engels74/EasyHDR - https://github.com/reedrosenbluth/oscen - https://github.com/futo-org/fcast - https://github.com/BiliRumble/Cloubit # Missing images # - "https://github.com/rurishigeo/Probe-Downloader" # - "https://github.com/Tricked-dev/stardew-mod-manager" # - "https://github.com/Erik7354/slint_minesweeper" # - "https://github.com/guimath/GeoQuiz" # Use our private API (eg: backend winit) # - "https://github.com/Davide255/LVIE" # - "https://github.com/Vadoola/Tomotroid" # - "https://github.com/Horbin-Magician/rotor-rs" # - "https://github.com/ilmai/plugin-things" # - "https://github.com/Hoverth/keyboardthing" # - "https://codeberg.org/moire/moire" # Broken because of other dependencies. # Needs extra dependencies # - "https://github.com/nununoisy/gb-presenter-rs" # - "https://github.com/nununoisy/nsf-presenter-rs" # - "https://github.com/bombless/slint-tree" # "https://github.com/AaronGulman/Rusty-CryptoMonitor" (lua54) #- "https://github.com/link9c/media_backup" #(windows only (failed to resolve: use of undeclared crate or module `winres`)) #- "https://github.com/Vadoola/ignition_npp_tools" #windows only #- "https://github.com/zloisupport/vanctrl" #windows only #- "https://github.com/GRX005/McModManager-rs" #- "https://github.com/un4ckn0wl3z/title-randomizer-rs" # Not updated to Slint 1.0 #- "https://github.com/jannes/han-cihui" #- "https://github.com/leofidus/ntfs-explorer" #- "https://github.com/gsuyemoto/rust-bombfield" #- "https://github.com/kizeevov/l5" #- "https://github.com/SergioGasquez/espup-slint" sub_path: ["."] os: ["ubuntu-24.04"] include: - git_url: "https://github.com/jturcotte/chiptrack" sub_path: "." extra_packages: libasound2-dev libfreetype-dev libfontconfig-dev - git_url: "https://github.com/Futsch1/image-sieve" sub_path: "." extra_packages: libgtk-3-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev llvm libheif-dev - git_url: "https://github.com/griccardos/rusl" sub_path: "slint" #- git_url: "https://github.com/DanielMadmon/tasker" # needs "themis dep" # sub_path: "bin/tasker_gui" # - git_url: "https://github.com/Kllngii/Wetterstation" # can't check submodules # sub_path: "wetterstation-ui" - git_url: "https://github.com/plule/theremotion" sub_path: "theremotion-ui" - git_url: "https://github.com/matthiasbeyer/deskodon" sub_path: "frontend" - git_url: "https://github.com/malwaredb/malwaredb-rs" extra_args: "--features=admin,admin-gui" sub_path: "." #- git_url: "https://github.com/Martoni/QRNote" # Pins slint version # sub_path: "qr-note" #- git_url: "https://github.com/planet0104/satellite_wallpaper" # sub_path: "." # os: windows-latest #- git_url: "https://github.com/colelawrence/here-now" # Compile error # sub_path: "hn-desktop-ui" - git_url: "https://github.com/MunyaradziMagura/Pomodoro" sub_path: "pomodoro" - git_url: "https://github.com/flukejones/asusctl" sub_path: "rog-control-center" #- git_url: "https://github.com/ellenhp/openlmr" #doesn't compile # sub_path: "openlmr-core" #- git_url: "https://github.com/jenkinsmichpa/coconut_crab" # sub_path: "coconut_crab_client" # os: windows-latest - git_url: "https://github.com/ivabus/lonelyradio" sub_path: "monoclient-s" extra_packages: alsa-utils libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libasound2-dev - git_url: "https://github.com/ahqsoftwares/tauri-ahq-store" sub_path: "src-setup" - git_url: "https://github.com/qarmin/czkawka" sub_path: krokiet - git_url: "https://github.com/Vjze/Rust_demo" # this is using SurrealismUI sub_path: Rust_Slint #- git_url: "https://github.com/LeeeSe/MessAuto" # os: macos-latest # sub_path: "." - git_url: "https://github.com/planet0104/USB-Screen/" extra_args: "--no-default-features --features=editor" sub_path: "." - git_url: "https://github.com/newfla/daily-strip" sub_path: "." extra_args: "--no-default-features --features=slint_frontend" - git_url: "https://github.com/niqt/matrust/" sub_path: "." - git_url: "https://github.com/obhq/obliteration" sub_path: "gui" - git_url: "https://github.com/jaymedavis/romboy" sub_path: "romboy" #- git_url: "https://github.com/somantics/retrodungeon" # compile error # sub_path: "." # extra_args: "--features=femtovg" #- git_url: "https://github.com/jonfast565/runinator" # compile error (Used `MainWindow instead of `App` in rust) # sub_path: "command-center" # - git_url: "https://github.com/stars-labs/mpc-wallet" # uses properties that don't exist (AI hallucinations?) # sub_path: "apps/native-node" - git_url: "https://github.com/larus-breeze/sw_frontend_rs" sub_path: "device/sim" extra_packages: alsa-utils libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libasound2-dev # - git_url: "https://github.com/gramistella/cornerstone" # depends indirectly on old versions of crates (deno_ast/swc) that do not compile # sub_path: "frontend_slint" - git_url: "https://github.com/dilo145/password_generator" sub_path: "password_generator_gui" - git_url: "https://github.com/DiD92/map-generator" sub_path: "generator-ui" - git_url: "https://github.com/Szybet/snake-slint" sub_path: "snake_bin_std" - git_url: "https://github.com/Heng30/flymoon" sub_path: "flymoon" - git_url: "https://github.com/gopher64/gopher64" sub_path: "." extra_packages: libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev libaudio-dev libfribidi-dev libsndio-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev - git_url: "https://github.com/PonasKovas/salix" sub_path: "client-gui" - git_url: "https://github.com/chrishengler/terraingen" sub_path: "gui-slint" - git_url: "https://github.com/Vinegret43/dispute" sub_path: "." extra_packages: libdbus-1-dev alsa-utils libasound2-dev libavcodec-dev libavformat-dev libavutil-dev - git_url: "https://github.com/Heng30/chatbox" sub_path: "." extra_packages: alsa-utils libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libjack-jackd2-dev - git_url: "https://github.com/Risuleia/Tranquilo" sub_path: "." extra_packages: alsa-utils libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libjack-jackd2-dev - git_url: "https://github.com/R3alCl0ud/Rust4Diva" sub_path: "." extra_packages: libarchive-dev - git_url: "https://github.com/Enn3Developer/n_music" extra_packages: libasound2-dev libfontconfig-dev libfreetype-dev sub_path: "n_player" - git_url: "https://github.com/BiliRumble/Cloubit" sub_path: "." extra_packages: build-essential perl nasm libasound2-dev pkg-config - git_url: "https://github.com/TsaoLun/always-blue" sub_path: "." extra_packages: libasound2-dev libfreetype-dev libfontconfig-dev - git_url: "https://github.com/jturcotte/beskope" sub_path: "." extra_packages: libasound2-dev libfreetype-dev libfontconfig-dev libpipewire-0.3-dev - git_url: "https://github.com/sloganking/quick-assistant" sub_path: "." extra_packages: alsa-utils libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libasound2-dev - git_url: "https://github.com/laycookie/record" sub_path: "." extra_packages: alsa-utils libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libasound2-dev - git_url: "https://github.com/Danila-Bain/wav-wav" sub_path: "." extra_packages: alsa-utils libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libasound2-dev - git_url: "https://github.com/mackler/Lodge-Musician" sub_path: "." extra_packages: alsa-utils libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libasound2-dev - git_url: "https://github.com/game-exchange-token/gxt" sub_path: "gxt-cli" extra_args: "--features=ui" - git_url: "https://github.com/paulusminus/lipl-display" sub_path: "crates/lipl-display-slint" - git_url: "https://github.com/vimyoung/spell" sub_path: "spell-framework" - git_url: "https://github.com/Farmadupe/vid_dup_finder_lib" sub_path: vid_dup_finder_app - git_url: "https://github.com/simmsb/inkview-rs" sub_path: inkview-slint - git_url: "https://codeberg.org/psylink/psylink" - git_url: "https://github.com/shuntia/bestest" extra_args: "--features=gui" - git_url: https://github.com/Konstantin-Dudersky/rsiot extra_args: "--features=cmp_slint" - git_url: https://github.com/Szybet/snake-slint/ sub_path: "snake_bin_std" - git_url: https://github.com/maplibre/maplibre-native-slint sub_path: rust - git_url: https://github.com/AgustinSRG/PersonalMediaVault sub_path: launcher-gui - git_url: https://github.com/MERCorg/merc sub_path: tools/gui - git_url: https://github.com/guycorbaz/rbibli sub_path: frontend - git_url: https://github.com/tadghh/transparent-windows extra_args: "--features=tray-item/ksni" - git_url: "https://codeberg.org/vivi-ui/lili" extra_packages: alsa-utils libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libasound2-dev - git_url: "https://github.com/bandipapa/rsaber" extra_packages: alsa-utils libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libasound2-dev - git_url: "https://github.com/heng30/wayshot" extra_packages: alsa-utils libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libasound2-dev - git_url: https://github.com/heng30/tasklog sub_path: tasklog extra_args: "--features=desktop" runs-on: "ubuntu-latest" steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies if: runner.os == 'Linux' with: extra-packages: libpango1.0-dev libgtk-3-dev libatk1.0-dev libjack-jackd2-dev autoconf libxcb-xrm0 libxcb-xrm-dev automake libxcb-keysyms1-dev libxcb-util0-dev libxcb-icccm4-dev libyajl-dev libstartup-notification0-dev libxcb-randr0-dev libev-dev libxcb-cursor-dev libxcb-xinerama0-dev libxcb-xkb-dev libxkbcommon-dev libxkbcommon-x11-dev libudev-dev clang pkg-config nasm libsoup2.4-dev libfuse3-dev libx11-xcb-dev ${{ matrix.extra_packages }} # Don't use the cache because we don't run this job often, and it would cache the .cargo/config.toml with extra keys - uses: dtolnay/rust-toolchain@master with: toolchain: stable - name: setup patch run: | echo "" >> ~/.cargo/config.toml echo [patch.crates-io] >> ~/.cargo/config.toml echo slint = { path = \"$GITHUB_WORKSPACE/api/rs/slint\" } >> ~/.cargo/config.toml echo slint-build = { path = \"$GITHUB_WORKSPACE/api/rs/build\" } >> ~/.cargo/config.toml echo slint-interpreter = { path = \"$GITHUB_WORKSPACE/internal/interpreter\" } >> ~/.cargo/config.toml echo i-slint-backend-winit = { path = \"$GITHUB_WORKSPACE/internal/backends/winit\" } >> ~/.cargo/config.toml echo i-slint-core = { path = \"$GITHUB_WORKSPACE/internal/core\" } >> ~/.cargo/config.toml - name: Checkout the repo run: | cd $HOME git clone ${{ matrix.git_url }} the_repo --depth 1 cd the_repo git submodule update --init --recursive - name: build run: | cd $HOME/the_repo rm -r rust-toolchain.toml .cargo/config.toml || true cd ./${{ matrix.sub_path }} sed -i "s/{{project-name}}/the-project/" Cargo.toml cargo update cargo check ${{ matrix.extra_args }} # Slint only: how can we test them? # https://github.com/Surrealism-All/SurrealismUI # https://github.com/8yteDance/SlintSubMenu # C++ # https://github.com/LeVietXuanKG/TodoApp_Slint # https://github.com/NatnaelTaddese/slint_cpp_bilinear_interpolation # https://github.com/vudinhkhoa0/DEMO_DOAN_CTDL-GT # https://github.com/progzone122/micropad ================================================ FILE: .github/workflows/embedded_build.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Embedded Build on: workflow_dispatch: schedule: - cron: "0 1 * * *" jobs: build_containers: runs-on: ubuntu-22.04 strategy: matrix: target: - armv7-unknown-linux-gnueabihf - aarch64-unknown-linux-gnu - riscv64gc-unknown-linux-gnu - x86_64-unknown-linux-gnu env: SLINT_NO_QT: 1 SLINT_STYLE: fluent steps: - uses: actions/checkout@v6 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Login to GitHub Container Registry uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.CR_PAT }} - name: Build and push uses: docker/build-push-action@v7 with: context: . file: ./docker/Dockerfile.${{ matrix.target }} push: true tags: ghcr.io/slint-ui/slint/${{matrix.target}}:latest - name: Build and push C++ image uses: docker/build-push-action@v7 with: context: . file: ./docker/Dockerfile.cpp-image push: true tags: ghcr.io/slint-ui/slint/${{matrix.target}}-cpp:latest build-args: | arch=${{matrix.target}} build_demos: needs: [build_containers] runs-on: ubuntu-22.04 strategy: matrix: target: - armv7-unknown-linux-gnueabihf - aarch64-unknown-linux-gnu - x86_64-unknown-linux-gnu steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: target: ${{ matrix.target }} - name: Enable Skia build if: matrix.target != 'riscv64gc-unknown-linux-gnu' run: | echo "EXTRA_ARGS=--features slint/renderer-skia" >> $GITHUB_ENV - uses: baptiste0928/cargo-install@v3 with: crate: cross - name: Build run: cross build --release --target=${{ matrix.target }} ${{ env.EXTRA_ARGS }} --features slint/backend-linuxkms-noseat,slint/renderer-skia -p energy-monitor -p printerdemo -p todo -p slint-viewer -p gallery -p home-automation - name: "Upload demo artifacts" uses: actions/upload-artifact@v7 with: name: slint-embedded-demos-${{ matrix.target }} path: | target/${{ matrix.target }}/release/printerdemo target/${{ matrix.target }}/release/todo target/${{ matrix.target }}/release/gallery target/${{ matrix.target }}/release/viewer target/${{ matrix.target }}/release/energy-monitor target/${{ matrix.target }}/release/home-automation ================================================ FILE: .github/workflows/issue_triage.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Auto-triage on Comment on: issue_comment: types: [created] permissions: issues: write pull-requests: write jobs: update_labels: runs-on: ubuntu-latest steps: - name: Check whether we should update the tags id: check env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | has_needs_info=$(echo '${{ toJson(github.event.issue.labels.*.name) }}' | jq 'contains(["needs info"])') is_author="${{ github.event.comment.user.login == github.event.issue.user.login }}" echo "has_needs_info=$has_needs_info" >> $GITHUB_OUTPUT echo "is_author=$is_author" >> $GITHUB_OUTPUT - name: Update labels if: steps.check.outputs.has_needs_info == 'true' && steps.check.outputs.is_author == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh api --method DELETE -H "Accept: application/vnd.github+json" /repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/labels/needs%20info gh api --method POST -H "Accept: application/vnd.github+json" /repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/labels -f labels[]='need triaging' ================================================ FILE: .github/workflows/material.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Material - Build, test and publish on: workflow_dispatch: inputs: deploy_production: type: boolean description: "Deploy to material.slint.dev \n Only tick this box if you want to deploy the current state of the master branch to material.slint.dev." default: false required: false workflow_call: secrets: CLOUDFLARE_API_TOKEN: required: true CLOUDFLARE_API_TOKEN_2: required: true CLOUDFLARE_ACCOUNT_ID: required: true CLOUDFLARE_ACCOUNT_ID_2: required: true ANDROID_KEYSTORE_PASSWORD: required: true ANDROID_KEYSTORE_BASE64: required: true permissions: contents: read env: MATERIAL_ZIP_VERSION: "1.0.1" jobs: material_wasm_demo: uses: ./.github/workflows/material_wasm_gallery.yaml material_apk_demo: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} uses: ./.github/workflows/material_gallery.yaml secrets: ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} material_tests: env: CARGO_PROFILE_RELEASE_OPT_LEVEL: s CARGO_INCREMENTAL: false runs-on: ubuntu-latest defaults: run: working-directory: ui-libraries/material steps: - uses: actions/checkout@v6 - name: Install Linux dependencies if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libxkbcommon-x11-dev libfontconfig-dev shell: bash - name: Install Rust uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 with: key: tests - name: Build & run tests run: cargo test -p material-gallery deploy: runs-on: ubuntu-latest needs: [material_wasm_demo, material_apk_demo] permissions: contents: read deployments: write name: Deploy to Cloudflare Pages defaults: run: working-directory: ui-libraries/material/docs steps: - name: Checkout uses: actions/checkout@v6 - name: Install Linux dependencies run: | sudo apt-get update sudo apt-get install libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libxkbcommon-x11-dev libfontconfig-dev shell: bash - name: Install Rust uses: dtolnay/rust-toolchain@stable - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 - uses: actions/setup-node@v6 with: node-version: 24 package-manager-cache: false - name: Take screenshots run: cargo run -p slint-docsnapper -- -Lmaterial=$PWD/src/material.slint docs working-directory: ui-libraries/material - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build run: pnpm build - uses: actions/download-artifact@v8 with: name: material_wasm_gallery path: ui-libraries/material/docs/dist/wasm - uses: actions/download-artifact@v8 if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} with: name: material_gallery path: ui-libraries/material/docs/dist/apk - name: Normalize APK names if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} working-directory: ui-libraries/material run: | if [ -f docs/dist/apk/material-gallery.apk ]; then mv docs/dist/apk/material-gallery.apk docs/dist/apk/slint_material.apk; fi if [ -f docs/dist/apk/material-gallery.aab ]; then mv docs/dist/apk/material-gallery.aab docs/dist/apk/slint_material.aab; fi - name: Zip material library working-directory: ui-libraries/material run: | cp -a src material-${MATERIAL_ZIP_VERSION} mkdir -p docs/dist/zip curl -fsSL -o docs/dist/zip/material-1.0.zip https://releases.slint.dev/material-1.0/material-1.0.zip zip -r docs/dist/zip/material-${MATERIAL_ZIP_VERSION}.zip material-${MATERIAL_ZIP_VERSION} rm -rf material-${MATERIAL_ZIP_VERSION} - name: Deploy to materialui (Pages) if: github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_production == 'true' uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: pages deploy dist --project-name=materialui --branch=master gitHubToken: ${{ secrets.GITHUB_TOKEN }} packageManager: pnpm workingDirectory: ui-libraries/material/docs - name: Deploy to material-staging (Workers production) if: (github.event_name != 'workflow_dispatch' || github.event.inputs.deploy_production != 'true') && github.ref == 'refs/heads/master' uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN_2 }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID_2 }} command: deploy gitHubToken: ${{ secrets.GITHUB_TOKEN }} packageManager: pnpm workingDirectory: ui-libraries/material/docs - name: Deploy preview to material-staging (Workers) id: preview if: (github.event_name != 'workflow_dispatch' || github.event.inputs.deploy_production != 'true') && github.ref != 'refs/heads/master' run: | OUTPUT=$(pnpm dlx wrangler@3 versions upload 2>&1) echo "$OUTPUT" PREVIEW_URL=$(echo "$OUTPUT" | grep -oE 'https://[^[:space:]]+\.workers\.dev' | head -1) echo "url=$PREVIEW_URL" >> $GITHUB_OUTPUT working-directory: ui-libraries/material/docs env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN_2 }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID_2 }} - name: Create GitHub deployment for PR if: steps.preview.outputs.url != '' && github.event_name == 'pull_request' uses: actions/github-script@v8 env: PREVIEW_URL: ${{ steps.preview.outputs.url }} with: script: | const ref = context.payload.pull_request.head.sha; const deployment = await github.rest.repos.createDeployment({ owner: context.repo.owner, repo: context.repo.repo, ref, environment: 'preview', auto_merge: false, required_contexts: [] }); if (deployment.data.id) { await github.rest.repos.createDeploymentStatus({ owner: context.repo.owner, repo: context.repo.repo, deployment_id: deployment.data.id, state: 'success', environment_url: process.env.PREVIEW_URL, log_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}` }); } ================================================ FILE: .github/workflows/material_gallery.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Build an APK and AAB for the gallery example on: workflow_dispatch: workflow_call: secrets: ANDROID_KEYSTORE_PASSWORD: required: true ANDROID_KEYSTORE_BASE64: required: true jobs: material_gallery_android: env: CARGO_PROFILE_RELEASE_OPT_LEVEL: s CARGO_INCREMENTAL: false CARGO_APK_RELEASE_KEYSTORE: /home/runner/.android/release.keystore CARGO_APK_RELEASE_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} runs-on: ubuntu-latest defaults: run: working-directory: ui-libraries/material steps: - uses: actions/checkout@v6 # 1. Check Artifact Caches - name: Cache apk build id: cache-apk uses: actions/cache@v5 with: path: | target/x/release/android/material-gallery.apk key: apk-build-${{ hashFiles('ui-libraries/material/examples/gallery/**', 'ui-libraries/material/ui/**', 'ui-libraries/material/Cargo.toml', 'ui-libraries/material/material.slint') }} - name: Cache aab build id: cache-aab uses: actions/cache@v5 with: path: | target/x/release/android/material-gallery.aab key: aab-build-${{ hashFiles('ui-libraries/material/examples/gallery/**', 'ui-libraries/material/ui/**', 'ui-libraries/material/Cargo.toml', 'ui-libraries/material/material.slint') }} # 2. Install Dependencies (Run only if artifacts are missing) - uses: ./.github/actions/install-linux-dependencies if: steps.cache-apk.outputs.cache-hit != 'true' || steps.cache-aab.outputs.cache-hit != 'true' - name: Install Rust if: steps.cache-apk.outputs.cache-hit != 'true' || steps.cache-aab.outputs.cache-hit != 'true' uses: dtolnay/rust-toolchain@stable with: target: aarch64-linux-android - name: Install Android API level 30 if: steps.cache-apk.outputs.cache-hit != 'true' || steps.cache-aab.outputs.cache-hit != 'true' run: ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --install "platforms;android-30" # 3. Setup Environment (Run only if artifacts are missing) - name: Add NDK LLVM to PATH if: steps.cache-apk.outputs.cache-hit != 'true' || steps.cache-aab.outputs.cache-hit != 'true' run: echo "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin" >> $GITHUB_PATH - name: Dump keystore if: steps.cache-apk.outputs.cache-hit != 'true' || steps.cache-aab.outputs.cache-hit != 'true' run: | mkdir -p /home/runner/.android echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > $CARGO_APK_RELEASE_KEYSTORE # 4. Tool Caching & Installation - name: Cache xbuild if: steps.cache-apk.outputs.cache-hit != 'true' || steps.cache-aab.outputs.cache-hit != 'true' id: xbuild-cache uses: actions/cache@v5 with: path: | ~/.cargo/bin/x ~/.cargo/bin/xbuild key: xbuild-cache-v2 - uses: Swatinem/rust-cache@v2 if: steps.cache-apk.outputs.cache-hit != 'true' || steps.cache-aab.outputs.cache-hit != 'true' with: key: android-build - name: Install xbuild if: (steps.cache-apk.outputs.cache-hit != 'true' || steps.cache-aab.outputs.cache-hit != 'true') && steps.xbuild-cache.outputs.cache-hit != 'true' run: cargo install --git https://github.com/burhankhanzada/xbuild --rev 5eb203e9e0718bd992632b803d59bed80ae0bdc0 xbuild # 5. Build - name: Build apk if: steps.cache-apk.outputs.cache-hit != 'true' working-directory: ui-libraries/material/examples/gallery run: x build --platform android --arch arm64 --release --format 'apk' - name: Build aab if: steps.cache-aab.outputs.cache-hit != 'true' working-directory: ui-libraries/material/examples/gallery run: x build --platform android --arch arm64 --release --format 'aab' - name: Verify aab if: steps.cache-aab.outputs.cache-hit != 'true' working-directory: ui-libraries/material/examples/gallery run: | wget https://github.com/google/bundletool/releases/download/1.18.3/bundletool-all-1.18.3.jar java -jar bundletool-all-1.18.3.jar validate --bundle $GITHUB_WORKSPACE/target/x/release/android/material-gallery.aab # 6. Upload - name: "Upload Artifacts" uses: actions/upload-artifact@v7 with: name: material_gallery path: | target/x/release/android/material-gallery.apk target/x/release/android/material-gallery.aab ================================================ FILE: .github/workflows/material_wasm_gallery.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Build a WASM version of the gallery example on: workflow_dispatch: workflow_call: jobs: material_wasm_build: env: CARGO_PROFILE_RELEASE_OPT_LEVEL: s CARGO_INCREMENTAL: false runs-on: ubuntu-latest defaults: run: working-directory: ui-libraries/material steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies - name: Cache WASM build id: cache-wasm uses: actions/cache@v5 with: path: | ui-libraries/material/examples/gallery/pkg ui-libraries/material/examples/gallery/index.html ui-libraries/material/examples/gallery/frame-tablet.webp key: wasm-build-${{ hashFiles('ui-libraries/material/examples/gallery/**', 'ui-libraries/material/ui/**', 'ui-libraries/material/Cargo.toml', 'ui-libraries/material/material.slint') }} - name: Install Rust if: steps.cache-wasm.outputs.cache-hit != 'true' uses: dtolnay/rust-toolchain@stable with: target: wasm32-unknown-unknown - name: Install wasm-pack if: steps.cache-wasm.outputs.cache-hit != 'true' run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - uses: Swatinem/rust-cache@v2 if: steps.cache-wasm.outputs.cache-hit != 'true' with: key: wasm-build - name: Build if: steps.cache-wasm.outputs.cache-hit != 'true' working-directory: ui-libraries/material/examples/gallery run: wasm-pack build --target web - name: "Upload Artifacts" uses: actions/upload-artifact@v7 with: name: material_wasm_gallery path: | ui-libraries/material/examples/gallery/index.html ui-libraries/material/examples/gallery/frame-tablet.webp ui-libraries/material/examples/gallery/pkg ================================================ FILE: .github/workflows/nightly_snapshot.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # Build various demo binaries, c++ packages and documentation and publish them on the website name: Nightly snapshot on: workflow_dispatch: inputs: private: type: boolean default: true required: false description: "Private build? True means artifacts are only built. False means the artefacts are published (docs, vscode extension) to the web/marketplace" release: type: boolean default: false required: false description: "Release? Enable options for building binaries for a release (i.e. don't have a -nightly suffix for the extension)" env: # Keep in sync with features in slint_tool_binary.yaml, cpp_package.yaml, api/node/Cargo.toml, and api/python/slint/Cargo.toml SLINT_BINARY_FEATURES: "backend-linuxkms-noseat,backend-winit,renderer-femtovg,renderer-skia,renderer-software" MACOSX_DEPLOYMENT_TARGET: "11.0" jobs: slint-viewer-binary: uses: ./.github/workflows/slint_tool_binary.yaml with: program: "viewer" secrets: certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }} certificate_password: ${{ secrets.APPLE_CERTIFICATE_P12_PASSWORD }} keychain_password: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }} developer_id: ${{ secrets.APPLE_DEV_ID }} slint-lsp-binary: uses: ./.github/workflows/slint_tool_binary.yaml with: program: "lsp" secrets: certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }} certificate_password: ${{ secrets.APPLE_CERTIFICATE_P12_PASSWORD }} keychain_password: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }} developer_id: ${{ secrets.APPLE_DEV_ID }} docs: uses: ./.github/workflows/build_docs.yaml secrets: inherit with: release: ${{ github.event.inputs.release }} app-id: ${{ vars.READ_WRITE_APP_ID }} wasm_demo: uses: ./.github/workflows/wasm_demos.yaml with: build_artifacts: true wasm: uses: ./.github/workflows/wasm_editor_and_interpreter.yaml cpp_package: uses: ./.github/workflows/cpp_package.yaml with: extra_cmake_flags: ${{ github.event.inputs.release != 'true' && '-DCPACK_PACKAGE_VERSION=nightly' || '' }} check-for-secrets: runs-on: ubuntu-latest outputs: has-vscode-marketplace-pat: ${{ steps.one.outputs.has-vscode-marketplace-pat }} has-openvsx-pat: ${{ steps.one.outputs.has-openvsx-pat }} steps: - id: one run: | [ -n "${{ secrets.VSCODE_MARKETPLACE_PAT }}" ] && echo "has-vscode-marketplace-pat=yes" >> "$GITHUB_OUTPUT" [ -n "${{ secrets.OPENVSX_PAT }}" ] && echo "has-openvsx-pat=yes" >> "$GITHUB_OUTPUT" build_vscode_lsp_linux_windows: env: SLINT_NO_QT: 1 strategy: matrix: include: - os: ubuntu-22.04 toolchain: x86_64-unknown-linux-gnu binary_built: slint-lsp artifact_name: slint-lsp-x86_64-unknown-linux-gnu - os: windows-2022 toolchain: x86_64-pc-windows-msvc binary_built: slint-lsp.exe artifact_name: slint-lsp-x86_64-pc-windows-msvc.exe - os: windows-11-arm toolchain: aarch64-pc-windows-msvc binary_built: slint-lsp.exe artifact_name: slint-lsp-aarch64-pc-windows-msvc.exe runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: target: ${{ matrix.toolchain }} - uses: ./.github/actions/install-linux-dependencies with: old-ubuntu: true - name: Build LSP run: cargo build --target ${{ matrix.toolchain }} --features ${{ env.SLINT_BINARY_FEATURES }} --release -p slint-lsp - name: Create artifact directory run: | mkdir bin cp target/${{ matrix.toolchain }}/release/${{ matrix.binary_built }} bin/${{ matrix.artifact_name }} - name: "Upload LSP Artifact" uses: actions/upload-artifact@v7 with: name: vscode-lsp-binary-${{ matrix.toolchain }} path: | bin build_vscode_lsp_macos_x86_64: env: SLINT_NO_QT: 1 runs-on: macos-15-intel steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: target: x86_64-apple-darwin - name: Install cargo-bundle run: cargo install --version=0.6.0 cargo-bundle - name: Build Main LSP Bundle working-directory: tools/lsp run: cargo bundle --release --features ${{ env.SLINT_BINARY_FEATURES }} - name: Create artifact directory run: | mkdir bin cp -a target/release/bundle/osx/Slint\ Live\ Preview.app bin - name: "Upload LSP Artifact" uses: actions/upload-artifact@v7 with: name: vscode-lsp-binary-x86_64-apple-darwin path: | bin build_vscode_lsp_macos_aarch64: env: SLINT_NO_QT: 1 runs-on: macos-26 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: target: aarch64-apple-darwin - name: Build AArch64 LSP run: cargo build --target aarch64-apple-darwin --features ${{ env.SLINT_BINARY_FEATURES }} --release -p slint-lsp - name: Create artifact directory run: | mkdir bin cp -a target/aarch64-apple-darwin/release/slint-lsp bin/slint-lsp-aarch64-apple-darwin - name: "Upload LSP Artifact" uses: actions/upload-artifact@v7 with: name: vscode-lsp-binary-aarch64-apple-darwin path: | bin build_vscode_lsp_macos_bundle: needs: [build_vscode_lsp_macos_x86_64, build_vscode_lsp_macos_aarch64] runs-on: macos-15 steps: - uses: actions/checkout@v6 with: path: "src" - uses: actions/download-artifact@v8 with: name: vscode-lsp-binary-x86_64-apple-darwin - uses: actions/download-artifact@v8 with: name: vscode-lsp-binary-aarch64-apple-darwin path: bin - name: Add macOS AArch64 binary to bundle run: | lipo -create -output tmp Slint\ Live\ Preview.app/Contents/MacOS/slint-lsp bin/slint-lsp-aarch64-apple-darwin mv tmp Slint\ Live\ Preview.app/Contents/MacOS/slint-lsp rm -rf bin - uses: ./src/.github/actions/codesign with: binary: "Slint Live Preview.app" certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }} certificate_password: ${{ secrets.APPLE_CERTIFICATE_P12_PASSWORD }} keychain_password: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }} developer_id: ${{ secrets.APPLE_DEV_ID }} - name: "Remove temporary source checkout" run: rm -rf src - name: "Upload LSP macOS bundle Artifact" uses: actions/upload-artifact@v7 with: name: vscode-lsp-binary-darwin path: . build_vscode_cross_linux_lsp: env: SLINT_NO_QT: 1 strategy: matrix: target: - armv7-unknown-linux-gnueabihf - aarch64-unknown-linux-gnu runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: target: ${{ matrix.target }} - uses: baptiste0928/cargo-install@v3 with: crate: cross - name: Build LSP run: cross build --target ${{ matrix.target }} --features ${{ env.SLINT_BINARY_FEATURES }} --release -p slint-lsp - name: Create artifact directory run: | mkdir bin cp target/${{ matrix.target }}/release/slint-lsp bin/slint-lsp-${{ matrix.target }} - name: "Upload LSP Artifact" uses: actions/upload-artifact@v7 with: name: vscode-lsp-binary-${{ matrix.target }} path: | bin build_vscode_extension: needs: [ build_vscode_lsp_linux_windows, build_vscode_lsp_macos_bundle, build_vscode_cross_linux_lsp, check-for-secrets, ] runs-on: macos-14 if: ${{ needs.check-for-secrets.outputs.has-openvsx-pat == 'yes' && needs.check-for-secrets.outputs.has-vscode-marketplace-pat == 'yes' }} steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 - name: Install GNU Sed run: brew install gnu-sed - uses: actions/setup-node@v6 with: node-version: 24 package-manager-cache: false - uses: actions/download-artifact@v8 with: name: vscode-lsp-binary-x86_64-unknown-linux-gnu path: editors/vscode/bin - uses: actions/download-artifact@v8 with: name: vscode-lsp-binary-x86_64-pc-windows-msvc path: editors/vscode/bin - uses: actions/download-artifact@v8 with: name: vscode-lsp-binary-aarch64-pc-windows-msvc path: editors/vscode/bin - uses: actions/download-artifact@v8 with: name: vscode-lsp-binary-darwin path: editors/vscode/bin - uses: actions/download-artifact@v8 with: name: vscode-lsp-binary-armv7-unknown-linux-gnueabihf path: editors/vscode/bin - uses: actions/download-artifact@v8 with: name: vscode-lsp-binary-aarch64-unknown-linux-gnu path: editors/vscode/bin - name: Fix permissions run: chmod 755 editors/vscode/bin/* editors/vscode/bin/*.app/Contents/MacOS/* - name: "Prepare meta-data files for nightly package" env: RELEASE_INPUT: ${{ github.event.inputs.release }} working-directory: editors/vscode run: | if grep -q 'lines below this marker are stripped from the release' README.md; then gsed -i "/lines below this marker are stripped from the release/,\$d" README.md else echo "Missing strip marker in VS Code Extension README. Either add or fix this script." exit 1 fi if [ "$RELEASE_INPUT" != "true" ]; then ../../scripts/prepare_vscode_nightly.sh fi shell: bash - name: "pnpm install" working-directory: editors/vscode run: pnpm install --frozen-lockfile - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - name: Build package and optionally publish to Visual Studio Marketplace id: publishToVSCM uses: HaaLeo/publish-vscode-extension@v2 with: pat: ${{ secrets.VSCODE_MARKETPLACE_PAT }} registryUrl: https://marketplace.visualstudio.com dryRun: ${{ github.event.inputs.private == 'true' || (github.ref != 'refs/heads/master' && github.event.inputs.release != 'true') }} packagePath: editors/vscode dependencies: false - name: Publish to Open VSX Registry continue-on-error: true if: ${{ github.event.inputs.private != 'true' && (github.ref == 'refs/heads/master' || github.event.inputs.release == 'true') }} uses: HaaLeo/publish-vscode-extension@v2 with: pat: ${{ secrets.OPENVSX_PAT }} extensionFile: ${{ steps.publishToVSCM.outputs.vsixPath }} packagePath: "" - name: "Upload extension artifact" uses: actions/upload-artifact@v7 with: name: slint-vscode.zip path: | ${{ steps.publishToVSCM.outputs.vsixPath }} build-figma-plugin: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: 24 package-manager-cache: false id: node-install - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 - name: Run pnpm install working-directory: tools/figma-inspector run: pnpm install --frozen-lockfile - name: Add build timestamp to readme working-directory: tools/figma-inspector/public-zip run: echo "Built on $(date '+%d-%m-%Y %H:%M:%S %Z')" >> readme.txt - name: Build zip working-directory: tools/figma-inspector run: pnpm zip - name: Prepare Figma plugin artifact run: mv *.zip figma-plugin.zip working-directory: tools/figma-inspector/zip - name: Archive zip uses: actions/upload-artifact@v7 with: name: figma-plugin path: tools/figma-inspector/zip # publish_tree_sitter: # if: github.event.inputs.private != 'true' # runs-on: ubuntu-22.04 # steps: # - uses: actions/checkout@v6 # - name: Upload artifact # uses: actions/upload-artifact@v7 # with: # name: tree-sitter-slint # path: editors/tree-sitter-slint publish_artifacts: if: ${{ github.event.inputs.private != 'true' }} needs: [docs, wasm_demo, wasm, check-for-secrets, android] runs-on: ubuntu-22.04 steps: - uses: actions/download-artifact@v8 with: pattern: docs-* merge-multiple: true path: . - name: Generate a token id: app-token-website uses: actions/create-github-app-token@v3 with: app-id: ${{ vars.READ_WRITE_APP_ID }} private-key: ${{ secrets.READ_WRITE_PRIVATE_KEY }} repositories: website - name: Clone website directory uses: actions/checkout@v6 with: repository: slint-ui/website ref: prod path: website token: ${{ steps.app-token-website.outputs.token }} persist-credentials: false - name: Generate release-docs.html and 404.html run: | mkdir -p website/output cd website && go run generator/generator.go -skip-agreements - name: Copy release-docs.html and 404.html run: | cp website/output/release-docs.html docs/index.html cp website/output/404.html docs/404.html rm -rf website - uses: actions/download-artifact@v8 with: name: slintpad path: slintpad - uses: actions/download-artifact@v8 with: name: wasm - uses: actions/download-artifact@v8 with: name: wasm_demo - uses: actions/download-artifact@v8 with: name: android-demo path: android - uses: actions/checkout@v6 with: path: "slint-src" sparse-checkout: . - name: Extract Version from Cargo.toml id: version run: | version=$(awk '/^\[workspace.package\]/ {found=1} found && /^version = / {gsub(/version = |"/, "", $0); print $0; exit}' slint-src/Cargo.toml) if [[ -z "$version" ]]; then echo "Version not found" exit 1 fi echo "VERSION=$version" >> $GITHUB_OUTPUT - name: Generate a token id: app-token uses: actions/create-github-app-token@v3 with: app-id: ${{ vars.READ_WRITE_APP_ID }} private-key: ${{ secrets.READ_WRITE_PRIVATE_KEY }} repositories: www-releases - name: Clone www-releases/releases and slintpad directory uses: actions/checkout@v6 if: ${{ github.event.inputs.release == 'true' }} with: repository: slint-ui/www-releases path: www-releases token: ${{ steps.app-token.outputs.token }} sparse-checkout: | releases slintpad - name: Clone www-releases/snapshots directory uses: actions/checkout@v6 if: ${{ github.event.inputs.release != 'true' }} with: repository: slint-ui/www-releases path: www-releases token: ${{ steps.app-token.outputs.token }} sparse-checkout: | snapshots - name: Publish Docs and Demos working-directory: ./www-releases run: | if [[ "${{ github.event.inputs.release }}" == "true" ]]; then output_path="releases/${{ steps.version.outputs.VERSION }}" else output_path="snapshots/${GITHUB_REF##*/}" fi rm -rf $output_path/demos mkdir -p $output_path/demos for demo_subdir in examples,gallery, demos,printerdemo,rust examples,todo,rust examples,todo-mvc,rust examples,slide_puzzle, examples,speedometer,rust examples,memory, examples,imagefilter,rust examples,plotter, examples,opengl_underlay, examples,carousel,rust demos,energy-monitor, demos,weather-demo, demos,home-automation,rust demos,usecases,rust; do IFS=',' read example_or_demo demo subdir <<< "${demo_subdir}" mkdir -p $output_path/demos/$demo cp -a ../$example_or_demo/$demo/$subdir/{pkg,index.html} $output_path/demos/$demo/ done mkdir -p $output_path/demos/android cp -a ../android/* $output_path/demos/android/ rm -rf $output_path/wasm-interpreter mkdir -p $output_path/wasm-interpreter cp -a ../api/wasm-interpreter/pkg/* ./$output_path/wasm-interpreter/ rm -rf $output_path/editor mkdir -p $output_path/editor cp -a ../slintpad/* $output_path/editor/ if [[ "${{ github.event.inputs.release }}" == "true" ]]; then version="${{ steps.version.outputs.VERSION }}" else version="development snapshot" fi sed -i "s/VERSION/$version/g" ../docs/index.html rm -rf $output_path/docs mv ../docs $output_path # Fix up link to Slint language documentation sed -i "s!https://slint.dev/releases/.*/docs/!../../!" $output_path/docs/rust/slint/*.html - name: Adjust redirections if: github.event.inputs.release == 'true' run: | sed -i "/1.0.2/! s,[0-9]*\.[0-9]*\.[0-9]*/\(.*\),${{ steps.version.outputs.VERSION }}/\1," www-releases/releases/_redirects - name: Adjust slintpad default tag if: github.event.inputs.release == 'true' run: sed -i "s,XXXX_DEFAULT_TAG_XXXX,v${{ steps.version.outputs.VERSION }}," www-releases/releases/${{ steps.version.outputs.VERSION }}/editor/assets/*.js - name: Update versions.txt if: github.event.inputs.release == 'true' working-directory: www-releases/releases run: ls -1d */ | cut -f1 -d'/' | sort --version-sort -r > versions.txt - name: Print contents before update if: github.event.inputs.release == 'true' working-directory: www-releases/releases run: cat versions.json - name: Update version in versions.json if: github.event.inputs.release == 'true' working-directory: www-releases/releases run: | sed -i '/"name": "development snapshot"/,/"preferred": true,/c\ "name": "development snapshot",\ "url": "https://snapshots.slint.dev/master/docs/slint"\ },\ {\ "preferred": true,\ "version": "${{ steps.version.outputs.VERSION }}",\ "url": "https://releases.slint.dev/${{ steps.version.outputs.VERSION }}/docs/slint"\ },\ {' versions.json - name: Print contents before update if: github.event.inputs.release == 'true' working-directory: www-releases/releases run: cat versions.json - name: Update SlintPad if: github.event.inputs.release == 'true' run: | rm -rf www-releases/slintpad cp -r www-releases/releases/${{ steps.version.outputs.VERSION }}/editor www-releases/slintpad for f in 404.html script.js LICENSE.md package.json; do cp www-releases/releases/$f www-releases/slintpad done echo "${{ steps.version.outputs.VERSION }}" > www-releases/slintpad/versions.txt - name: Get GitHub App User ID id: get-user-id run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ steps.app-token.outputs.token }} - name: check for diff id: www-releases working-directory: ./www-releases run: | git add . git add -u . git diff-index --cached --quiet HEAD || echo "has-diff=yes" >> "$GITHUB_OUTPUT" - name: commit and push if: ${{ steps.www-releases.outputs.has-diff == 'yes' }} working-directory: ./www-releases run: | git config user.name '${{ steps.app-token.outputs.app-slug }}[bot]' git config user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' git commit --message "Update $NAME from $GITHUB_REPOSITORY" --message "Pull web demos and C++/Rust reference docs from commit $GITHUB_SHA ($GITHUB_REF)" git push prepare_release: if: github.event.inputs.private != 'true' needs: [cpp_package, slint-viewer-binary, slint-lsp-binary, build-figma-plugin] runs-on: ubuntu-22.04 permissions: contents: write steps: - uses: actions/download-artifact@v8 with: pattern: cpp_bin-* merge-multiple: true - uses: actions/download-artifact@v8 with: pattern: cpp_mcu_bin-* merge-multiple: true - uses: actions/download-artifact@v8 with: pattern: slint-compiler-bin-* merge-multiple: true - uses: actions/download-artifact@v8 with: pattern: slint-viewer-* merge-multiple: true - uses: actions/download-artifact@v8 with: pattern: slint-lsp-* merge-multiple: true - uses: actions/download-artifact@v8 with: name: figma-plugin - name: Extract files run: | ls -l mkdir artifacts mv Slint-cpp-*-win64*.exe artifacts/ mv Slint-cpp-*.tar.gz artifacts/ mv slint-viewer-linux.tar.gz artifacts/ mv slint-viewer-armv7-unknown-linux-gnueabihf.tar.gz artifacts/ mv slint-viewer-aarch64-unknown-linux-gnu.tar.gz artifacts/ mv slint-viewer-macos.tar.gz artifacts/ mv slint-viewer-windows*.zip artifacts/ mv slint-lsp-linux.tar.gz artifacts/ mv slint-lsp-armv7-unknown-linux-gnueabihf.tar.gz artifacts/ mv slint-lsp-aarch64-unknown-linux-gnu.tar.gz artifacts/ mv slint-lsp-macos.tar.gz artifacts/ mv slint-lsp-windows*.zip artifacts/ mv slint-compiler-*.tar.gz artifacts/ mv figma-plugin.zip artifacts/ - uses: actions/checkout@v6 with: path: "slint-src" sparse-checkout: docs - name: prepare release notes id: version env: RELEASE_INPUT: ${{ github.event.inputs.release }} run: | if [ "$RELEASE_INPUT" != "true" ]; then notes_file=slint-src/docs/nightly-release-notes.md version=nightly download_version=$version else version=$(echo artifacts/Slint-cpp-*-win64-MSVC-AMD64.exe | sed -nre 's/^.*-([0-9]+\.[0-9]+\.[0-9]+).*$/\1/p') if [[ -z "$version" ]]; then echo "Version not found" exit 1 fi major_version=`echo $version | sed -e "s,\([0-9]*\)\.[0-9]*\.[0-9]*,\1,"` minor_version=`echo $version | sed -e "s,[0-9]*\.\([0-9]*\)\.[0-9]*,\1,"` notes_file=slint-src/docs/release-notes.md download_version=v$version fi echo "VERSION=$version" >> $GITHUB_OUTPUT cat $notes_file slint-src/docs/release-artifacts.md > release-notes.md branch="${GITHUB_REF#refs/heads/}" feature="${GITHUB_REF##*/}" sed -i -e "s,{branch},$branch,g" release-notes.md sed -i -e "s,{feature},$feature,g" release-notes.md sed -i -e "s,{version},$version,g" release-notes.md sed -i -e "s,{download_version},$download_version,g" release-notes.md sed -i -e "s,{major_version},$major_version,g" release-notes.md sed -i -e "s,{minor_version},$minor_version,g" release-notes.md cat release-notes.md - name: generate STM32 template packages env: RELEASE_INPUT: ${{ github.event.inputs.release }} run: | git clone https://github.com/slint-ui/slint-cpp-templates-stm32 --depth 1 cd slint-cpp-templates-stm32 if [ "$RELEASE_INPUT" = "true" ]; then find . -name "CMakeLists.txt" | xargs sed -i "s/find_package(Slint)/find_package(Slint ${{ steps.version.outputs.VERSION }})/g" fi for board in stm32h735g-dk stm32h747i-disco; do mv $board slint-cpp-template-$board zip -r ../artifacts/slint-cpp-template-$board.zip slint-cpp-template-$board/ done - uses: ncipollo/release-action@v1 if: github.event.inputs.release == 'true' with: draft: true artifacts: "artifacts/*" bodyFile: release-notes.md name: ${{ steps.version.outputs.VERSION }} tag: v${{ steps.version.outputs.VERSION }} commit: ${{ github.sha }} - uses: ncipollo/release-action@v1 if: github.event.inputs.release != 'true' && github.ref == 'refs/heads/master' with: allowUpdates: true prerelease: true removeArtifacts: true replacesArtifacts: true artifacts: "artifacts/*" bodyFile: release-notes.md name: nightly tag: nightly commit: ${{ github.sha }} - name: trigger stm32 build if: github.ref == 'refs/heads/master' run: | curl -L \ -X POST \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer ${{ secrets.STM32_CPP_TRIGGER }}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/repos/slint-ui/slint-cpp-templates-stm32/actions/workflows/ci.yaml/dispatches \ -d '{"ref":"main"}' nightly-tests: if: github.event.inputs.release != 'true' needs: [prepare_release] uses: ./.github/workflows/nightly_tests.yaml nightly-build-and-test: if: github.event.inputs.release != 'true' strategy: matrix: include: - os: ubuntu-22.04 name: "Ubuntu 22.04 1.92" rust_version: "1.92" extra_args: "--exclude plotter" - os: windows-2022 name: "Windows 2022 nightly" rust_version: "nightly" extra_args: "--exclude ffmpeg --exclude gstreamer-player" - os: macos-14 name: "MacOS 14 nightly" rust_version: "nightly" extra_args: "--exclude ffmpeg --exclude gstreamer-player" uses: ./.github/workflows/build_and_test_reusable.yaml with: os: ${{ matrix.os }} name: ${{ matrix.name }} rust_version: ${{ matrix.rust_version }} extra_args: ${{ matrix.extra_args }} # In the nightly CI run, run cargo update to test if any downstream dependency broke our build # Do not update the cache though, as that would trash the cache for the next runner, which uses the non-updated # lockfile. update: true save_if: false android: env: CARGO_APK_RELEASE_KEYSTORE: /home/runner/.android/release.keystore CARGO_APK_RELEASE_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies - name: Install Android API level 30 run: ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --install "platforms;android-30" - name: Cache cargo-apk id: cargo-apk-cache uses: actions/cache@v5 with: path: ~/.cargo/bin/cargo-apk key: cargo-apk-cache # Only build cargo-apk if not cached - uses: dtolnay/rust-toolchain@stable if: steps.cargo-apk-cache.outputs.cache-hit != 'true' - name: Install cargo-apk if: steps.cargo-apk-cache.outputs.cache-hit != 'true' run: cargo install cargo-apk - uses: ./.github/actions/setup-rust with: target: aarch64-linux-android - name: dump keystore run: | mkdir -p /home/runner/.android echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > $CARGO_APK_RELEASE_KEYSTORE - name: Build energy-monitor example run: cargo apk build -p energy-monitor --target aarch64-linux-android --lib --release - name: Build todo demo run: cargo apk build -p todo --target aarch64-linux-android --lib --release - name: Build weather-demo example run: cargo apk build -p weather-demo --target aarch64-linux-android --lib --release - name: Build usecases demo run: cargo apk build -p usecases --target aarch64-linux-android --lib --release - name: Build home automation demo run: cargo apk build -p home-automation --target aarch64-linux-android --lib --release - name: "upload APK artifact" uses: actions/upload-artifact@v7 with: name: android-demo path: | target/release/apk/energy-monitor.apk target/release/apk/todo_lib.apk target/release/apk/weather_demo.apk target/release/apk/usecases_lib.apk target/release/apk/usecases_lib.apk target/release/apk/home_automation_lib.apk ================================================ FILE: .github/workflows/nightly_tests.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # Run a bunch of slower tests once a day (or night) name: Nightly tests on: workflow_dispatch: workflow_call: jobs: qa-esp-idf: strategy: matrix: esp-idf-target: - release-v5.5 runs-on: ubuntu-22.04 container: espressif/idf:${{ matrix.esp-idf-target }} steps: - name: Fix up pydantic regression (https://github.com/espressif/idf-component-manager/issues/97#issuecomment-3380777944) run: | . ${IDF_PATH}/export.sh cd $IDF_PYTHON_ENV_PATH bin/pip install pydantic==2.11.10 - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - uses: esp-rs/xtensa-toolchain@v1.6 with: default: true buildtargets: esp32 ldproxy: false - uses: Swatinem/rust-cache@v2 - name: Build and Test Printer demo shell: bash working-directory: demos/printerdemo_mcu/esp-idf run: | . ${IDF_PATH}/export.sh idf.py -D SLINT_ESP_LOCAL_EXAMPLE=OFF build - name: Build and Test Carousel example s3 box shell: bash working-directory: examples/carousel/esp-idf/s3-box run: | . ${IDF_PATH}/export.sh idf.py -D SLINT_ESP_LOCAL_EXAMPLE=OFF build qa-tree-sitter-latest: uses: ./.github/workflows/tree_sitter.yaml with: latest: true qa-yocto-build: strategy: matrix: include: - sdk_url: https://nextcloud.slint.dev/s/SCXYDmEmr45pkak/download/poky-glibc-x86_64-core-image-weston-cortexa57-qemuarm64-toolchain-4.0.9.sh env_setup: environment-setup-cortexa57-poky-linux target: aarch64-unknown-linux-gnu - sdk_url: https://nextcloud.slint.dev/s/BTL5NtLACjgS7Pf/download/poky-glibc-x86_64-core-image-weston-cortexa15t2hf-neon-qemuarm-toolchain-4.0.9.sh env_setup: environment-setup-cortexa15t2hf-neon-poky-linux-gnueabi target: armv7-unknown-linux-gnueabihf runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - name: Downgrade cc and ring crate to work around https://github.com/slint-ui/slint/issues/6875 run: | cargo update -p ring --precise 0.17.9 - name: Fetch Yocto SDK run: | # Fetch pre-built SDK built via populate_sdk for core-image-weston with setup from https://github.com/slint-ui/meta-slint/blob/main/.github/workflows/ci.yml wget -O sdk.sh ${{ matrix.sdk_url }} chmod +x sdk.sh ./sdk.sh -d ${{ runner.workspace }}/yocto-sdk -y rm -f sdk.sh - name: Install Rust uses: dtolnay/rust-toolchain@stable with: toolchain: stable target: ${{ matrix.target }} - name: C++ Build run: | . ${{ runner.workspace }}/yocto-sdk/${{ matrix.env_setup }} # Only needed for 32-bit arm builds where soft-fp/hard-fp affects header file lookup, hence the need to drag in these flags. See also commit # f5c3908b7ec5131b7b19ff642b5975660c7484f8 export BINDGEN_EXTRA_CLANG_ARGS=$OECORE_TUNE_CCARGS mkdir ${{ runner.workspace }}/cppbuild cmake -GNinja -B ${{ runner.workspace }}/cppbuild -S . -DRust_CARGO_TARGET=${{ matrix.target }} -DSLINT_BUILD_TESTING=ON -DSLINT_BUILD_EXAMPLES=ON -DCMAKE_BUILD_TYPE=Debug -DSLINT_FEATURE_RENDERER_SKIA=ON -DSLINT_FEATURE_BACKEND_QT=OFF -DSLINT_FEATURE_BACKEND_LINUXKMS=ON -DSLINT_FEATURE_INTERPRETER=ON cmake --build ${{ runner.workspace }}/cppbuild mcu_zephyr: strategy: matrix: include: - board: native_sim/native/64 toolchain: nightly target: x86_64-unknown-linux-gnu extra-cmake-args: '' - board: mimxrt1170_evk@B/mimxrt1176/cm7 toolchain: stable target: thumbv7em-none-eabihf extra-cmake-args: -DSHIELD=rk055hdmipi4ma0 fail-fast: false runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 with: path: slint - name: Install linux dependencies, including Zephyr dependencies uses: ./slint/.github/actions/install-linux-dependencies with: extra-packages: | git cmake ninja-build gperf ccache dfu-util device-tree-compiler wget python3-dev python3-pip python3-setuptools python3-tk python3-wheel xz-utils file make gcc gcc-multilib g++-multilib libsdl2-dev libmagic1 - uses: ./slint/.github/actions/setup-rust with: toolchain: ${{matrix.toolchain}} components: rust-src target: ${{matrix.target}} - name: Setup Zephyr project uses: zephyrproject-rtos/action-zephyr-setup@v1.0.12 with: app-path: slint manifest-file-name: demos/zephyr-common/west.yaml sdk-version: 0.16.8 - name: Export the Zephyr CMake package run: west zephyr-export - name: Build for ${{matrix.board}} run: | west build -b ${{matrix.board}} -p always slint/demos/printerdemo/zephyr -- -DCMAKE_BUILD_TYPE=Release ${{matrix.extra-cmake-args}} uefi-demo: env: CARGO_PROFILE_DEV_DEBUG: 0 runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: toolchain: stable target: x86_64-unknown-uefi - name: Check run: cargo check --target=x86_64-unknown-uefi -p uefi-demo ios: strategy: matrix: demo: - demos/home-automation/rust - demos/energy-monitor include: - demo: demos/home-automation/rust scheme: "Home Automation" bundle_id: dev.slint.demos.HomeAutomation device: iPad Pro 13-inch (M5) - demo: demos/energy-monitor scheme: "Energy Monitor" bundle_id: dev.slint.demos.EnergyMonitor device: iPhone 17 runs-on: macos-26 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: target: aarch64-apple-ios-sim - run: brew install xcodegen - run: xcodegen -s ios-project.yml working-directory: ${{ matrix.demo }} - name: "Find Simulator UDID from iOS 26.0 runtime" run: | echo "=== Finding device from iOS 26.0 runtime (default Xcode) ===" # Find the device UDID from iOS 26.0 runtime to avoid ambiguity # Multiple runtimes have devices with the same name, so we need the specific one # Output format: " Device Name (UDID) (Shutdown)" DEVICE_LINE=$(xcrun simctl list devices available | grep -A 50 "iOS 26.0" | grep "${{ matrix.device }}" | head -n 1) if [ -z "$DEVICE_LINE" ]; then echo "ERROR: Could not find ${{ matrix.device }} in iOS 26.0 runtime" echo "Available iOS 26.0 devices:" xcrun simctl list devices available | grep -A 30 "iOS 26.0" exit 1 fi # Extract UDID from the line (format: " Device Name (UDID) (Shutdown)") DEVICE_UDID=$(echo "$DEVICE_LINE" | sed -n 's/.*(\([A-F0-9-]\{36\}\)).*/\1/p') if [ -z "$DEVICE_UDID" ]; then echo "ERROR: Could not extract UDID from line: $DEVICE_LINE" exit 1 fi echo "Device: ${{ matrix.device }}" echo "UDID: $DEVICE_UDID" # Export UDID for use in xcodebuild and boot steps echo "SIMULATOR_UDID=$DEVICE_UDID" >> $GITHUB_ENV - run: xcodebuild -scheme "${{ matrix.scheme }}" -destination "platform=iOS Simulator,id=${{ env.SIMULATOR_UDID }}" build working-directory: ${{ matrix.demo }} # This isn't working reliably. Sometimes the simulator doesn't boot, somethings launching fails, and sometimes screenshotting fails. # - name: "Boot Simulator" # timeout-minutes: 5 # run: | # echo "=== Booting simulator by UDID ===" # echo "Device: ${{ matrix.device }}" # echo "UDID: ${{ env.SIMULATOR_UDID }}" # echo "Start time: $(date '+%Y-%m-%d %H:%M:%S')" # # set -x # Enable command echoing # xcrun simctl boot "${{ env.SIMULATOR_UDID }}" # BOOT_RESULT=$? # set +x # Disable command echoing # # echo "Boot command completed with exit code: $BOOT_RESULT" # echo "" # # echo "=== Simulator state after boot command ===" # SIMULATOR_STATUS=$(xcrun simctl list devices | grep "${{ env.SIMULATOR_UDID }}") # echo "$SIMULATOR_STATUS" # # if echo "$SIMULATOR_STATUS" | grep -q "(Booted)"; then # echo "Simulator is booted successfully at: $(date '+%Y-%m-%d %H:%M:%S')" # else # echo "ERROR: Simulator failed to boot properly" # echo "Current status: $SIMULATOR_STATUS" # exit 1 # fi # - name: "Install" # run: | # APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData -name "${{ matrix.scheme }}.app" | head -n 1) # echo "Found app bundle at $APP_PATH" # xcrun simctl install booted "$APP_PATH" # echo "Verifying installation..." # xcrun simctl listapps booted | grep -q "${{ matrix.bundle_id }}" && echo "App installed successfully" || echo "WARNING: App may not be installed" # - name: "Launch" # timeout-minutes: 5 # run: | # echo "Attempting to launch ${{ matrix.bundle_id }}..." # # Retry loop to wait for FrontBoard to register the app # MAX_ATTEMPTS=10 # ATTEMPT=1 # while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do # echo "Launch attempt $ATTEMPT of $MAX_ATTEMPTS..." # if xcrun simctl launch booted "${{ matrix.bundle_id }}" 2>&1 | tee launch_output.txt; then # echo "App launched successfully on attempt $ATTEMPT" # sleep 5 # exit 0 # fi # # if grep -q "unknown to FrontBoard" launch_output.txt; then # echo "FrontBoard hasn't registered app yet, waiting 2 seconds..." # sleep 2 # ATTEMPT=$((ATTEMPT + 1)) # else # echo "Launch failed with a different error:" # cat launch_output.txt # echo "Checking simulator logs..." # xcrun simctl spawn booted log show --last 30s --predicate 'processImagePath contains "${{ matrix.scheme }}"' || true # exit 1 # fi # done # # echo "Failed to launch after $MAX_ATTEMPTS attempts" # exit 1 # - name: "Screenshot" # run: xcrun simctl io booted screenshot "${{ matrix.scheme }}.png" # - name: Upload Screenshot Artifact # uses: actions/upload-artifact@v7 # with: # name: ${{matrix.scheme}}-screenshot # path: ${{ matrix.scheme }}.png # - name: "Cleanup - Shutdown Simulator" # if: always() # run: | # echo "Shutting down simulator to avoid orphan processes..." # xcrun simctl shutdown "${{ env.SIMULATOR_UDID }}" || echo "Simulator already shutdown" bevy_examples: uses: ./.github/workflows/bevy_examples.yaml with: # In the nightly CI run, run cargo update to test if any downstream dependency broke our build # Do not update the cache though, as that would trash the cache for the next runner, which uses the non-updated # lockfile. update: true save_if: false android_wgpu: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Install Android API level 30 run: ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --install "platforms;android-30" - uses: dtolnay/rust-toolchain@stable - name: Install cargo-apk run: cargo install cargo-apk - uses: ./.github/actions/setup-rust with: target: aarch64-linux-android - uses: ./.github/actions/install-linux-dependencies - uses: ./.github/actions/install-skia-dependencies - name: Build wgpu_texture demo run: cargo apk build -p wgpu_texture --target aarch64-linux-android --lib python: strategy: matrix: os: [ubuntu-22.04, macos-14, windows-2022] uses: ./.github/workflows/python_test_reusable.yaml with: name: "Python ${{ matrix.os }}" os: ${{ matrix.os }} node: strategy: matrix: os: [ubuntu-22.04, macos-14, windows-2022] uses: ./.github/workflows/node_test_reusable.yaml with: name: "Node.js ${{ matrix.os }}" os: ${{ matrix.os }} miri: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: toolchain: nightly key: miri components: miri - name: Run Miri run: cargo miri test -p vtable -p const-field-offset -p i-slint-common ================================================ FILE: .github/workflows/node_test_reusable.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Node.js Test (Reusable) on: workflow_call: inputs: name: description: 'Name of the job' required: true type: string os: description: 'Operating system to run on' required: true type: string env: RUSTFLAGS: -D warnings CARGO_PROFILE_DEV_DEBUG: 0 CARGO_INCREMENTAL: false RUST_BACKTRACE: 1 jobs: node_test: name: ${{ inputs.name }} runs-on: ${{ inputs.os }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies - uses: ./.github/actions/install-skia-dependencies - name: Setup headless display if: runner.os != 'macOS' uses: pyvista/setup-headless-display-action@v4 - uses: actions/setup-node@v6 with: node-version: 24 package-manager-cache: false id: node-install - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 - uses: ./.github/actions/setup-rust with: key: x-napi-v2-${{ steps.node-install.outputs.node-version }} # the cache key consists of a manually bumpable version and the node version, as the cached rustc artifacts contain linking information where to find node.lib, which is in a versioned directory. - name: Run pnpm install working-directory: api/node run: pnpm install --frozen-lockfile - name: Build node plugin in debug run: pnpm build:testing working-directory: api/node - name: Run node tests working-directory: api/node run: pnpm test - name: Run test-driver-nodejs # Release is only applied to the harness that drives the node.js invocations, but needed # to avoid crashing on Windows with what looks like an out of stack exception. run: cargo test --verbose --release --all-features -p test-driver-nodejs - name: Check image-filter example working-directory: examples/imagefilter/node run: pnpm check ================================================ FILE: .github/workflows/publish_npm_package.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Publish npm package to npm registry on: workflow_dispatch: inputs: private: type: boolean default: true required: false description: "Private build? True means artifacts are only built. False means the package will be published to the NPM registry" release: type: boolean default: false required: false description: "Release? Enable options for building binaries for a release (i.e. apply a nightly tag, nightly version)" schedule: - cron: "0 5 * * *" permissions: id-token: write # Required for OIDC contents: read jobs: determine_version: runs-on: ubuntu-latest outputs: PKG_VERSION: ${{ steps.mkversion.outputs.PKG_VERSION }} steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 - uses: actions/setup-node@v6 with: package-manager-cache: false - name: Determine version id: mkversion env: RELEASE_INPUT: ${{ github.event.inputs.release }} working-directory: api/node run: | version=`pnpm pkg get version | jq -r` if [ "$RELEASE_INPUT" != "true" ]; then nightly_version_suffix=`git log -1 --format=%cd --date="format:%Y%m%d%H"` version="$version-nightly.$nightly_version_suffix" fi echo $version echo "PKG_VERSION=$version" >> $GITHUB_OUTPUT build_binaries: env: PKG_VERSION: ${{ needs.determine_version.outputs.PKG_VERSION }} RELEASE_INPUT: ${{ github.event.inputs.release }} MACOSX_DEPLOYMENT_TARGET: "11.0" strategy: matrix: include: - os: ubuntu-22.04 rust-target: x86_64-unknown-linux-gnu napi-rs-target: linux-x64-gnu - os: ubuntu-22.04-arm rust-target: aarch64-unknown-linux-gnu napi-rs-target: linux-arm64-gnu - os: macos-14 rust-target: aarch64-apple-darwin napi-rs-target: darwin-arm64 - os: windows-2022 rust-target: x86_64-pc-windows-msvc napi-rs-target: win32-x64-msvc msvc-arch: x64 - os: windows-11-arm rust-target: aarch64-pc-windows-msvc napi-rs-target: win32-arm64-msvc msvc-arch: arm64 needs: determine_version runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies with: old-ubuntu: true - uses: ./.github/actions/install-skia-dependencies - uses: ilammy/msvc-dev-cmd@v1 if: runner.os == 'Windows' with: arch: ${{ matrix.msvc-arch }} - uses: ./.github/actions/setup-rust with: target: ${{ matrix.rust-target }} - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 # Setup .npmrc file to publish to npm - uses: actions/setup-node@v6 with: node-version: "24" registry-url: "https://registry.npmjs.org" package-manager-cache: false - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 - name: Set version working-directory: api/node shell: bash run: | if [ "$RELEASE_INPUT" != "true" ]; then pnpm version $PKG_VERSION fi - uses: baptiste0928/cargo-install@v3 with: crate: taplo-cli - name: Prepare feature config for binaries working-directory: api/node shell: bash run: | cat Cargo.toml | taplo format --option column_width=100000 --stdin-filepath=Cargo.toml - | \ perl -p -e 's,^\s*default\s*=.*,,' | \ perl -p -e 's,# binaries:\s?,,' > Cargo.toml.new cat Cargo.toml.new | taplo format --stdin-filepath=Cargo.toml - > Cargo.toml rm Cargo.toml.new taplo get -f Cargo.toml features.default - name: Build binary shell: bash working-directory: api/node run: | pnpm install --ignore-scripts pnpm run build --target ${{ matrix.rust-target }} - name: Create package shell: bash working-directory: api/node run: | npx napi create-npm-dirs --npm-dir . -c ./binaries.json mv slint-ui.${{ matrix.napi-rs-target }}.node ${{ matrix.napi-rs-target }}/ cd ${{ matrix.napi-rs-target }}/ pnpm pkg set repository.type=git pnpm pkg set repository.url=https://github.com/slint-ui/slint pnpm pack - name: Upload artifact uses: actions/upload-artifact@v7 with: name: binaries-${{ matrix.rust-target }} path: "api/node/${{ matrix.napi-rs-target }}/*.tgz" build_and_publish_npm_package: runs-on: ubuntu-22.04 needs: [determine_version, build_binaries] env: PKG_VERSION: ${{ needs.determine_version.outputs.PKG_VERSION }} RELEASE_INPUT: ${{ github.event.inputs.release }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies - uses: ./.github/actions/setup-rust - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 # Setup .npmrc file to publish to npm - uses: actions/setup-node@v6 with: node-version: "24" registry-url: "https://registry.npmjs.org" package-manager-cache: false - name: Update npm as Node 20.x might not have an new enough npm for trusted publishing run: npm install -g npm@latest - name: Set version working-directory: api/node run: | if [ "$RELEASE_INPUT" != "true" ]; then pnpm version $PKG_VERSION fi - name: Select git revision if: github.event.inputs.release != 'true' run: | echo "PKG_EXTRA_ARGS=--sha1=$GITHUB_SHA" >> $GITHUB_ENV echo "PUBLISH_TAG=--tag nightly" >> $GITHUB_ENV - name: Compile index.js and index.d.ts working-directory: api/node run: | pnpm install pnpm run build pnpm run compile - name: Prepare binary packages working-directory: api/node run: | npx napi create-npm-dirs --npm-dir . -c ./binaries.json - name: Download artifacts uses: actions/download-artifact@v8 with: name: binaries-x86_64-unknown-linux-gnu path: api/node/ - name: Download artifacts uses: actions/download-artifact@v8 with: name: binaries-aarch64-unknown-linux-gnu path: api/node/ - name: Download artifacts uses: actions/download-artifact@v8 with: name: binaries-aarch64-apple-darwin path: api/node/ - name: Download artifacts uses: actions/download-artifact@v8 with: name: binaries-x86_64-pc-windows-msvc path: api/node/ - name: Download artifacts uses: actions/download-artifact@v8 with: name: binaries-aarch64-pc-windows-msvc path: api/node/ - name: Add binary dependencies working-directory: api/node run: | for package in @slint-ui/slint-ui-binary-linux-x64-gnu @slint-ui/slint-ui-binary-linux-arm64-gnu @slint-ui/slint-ui-binary-darwin-arm64 @slint-ui/slint-ui-binary-win32-x64-msvc @slint-ui/slint-ui-binary-win32-arm64-msvc; do jq --arg pkg "$package" --arg version "$PKG_VERSION" '.optionalDependencies[$pkg]=$version' package.json > new.json mv new.json package.json done - name: Build package run: | cargo xtask node_package $PKG_EXTRA_ARGS - name: "Upload npm package Artifact" uses: actions/upload-artifact@v7 with: name: slint-ui-node-package path: | api/node/slint-ui-${{ env.PKG_VERSION }}.tgz - name: Smoke test package to see if it builds at least run: | mkdir /tmp/nodetest cd /tmp/nodetest echo "neverBuiltDependencies: []" > pnpm-workspace.yaml pnpm init pnpm install --dangerously-allow-all-builds --verbose $GITHUB_WORKSPACE/api/node/slint-ui-$PKG_VERSION.tgz - name: Build and publish packages if: ${{ github.event.inputs.private != 'true' && (github.ref == 'refs/heads/master' || github.event.inputs.release == 'true') }} run: | pnpm publish --no-git-checks --access public $PUBLISH_TAG api/node/slint-ui-slint-ui-binary-linux-x64-gnu-$PKG_VERSION.tgz pnpm publish --no-git-checks --access public $PUBLISH_TAG api/node/slint-ui-slint-ui-binary-linux-arm64-gnu-$PKG_VERSION.tgz pnpm publish --no-git-checks --access public $PUBLISH_TAG api/node/slint-ui-slint-ui-binary-darwin-arm64-$PKG_VERSION.tgz pnpm publish --no-git-checks --access public $PUBLISH_TAG api/node/slint-ui-slint-ui-binary-win32-x64-msvc-$PKG_VERSION.tgz pnpm publish --no-git-checks --access public $PUBLISH_TAG api/node/slint-ui-slint-ui-binary-win32-arm64-msvc-$PKG_VERSION.tgz pnpm publish --no-git-checks $PUBLISH_TAG api/node/slint-ui-$PKG_VERSION.tgz ================================================ FILE: .github/workflows/python_test_reusable.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Python Test (Reusable) on: workflow_call: inputs: name: description: 'Name of the job' required: true type: string os: description: 'Operating system to run on' required: true type: string env: DYLD_FRAMEWORK_PATH: /Users/runner/work/slint/Qt/5.15.2/clang_64/lib QT_QPA_PLATFORM: offscreen RUSTFLAGS: -D warnings CARGO_PROFILE_DEV_DEBUG: 0 CARGO_INCREMENTAL: false RUST_BACKTRACE: full SLINT_BACKEND: testing MATURIN_PEP517_ARGS: --features backend-testing jobs: python_test: name: ${{ inputs.name }} runs-on: ${{ inputs.os }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies - uses: ./.github/actions/setup-rust with: key: x-python-v0-${{ steps.node-install.outputs.node-version }} # the cache key consists of a manually bumpable version and the node version, as the cached rustc artifacts contain linking information where to find node.lib, which is in a versioned directory. - uses: actions/setup-python@v6 with: python-version: "3.12" - name: Install uv uses: astral-sh/setup-uv@v7 - name: Build and sync environment working-directory: api/python/slint run: uv sync - name: Run ty working-directory: api/python/slint run: uvx ty check - name: Run ruff linter working-directory: api/python/slint run: uv tool run ruff check - name: Run ruff linter working-directory: api/python/briefcase run: uv tool run ruff check - name: Run python tests working-directory: api/python/slint run: uv run pytest -s -v - name: Run python test driver run: cargo test -p test-driver-python ================================================ FILE: .github/workflows/schedule_nightly_snapshot.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # We have a different workflow to schedule the nightly snapshot because we want to be able to run # it from a different branch name: Schedule Nightly snapshot on: schedule: - cron: "18 2 * * *" jobs: trigger: runs-on: ubuntu-latest strategy: matrix: branch: - pre-release/1.15 - master steps: - uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ matrix.branch }} - name: Check for recent commits id: recent run: | if git log origin/${{ matrix.branch }} --since="24 hours ago" --oneline | grep .; then echo "has_commits=true" >> "$GITHUB_OUTPUT" else echo "has_commits=false" >> "$GITHUB_OUTPUT" fi - name: Trigger nightly workflow if: steps.recent.outputs.has_commits == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh workflow run "Nightly snapshot" --ref ${{ matrix.branch }} --repo ${{ github.repository }} -f private=false ================================================ FILE: .github/workflows/servo_example.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Servo example on: workflow_dispatch: workflow_call: jobs: matrix_build: strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} name: servo ${{ matrix.os }} steps: - uses: actions/checkout@v6 # required for servo - name: Install uv uses: astral-sh/setup-uv@v7 - name: Install rust uses: ./.github/actions/setup-rust - name: Install dependencies uses: ./.github/actions/install-linux-dependencies - name: Install skia dependencies uses: ./.github/actions/install-skia-dependencies - name: Build working-directory: examples/servo run: cargo build --release android_build: name: servo android runs-on: ubuntu-latest env: CARGO_INCREMENTAL: false CARGO_PROFILE_DEV_DEBUG: 0 steps: - uses: actions/checkout@v6 # required for servo - name: Install uv uses: astral-sh/setup-uv@v7 - name: Install rust uses: ./.github/actions/setup-rust with: target: aarch64-linux-android - name: Install dependencies uses: ./.github/actions/install-linux-dependencies - name: Install skia dependencies uses: ./.github/actions/install-skia-dependencies # required for android - name: Install API level run: ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --install "platforms;android-30" - name: Install cargo-apk run: cargo install cargo-apk - name: Build working-directory: examples/servo run: | export BINDGEN_EXTRA_CLANG_ARGS="--target=aarch64-linux-android30 --sysroot=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/sysroot" cargo apk build --target aarch64-linux-android --lib ================================================ FILE: .github/workflows/slint_tool_binary.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Build slint-viewer or -lsp binary on: workflow_dispatch: inputs: program: type: choice description: binary to build options: - viewer - lsp features: type: string description: features to enable for build # Keep in sync with features in nightly_snapshot.yaml, cpp_package.yaml,api/node/Cargo.toml, and api/python/slint/Cargo.toml default: "backend-linuxkms-noseat,backend-winit,renderer-femtovg,renderer-skia,renderer-software" codesign: type: boolean description: Sign binaries on macOS (false for manual builds) default: false workflow_call: inputs: program: type: string description: binary to build features: type: string description: features to enable for build # Keep in sync with features in nightly_snapshot.yaml, cpp_package.yaml,api/node/Cargo.toml, and api/python/slint/Cargo.toml default: "backend-linuxkms-noseat,backend-winit,renderer-femtovg,renderer-skia,renderer-software" codesign: type: boolean description: Sign binaries on macOS default: true secrets: certificate: description: "certificate secret" required: false certificate_password: description: "certificate password" required: false keychain_password: description: "keychain password to use" required: false developer_id: description: "developer id to use" required: false env: MACOSX_DEPLOYMENT_TARGET: "11.0" jobs: build_windows: strategy: matrix: arch: [x86_64-pc-windows-msvc, aarch64-pc-windows-msvc] include: - arch: x86_64-pc-windows-msvc runner: windows-2022 package_suffix: x86_64 - arch: aarch64-pc-windows-msvc runner: windows-11-arm package_suffix: arm64 runs-on: ${{ matrix.runner}} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: target: ${{ matrix.arch }} - uses: baptiste0928/cargo-install@v3 with: crate: cargo-about version: "=0.6.6" - name: Build run: cargo build --verbose --no-default-features --features ${{ github.event.inputs.features || inputs.features }} --release -p slint-${{ github.event.inputs.program || inputs.program }} - name: Create artifact directory run: | mkdir pkg cd pkg mkdir slint-${{ github.event.inputs.program || inputs.program }} cd slint-${{ github.event.inputs.program || inputs.program }} cp ..\..\target/release/slint-${{ github.event.inputs.program || inputs.program }}.exe ./ cd .. cd .. cd tools\${{ github.event.inputs.program || inputs.program }} bash -x ../../scripts/prepare_binary_package.sh ..\..\pkg\slint-${{ github.event.inputs.program || inputs.program }} - name: Create archive shell: powershell run: | cd pkg Compress-Archive -Path slint-${{ github.event.inputs.program || inputs.program }}\* -Destination ..\slint-${{ github.event.inputs.program || inputs.program }}-windows-${{ matrix.package_suffix }}.zip - name: Upload artifact uses: actions/upload-artifact@v7 with: name: slint-${{ github.event.inputs.program || inputs.program }}-windows-${{ matrix.package_suffix }} path: | slint-${{ github.event.inputs.program || inputs.program }}-windows-${{ matrix.package_suffix }}.zip build_linux: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies with: old-ubuntu: true - uses: ./.github/actions/setup-rust with: target: x86_64-unknown-linux-gnu - uses: baptiste0928/cargo-install@v3 with: crate: cargo-about version: "=0.6.6" - name: Build run: cargo build --verbose --no-default-features --features ${{ github.event.inputs.features || inputs.features }} --release -p slint-${{ github.event.inputs.program || inputs.program }} - name: Create artifact directory run: | mkdir -p slint-${{ github.event.inputs.program || inputs.program }} cp target/release/slint-${{ github.event.inputs.program || inputs.program }} slint-${{ github.event.inputs.program || inputs.program }}/ cd tools/${{ github.event.inputs.program || inputs.program }} ../../scripts/prepare_binary_package.sh ../../slint-${{ github.event.inputs.program || inputs.program }} - name: Tar artifacts to preserve permissions run: tar czvf slint-${{ github.event.inputs.program || inputs.program }}-linux.tar.gz slint-${{ github.event.inputs.program || inputs.program }} - name: Upload artifact uses: actions/upload-artifact@v7 with: name: slint-${{ github.event.inputs.program || inputs.program }}-linux path: slint-${{ github.event.inputs.program || inputs.program }}-linux.tar.gz build_linux_arm: runs-on: ubuntu-22.04 strategy: matrix: target: - armv7-unknown-linux-gnueabihf - aarch64-unknown-linux-gnu steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: target: x86_64-unknown-linux-gnu - uses: baptiste0928/cargo-install@v3 with: crate: cross - uses: baptiste0928/cargo-install@v3 with: crate: cargo-about version: "=0.6.6" - name: Build run: cross build --target=${{ matrix.target }} --no-default-features --features ${{ github.event.inputs.features || inputs.features }} --release -p slint-${{ github.event.inputs.program || inputs.program }} - name: Create artifact directory run: | mkdir -p slint-${{ github.event.inputs.program || inputs.program }}-${{ matrix.target }} cp target//${{ matrix.target }}/release/slint-${{ github.event.inputs.program || inputs.program }} slint-${{ github.event.inputs.program || inputs.program }}-${{ matrix.target }}/ cd tools/${{ github.event.inputs.program || inputs.program }} ../../scripts/prepare_binary_package.sh ../../slint-${{ github.event.inputs.program || inputs.program }}-${{ matrix.target }} - name: Tar artifacts to preserve permissions run: tar czvf slint-${{ github.event.inputs.program || inputs.program }}-${{ matrix.target }}.tar.gz slint-${{ github.event.inputs.program || inputs.program }}-${{ matrix.target }} - name: Upload artifact uses: actions/upload-artifact@v7 with: name: slint-${{ github.event.inputs.program || inputs.program }}-${{ matrix.target }} path: slint-${{ github.event.inputs.program || inputs.program }}-${{ matrix.target }}.tar.gz build_macos: runs-on: macos-26 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: target: x86_64-apple-darwin - uses: ./.github/actions/setup-rust with: target: aarch64-apple-darwin - uses: baptiste0928/cargo-install@v3 with: crate: cargo-about version: "=0.6.6" - name: Build x86-64 run: cargo build --verbose --target x86_64-apple-darwin --no-default-features --features ${{ github.event.inputs.features || inputs.features }} --release -p slint-${{ github.event.inputs.program || inputs.program }} - name: Build aarch64 run: cargo build --verbose --target aarch64-apple-darwin --no-default-features --features ${{ github.event.inputs.features || inputs.features }} --release -p slint-${{ github.event.inputs.program || inputs.program }} - name: Create artifact directory run: | mkdir -p slint-${{ github.event.inputs.program || inputs.program }} cd slint-${{ github.event.inputs.program || inputs.program }} lipo -create -output ./slint-${{ github.event.inputs.program || inputs.program }} ../target/aarch64-apple-darwin/release/slint-${{ github.event.inputs.program || inputs.program }} ../target/x86_64-apple-darwin/release/slint-${{ github.event.inputs.program || inputs.program }} install_name_tool -add_rpath @executable_path/. ./slint-${{ github.event.inputs.program || inputs.program }} cd .. cd tools/${{ github.event.inputs.program || inputs.program }} ../../scripts/prepare_binary_package.sh ../../slint-${{ github.event.inputs.program || inputs.program }} - uses: ./.github/actions/codesign if: ${{ github.event.inputs.codesign == 'true' }} with: binary: slint-${{ github.event.inputs.program || inputs.program }}/slint-${{ github.event.inputs.program || inputs.program }} certificate: ${{ github.event.inputs.certificate }} certificate_password: ${{ github.event.inputs.certificate_password }} keychain_password: ${{ github.event.inputs.keychain_password }} developer_id: ${{ github.event.inputs.developer_id }} - name: Tar artifacts to preserve permissions run: tar czvf slint-${{ github.event.inputs.program || inputs.program }}-macos.tar.gz slint-${{ github.event.inputs.program || inputs.program }} - name: Upload artifact uses: actions/upload-artifact@v7 with: name: slint-${{ github.event.inputs.program || inputs.program }}-macos path: slint-${{ github.event.inputs.program || inputs.program }}-macos.tar.gz ================================================ FILE: .github/workflows/spellcheck.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: "Check spelling" on: # push: # pull_request: workflow_dispatch: jobs: spellcheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: streetsidesoftware/cspell-action@v8 with: config: "./cspell.json" strict: false ================================================ FILE: .github/workflows/torizon_demos.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Rust Demos built for Torizon on: workflow_dispatch: inputs: push: type: boolean description: "Push the built images" workflow_call: inputs: push: type: boolean description: "Push the built images" jobs: build_containers: runs-on: ubuntu-22.04 strategy: matrix: include: # Default software rendering (GPU-less) - target: arm64 image_arch: linux/arm64 rust_toolchain_arch: aarch64-unknown-linux-gnu base_image_suffix: "" build_home_automation_sw_renderer: true # GPU-specific variants # https://developer.toradex.com/torizon/application-development/provided-containers/debian-containers-for-torizon - target: arm64 image_arch: linux/arm64 rust_toolchain_arch: aarch64-unknown-linux-gnu base_image_suffix: "-imx8" build_home_automation_sw_renderer: false - target: arm64 image_arch: linux/arm64 rust_toolchain_arch: aarch64-unknown-linux-gnu base_image_suffix: "-am62" build_home_automation_sw_renderer: false - target: arm64 image_arch: linux/arm64 rust_toolchain_arch: aarch64-unknown-linux-gnu base_image_suffix: "-imx95" build_home_automation_sw_renderer: false steps: - uses: actions/checkout@v6 - name: Set up QEMU uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Login to GitHub Container Registry uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.CR_PAT }} - name: Build and push uses: docker/build-push-action@v7 with: context: . file: ./docker/Dockerfile.torizon-demos push: ${{ github.event.inputs.push || inputs.push }} tags: ghcr.io/slint-ui/slint/torizon-demos-${{matrix.target}}${{matrix.base_image_suffix}}:latest platforms: ${{matrix.image_arch}} build-args: | TOOLCHAIN_ARCH=${{matrix.target}} IMAGE_ARCH=${{matrix.image_arch}} RUST_TOOLCHAIN_ARCH=${{matrix.rust_toolchain_arch}} BASE_NAME=wayland-base${{matrix.base_image_suffix}} WEATHER_API_KEY=${{secrets.WEATHER_API_KEY}} BUILD_HOME_AUTOMATION_SW_RENDERER=${{matrix.build_home_automation_sw_renderer}} ================================================ FILE: .github/workflows/translations.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # Run slint-tr-extractor and msgfmt name: Update Translations on: workflow_dispatch: # schedule: # - cron: "0 3 * * *" jobs: update-translations: runs-on: ubuntu-latest env: CARGO_PROFILE_DEV_DEBUG: 0 CARGO_INCREMENTAL: false RUST_BACKTRACE: 1 steps: - name: "install gettext tools" run: sudo apt install gettext - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust - name: "gallery: Run slint-tr-extractor" run: find -name \*.slint | xargs cargo run -p slint-tr-extractor -- -d gallery -o gallery.pot working-directory: examples/gallery - name: "gallery: msgmerge and msgfmt" working-directory: examples/gallery run: | for po in lang/*/LC_MESSAGES do msgmerge $po/gallery.po gallery.pot -o $po/gallery.po msgfmt $po/gallery.po -o $po/gallery.mo done - name: "printerdemo: Run slint-tr-extractor" run: find -name \*.slint | xargs cargo run -p slint-tr-extractor -- -d printerdemo -o printerdemo.pot working-directory: demos/printerdemo - name: "printerdemo: msgmerge and msgfmt" working-directory: demos/printerdemo run: | for po in lang/*/LC_MESSAGES do msgmerge $po/printerdemo.po printerdemo.pot -o $po/printerdemo.po msgfmt $po/printerdemo.po -o $po/printerdemo.mo done - name: "Fix license headers" run: cargo xtask check_license_headers --fix-it - name: Check for interesting changes id: git_diff run: git diff --ignore-matching-lines="POT-Creation-Date:.*" --exit-code --quiet HEAD continue-on-error: true - name: commit # Only run this if there was a diff! if: steps.git_diff.outcome == 'failure' run: | git config --global user.email "noreply@slint.dev" git config --global user.name "Update Translations Bot" git add examples git commit -a -m 'Update Translations: extract strings' - name: Result # Only run this if there was a diff! run: if [ '${{ steps.git_diff.outcome }}' = 'failure' ]; then echo "I committed this change to git:"; git show ; else echo "This change was ignored:" ; git diff HEAD ; fi - name: Push changes uses: ad-m/github-push-action@master with: branch: ${{ github.ref }} ================================================ FILE: .github/workflows/tree_sitter.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: "Tree Sitter Test" on: # push: # pull_request: workflow_call: inputs: tag: type: string description: tree sitter release tag to use latest: type: boolean description: Use the latest tree-sitter release default: true jobs: tree-sitter-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: robinraju/release-downloader@v1.12 with: repository: "tree-sitter/tree-sitter" tag: ${{ inputs.tag }} latest: ${{ inputs.latest }} fileName: "tree-sitter-linux-x64.gz" out-file-path: ${{ runner.workspace }} - name: Extract tree-sitter-cli run: | gunzip tree-sitter-linux-x64.gz chmod 755 tree-sitter-linux-x64 mv tree-sitter-linux-x64 tree-sitter working-directory: ${{ runner.workspace }} - name: Generate tree-sitter corpus run: find ../../tests/cases -type d -exec ./test-to-corpus.py --tests-directory {} \; working-directory: editors/tree-sitter-slint - name: Generate tree-sitter parser run: ${{ runner.workspace }}/tree-sitter generate working-directory: editors/tree-sitter-slint - name: Run tree-sitter tests run: ${{ runner.workspace }}/tree-sitter test -u working-directory: editors/tree-sitter-slint - name: Check for parse ERRORs from tree-sitter run: sh -c "! grep -q ERROR corpus/*.txt" working-directory: editors/tree-sitter-slint ================================================ FILE: .github/workflows/upgrade_version.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Upgrade Version Number on: workflow_dispatch: inputs: new_version: description: "The new version number" required: true jobs: upgrade_version_number: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Do replacements run: | # Each Cargo.toml need to have the version updated git ls-files | grep Cargo.toml | grep -v helper_crates | xargs sed -i 's/^version = "[0-9]*\.[0-9]*\.[0-9]*"/version = "${{ github.event.inputs.new_version }}"/' # Each dependencies in cargo.toml git ls-files | grep Cargo.toml | xargs sed -i 's/\(slint.*version = \)"=[0-9]*\.[0-9]*\.[0-9]*"/\1"=${{ github.event.inputs.new_version }}"/' # Update the version in CmakeLists.txt sed -i 's/ VERSION [0-9]*\.[0-9]*\.[0-9]*)$/ VERSION ${{ github.event.inputs.new_version }})/' api/cpp/CMakeLists.txt # The version is also in these files sed -i "s/^version = \"[0-9]*\.[0-9]*\.[0-9]*\(.*\)\"/version = \"${{ github.event.inputs.new_version }}\1\"/" api/cpp/docs/conf.py api/python/slint/pyproject.toml api/python/briefcase/pyproject.toml tools/compiler/pyproject.toml editors/zed/extension.toml # Version in package.json files git ls-files | grep package.json | xargs sed -i 's/"version": ".*"/"version": "${{ github.event.inputs.new_version }}"/' # Update version in Node.js binary package config sed -i 's/"version": ".*"/"version": "${{ github.event.inputs.new_version }}"/' api/node/binaries.json # VersionCheck sed -i "s/VersionCheck_[0-9]*_[0-9]*_[0-9]*;/VersionCheck_`echo ${{ github.event.inputs.new_version }} | sed "s/\([0-9]*\)\.\([0-9]*\).\([0-9]*\)/\1/"`_`echo ${{ github.event.inputs.new_version }} | sed "s/\([0-9]*\)\.\([0-9]*\).\([0-9]*\)/\2/"`_`echo ${{ github.event.inputs.new_version }} | sed "s/\([0-9]*\)\.\([0-9]*\).\([0-9]*\)/\3/"`;/" api/rs/slint/lib.rs # Version in the AboutSlint widget sed -i "s/Version [0-9]*\.[0-9]*\.[0-9]*\\\\n/Version ${{ github.event.inputs.new_version }}\\\\n/" internal/compiler/widgets/common/about-slint.slint # Version in the docs (cargo add slint@): git ls-files | grep "\(^\|/\)docs/.*\.\(md\|rst\)\$" | xargs sed -i 's/slint@[0-9]\+\.[0-9]\+\.[0-9]\+/slint@${{ github.event.inputs.new_version }}/' # Version in esp-idf component sed -i 's/^version: "[0-9]*\.[0-9]*\.[0-9]*.*"/version: "${{ github.event.inputs.new_version }}"/' api/cpp/esp-idf/slint/idf_component.yml sed -i 's/GIT_TAG v[0-9]*\.[0-9]*\.[0-9]*/GIT_TAG v${{ github.event.inputs.new_version }}/' api/cpp/esp-idf/slint/CMakeLists.txt sed -i 's/find_package(Slint [0-9]*\.[0-9]*\.[0-9]*)/find_package(Slint ${{ github.event.inputs.new_version }})/' api/cpp/esp-idf/slint/CMakeLists.txt # Version in the Android docs sed -i 's/^slint = { version = "[^"]+"/slint = { version = "${{ github.event.inputs.new_version }}"/' docs/astro/src/content/docs/guide/platforms/mobile/android.mdx # Some documentation use the ~syntax sed -i 's/\(slint.*version = \)"~[0-9]*\.[0-9]*"/\1"~'"$(echo ${{ github.event.inputs.new_version }} | sed 's/\([0-9]*\.[0-9]*\)\.[0-9]*/\1/')"'"/' api/rs/slint/Cargo.toml api/rs/slint/lib.rs echo "Note that the version is not updated in the documentation and README yet" - name: Commit run: | git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" git config --global user.name "${GITHUB_ACTOR}" git commit -a --message "Bump version number to ${{ github.event.inputs.new_version }}" - name: Result run: | git diff - name: Push changes uses: ad-m/github-push-action@master with: branch: wip/version-bump # update https://releases.slint.dev/versions.json update_versions_json: runs-on: ubuntu-latest if: github.ref == 'refs/heads/master' steps: - name: Generate a token id: app-token uses: actions/create-github-app-token@v3 with: app-id: ${{ vars.READ_WRITE_APP_ID }} private-key: ${{ secrets.READ_WRITE_PRIVATE_KEY }} repositories: www-releases - name: Clone www-releases directory uses: actions/checkout@v6 with: repository: slint-ui/www-releases sparse-checkout: | releases/versions.json path: www-releases token: ${{ steps.app-token.outputs.token }} - name: Update version in versions.json run: | sed -i '0,/"version": "[0-9]\+\.[0-9]\+\.[0-9]\+"/s//"version": "${{ github.event.inputs.new_version }}"/' www-releases/releases/versions.json - name: Adjust redirections run: | sed -i "/\/[0-9]*\.[0-9]*\.[0-9]*\/* https:\/\/snapshots\.slint\.dev\/master\/:splat/ s/[0-9]*\.[0-9]*\.[0-9]*/${{ github.event.inputs.new_version }}/" www-releases/releases/_redirects - name: Get GitHub App User ID id: get-user-id run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ steps.app-token.outputs.token }} - name: commit and push working-directory: ./www-releases run: | git config user.name '${{ steps.app-token.outputs.app-slug }}[bot]' git config user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' git add . git add -u . git commit --message "Update $NAME from $GITHUB_REPOSITORY" --message "Update versions.json" git push ================================================ FILE: .github/workflows/upload_esp_idf_component.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Upload component to ESP-IDF component registry on: workflow_dispatch: inputs: private: type: boolean default: true required: false description: "Private build? True means artifacts are only built. False means the artefacts are published to components.espressif.com" release: type: boolean default: false required: false description: "Release? Uploads to esp-idf component registry as release if true; otherwise as slint/slint-nightly" jobs: upload_components: runs-on: ubuntu-22.04 container: espressif/idf:latest steps: - uses: actions/checkout@v6 - run: apt-get update && apt-get install -y yq - name: Make subsequent git calls work run: git config --global --add safe.directory '*' - name: Extract Version and determine name working-directory: api/cpp/esp-idf/slint id: version env: RELEASE_INPUT: ${{ github.event.inputs.release }} run: | version=$(grep -oP '(?<=GIT_TAG v)[0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt) if [[ -z "$version" ]]; then echo "Version not found" exit 1 fi if [ "$RELEASE_INPUT" != "true" ]; then echo "PKG_NAME=slint-nightly" >> $GITHUB_OUTPUT nightly_version_suffix=`git log -1 --format=%cd --date="format:%Y%m%d%H"` version="$version+nightly.$nightly_version_suffix" git show HEAD:./idf_component.yml | yq -y --arg nightly_version "${version}" '.version = $nightly_version' > idf_component.yml sed -i 's/GIT_TAG v[0-9]*\.[0-9]*\.[0-9]*/GIT_TAG ${{ github.sha }}/' CMakeLists.txt sed -i '1iset(SLINT_NIGHTLY true)' CMakeLists.txt else echo "PKG_NAME=slint" >> $GITHUB_OUTPUT fi echo "VERSION=$version" >> $GITHUB_OUTPUT - name: Remove use of nightly version if: github.event.inputs.release == 'true' working-directory: api/cpp/esp-idf/slint run: | sed -i "s/find_package(Slint)/find_package(Slint ${{ steps.version.outputs.VERSION }})/g" CMakeLists.txt - name: Upload component uses: espressif/upload-components-ci-action@v2 with: namespace: "slint" api_token: ${{ secrets.ESP_IDF_COMPONENTS_TOKEN }} components: ${{ steps.version.outputs.PKG_NAME }}:api/cpp/esp-idf/slint dry_run: ${{ github.event.inputs.private == 'true' }} - name: Package component if: github.event.inputs.release != 'true' working-directory: "api/cpp/esp-idf/slint" run: | . ${IDF_PATH}/export.sh compote component pack --name ${{ steps.version.outputs.PKG_NAME }} - name: Archive component if: github.event.inputs.release != 'true' uses: actions/upload-artifact@v7 with: path: "api/cpp/esp-idf/slint/dist/*" ================================================ FILE: .github/workflows/upload_pypi.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Upload component to Python Package Index on: workflow_dispatch: inputs: release: type: boolean default: false required: false description: "Release? If false, publish to test.pypi.org, if true, publish to pypi.org" jobs: build_binaries: env: MACOSX_DEPLOYMENT_TARGET: "11.0" strategy: matrix: platform: - runner: windows-latest target: x64 container: auto # Pending https://github.com/actions/partner-runner-images/issues/85 # - runner: windows-11-arm # target: aarch64 # container: auto - runner: macos-14 target: aarch64 container: auto - runner: ubuntu-22.04 target: x86_64 container: auto - runner: ubuntu-22.04 target: aarch64 container: "ghcr.io/slint-ui/slint/aarch64-unknown-linux-gnu" - runner: ubuntu-22.04 target: armv7 container: "ghcr.io/slint-ui/slint/armv7-unknown-linux-gnueabihf" runs-on: ${{ matrix.platform.runner }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies if: runner.os == 'Linux' - uses: actions/setup-python@v6 with: python-version: '3.12' - uses: ./.github/actions/setup-rust - uses: baptiste0928/cargo-install@v3 with: crate: taplo-cli - name: Prepare feature config for binaries working-directory: api/python/slint shell: bash run: | cat Cargo.toml | taplo format --option column_width=100000 --stdin-filepath=Cargo.toml - | \ perl -p -e 's,^\s*default\s*=.*,,' | \ perl -p -e 's,# binaries:\s?,,' > Cargo.toml.new cat Cargo.toml.new | taplo format --stdin-filepath=Cargo.toml - > Cargo.toml rm Cargo.toml.new taplo get -f Cargo.toml features.default - name: Build a binary wheel uses: PyO3/maturin-action@v1 with: working-directory: api/python/slint target: ${{ matrix.platform.target }} args: --release --out wheelhouse --find-interpreter container: ${{ matrix.platform.container }} - name: Store the distribution packages uses: actions/upload-artifact@v7 with: name: python-package-distributions-${{ matrix.platform.runner }}-${{ strategy.job-index }} path: api/python/slint/wheelhouse/*.whl ios_binaries: strategy: matrix: target: - aarch64-apple-ios-sim - aarch64-apple-ios include: - target: aarch64-apple-ios-sim sim_suffix: SIM_ python_sim_suffix: _x86_64-simulator arch: arm64 sdk_name: iphonesimulator tag: ios_13_0_arm64_iphonesimulator - target: aarch64-apple-ios arch: arm64 sdk_name: iphoneos tag: ios_13_0_arm64_iphoneos runs-on: macos-14 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust with: target: aarch64-apple-ios - uses: actions/setup-python@v6 with: python-version: '3.13' - uses: baptiste0928/cargo-install@v3 with: crate: taplo-cli - name: Prepare feature config for binaries working-directory: api/python/slint shell: bash run: | cat Cargo.toml | taplo format --option column_width=100000 --stdin-filepath=Cargo.toml - | \ perl -p -e 's,^\s*default\s*=.*,,' | \ perl -p -e 's,# binaries:\s?,,' > Cargo.toml.new cat Cargo.toml.new | taplo format --stdin-filepath=Cargo.toml - > Cargo.toml rm Cargo.toml.new taplo get -f Cargo.toml features.default - run: | export GIT_LFS_SKIP_SMUDGE=1 git clone https://github.com/beeware/mobile-forge.git cd mobile-forge source ./setup-iOS.sh 3.13 PYTHONDIR="$PWD/support/3.13/iOS/Python.xcframework/ios-arm64${{ matrix.python_sim_suffix}}" echo PYTHONDIR="$PYTHONDIR" >> $GITHUB_ENV OSX_SDKROOT=$(xcrun --sdk macosx --show-sdk-path) echo "OSX_SDKROOT=$OSX_SDKROOT" >> $GITHUB_ENV IOS_SDKROOT=$(xcrun --sdk ${{ matrix.sdk_name }} --show-sdk-path) echo "IOS_SDKROOT=$IOS_SDKROOT" >> $GITHUB_ENV echo "PYO3_CROSS_PYTHON_VERSION=3.13" >> $GITHUB_ENV echo "SDKROOT=$OSX_SDKROOT" >> $GITHUB_ENV echo "PYO3_CROSS_LIB_DIR=$PYTHONDIR" >> $GITHUB_ENV echo CARGO_TARGET_AARCH64_APPLE_IOS_${{ matrix.sim_suffix }}RUSTFLAGS="-C link-arg=-isysroot -C link-arg=$IOS_SDKROOT -C link-arg=-arch -C link-arg=${{ matrix.arch }} -C link-arg=-miphoneos-version-min=14.0 -C link-arg=-F -C link-arg=$PYTHONDIR -C link-arg=-framework -C link-arg=Python" >> $GITHUB_ENV - name: Build a binary wheel uses: PyO3/maturin-action@v1 env: _PYTHON_HOST_PLATFORM: ${{ matrix.tag }} with: working-directory: api/python/slint target: ${{ matrix.target }} args: --release --out wheelhouse --interpreter 3.13 - name: Store the distribution packages uses: actions/upload-artifact@v7 with: name: python-package-distributions-${{ matrix.target }} path: api/python/slint/wheelhouse/*.whl build_source_package: name: Build source package runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: baptiste0928/cargo-install@v3 with: crate: taplo-cli - name: Prepare feature config for binaries working-directory: api/python/slint shell: bash run: | cat Cargo.toml | taplo format --option column_width=100000 --stdin-filepath=Cargo.toml - | \ perl -p -e 's,^\s*default\s*=.*,,' | \ perl -p -e 's,# binaries:\s?,,' > Cargo.toml.new cat Cargo.toml.new | taplo format --stdin-filepath=Cargo.toml - > Cargo.toml rm Cargo.toml.new taplo get -f Cargo.toml features.default - name: Build source package uses: PyO3/maturin-action@v1 with: working-directory: api/python/slint command: sdist args: --out dist - uses: actions/upload-artifact@v7 with: name: python-package-distributions-source path: api/python/slint/dist/*.tar.gz publish-to-test-pypi: if: ${{ github.event.inputs.release != 'true' }} name: >- Publish Python 🐍 distribution 📦 to Test PyPI needs: [build_binaries, ios_binaries, build_source_package] runs-on: ubuntu-latest environment: name: testpypi url: https://test.pypi.org/p/slint permissions: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - uses: actions/download-artifact@v8 with: pattern: python-package-distributions-* path: dist merge-multiple: true - name: Publish distribution 📦 to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ publish-to-pypi: if: ${{ github.event.inputs.release == 'true' }} name: >- Publish Python 🐍 distribution 📦 to PyPI needs: [build_binaries, ios_binaries, build_source_package] runs-on: ubuntu-latest environment: name: pypi url: https://test.pypi.org/p/slint permissions: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - uses: actions/download-artifact@v8 with: pattern: python-package-distributions-* path: dist merge-multiple: true - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: verbose: true ================================================ FILE: .github/workflows/upload_pypi_briefcase.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Upload briefcasex-slint to Python Package Index on: workflow_dispatch: inputs: release: type: boolean default: false required: false description: "Release? If false, publish to test.pypi.org, if true, publish to pypi.org" jobs: build_source_package: name: Build source package runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Install uv uses: astral-sh/setup-uv@v7 - name: Build source package working-directory: api/python/briefcase run: uv build --sdist - uses: actions/upload-artifact@v7 with: name: python-package-distributions-source path: api/python/briefcase/dist/*.tar.gz publish-to-test-pypi: if: ${{ github.event.inputs.release != 'true' }} name: >- Publish Python 🐍 distribution 📦 to Test PyPI needs: [build_source_package] runs-on: ubuntu-latest environment: name: testpypi url: https://test.pypi.org/p/briefcasex-slint permissions: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - uses: actions/download-artifact@v8 with: pattern: python-package-distributions-* path: dist merge-multiple: true - name: Publish distribution 📦 to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ publish-to-pypi: if: ${{ github.event.inputs.release == 'true' }} name: >- Publish Python 🐍 distribution 📦 to PyPI needs: [build_source_package] runs-on: ubuntu-latest environment: name: pypi url: https://test.pypi.org/p/briefcasex-slint permissions: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - uses: actions/download-artifact@v8 with: pattern: python-package-distributions-* path: dist merge-multiple: true - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: verbose: true ================================================ FILE: .github/workflows/upload_pypi_slint_compiler.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Upload slint-compiler to Python Package Index on: workflow_dispatch: inputs: release: type: boolean default: false required: false description: "Release? If false, publish to test.pypi.org, if true, publish to pypi.org" jobs: build-wheels: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ ubuntu-22.04, ubuntu-22.04-arm, windows-2022, windows-11-arm, macos-14, ] steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-rust - name: Build wheels uses: PyO3/maturin-action@v1 with: working-directory: tools/compiler args: --release --out wheelhouse --find-interpreter - uses: actions/upload-artifact@v7 with: name: wheels-${{ matrix.os }} path: tools/compiler/wheelhouse/*.whl build-sdist: name: Build source distribution runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: astral-sh/setup-uv@v7 - name: Build sdist run: uv build --sdist -o dist tools/compiler - uses: actions/upload-artifact@v7 with: name: sdist path: dist/*.tar.gz publish-to-test-pypi: if: ${{ github.event.inputs.release != 'true' }} needs: [build-wheels, build-sdist] runs-on: ubuntu-latest environment: name: testpypi url: https://test.pypi.org/p/slint-compiler permissions: id-token: write steps: - uses: astral-sh/setup-uv@v7 - uses: actions/download-artifact@v8 with: pattern: wheels-* path: dist merge-multiple: true - uses: actions/download-artifact@v8 with: name: sdist path: dist - name: Publish to Test PyPI env: UV_PUBLISH_URL: https://test.pypi.org/legacy/ run: uv publish dist/* publish-to-pypi: if: ${{ github.event.inputs.release == 'true' }} needs: [build-wheels, build-sdist] runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/slint-compiler permissions: id-token: write steps: - uses: astral-sh/setup-uv@v7 - uses: actions/download-artifact@v8 with: pattern: wheels-* path: dist merge-multiple: true - uses: actions/download-artifact@v8 with: name: sdist path: dist - name: Publish to PyPI run: uv publish dist/* ================================================ FILE: .github/workflows/wasm_demos.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 name: Build wasm demos on: workflow_dispatch: workflow_call: inputs: build_artifacts: type: boolean required: true description: True if we should build all demos and produce artefacts jobs: wasm_demo: env: CARGO_PROFILE_RELEASE_OPT_LEVEL: s CARGO_INCREMENTAL: false runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies - uses: ./.github/actions/setup-rust with: target: wasm32-unknown-unknown - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - name: Printerdemo WASM build run: | sed -i "s/#wasm# //" Cargo.toml wasm-pack build --release --target web working-directory: demos/printerdemo/rust - name: Gallery WASM build if: ${{ inputs.build_artifacts }} run: | sed -i "s/#wasm# //" Cargo.toml export SLINT_STYLE=fluent && wasm-pack build --release --out-dir pkg/fluent --target web export SLINT_STYLE=material && wasm-pack build --release --out-dir pkg/material --target web export SLINT_STYLE=cupertino && wasm-pack build --release --out-dir pkg/cupertino --target web export SLINT_STYLE=cosmic && wasm-pack build --release --out-dir pkg/cosmic --target web working-directory: examples/gallery - name: Remaining wasm demos if: ${{ inputs.build_artifacts }} run: | for demo in demos/printerdemo/rust demos/usecases/rust examples/todo/rust examples/todo-mvc/rust examples/carousel/rust examples/slide_puzzle examples/memory examples/imagefilter/rust examples/plotter examples/opengl_underlay demos/home-automation/rust examples/speedometer/rust; do pushd $demo sed -i "s/#wasm# //" Cargo.toml wasm-pack build --release --target web popd done - name: Energy Monitor example WASM build if: ${{ inputs.build_artifacts }} run: | sed -i "s/#wasm# //" Cargo.toml wasm-pack build --release --target web --no-default-features --features slint/default,chrono working-directory: demos/energy-monitor - name: Weather Demo example WASM build run: | sed -i "s/#wasm# //" Cargo.toml wasm-pack build --release --target web --no-default-features --features slint/default,chrono working-directory: demos/weather-demo - name: "Upload Demo Artifacts" if: ${{ inputs.build_artifacts }} uses: actions/upload-artifact@v7 with: name: wasm_demo path: | examples/gallery/ demos/printerdemo/rust/ examples/todo/ examples/todo-mvc/rust/ examples/carousel/rust/ examples/memory/ examples/slide_puzzle/ examples/speedometer/rust/ examples/imagefilter/rust examples/plotter/ examples/opengl_underlay/ demos/energy-monitor/ demos/home-automation/rust demos/weather-demo/ demos/usecases/rust !/**/.gitignore - name: Clean cache # Otherwise the cache is much too big run: | du -hs target rm -rf target/*/incremental rm -rf target/*/*/*slint* du -hs target ================================================ FILE: .github/workflows/wasm_editor_and_interpreter.yaml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 --- name: Build and test the slintpad and required WASM binaries on: workflow_dispatch: workflow_call: jobs: wasm: env: CARGO_PROFILE_RELEASE_OPT_LEVEL: s CARGO_INCREMENTAL: false runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/install-linux-dependencies - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 - uses: actions/setup-node@v6 with: node-version: 24 package-manager-cache: false - uses: ./.github/actions/setup-rust with: target: wasm32-unknown-unknown - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - name: Compile slint-wasm-interpreter run: wasm-pack build --release --target web -- --features console_error_panic_hook working-directory: api/wasm-interpreter - name: Build slint-wasm-lsp env: SLINT_STYLE: fluent-light run: pnpm build:wasm_lsp-release working-directory: tools/slintpad - name: "Upload wasm Artifacts" uses: actions/upload-artifact@v7 with: name: wasm path: | api/wasm-interpreter/pkg/ tools/lsp/pkg/ build-slintpad: needs: [wasm] runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: 24 package-manager-cache: false - uses: pnpm/action-setup@v4.4.0 with: version: 10.29.3 - name: Download wasm_lsp Artifacts uses: actions/download-artifact@v8 with: name: wasm - name: Install NPM dependencies run: pnpm install --frozen-lockfile working-directory: tools/slintpad - name: Install playwright dependencies run: pnpm exec playwright install --with-deps working-directory: tools/slintpad - name: Compile slintpad run: pnpm build working-directory: tools/slintpad - name: Smoke-Test slintpad run: pnpm exec playwright test working-directory: tools/slintpad - name: Publish Test Summary Results if: ${{ always() }} working-directory: tools/slintpad run: npx github-actions-ctrf playwright-report/ctrf-report.json - name: "Upload slintpad Artifacts" uses: actions/upload-artifact@v7 with: name: slintpad path: tools/slintpad/dist/ # test-slintpad-ff: # needs: [wasm, build-slintpad] # runs-on: ubuntu-latest # container: # image: cypress/browsers:node16.13.0-chrome95-ff94 # options: --user 1001 # # steps: # - uses: actions/checkout@v6 # - uses: ./.github/actions/test-slintpad # with: # browser: "firefox" # wasm-binaries: wasm # slintpad-artifact: slintpad # # test-slintpad-chrome: # needs: [wasm, build-slintpad] # runs-on: ubuntu-latest # container: # image: cypress/browsers:node16.13.0-chrome95-ff94 # options: --user 1001 # # steps: # - uses: actions/checkout@v6 # - uses: ./.github/actions/test-slintpad # with: # browser: "chrome" # wasm-binaries: wasm # slintpad-artifact: slintpad ================================================ FILE: .gitignore ================================================ # The servo example requires a lock file as servo only builds with pinned dependencies. !examples/servo/Cargo.lock tsconfig.tsbuildinfo /target node_modules /tools/figma_import/target /tools/figma_import/figma_output *.code-workspace /build /build-release /_deps .DS_Store Pipfile.lock /docs/astro/src/content/collections/enums/ /docs/astro/src/content/collections/structs/ /playwright-report global-structs-enums.mdx # Ignore all package-lock.json files **/package-lock.json # But keep these specific ones !editors/vscode/package-lock.json # MISE local files: /.mise.local.toml /.mise/tasks/local/ *.node *.d.ts __pycache__ .cache uv.lock .python-version MODULE.bazel.lock ================================================ FILE: .mailmap ================================================ Aurindam Jana Florian Blasius Olivier Goffart Olivier Goffart Olivier Goffart Olivier Goffart Simon Hausmann Simon Hausmann Simon Hausmann Tobias Hunger Tobias Hunger Tobias Hunger ================================================ FILE: .mise/config.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # cspell:ignore pipx [tools] ## C++: "ubi:EmbarkStudios/cargo-about" = "0.6.6" "pipx:cmake" = "4.0.0" "pipx:ninja" = "1.11.1.4" "pipx:pipenv" = "2025.0.2" # doxygen = "1.13.2" ## This is not available anywhere # graphviz = "12.2.1" ## This is not available anywhere "ubi:sharkdp/fd" = "10.2.0" "rust" = { version = "stable", profile = "default" } node = "24" pnpm = "10.29.3" taplo = "0.9.3" "ubi:drager/wasm-pack" = "0.13.1" uv = "0.10.4" [task_config] includes = [".mise/tasks.toml", ".mise/tasks"] ================================================ FILE: .mise/tasks/fix/text/trailing_spaces ================================================ #!/usr/bin/env bash # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #MISE description="Run trailing WS fix on all files" set -e if [[ "$OSTYPE" == "darwin"* ]]; then SED_INPLACE="sed -i ''" else SED_INPLACE="sed -i" fi if test -f .git/HEAD ; then git grep -I -l -O"$SED_INPLACE \"s/[[:space:]]*$//\"" -e '' -- ':!*.patch' else while IFS= read -r -d '' -u 9 do if [[ "$(file -bs --mime-type -- "$REPLY")" = text/* ]] then $SED_INPLACE "s/[[:space:]]*$//" -- "$REPLY" fi done 9< <(fd -0) fi ================================================ FILE: .mise/tasks/lint/legal/reuse ================================================ #!/usr/bin/env bash # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #MISE description="Run xtask check reuse compliance on all files" #MISE tools={ "pipx:reuse" = "6" } set -e if test -d "target" ; then if test -n "$CI" ; then echo "ERROR: Directory is not clean in CI run, NOT running reuse" exit 1 else echo "Warning: Directory is not clean, not running reuse" exit 0 fi fi cargo xtask check_reuse_compliance ================================================ FILE: .mise/tasks.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # Use https://mise.jdx.dev/schema/mise-task.json for validation. ### fixes ["fix:cpp:format"] description = "Run clang format fix on all C++ files" run = '''fd '.*\.(h|H|hpp|hxx|h\+\+|c|C|cpp|cxx|c\+\+)$' -0 | xargs -0 clang-format -i''' tools = { "pipx:clang-format" = "20.1.3" } ["fix:rust:lockfile"] description = "Update the lockfile (if absolutely necessary)" # Use cargo build -p xtask to test if the lockfile needs upating. # The fix:legal:copyright task needs the xtask anyhow, so this has a double use. run = "cargo build --locked -p xtask || (echo \"### Updating Lockfile! ###\" && cargo update)" ["fix:legal:copyright"] description = "Run the check_license_headers --fix xtask" run = "cargo xtask check_license_headers --fix-it" depends = ["fix:rust:lockfile"] ["fix:python:format"] description = "Run ruff format" # Run via uv as the ruff installation via mise is unreliable in the CI. # It keeps failing to find ruff, despite saying that it was installed. run = "uv tool run ruff@0.11.8 format" ["fix:rust:format"] description = "Run cargo fmt --all" run = "cargo fmt --all" ["fix:rust:format:root"] description = "Run cargo fmt --all" run = "cargo fmt --all" dir = "." ["fix:rust:format:bevy"] description = "Run cargo fmt --all" run = "cargo fmt --all" dir = "examples/bevy" ["fix:rust:format:servo"] description = "Run cargo fmt --all" run = "cargo fmt --all" dir = "examples/servo" ["fix:rust:format:safeui"] description = "Run cargo fmt --all" run = "cargo fmt --all" dir = "examples/safe-ui" ["fix:rust:format:all"] depends = ["fix:rust:format:root", "fix:rust:format:bevy", "fix:rust:format:servo", "fix:rust:format:safeui"] ["fix:toml:format"] description = "Run taplo format" run = "taplo format" ["fix:pnpm:lock"] description = "Update pnpm lock file" run = "pnpm i --lockfile-only" ["fix:pnpm:dedupe"] description = "Run pnpm dedupe" run = "pnpm dedupe" depends = ["prepare:pnpm-install"] ["fix:ts:format"] description = "Run pnpm format:fix" run = "pnpm run format:fix" depends = ["prepare:pnpm-install"] ["fix:ts:biome"] description = "Run pnpm lint:fix" run = "pnpm run lint:fix" depends = ["prepare:pnpm-install"] ### Lints ["lint:ts:typecheck"] description = "Run pnpm format:fix" run = "pnpm type-check:ci" depends = ["prepare:pnpm-install"] ### Build ["build:lsp:wasm"] description = "Build the LSP (WASM)" dir = "editors/vscode" sources = ["tools/lsp/Cargo.toml", "tools/lsp/**/*.rs", "tools/lsp/ui/**/*.slint"] outputs = ["tools/lsp/pkg/*"] run = "pnpm run build:wasm_lsp" tools = { "ubi:drager/wasm-pack" = "0.13.1" } depends = ["prepare:pnpm-install"] ["build:interpreter:wasm"] description = "Build the Slint interpreteer (WASM)" dir = "tools/slintpad" sources = ["api/wasm-interpreter/Cargo.toml", "api/wasm-interpreter/src/**/*.rs"] outputs = ["api/wasm-interpreter/pkg/*"] run = "pnpm run build:wasm_interpreter" tools = { "ubi:drager/wasm-pack" = "0.13.1" } depends = ["prepare:pnpm-install"] ["prepare:pnpm-install"] hide = true run = "pnpm install --frozen-lockfile" depends = ["fix:pnpm:lock"] ### CI ["ci:autofix:fix"] description = "CI autofix job -- fix steps" depends = [ "fix:cpp:format", "fix:legal:copyright", "fix:python:format", "fix:rust:format:all", "fix:text:trailing_spaces", "fix:toml:format", "fix:pnpm:lock", "fix:pnpm:dedupe", "fix:ts:biome", "fix:ts:format", ] ["ci:autofix:lint"] description = "CI autofix job -- lint steps" depends = ["lint:legal:reuse", "lint:ts:typecheck"] ================================================ FILE: .npmrc ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 save-prefix="" node-linker=hoisted ================================================ FILE: .taplo.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 [formatting] column_width = 120 inline_table_expand = false ================================================ FILE: .vscode/.gitignore ================================================ launch.json settings.json ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": [ "rust-lang.rust-analyzer", "vadimcn.vscode-lldb", "ms-vscode.cmake-tools", "llvm-vs-code-extensions.vscode-clangd" ] } ================================================ FILE: AGENTS.md ================================================ # AGENTS.md This file provides guidance to AI coding assistants when working with code in this repository. ## Project Overview Slint is a declarative GUI toolkit for building native user interfaces across embedded systems, desktops, mobile, and web platforms. UIs are written in `.slint` markup files and connected to business logic in Rust, C++, JavaScript, or Python. ## Build Commands ### Rust (Primary) ```sh cargo build # Build the workspace cargo build --release # Release build (use this flag whenever testing performance) cargo test # Run tests (requires cargo build first!) cargo build --workspace --exclude uefi-demo # Build all examples ``` ### Running Examples ```sh cargo run -p gallery # Run the gallery example cargo run --bin slint-viewer -- path/to/file.slint # View a .slint file ``` ### C++ Build ```sh cargo build --lib -p slint-cpp # Build C++ library mkdir cppbuild && cd cppbuild cmake -GNinja .. cmake --build . ``` ### Node.js Build ```sh cd api/node && pnpm install ``` ## Testing **Important**: Run `cargo build` before `cargo test` - the dynamic library must exist first. ### Test Drivers ```sh cargo test -p test-driver-interpreter # Fast interpreter tests cargo test -p test-driver-rust # Rust API tests cargo test -p test-driver-cpp # C++ tests (build slint-cpp first) cargo test -p test-driver-nodejs # Node.js tests cargo test -p doctests # Documentation snippet tests ``` ### Filtered Testing ```sh tests/run_tests.sh rust layout # Filter by name ``` ### Syntax Tests (Compiler Errors) ```sh cargo test -p i-slint-compiler --features display-diagnostics --test syntax_tests SLINT_SYNTAX_TEST_UPDATE=1 cargo test -p i-slint-compiler --test syntax_tests # Update expected errors ``` ### Screenshot Tests ```sh cargo test -p test-driver-screenshots # Compare against references SLINT_CREATE_SCREENSHOTS=1 cargo test -p test-driver-screenshots # Generate references ``` ## Architecture ### Core Components - **`internal/compiler/`** - Slint language compiler (lexer, parser, code generators) - `parser/` - .slint syntax parsing using Rowan - `passes/` - Optimization passes - `generator/` - Code generators for C++, Rust, Python, JS - `tests/syntax/` - Syntax error test cases - **`internal/core/`** - Runtime library (properties, layout, animations, accessibility) - **`internal/core-macros/`** - Procedural macros for i-slint-core - **`internal/common/`** - Shared code and data structures between compiler and runtime - **`internal/interpreter/`** - Dynamic compilation for scripting languages - **`internal/backends/`** - Platform windowing/input: - `winit/` - Cross-platform (primary) - `qt/` - Qt integration - `android-activity/` - Android platform support - `linuxkms/` - Linux KMS/DRM direct rendering - `selector/` - Runtime backend selection - `testing/` - Testing backend for automated tests - **`internal/renderers/`** - Rendering engines: - `femtovg/` - OpenGL ES 2.0 - `skia/` - Skia graphics - `software/` - CPU-only fallback ### Language APIs (`api/`) - `rs/slint/` - Rust public crate - `rs/macros/` - `slint!` procedural macro - `rs/build/` - Build script support - `cpp/` - C++ API with CMake integration - `node/` - Node.js bindings (Neon) - `python/` - Python bindings (PyO3) - `wasm-interpreter/` - WebAssembly bindings for browser use ### Tools - `tools/lsp/` - Language Server Protocol for editor integration - `tools/compiler/` - CLI compiler - `tools/viewer/` - .slint file viewer with hot reload - `tools/slintpad/` - Web-based Slint editor/playground - `tools/figma_import/` - Import designs from Figma - `tools/tr-extractor/` - Translation string extractor for i18n - `tools/updater/` - Migration tool for Slint version updates ### Editor Support (`editors/`) - `vscode/` - Visual Studio Code extension - `zed/` - Zed editor integration - `kate/` - Kate editor syntax highlighting - `sublime/` - Sublime Text support - `tree-sitter-slint/` - Tree-sitter grammar for syntax highlighting ### Key Patterns - Internal crates (`internal/`) are not semver-stable - they use exact version pinning - FFI modules are gated with `#[cfg(feature = "ffi")]` - C++ headers generated via `cargo xtask cbindgen` - Extensive Cargo features control renderers (`renderer-femtovg`, `renderer-skia`, `renderer-software`) and backends (`backend-winit`, `backend-qt`) ## Version Control (Git) - The default git branch is `master` ## Code Style - Rust: `rustfmt` enforced in CI - C++: `clang-format` enforced in CI - Linear git history preferred (rebase/squash merges) ## Platform Prerequisites ### Linux ```sh # Debian/Ubuntu sudo apt install libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev \ libfontconfig-dev libssl-dev clang libavcodec-dev libavformat-dev \ libavutil-dev libavfilter-dev libavdevice-dev libasound2-dev pkg-config ``` ### macOS ```sh xcode-select --install brew install pkg-config ffmpeg ``` ### Windows - Enable symlinks: `git clone -c core.symlinks=true https://github.com/slint-ui/slint` - Install MSVC Build Tools - FFMPEG via vcpkg or manual installation ## Deep Dive Documentation For tasks requiring deeper architectural understanding, see: - **`docs/development/compiler-internals.md`** - Compiler pipeline, passes, LLR, code generation. Load when working on `internal/compiler/`. - **`docs/development/type-system.md`** - Type definitions, unit types, type conversions, name resolution, type register. Load when working on `langtype.rs`, `lookup.rs`, or type checking. - **`docs/development/property-binding-deep-dive.md`** - Reactive property system, dependency tracking, two-way bindings, PropertyTracker, ChangeTracker. Load when working on `internal/core/properties.rs` or debugging binding issues. - **`docs/development/custom-renderer.md`** - Renderer traits, drawing API, backend integration, testing. Load when working on `internal/renderers/` or fixing drawing bugs. - **`docs/development/animation-internals.md`** - Animation timing, easing curves, performance, debugging. Load when working on `internal/core/animations.rs` or animation-related issues. - **`docs/development/layout-system.md`** - Layout solving, constraints, GridLayout/BoxLayout, compile-time lowering. Load when working on `internal/core/layout.rs` or sizing/positioning bugs. - **`docs/development/item-tree.md`** - Item tree structure, component instantiation, traversal, focus. Load when working on `internal/core/item_tree.rs`, event handling, or runtime component model. - **`docs/development/model-repeater-system.md`** - Model trait, VecModel, adapters (map/filter/sort), Repeater, ListView virtualization. Load when working on `internal/core/model.rs` or data binding in `for` loops. - **`docs/development/input-event-system.md`** - Mouse/touch/keyboard events, event routing, focus management, drag-drop, shortcuts. Load when working on `internal/core/input.rs` or event handling. - **`docs/development/text-layout.md`** - Text shaping, line breaking, paragraph layout, styled text parsing. Load when working on `internal/core/textlayout/` or text rendering. - **`docs/development/window-backend-integration.md`** - WindowAdapter trait, Platform trait, WindowEvent, popup management, backend implementations. Load when working on `internal/core/window.rs` or `internal/backends/`. - **`docs/development/lsp-architecture.md`** - LSP server, code completion, hover, semantic tokens, live preview. Load when working on `tools/lsp/` or IDE tooling. - **`docs/development/ffi-language-bindings.md`** - C++/Node.js/Python bindings, cbindgen, FFI patterns, adding new cross-language APIs. Load when working on `api/` or internal FFI modules. ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project are documented in this file. ## [1.16.0] - Unreleased ### General - SoftwareRenderer: avoid doing dirty-region computation when we need to redraw the whole buffer ### Slint - Fixed empty GridLayout not taking padding into account - Added `keys` type and `@keys(...)` macro - Added printer charachter in the `Key` namespace ### Widgets - CheckBox no longer intercepts the scroll event with the Qt style ### Rust - Added `slint::platform::femtovg_renderer::FemtoVGWGPURenderer` - Added variants for printable keys in the `slint::platform::Key` enum ### C++ - Added contant for printable keys in the `slint::platform::key_codes` namespace ## [1.15.1] - 2026-02-12 - Fixed text rendering eliding when not required or not showing text due to rounding differences. - `GridLayout`: Honor colspan and rowspan in repeated rows. (#10727) - Rust: Fix cross-compilation when the target environment doens't provide a host fontconfig via pkg-config. - Interpreter: Fix two way bindings with properties in a parent scope. (#10704) - Winit: Fixed the "redo" (Ctrl+Shift+Z) shortcut. - Qt backend: Fixed blurry svg rendering with a scale factor. (#10726) - live-preview: Properly close the preview when the LSP exits instead of killing the process. - LSP: Fixed error when the loaded file is deleted on disk. ## [1.15.0] - 2026-02-04 ### General - **breaking change**: Adjust Android implementation of the window safe area to match the iOS implementation. - When resizing the window, try to keep the focus item visible if it's in a `Flickable`. - When focusing an element that's not fully visible, try to scroll a parent `Flickable` so that it is. - winit backend: Do not enable the x11/wayland feature through accessibility feature. - Skia: Fixed colorized tile rendering. (#9860) - partial renderer: Fixed `BorrowMutError` panic if items gets destroyed during rendering. (#9882) - software renderer: Added support for the `Path` element (not supported in `no_std` or freestanding). - Fixed rendering of clipped layers. (#10037) - slint-compiler: Change diagnostics to use the `annotate-snippets` crate. - slint-compiler: Diagnostics now report range instead of just a position. - Translations: allow to opt out of default context. - Fixed debug performance overlay not working. (#10198) - Qt backend: worked around leak in Plasma when setting a window icon. - Fixed flicking animation in ListView. (#7043) - Align text and image rendering to pixel boundaries. ### Slint Language - Added support for two way bindings to struct fields. - Added four new properties to the Window element for exposing the safe area to Slint. - Added the `accessible-id` property. - Added `AccessibleRole.radio-button`. - Added `stroke-line-join` for Path. (#9912) - Added `from ` syntax to `@conic-gradient`. - `GridLayout`: `row`, `col`, `colspan`, and `rowspan` properties can now be changed at runtime. - `GridLayout`: Support for `if` and `for`. - Fixed missing dependency detection on `Image.source-clip`. - Accessing properties within `Menu` from the outside is now a compile error instead of a panic. (#9443) - Added `Colors.oklch()` and `.to-oklch()` functions. - Fixed reactivity of animated bindings. (#348) - Add warning about non-top-level `Window` usage. ### Widgets - `ScrollView`: In fluent style, fixed scroll bars to adjust to each other's visibility. - `Slider`: Implemented `increment`, `decrement` and `set-value` accessibility actions on Slider. (#9975) - `Slider`: Inverted vertical slider direction. - Fixed menu item spacing in fluent style (#10484) - Fixed some widgets that could still be edited when disabled or read-only. - `SpinBox`: added `read-only` property - `TabWidget`: added `orientation` property (#3688) - `TextEdit` and `LineEdit`: Added `font-family` and `font-italic` properties ### Rust - Added `slint::fontique_07` module, guarded with `unstable-fontique-07` feature, to provide access to fontique collection types for registering custom fonts at run-time. - Added `slint::language::ColorScheme`. - In live preview mode, fixed panic when custom models access the component's property. (#10278) - Relaxed bounds on associated functions in `slint::SortModel`. - Don't generate full code for `slint!` macro when ran under rust-analyzer. - Updated to WGPU 28 and drop WGPU 26. ### C++ - Fixed crash when binding is accessing a deleted parent. (#3464) - Fixed mingw-llvm builds. - Fixed build generation failing when compiling multiple .slint files. - Fixed crashes with freestanding builds. (#10077) - It's now possible to access platform native window handles, like a HWND on Windows. ### Python - Fixed support for underscores in async callback decorators. (#10024) - slint-compiler: added support for generating Python stubs. (#4136) ### Tools: - LSP: Fixed column position of non-acii for UTF16-based editor. (#5669) - LSP: Fixed `vscode-remote://` url. - LSP: Added support for `@conic-gradient` completion. (#10444) - LSP: Fixed reloading dependencies when file changes on disk. - LSP: add function argument name when auto-complete function call. (#10560) - tr-extractor: Make the paths argument required. (#10156) - Added gdb pretty printer for `SharedVector` and `Slice`. ## [1.14.1] - 2025-10-23 - Updated xkbcommon and fsdm dependencies - Relicensed Zed editor extension to fullfill Zed's new license requirements - Rust: Fixed incorrect conversion to and from premultiplied ARGB in `Image::to_rgba8` and `Image::to_rgba8_premultiplied` (#9810) - winit: Fixed panic when accessing `Palette.color-scheme` during muda menubar build (#9792) - Fixed docs.rs build by adjusting features metadata ## [1.14.0] - 2025-10-21 ### General - Fixed panic when clicking outside of a menu for a ContextMenuArea that is in a condition - Close active sibling popups before creating a new one (#9178) - Skia/WGPU/DX12: Fixed crash when resizing window (#9320) - Skia: Upgrade to skia-safe 0.88 - Android: Hide Android selection handles when scrolled out of view - Wasm: fix mac-specific shortcut when detecting macOs via browser User-Agent - macOs: Implement Cmd+Backspace to delete to the start of a line in a TextInput - muda: On Windows, force the menu bar to be redrawn after menus are rebuilt (#9435) - use `fontique` and `parley` crate for text layout - Fixed maximum size of empty layout with alignment - partial renderer: Don't mark region dirty if the geometry is dirty but hasn't changed - Close PopupWindow when their parent is destroyed - Fixed scrolling of ListView with varying item heights (#9208) - Display the dirty region when running the software renderer with `SLINT_DEBUG_PERFORMANCE`. ### Slint Language - Added support for rotation and scaling of all elements and their children - GridLayout: allow access to row/col/rowspan/colspan properties from other bindings - Added `Math.sign()` (#9444) - The slint compiler now emits a warning if a statement is without effect (#9474) - Addded `LayoutAlignment.space-evenly` (#9545) ### Widgets - TextInput: don't allow undo/redo when read-only (#9609) - Added Button::icon-size (#9279) - Fixed TimePickerPopup placement logic (#9262) - LineEdit: implemented show-password icon for the Qt style - Slider: Fixed track geometry to account for handle size (#9449) - Menu: fixed menu separator appearence (#8339) - LineEdit: call `edited` callback when the "x" button is pressed - ScrollView: Fixed scrolled callback with Qt style (#9574) - TextEdit: made `has-focus` an `out` property ### Rust - Minimum Supported Rust Version (MSRV) is 1.88 - Slint macro: Use new Rust 1.88 API proc_macro API to be able to access file relative to the .rs file - Fixed error in generated Rust code when convering some expressions to void - Upgraded WGPU dependency to version 27: The `unstable-wgpu-27` Cargo feature exists next to the old `unstable-wgpu-26` feature, alongside the `slint::wgpu_27` module. - Added support for `unstable-wgpu-*` and `BackendSelector`'s `require_wgpu_*` on Android. ### Python - Added support for asyncio by making the Slint event loop act as asyncio event loop. - Added suport for translations via `slint.init_translations()` accepting a `gettext.GNUTranslation`. - Added support for using the `@slint.callback()` decorator with `async` functions, as long as they don't return any value. ### Tools: - SlintPad: add a way to load libraries with `?lib=...` - live-preview: Added a context menu to the library panel to rename or preview components - live-preview: Added search in the properties list - live-preview: Fixed resizing elements not in layout - live-preview: Fixed resetting binding of declared properties - live-preview: Added a way to always see the code of properties - live-preview: Added support for editing `@conical-gradient` in the color picker - formatter: Format `import` statements ## [1.13.1] - 2025-09-11 - Windows: Fixed flickering when updating the menu bar. - LinuxKMS: Fixed build with just renderer-femtovg - LinuxKMS: Fixed GPU based rendering on systems where the driver reported no DRM planes. - Qt: use the cursor flash time from the config - Fixed spurious Slint compiler error when using `ContextMenuArea` in component within a `if` or `for` - C++: fixed the live preview feature missing the `slint_live_preview.h` header (#9335) - FemtoVG: added support for conical gradients (#9334) - FemtoVG: Fixed panic when using rendering notifiers in Wasm with WebGL. - `SwipeGestureHandler`: improved thresholds and destection of move when embedded in another `SwipeGestureHandler` - MCU: fix timer not starting if started before first call to `update_timers_and_animations` - wasm: Fix sizing of the window based on the canvas size or the preferred size - LSP: fix renaming elements id that have a `-` or `_` mismatch. - live-preivew: allow to edit element id - live-preview: search line edit for the library - Slintpad: compress the snippet in the URL ## [1.13.0] - 2025-09-03 ### General - winit: Fixed the maximize window not being disabled for fixed-size windows. - winit: Added support for timer based frame throttling (#8826). - winit: Added support for custom event hooks (`with_winit_custom_application_handler`). - winit: Fall back to software rendering if there are no GPU-backed WGPU adapters (#9164). - LinuxKMS: Added support for overriding the default framebuffer interface selection/ - LinuxKMS: Added support for a padded legacy linux framebuffers. - LinuxKMS: Added support for libinput event hooks (behind `unstable-libinput-09` feature flag) - Skia: Fixed `no-wrap` still wrapping text (#7080) - Skia: Added support for importing WGPU textures, via `unstable-wgpu-26` when Skia is enabled. - Software renderer: Add radial gradient support (#8980) - Software renderer: Fix rendering of the Qt style (#9006) - Windows: Fixed menu bar in fullscreen mode - Windows: Context menus are now using native look and feel. - Fixed gradient rendering bugs in Qt and FemtoVG renderers (#9030, #7909) ### Slint Language - Callback handlers no longer need curly-braces. Extra semi-colon is no longer an error. (#8401) - Added support for local variable with `let` (#2752) - `MenuItem`: Added `icon`, `checkable`, and `checked` properties. - `MenuBar` can now be hidden by placing it in a `if`. - Fixed `MenuSeparator` not always being visible. - `Flickable`: Forward wheel events in a orthogonal direction to their parent. - Added a compiler warning when using `padding` outside of layouts (#6288). - `Timer`: Added `stop()`, `start()`, and `restart()` functions (#8821). - `FocusScope`: Added `focus-on-click` and `focus-on-tab-navigation` properties. - `FocusScope`: Added `capture_key_pressed` and `capture_key_released` callbacks - `Dialog` and `Window` that aren't top-level now draw their background. - Added support for `@conic-gradient` (#9021) - `Path`: Fixed changing `commands` or path sub-elements in a component that inherit from `Path`. - `Path`: Fix settings `commands` from states (#4080) - Added `Key.Back` for the back key on android. - Added an `Easing` namespace to reference easing curve outside of `easing` properties. - `focus()` can now be called on invisible items. - `Window`: Fixed `default-font-size` not propagating into `PopupWindow`. ### Widgets - `LineEdit`: Show a clear icon when not empty. - `LineEdit`: Users can toggle password visibility via an icon when `input-type` is set to `password`. ### Rust - Minimum Supported Rust Version (MSRV) is 1.85 - Upgraded WGPU dependency to version 26: The `unstable-wgpu-26` Cargo feature replaces the old `unstable-wgpu-24` feature, and the `slint::wgpu_26` module replaces the `slint::wgpu_24` module. There were no further changes to the API. - Fixed compilation of generated code if the slint code declares a type named `core`. - Support for live-preview with the `slint/live-preview` feature and `SLINT_LIVE_PREVIEW` env variable - winit: Added API to await for the existence of the winit window - Added `FromIterator` and `Extend` for `SharedString` - Added `SharedVector::reserve()` ### C++ - Added `SharedString::clear()`. - Support for live-preview with the `SLINT_FEATURE_LIVE_PREVIEW` feature and `SLINT_LIVE_PREVIEW` env variable - `SLINT_FEATURE_RENDERER_FEMTOVG_WGPU` is no longer enabled by default - esp-idf: `slint::invoke_from_event_loop` can now be invoked before starting the event loop. - Fixed Windows AArch64 support ### Node.js API - Fixed panic when attempting to convert brushes to colors. ### Python - Added support for automatically mapping exported Slint enums to property Python `enum.Enum` subclasses. - Add support for creating slint.Image objects from arrays (#9014) ### Tooling - lsp: allow to rename functions and callbacks - lsp: show documentation comments in the hoover/tooltip (#9057) - live-preview: Move the preview in a separate process - live-preview: allow dropping in a `ListView` by adding a `for` loop - live-preview: Added an "Outline" panel - live-preview: Fixed highlighted elements not following items - live-preview: Added undo/redo support - slint-compiler: Guess default output format from file extension - SlintPad: replace the web menu bar with the preview menu bar. Move the preview on the left. ## [1.12.1] - 2025-06-25 ### General - Fixed invalid code gen with return statements. (#8723) - Updated `muda` dependency to avoid outdated and insecure `gtk` dependency to appear in the `Cargo.lock` file even if it was not used. (#7800) - Fixed memory leaks and crash in change callbacks (#8768, #8741) - winit: Fixed window constraints (min, max, pref) not being applied sometimes (#8765) - No longer generate unused structs in the generated code to avoid warnings with Rust 1.89 ### Slint Language - Enums without value cause compilation errors (instead of panics) - Fixed runtime and compile errors when accessing the `Platform` global from within other globals. (#8777) ### Node.js API - Added packages for Windows on ARM. ### C++ - Fixed compilation of `PopupWindow::show()` in changed callbacks. (#8710) ### LSP and Tooling - Added binaries for Windows on ARM (VS Code extension, slint-lsp binaries). - Fixed potential crashes due to stack overflows on Windows. - live-preview: Lazily compute palette to speedup the UI. ## [1.12.0] - 2025-06-16 ### General - Added `renderer-femtovg-wgpu` (Rust) / `SLINT_FEATURE_RENDERER_FEMTOVG_WGPU` (CMake) as new rendering option, based on [WGPU](https://wgpu.rs/). - Fixed `Window::default-font` not working in the live preview. - Initial iOS support. - FemtoVG: Fixed extra space of the `\n` char in text rendering (#7970). - Android: Commit the preedit text when focus change (#8668). - Winit: Added support for SVG icons in the window title. - Winit: Fixed blinking window icon on Windows (#7994). - Updated AccessKit. ### Slint Language - Fixed detection of binding loops that apply to the `Window` itself. - Added `Math.exp` and `Math.ln`. - Added `Platform.style-name` and `Platform.os` properties to permit style and OS dependent code. - Fixed changed callback on private global properties (#8269). - Added `ContextMenuArea::enabled`. - Fixed Slint compilation error for comparison of types that can't be compared with less or greater operator. - `Flickable` now keeps in bounds when geometry changes (#2227, #7487). - Added `in-out` transition in states. - Added `focus-gained` and `focus-lost` callback to `FocusScope`. - Added a `FocusReason` argument to the `FocusScope` callbacks. - Fixed `TextInput` to selects its content when focused with the keyboard on Windows and Linux. - Fixed `TextInput` to no longer be focusable if disabled. ### Widgets - Fixed `ScrollView` scrollbar actions not triggering `scrolled` callback (#8170). - `GroupBox`: Added `content-padding` property (#8314). - `TextEdit`/`LineEdit`: Disable context menu action when the widget is disabled or read-only. - `ScrollView`: Added `mouse-drag-pan-enabled` property (#8512). ### Rust - Added `unstable-winit-030` feature along with `slint::winit_030` module in the API to provide access to winit APIs. - Added `unstable-wgpu-24` feature along with `slint::wgpu_24` module to enable Slint <> WGPU interoperatiblity. - Made `Debug` impl of `PlatformError` show the display string. - slint-build: Implement `Clone` for `CompilerConfiguration`. - slint-interpreter: Fixed `From for slint_interpreter::Value` to return a model that supports `set_row_data`. ### C++ - Made generated code more robust when in namespaces regarding forward declaration. - Added a few asserts to ensure the code is run in the right thread. - Don't crash when `Model::row_data` returns `nullopt`. - Rust 1.85 is now required to compile from source. ### Python - Upgraded to Pyo3 0.25. - Added iOS Simulator and Device wheels. ### LSP and Tooling - live preview: Do not apply live data changes after "Reload". - live preview: Added telemetry events. - live preview: support Palette names in color picker. - live-preview: Added a console panel with complation error and `debug(...)` messages. - figma-inspector: Enables the export of design tokens (variables for colors, numbers, strings, and booleans). direct from your Figma files. - figma-inspector: Inspected components now can include design token variable reference for convenient pasting into slint files.. ## [1.11.0] - 2025-04-23 ### General - Fixed compilation failure with mingw by disabling jemalloc. - Fixed non-square radial gradients. (#7899) - Fixed timer starting before property initialization is complete in the interpreter. (#7848) - Fixed ctrl/cmd swap in wasm in macOS browsers. (#7477) - Fixed panic in accesskit when opening popups. (#7854) - Software renderer: fixed drawing background of rotated screen. - Software renderer: Fixed overflow for font bigger than 256px. (#7936) - Skia renderer: Changed default on Windows to use the software renderer, instead of Direct3D. - Skia renderer: Fixed artifacts when using the partial renderer. (#8066) ### Slint Language - Added `float.to-fixed()` and `float.to-precision()`. - Added `string.to-lowercase()` and `string.to-uppercase()`. - Fixed change handler on an aliased property. (#7784, #7747) - Fixed compiler panic when one branch of the `if` statement is not a void expression. (#7864) - Fixed `@children` as sibling to `Timer` or `PopupWindow`. (#7887) - Deprecated alias to global callbacks and make it an error when setting it, instead of a panic. (#7806) - Conditional element no longer re-instantiates if the condition gets dirty without changing. (#3953) - Fixed crash if a component get destroyed when a function or callback of it is running. (#7880) - `Color.hsv()`: The hue value now wraps instead of clamping between 0 and 360. e.g. a hue of `480` would wrap as expected to `120`. Previously any value equal or greater than 360 would cause the function to output pure black. In the unlikely case an app relied on this keep an eye out as a color will now show up instead of black. - Fixed reading from `MenuItem::title` properties. (#8080, #8090) - Fixed inconsistencies when accessing arrays at negative indices. (#8222) - Allow trailing commas in import statements. (#4922) ### Widgets - Fixed horizontal tab stretch with material style. - `ScrollView`: Fixed scroll thumb size on small sizes. (#7809) - `MenuBar` reacts to hover event when the menu is open. (#7822) - Added `MenuSeparator` sub-element in `Menu`. (#7790) - Added `MenuItem::enabled`. - Added `StandardButton::primary`. ### C++ - Allow vendored corrosion by using find_package to search for it. (#7797) - Optimize memory usage avoiding creating dependencies for constant properties. - Fixed overflow when subtracting unsigned in the Slint language. - esp-idf: select `slint::Rgb8Pixel` by default when `CONFIG_BSP_LCD_COLOR_FORMAT_RGB888` is set. - Added support for overriding the translation domain via a CMake target property. - Fixed `Math.mod` with negative numbers. - Fixed segfault when calling `Model::row_changed` right after `Model::reset`. (#8021) - Added `notify_` prefix to the `Model` protected function, and deprecated old names. (#3888) ### LSP and Tooling - live-preview: Show why no live data is present. (#7783) - live-preview: New brush/color input widgets. - live-preview: Focus the right widget when the preview is reloaded. (#4055) - live-preview: Persist the live data when the preview is reloaded. - live-preview: Improve std-widget use detection. (#8086) - live-preview: Added a table to edit model in the data tab. - live-preview: Fixed preview of menu bar on platforms with native menu bar. - viewer: Support gradients in json data. (#7913) ## [1.10.0] - 2025-02-28 ### General - Minimum Supported Rust Version (MSRV) is 1.82. - Android: Fixed build with newer JDK. - Winit backend: Fixed panic when `PopupWindow` is opened while AccessKit is active. - Skia renderer: Added support for partial rendering when using software rendering. - Qt backend: Fixed crash at exit with Qt 5.8. (#7570) - Qt backend: Fixed the Escape key not closing `PopupWindow`. (#7332) - Software renderer: Fixed support for changing the `Window` background. - Software renderer: Added support for pre-rendering glyphs with signed distance fields. ### Slint Language - Elements of a `for` loop now only get re-created if the model is changed, not if it is only dirty. (#7245) - Binding loops involving the condition of a `if` in a layout are now detected. (#7126) - Added `.is-empty` and `.character-count` accessor to the string type. - Added `ContextMenu` and `MenuBar`. - Fixed panic change callbacks used with unused properties. (#7316) - Added `Path::stroke-line-cap` property. (#4676) - Special sub elements such as `Row`, `Tab` or `MenuItem` are now accepted in `@children`. - Accessibility: Added `accessible-expandable`, `accessible-expanded`, and `accessible-readonly` properties and `accessible-action-expand` callback. - Accessibility: Added `AccessibleRole.image` role. - Made `forward-focus` in a `PopupWindow` to focus a widget when a popup is open. (#7529) - Added `TextInput::page-height` to support PageUp and PageDown shortcuts. - Fixed panic when using gradient backgrounds with `Window`. ### Widgets - Fixed `StandardListView` not always getting the focus. - Fixed panic when accessing "negative" items if there are hidden elements in the `ListView`. - Fixed positions of elements in a `ListView` with millions of items. (3700) - `ListView`: adapt the viewport's width based on the minimum-width of delegate. - `Slider`: React to Home and End keys. - `ComboBox`: Improved accessibility. - `ComboBox`: Don't change selected item on mouse wheel, unless it has focus. (#5929) - `StandardTableView`: Added missing properties from `ListView`. (#7543) - `LineEdit`/`TextEdit`: Added a context menu with copy/paste/select all. ### Rust - Upgraded image crate to 0.25, added a new `image-default-formats` cargo feature to enable all image formats. - Ignore pedantic and nursery clippy warnings in generated code. - Fixed edition 2024 warnings in generated code. - Fixed `Sync` and `Send` bounds on `SharedVector`, `SharedString`, and `Weak`. - Removed the requirement that for `VecModel::default()` `T` has to implement `Default`. - Implement `Default` for `BackendSelector`. ### C++ - esp-idf: Added support for RGB8 rendering. - esp-idf: Rename `SlintPlatformConfiguration`'s `color_swap_16` to `byte_swap`. - esp-idf: Deprecated old version of `slint_esp_init` and restored 1.6 behavior with regards to color swapping. - Fixed bundled translation without custom backend. ### LSP and Tooling - live-preview: Fix loading the library path config - live-preview (macOS): Add a Window menu with keep the window on top - live-preview: selection popup no longer contains invisible or clipped items - live-preview: fix editing of translated string. - LSP: no longer suggest private properties in auto-completion - LSP: add ability to rename properties and globals - Simple Figma inspector plugin - Use jemalloc as default allocator for our binaries - VSCode extension: Removed the status bar item for the preview ## [1.9.2] - 2025-01-13 ### General - Improved support for building Slint with Bazel. - Expose `PopupWindow` in the accessibility tree. - Fixed support for older android versions (Android 6). - Fixed `Flickable` not scrolling when starting slow (#7152). - LinuxKMS backend: Fix support for triple-buffering with DRM outputs. - Fixed panic with FemtoVG and Skia renderers for certain drop shadows. - Fixed closing `PopupWindow` when the click opens another `PopupWindow` (#7322). - Fixed `popup.close()` not working in the interpreter (#7318). ### Slint Language - Added `AccessibleRole::tab-panel` and fixed accessibility on `TabWidget` (#7270) - Added `AccessibleRole::groupbox` and fixed accessibility on `GroupBox` - Better error recovery when element name is missing. - Added warning when a type name overwrites another. - Added `Path::anti-alias` property. - Fixed compiler panic with `Row{ @children }`. - Skip UTF-8 Byte order mark (BOM) at the beginning of a file (#7291). ### Widgets - Fixed `StandardButton` not being focusable (#7230). ### C++ - Updated corrosion dependency, fixing build with Rustup 1.28. - Added armhf/arm64 Linux binary packages to release. ### Rust - Added `Window::try_dispatch_event` which is a non-panicking version of `dispatch_event`. ### Tools - Binary packages: Disabled Qt backend. - LSP: Fix ranges in textDocument/documentSymbol. - LSP: Fixed panic in autocompletion of file patch with unicode. - LSP: Added ability to rename struct and enums. - LSP: Fixed error reporting when opening the live-preview fails (#7255). - Viewer: struct are now supported in callback argument (passed as JSON) (#7206). ## [1.9.1] - 2024-12-21 ### General - Skia: Fix Metal and D3D defaults on macOS / Windows. - Winit backend: Fix build with only software renderer enabled. - Fixed build on https://docs.rs/slint - Live-preview: Improvements of the filter in selection popup ## [1.9.0] - 2024-12-18 ### General - Minimum Supported Rust Version (MSRV) is 1.77. - Added functions to set the XDG app ID on Wayland/X11. (#1332) - Added ability to bundle translations in the binary. - Fixed panics in timer handling. (#6187, #6505) - Fixed support for older Android versions (9.0). - Android: handle the Destroy event properly. (#6626) - winit: automatically disabled maximize button when window resizing is disabled. - winit: react to dark/light color scheme changes on Linux (other platforms were already working before). (#4392) ### Slint Language - Callback arguments can now be named. - Animations: Added `direction` property. (#6260) - `TextInput`: Fixed selection colors not begin picked up from the selected style. (#6326) - `TextInput`: Added `key-pressed` and `key-released` callbacks to intercept key events. - Fixed `TextInput` mouse cursor after left click. (#6444) - Improved deselection behavior when pressing left/right in `TextInput`. (#6511) - Fixed `changed` callback on unused properties causing compiler panic. (#6331) - Fixed geometry constraints when they are partially inferred from the content and partially inferred from the explicit constraints. (#6285) - Deprecated two-way binding between `in` and `in-out` properties. (#6400) - `TouchArea`: When `enabled` is set to false while pressed, send cancel event and update `pressed` and `has-hover` properties. (#6422) - `Text`/`TextInput`: Added `font-metrics` property. (#6047) - Added `accessible-item-selectable`, `accessible-item-selected`, `accessible-enabled`, `accessible-item-index`, and `accessible-delegate-focus` properties. - Emit a warning when the case of the import file differs from the case of the file that was imported. (#4265) - Support property changed callbacks in globals. (#6599) - `Window`: Added `full-screen` (#6665) property. - `Window::icon` is now used as the big taskbar icon on Windows. - Fixed `min(..)` and `max(..)` functions with `rem` units. - Emit a warning when a `@linear-gradient` or `@radial-gradient` is assigned directly to a color property. (#6819) - Fixed `min`/`max`/`clamp` functions with percentage arguments. (#7118) - Adjusted thresholds and behavior of the `SwipeGestureHandler`. (#6344, #6542, #6543) - `PopupWindow`: Improved positioning to avoid clipping by the window. - `PopupWindow`: Supported multiple popup windows at the same time. (#4356) - `PopupWindow`: Added `close-policy` property, deprecated `close-on-click`. (#6614) - `PopupWindow`: Close when the escape key is pressed. - Fixed focus handling in `PopupWindow`. - Fixed bugs with global cross-references. (#6984) ### Widgets - `CheckBox`: Fixed text color in Fluent style. (#6239) - `CheckBox`: Removed margin in Fluent and Cupertino styles. (#6639) - `LineEdit`: Fixed cursor drawing out of bounds. (#6243) - `TabWidget`: Fixed tabs overflow behavior. (#6517) - `SpinBox`: added `horizontal-alignment` property. - Undeprecated `StyleMetrics` layout properties (`layout-spacing` / `layout-padding`). - `Slider`: Added `step` property. - `StandardListView`: Improved keyboard navigation. (#6955) - Fixed `init` and `changed` callbacks not always being called in `ListView`. (#6836) ### Rust API - Added `slint_build::compile_with_output_path`. - Fixed `init=>` callback on PopupWindow running twice in Rust-generated code. - Derived serde traits for `PhysicalPosition`, `LogicalPosition`, `PhysicalSize`, and `LogicalSize`. (#6534) - Use `approx_eq` to compare floats in the generated code. - Added `BackendSelector` to select backend, renderer, and renderer specific features. - Added `ToSharedString` trait. (#6845) - Implement `AsRef` and `AsRef` for `SharedString`. ### C++ API - Added `Image::to_rgb8/to_rgba8/to_rgba8_premultiplied` pixel buffer accessors. (#6399) - Added `SharedString::size()`. (#6417) - CMake: Support generator expressions for `SLINT_EMBED_RESOURCES` and `SLINT_SCALE_FACTOR`. - Add `_` to the end of generated identifiers that would otherwise be keywords. (#5613) - Improved float comparisons in the generated code. - esp-idf: Fixed vsync locking. - Added `to_lowercase` and `to_uppercase` to `slint::SharedString`. (#6869) - Added `slint::Window::take_snapshot()`. ### Node.js API - Added support for enums. - Added `initTranslations` function. (#6504) ### LSP and Tooling - LSP: Auto-completion of changed callbacks. - LSP: Preserve `_` when auto-completing element identifiers. (#6479) - LSP: Added image preview in tooltip for `@image-url`. - LSP: Fixed reloading files that import a changed file. - LSP: Implemented signature help. - LSP: Added a code lense to populate empty documents with a hello world. - live-preview: Added new selection popup. - live-preview: Bring the window to the front and focused it when clicking "Show preview" in the editor. (#196) - live-preview: On macOS, renamed "Quit" to "Close" and used cmd+w to close the window. - live-preview: Fixed panic if `run_event_loop` returns an error, showing an error in the editor instead. - live-preview: Fixed panic when dragging elements onto layouts. - live-preview: In the property editor, filter layout properties not applicable to the selected element. - live-preview: In the property editor, visualize negative numbers. - live-preview: Delay updating the preview. - live-preview: Reload when image resources changed on disk. - live-preview: macOS: Added support for reloading via cmd+r. - SlintPad: Updated Monaco editor and other dependencies. ### Renderer - FemtoVG: Fixed artifacts of texture sampling with accidental wrap-around on texture boundaries. - FemtoVG: Fixed rendering of fonts that rely on non-zero winding rule (such as Inter). - Software renderer: Fixed `char-wrap` not breaking between lines. - Software renderer: Fixed artifacts with partial drawing and rotation. - Software renderer: Fixed panic with fractional scale factor. (#6932) - Skia: Fixed opacity not being applied to box shadows correctly. (#6359) ## [1.8.0] - 2024-09-23 ### Slint language - Postfix function on numbers for math function. - `changed ` callbacks. (#112) - `Timer` built-in pseudo-element. (#5724) - `SwipeGestureHandler` element. - Fixed panic when accessing function within a PopupWindow. (#5852) - Fixed `@children` order in the root of a component. (#5865) - Fix conversion from float to string adding extra decimal precision by limiting to f32 - `debug` will now print space between the arguments instead of a comma. (#5991) - Added math function `Math.atan2`. - width and height expressed in `%` unit for an element in a Flickable now refer to the size of the Flickable instead of that of the viewport (#4163) - Path: Fix compiler panic when accessing path.commands in expressions (#5564) - The `mod` function was changed to always return a positive value (#6178) - Added `AccessibleRole.list-item` and used it where relevant - Added `PointerEventButton.back` and `PointerEventButton.forward` (#6043) ### Widgets - Fixed `TextEdit` not invoking `edited` callbacks (#5848). - Added `scrolled` callback to `ListView` and `ScrollView`. - Do not trigger `current-item-changed` on `StandardListView` if `current-item` is set on the same value. - Fixed `TimePickerPopup` does not open minute view by click on selected hour. - Visually clamp the slider even if the value is out of bounds (#5770) - Fixed button of Cupertino `ComboBox` not centered when height is bigger than default. - Fixed text in `SpinBox` not being selected on double click (#6104) ### Rust - Added `clear` and `swap` to `VecModel` - `spawn_local` can now be called before initializing the backend. (It will initialize it) (#5871) - Fixed error in generated code when calling as an expression a callback that don't return a value. (#5883) - Fixed error in generated code with struct containing `percent` value (#5887) - Added `JoinHandle::is_finished()` (#6034) - Implemented `FromIterator` for `VecModel` - Fixed Timer::set_interval() doesn't work in timer callback (#6141) ### C++ - Split the generated code into a header and an implementation file - Added STM32 platform integration - cmake: Add ability to download the Slint compiler binary for the host when cross-compiling using binary packages for the target. - Added Pre-build binary packages for cross compilation to arm and xtensa target, and host package for MacOs - Use the "fluent" style by default on freestanding build instead of the platform default ### LSP and tooling - Fixed vscode web extension - Fixed race condition in preview diagnostic causing previous diagnostic to remain displayed. - live-preview: Use preferred size when requesting preview. - Don't auto-complete `out` property of built-in elements at element scope. - Don't insert a semicolon after auto-complete of a property name at element scope. - Added support for tooltip on hover of symbols - formatter: don't change whitespaces or newlines before comments - live-preview: Fixed auto-refresh after closing and reopening the preview. - live-preview: Custom system menu bar so cmd+q don't quit the LSP. - live-preview: Polished UI - vscode extension: the wasm preview load code in another tab. - vscode extension: Added telemetry to report panics of the LSP server. - vscode extension: Added "Create New Project" command to start from one of our templates - vscode extension: Rename the output tab to from "Slint LSP" to "Slint", and make sure it is always there - slint-viewer: `--save-data`/`--load-data`: support for images with paths. (#6169) - SlintPad: Added "about" entry in the menu. ### Misc - Added ability to configure scale factor at compile time (useful for no_std). - Improved property inlining in the compiler. - Fixed colorized tiled images - Fixed generated getter and setter of alias properties in globals (#5855) - Use `raw-window-metal` to do layer creation in skia - Updated Skia library. (NOTE: requires updated MSVC toolchain) - Skia renderer: Improve rendering quality of layers - GridLayout: Fixed panic when rowspan or colspan is 0 (#6181) ## [1.7.2] - 2024-08-14 ### General - Added linux arm binary in the release. - Skia renderer: Fixed opacity not working on Image. (#5706) - Skia renderer: Fixed `SLINT_DEBUG_PERFORMANCE="overlay"`. (#5764) - Android: workaround `PollEvent::Wake` not being received (#5699) - LinuxKMS backend: Fixed EGL initialization with Mali drivers found on i.MX 95. - Winit backend: don't forward synthetic key pressed events to the application. - Fixed panic when pressing tab in a PopupWindow. (#5826) ### Slint Language - `debug()` is now consistently routed through Platform's `debug_log`, meaning it uses stderr by default instead of stdout previously with C++ and Rust code generator. (#5718) - Comparison between float is now using less precision so to float compare equal if they are approximately the same. - Conversion from float to a `int` property now always truncates (instead of rounding with the interpreter, or being inconsistent with the code generators) ### Widgets - TimePickerPopup: Fixed text input on 24hour time picker. (#5723) ### C++ - Added `assert_main_thread` in a few more functions. ### Rust - Fixed case when the rust generated code panics trying to access a destroyed parent. (part of #3464) ### LSP and tooling - LSP: report error opening the preview back to the editor instead of panicking. (#204) - LSP: don't suggest private properties or functions as bindings. - LSP: implement goto-definition on re-export - LSP: Fix goto-definition on properties with underscores - Design mode: fix dragging component on the last component of a file without trailing newline (#5695) - VSCode extension: add a language icon for .slint file. ## [1.7.1] - 2024-07-25 ### General - Winit backend: When running under WSL, always connect to X11 to avoid triggering compositor crashes. - Winit backend: Fix showing a previously hidden Window on macOS. - Android: Fix build with older Java versions. ### Widgets - `ComboBox`: Fix size of popup when there are less than six items. ### Rust - Fix warning in generated code for unused enums. ### Node API - Improve error reporting by including the diagnostic in the exception's message. ### LSP and Tooling - Various fixes to Live-preview's edit mode: - Recognize `GridLayout` for dropping widgets into. - Remove `PopupWindow`, `Window`, and `Dialog` from the component library. - Add support for component libraries. - Fix panic when moving around widgets. (#5642) - Don't show unrelated accessibility properties. ## [1.7.0] - 2024-07-18 ### General - Several compiler bugfixes. (#5260, #5246, #5220, #5259, #5249, #5430) - Android: fix cursor handle being visible despite input loses focus. (#5233) - Android: fix keyboard popping up when application moved to foreground without input focus. (#5235) - Gettext translation: clear internal gettext cache when changing translations at runtime. - Winit backend: Fixed setting the size with set_size before showing the window. (#6489) - Winit backend: upgraded to winit 0.30, accesskit 0.16, glutin. - Winit backend: Fixed Window::hide on Wayland. - Qt backend: fix PopupWindow exiting the application with recent Qt6. - LinuxKMS backend: Added support for software rendering and legacy framebuffers. - Software renderer: Added `set_rendering_rotation()` to rotate the rendering by a multiple of 90 degrees. - Software renderer: Fixed dirty region returned by `render()` to not have any overlaps. - Skia renderer: Fix quality of control rendering when using `cache-rendering-hint: true;`. - Skia renderer: Fix dithering of gradients (PR #5482) ### Slint Language - `Window`: Added `resize-border-width` property. - Support several exported component that inherits Window pr Dialog from the main .slint file - Mark exported component that doesn't inherit from Window or Dialog as deprecated - Deprecated generating the last import if there is no component in a file - Added `export { ... } from "...";` syntax - Add the `accessible-placeholder-text` property (PR #5464) - Added `char-wrap` variant to the `TextWrap` enum (PR #5381) - `Text` can be rotated - In `PopupWindow` permit access to own properties in bindings to `x`/`y`. ### Widgets - `TextEdit`: Added `placeholder-text` property. - `ComboBox`: Fixed `current-value` not updating when updating the model. - `ComboBox`: Fixed `current-value` not resetting when setting `current-index` to -1. - `ComboBox`: Added scrolling support. - `SpinBox`: Fixed issue where the text is not updated after value is changed from outside. - `SpinBox`: Added `step-size` property. - Added `TimePickerPopup` and `DatePickerPopup`. - Fixed accessible value and actions on `ProgressIndicator`, `Spinner`, `Spinbox`, `CheckBox`, `Switch`. ### C++ API - Added `LIBRARY_PATHS` multi-value argument to `slint_target_sources` that takes a list of `name=path` items, to allow for the use of component libraries. - Fixed compilation with Rust 1.81, caused by extra spaces in `stringify!`. - ESP-IDF: Added support for making RGB565 byte swap configurable. - Fix build with Rust 1.79. ### Rust API - Added missing implementation of the `Error` for some of the errors. - Allow all clippy warnings in generated code. - Added `slint::Image::to_rgb8()/to_rgba8()/to_rgba8_premultiplied()` to obtain pixels for a `slint::Image` if available. - Fix panic in `slint::Timer` when a new timer is started while stopping another. - Added `slint::Window::take_snapshot()`. ### Interpreter - Track model length changes when accessing a model out of bounds. - Added API to obtain list of functions. - Deprecated `slint_interpreter::ComponentCompiler` in favor of `slint_interpreter::Compiler` which supports compiling multiple components. ### Node API - Functions declared in Slint can now be invoked from JavaScript. ### LSP and Tooling - Added suggestion for imported globals in expression auto-completion. - Added ability to rename components. - Design mode: only allow to move element within the same component. - Design mode: Added the property editor pane. - viewer: added the `--component` command line arg to preview a specific component. ## [1.6.0] - 2024-05-13 ### General - The minimum Rust version is now 1.73. - When the Skia renderer is enabled at compile time, it is picked as the default at run-time. - FemtoVG renderer: Fixed selection of italic font styles. (#5056) - Fixed color animation involving transparency. (#5063) - Android: Fixed support for Android 10 to 12 and Java 1.8. - Android: Fixed build if the locale is not UTF-8. - Android: Added cursor handle on TextInput and selection handle with cut/copy/paste menu. - Android: added `backend-android-activity-06` feature. - Software renderer: Dirty regions can now be composed of multiple rectangles. - Added a function to mark all translations as dirty. ### Slint Language - Text: Added `stroke`, `stroke-width`, and `stroke-style` properties. - Added `Colors.hsv()` method to create colors in the HSV color space. - Added `to-hsv()` function to color. - Throw an error when `rgb()` or `argb()` have too many arguments. - Added support for `accessible-action-*`. - Fixed insertion point of `@children` when it is not the last child. (#4935) - Deprecated usage of internal `StyleMetrics` in the public API. - Fixed compiler panic with state property change involving a state in a parent component. (#5038) - Fixed interpreter overwriting property named `index`. (#4961) - Fixed compiler panic when a callback aliases itself. (#4938) - Fixed compiler panic when an init from a repeater is inlined into the parent component (#5146) - Added `clear-focus()` function to focusable elements, to allow for programmatic focus clearing. ### Widgets - Palette: Added `color-scheme` in-out property for accessing the style's color scheme. - Accessibility: Annotated more widgets with accessible properties and actions. - Qt style: Fixed rendering of focused or hovered ComboBox and CheckBox. - Qt style: Fixed widget that would react to every mouse button instead of only the left button. - SpinBox: Fixed scroll direction. - Allow scrolling through tabs. - Updated TabWidget in Cosmic style. - Added a `released` callback to `Slider`. - Fixed text and selection color of TextEdit and LineEdit. - Spinbox and Slider: The value now defaults to the minimum. ### Rust API - Added conversion of Color to and from HSV. - Added getter to the `raw-window-handle` of a window using the `raw-window-handle-06` feature. ### C++ API - Workaround breaking change in the build with Rust 1.79 (see https://github.com/corrosion-rs/corrosion/issues/501) - Added conversion of Color to and from HSV. - Fixed code generation of functions that don't return. - Fixed the `MapModel::reset` function. (#4968) - Fixed compilation of the generated code when an animated brush property is set in a sling callback. - Added include guard to the generated header. ### LSP and tooling - Design mode of the live preview can now drag into and from layout. With a drop marker when dragging an element. - Fixed formatting of function declarations. - Added `-L` command line args to `slint-lsp` to specify path of external libraries (#5144) - VSCode extension highlights Slint code blocks in markdown files. - `slint-viewer` will properly reload files saved with neovim, which renames and replaces files (#3641) ## [1.5.1] - 2024-03-20 - Fix clipping with a border-radius. (#4854) - Fix panic in the preview when showing a PopupWindow whose parent is optimized out. (#4884) - Fix compiler panic when the `focus` function is called with arguments. (#4883) - Fix panic when loading unsupported images. - LSP: Fixed formatting of states, transitions, and functions. - LSP preview: Avoid double scroll bar in the preview when showing errors. - LSP preview: Don't handle delete shortcut when showing errors. - LSP preview: Improved appearance of the element selection in the design mode. - LSP preview: Never set the with or height to NaN. (#4848) ## [1.5.0] - 2024-03-14 ## General - Added support for Android via the `backend-android-activity-05` feature. - Added API for maximized/minimized window. - TextInput: Added undo/redo support. - ListView: Fixed redraw when model changes. (#4538) - Disabled Qt backend by default on Windows and Mac even when Qt is found. - Qt: Explicitly hide PopupWindow instead of relying on destructor. ### Slint Language - Rectangle: Added `border-{top,bottom}-{left,right}-radius` - Image: Added `ImageFit.preserve` - Image: Added `horizontal-` and `vertical-alignment` - Image: Added support for 9 slice scaling - Image: Added `horizontal-` and `vertical-tiling` - Flickable: Added `flicked` callback - Slint: Expose `.red`, `.green`, `.blue`, and `.alpha` properties on `color` ### Widgets - Fixed edited callback of SpinBox for Cupertino and Material style. - Cupertino TabWidget: Tweaked visual appearance ### Rust - Fixed ReverseModel and FilterModel model not always forwarding notification correctly. - Re-export more type in the slint-interpreter crate. - Added `SharedVector::pop`. - Use const generics for construction of SharedVector from array. ### C++ - Fixed binary package that used to require Qt. - Added `Window::set_fullscreen`. - Fixed error in generated code when struct or enum has an underscore. (#4659) - Added `slint::interpreter::ComponentCompiler::set_translation_domain`. - Added `NAMESPACE` modifier in the `slint_target_sources` cmake macro to generate in a namespace. ### JavaScript - Fixed MapModel rowData() calling map function even if the source model returned undefined. - Better error reporting when the backend cannot be created. - Reading model properties now always returns a `Model`, regardless of whether an array was previously assigned. - `Model` now implements `Iterable`. ### LSP - Added support for code formatting. - Sort properties first in auto-completion. - Fixed completion in two way bindings - Preview: Design mode with drag and drop - Fixed wasm embedded preview on Windows ## [1.4.1] - 2024-02-02 - Skia: Update skia binding dependency. - SlintPad: Fixed initial rendering of the live-preview. - Qt backend: fix crash when closing popup on Wayland. (#4500) - Fixed rendering of linear gradient of 90deg. (#4495) - C++: Permit passing a value to VectorModel::set_vector by value (#4491) - slint-viewer: re-render after reloading when using software renderer. - Fixed panic in the software renderer when using the Qt style. - Rust: fix slint-build's formatter when source contains `\"` in a string. (#4520) ## [1.4.0] - 2024-01-31 ### General - Winit backend: Fixed `key-released` in `FocusScope` not being invoked when releasing the space bar key. - Fix `PopupWindow` close behavior: Close on release when the mouse is on the popup, and close on press when it's outside - to match standard behavior. - Fixed focus behavior on click in a TextInput - Fixed ListView not updating when model changes (#3125). - Fixed TextInput on Plasma/Wayland receiving many empty events causing selection to be cleared (#4148) - Added API to programmatically show a window in fullscreen mode (C++/Rust: `Window::set_fullscreen(bool)`, Node.js: `window.fullscreen`). - Added API to keep the event loop alive when the last window is closed (#1499). (Rust: `slint::run_event_loop_until_quit()`; C++: argument to `slint::run_event_loop()`; Node: argument to `runEventLoop`). - Fixed linear gradient rendering in non square rectangles (#3730). - LinuxKMS backend: Added support rendering output rotation via the `SLINT_KMS_ROTATION` environment variable. - LinuxKMS backend: Added support for `backend-linuxkms-noseat` feature to compile without libseat. - LinuxKMS backend: Added support for software rendering with Skia. - LinuxKMS backend: Added frame throttling. ### Slint Language - `if` statements no longer require parentheses. - Added a `double-clicked` callback in `TouchArea`, which is triggered when a `TouchArea` is clicked twice in rapid succession. - The `pointer-event` callback in `TouchArea` is now triggered on mouse move as well. - Errors are thrown when trying to modify properties that must be known at compile time. - Fixed property wrongly considered as const if it is modified through an alias (#4241). - Fixed missing invocation of init callbacks due to inlining (#4317). - Added `Key.Space`` to `Key` namespace. ### Widgets - Fixed SpinBox not being enabled by default. - Fixed wrong text input in cupertino SpinBox. - Added focus state to `StandardListView`. - Added `colorize-icon` property to `Button`. - Added `set-selection-offsets(int, int)` to `TextInput`, `LineEdit`, and `TextEdit`. - Added `Palette` global singleton. - Added `Cosmic` style. - Improved `Slider` drag and click behaviour. ### C++ - Added `ComponentInstance::definition()` getter to retrieve the `ComponentDefinition` for an instance. - Added `slint::VectorModel::clear()` and `slint::VectorModel::set_vector()` to conveniently clear or replace the underlying data. ### Rust - Compile-time improvements. - Fixed compilation when component has the same name as internal name (#4419). ### JavaScript - Pre-built binaries in the npm package. ### LSP - Added selection mode to select elements in the preview. - Implement code action to add missing import. - Fix error when going to the definition of built-in items (#4126). - Preserve underscores in property auto-completion. ## [1.3.2] - 2023-12-01 ### General - Fixed `accepted` and `edited` callbacks in `LineEdit` not being invoked with Fluent, Cupertino, and Material styles. - Fixed coordinate of events within PopupWindow. (#4036) - Fixed ComboBox not selecting entries. (#4033) - Fixed singleshot timers started via `start(...)` to not stay in running state. - Fluent style: Fixed color of disabled `LineEdit`. ### Slint Language - Added `KeyEvent.repeat` to detect repeated key press events. ### LSP - Added support for resizing the UI under preview without resizing the window, by providing resize handles and scrollbars. - Close previous PopupWindow before refreshing the preview. (#4035) ### C++ - Fixed compilation on Windows when cross-compiling (for example when using esp-idf). ## [1.3.1] - 2023-11-28 ### General - Bump various dependencies. - Fixed `has-hover` and `mouse-cursor` when opening a `PopupWindow`. (#3934) - Fluent style: fixed scrollbar size. (#3939 / #3932) - Skia Vulkan renderer: Fixed crash when resizing windows on X11. - Fixed cursor of LineEdit with right alignment (#4016) ### Slint Language - Added `clamp` function that takes a `value`, `minimum` and `maximum` and will return `maximum` if `value > maximum`, `minimum` if `value < minimum` or `value` otherwise. - Throw Slint error when returning no value when one is expected instead of generating invalid code. (#3962) - Fixed compiler panic when a component is called `Window`. (#3916) ### Rust API - Implement `std::error::Error` for `LoadImageError`. ### JavaScript API - Added `loadSource` function (#3971) - Added `requestRedraw` to Window (#3940) ### C++ API - Fixed undefined behavior in `SharedString::end()` ### LSP - Fix "recursion detected" panic in the preview with `forward-focus`. (#3950) - Don't expose empty name in the outline, this caused error in vscode. (#3979) - Fix enum ranges in the outline. - Added `--fullscreen` command line option. ## [1.3.0] - 2023-11-10 ### General - The minimum Rust version is now 1.70. - The `SLINT_DEBUG_PERFORMANCE` environment variable is now also compatible with the software renderer. - Fixed issues with text rendering and cursor positioning in elided or aligned multi-line text. - The default style has been changed to `fluent` on Windows and `cupertino` on macOS. - LinuxKMS backend: Added support for absolute motion pointer events, fixed support for touch input on scaled screens, and improved encoder/CRTC handling for EGL rendering. - Skia renderer / winit backend: Fall back to Skia software rendering when GPU acceleration is not available. - Fixed a bug where accessing model data in a callback after changing it within the same callback did not reflect the update. (#3740) ### Slint Language - Added `Number`, `Decimal` variants to the `InputType` enum. - Added `spacing-horizontal` and `spacing-vertical` to `GridLayout`. - Fixed conversion in an array of an array of structs (#3574). - Added `scroll-event` callback to `TouchArea`. - Added support for `protected` functions. - `ComboBox` selection can now be changed by a scroll event. - `SpinBox` value can now be incremented and decremented by a scroll event. - Added `focus-changed-event` callback to `FocusScope`. - Added many new easing curves. - Added `Spinner`. - Added `Palette` global. ### JavaScript - The system has been ported to napi-rs. - The API has been refreshed. ### Rust - Improved support for the `slint!` macro for rust-analyzer. - Added `source_model()` to `MapModel`, `FilterModel`, `SortModel`, `ReverseModel` to access the inner model. ### C++ - Removed the need for C++ exceptions in generated code. - Added the ability to only build the Slint compiler or use an external compiler. - ESP-IDF: Wait for vsync before swapping frame buffers. - Fixed a crash when accessing an empty model from Slint. ### LSP - Added "Wrap in element", "Remove element", "Repeat element", and "Make conditional" code actions. - Added a toolbar with a style picker in the preview. ## [1.2.2] - 2023-10-02 ### General - Skia renderer: Fixed the `source` property of `Image` elements sometimes not changing when setting dynamically loaded images. (#3510) - Fix compiler panic with `popup.close()` from outside of the popup. (#3513) - Fixed native style (Qt) not finishing its animations with Breeze. (#3482) - Fixed native style not clipping correctly GroupBox. (#3541) - Fixed native style ComboBox not always being shown. (#3527) - Winit backend: Fixed window resizing on macOS Sonoma. (#3559) - Skia / FemtoVG renderers: Default to vsync swap interval when rendering with OpenGL to avoid excessive CPU usage. (#3516) ### C++ - Fixed cross-compilation with CMake (against Yocto SDKs) ## [1.2.1] - 2023-09-19 ### General - Fixed generated C++ and Rust code in conversion from unnamed to named struct in complex expressions. (#2765) - Improved wasm preview in the documentation, especially on mobile. (#3389) - Improved `StandardTableView` to use `ListView` optimization for all styles. (#3425) - Fixed in WSL against a distro that uses X11 instead of Wayland. (#3406) - Added documentation about different renderers and their relation to backends. - Added support for partial rendering to the software renderer when used with the winit backend. (#3457) - Fixed builds of the Skia renderer on Windows when built from source within `vcvars.bat` environments. - Updated to Skia milestone 117. - Fixed panic when using `SLINT_FULLSCREEN=1` in combination with the Skia OpenGL renderer. (#3472) - Native style: Fixed scroll bar handle size for `ScrollView`. (#3489) - FemtoVG renderer: Fixed empty lines and vertical alignment when eliding text. (#3481) - Skia renderer: Fixed eliding of multi-line text. (#3481) - `StandardTableView`: Fixed scrollbar only responding to the second click. - Polished the experimental Cupertino style further. ### Rust API - Derive serde traits for `slint::Color`, when the feature is active. (#3411) ### C++ - Fixed crash when code in a `clicked` handler in a sub-component would cause a conditional or repeated element to disappear. (#3465) - Fixed Skia renderer builds finding clang correctly. ### VS Code Extension - Fixed "Show Preview" command when invoked from the command palette. (#3412) ### Language Server - Fixed automatic indentation within named elements. (#3409) - Fixed panic when editing path in import statements. (#3468) ## [1.2.0] - 2023-09-04 ### General - Fixed accessibility tree on Linux when UI has no repeaters. - Fixed native style animations. - Fixed setting rotation-angle and opacity from a callback. - Fixed touch in the `Flickable` not resulting in a click. - Added support for a new experimental backend that renders fullscreen on Linux using KMS (`backend-linuxkms`). - Calling `show()` on a component (or its window) now keeps the component alive for as long as the window is visible. ### Slint Language - Improve reporting of diagnostics when there are errors, by attempting to run more passes. - Fixed compiler panic when an unresolved alias has a binding. - Added `edited` callback to `SpinBox`. - Added `row-pointer-event` callback to `StandardTableView`. - Fixed enabled property with `ComboBox` in Fluent Design style. - Fixed duplicated import when importing file relative to the project instead of the current path. Deprecated importing files relative to the project path. - Added `current-item-changed` to `StandardListView`. - Added `current-row-changed` to `StandardTableView`. - Added `item-pointer-event` to `StandardListView`. - Added `orientation` property to `Slider`. - Added experimental `cupertino` style. ### Rust API - Implemented `Default` for `slint::Weak`. - Added `ReverseModel` and `ModelExt::reverse`. - Added `fn set_visible(&self, _visible: bool)` to the `slint::platform::WindowAdapter` trait. - Added ways to create a `SoftwareRenderer` without a `MinimalSoftwareWindow`. - The features `renderer-winit-*` were renamed to `renderer-*`. - Added `BorrowedOpenGLTextureBuilder` to configure more aspects of borrowed OpenGL textures. ### C++ - Added Platform API to write your own platform that drives its own event loop. - Added `SLINT_LIBRARY_CARGO_FLAGS` cmake variable. - Added `ReverseModel`. - Added functions in Window to dispatch pointer events. - The `slint_interpreter.h` file was renamed `slint-interpreter.h`, a deprecated header was added. - The features `SLINT_FEATURE_RENDERER_WINIT_*` were renamed to `SLINT_FEATURE_RENDERER_*`. - Extended `slint::Image::create_from_borrowed_gl_2d_rgba_texture` with an option to configure more aspects of texture rendering. - Fixed cmake dependencies of the generated header so it is generated if and only if the .slint files have changed ### LSP - Fixed termination of the lsp process. ## [1.1.1] - 2023-07-10 ### General - Fixed panic in accesskit at startup on Linux. (#3055) - Fixed compiler panics when some complex expressions are used for the model expression in `for` (#2977) - Native style: Fixed support for floating point ranges in Slider. - Fixed panics in the software renderer related to text rendering. ### Slint Language - Added `clear-selection()` to `TextInput`, `LineEdit`, and `TextEdit`. - The `PointerEvent` struct now has the `modifiers: KeyboardModifiers` field. ### C++ - Added `slint::Window::scale_factor()` as getter to read the system device pixel ratio. ### LSP - Correctly use the CARGO_MANIFEST_DIR as the base for import and image in `slint!` macro ## [1.1.0] - 2023-06-26 ### General - Fixed missing items compilation error in the generated code related to public functions (#2655). - Added support for Window transparency on supported platforms. - Fixed TabWidget not filling the parent in non-native style. - Skia: Add support for rendering with Vulkan. - Wasm: Added copy and paste support. - Fixed TouchArea::has-hover not being reset in some cases involving multiple `TouchArea` or `Flickable` elements. - Fixed ListView panic when model reset in some cases. (#2780) - Fixed read-only `TextInput` reporting input method access. (#2812) - Fixed `LineEdit`'s placeholder text not being rendered with the same font attributes as regular `LineEdit` text. - Fixed rendering of SVGs with text. (#2646) - Software renderer: Show the cursor in TextInput ### Slint Language - Added support for declaring enumerations. - Added support negative numbers in `cubic-bezier(...)` function. - Added `ProgressIndicator` widget. - Added `Switch` widget. - Added boolean `font-italic` property to `Text` and `TextInput`. - Added `select-all()`, `cut()`, `copy()`, and `paste()` to `TextInput`, `LineEdit`, and `TextEdit`. - Added functions on color: `transparentize`, `mix`, and `with-alpha`. - Added a `close()` function and a `close-on-click` boolean property to `PopupWindow`. - Added basic translation infrastructure with `@tr("...")`. - Added `absolute-position` property to every element, for computing window-absolute positions. - Added `primary` boolean property to `Button` to configure its visual appearance. - Added `current-row` to `StandardTableView`. ### Rust - Added `slint::Image::load_from_svg_data(buffer: &[u8])` to load SVGs from memory. - Added `slint::platform::WindowEvent::ScaleFactorChanged` to allow backends to report the current window scale factor. - Added `slint::Image::from_borrowed_gl_2d_rgba_texture` to create images from borrowed OpenGL textures. - In the Slint language, struct can be annotated with `@rust-attr(...)` that is forwarded as a Rust attribute (`#[...]`) for the generated struct. - Added a `serde` feature to enable serialization of some Slint data structures. - Added convenience `From` conversions for `ModelRc` from slices and arrays. - `slint-viewer` gained the ability to preview .rs files with a `slint!` macro. - Added a `spawn_local` function to run async code in the Slint event loop. ### C++ - Added `slint::Image::create_from_borrowed_gl_2d_rgba_texture` to create images from borrowed OpenGL textures. - Added `[[nodiscard]]` in a function signatures. - Experimental: the `slint::platform::WindowAdapter` no longer takes a template parameter and has a different constructor signature. ### LSP - Fixed auto-completion of local properties or arguments in callbacks or functions. - Fixed panic when the editor tries to open non-local urls. ### VSCode extension - Make a visible error message when the `slint-lsp` panics. ## [1.0.2] - 2023-04-26 ### General - Fixed the compiler embedding images unnecessarily multiple times when referenced via different relative paths. (#2608) - Winit backend: Adjust the window size automatically when an update minimum or maximum size would constrain the existing size. - Winit backend: Added internal API in the `i-slint-backend-winit` crate to access the winit window - Fix focusing element whose base is focusable (#2622) - Fix infinite loop in the compiler when there is a loop in `forward-focus` - Skia renderer: Add support for password input fields. ### C++ - Fix build against macOS deployment target 10.10 ### VSCode extension - Fixed preview not working (#2609) - Added design mode commands - Browser extension: fix preview not previewing specific component ### SlintPad - Fix editing in non-main files was getting ignored (#2630) ## [1.0.1] - 2023-04-20 ### General - Fixed compiler panic when binding `Path`'s `commands` property to the field of a model entry. - Qt renderer: Fixed support for horizontal alignment in `TextInput`. - Winit backend: Fix detect of dark color scheme in some circumstances. - ListView: fix resizing a ListView to empty height would make all items invisible even if resized back (#2545) - Fixed compiler panic when accessing unset layout properties such as `spacing` or `alignment` (#2483) - Fixed compiler panic when accessing property from parent context in a `init =>` callback - Load fontconfig with dlopen instead of dynamic linking. - Software renderer: renders the text in TextInput - Fixed `TouchArea::has-hover` not becoming false when items become invisible ### Slint Language - Fixed parent `FocusScope` objects stealing the focus from inner `FocusScope`s when clicked. - Added `TextInputInterface.text-input-focused` to detect when a virtual keyboard should open - Added `always-on-top` property of a `Window` to show the window above others - The error message when referring to an id reports a suggestion if there is a enum value or a property with the same name. ### C++ - macOS: Fixed `install_name` for `libslint_cpp.dylib` use `@rpath` instead of absolute paths to the build directory. - Fixed memory corruption when embedding images in generated C++ code. - Add support for dispatching key press and key release events to `slint::Window` via `dispatch_key_*_event`. This replaces the experimental `slint::experimental::platform::WindowAdapter::dispatch_key_event`. - MSVC: /bigobj is enabled by default as compile option when linking against the Slint CMake target, removing the need for users who run into linking issues to add that to their build. ### LSP - Don't throw a protocol error when using the rename function on a symbol that can't be renamed - Always auto-complete widgets from the style even if no widgets is imported - Don't auto-complete reserved properties or sub components for globals - Auto-completion in the document root (component, import, global) ### VSCode extension - Added an option to show the preview with wasm in a vscode tab (just like in the online version) - Make code lenses work better on Windows ### SlintPad - Warn when ServiceWorker could not get set up (e.g. in private browsing mode on Firefox) - Add design mode ## [1.0.0] - 2023-04-03 ### General - `TextEdit` performs better with the FemtoVG renderer when showing many lines. - Software renderer: Added support for linear-gradients. - Software renderer: Fixed artifacts when components are deleted or elements become invisible. - Fixed Infinite loop in the accessibility backend on Windows (#2195). - Skia renderer: Enable anti-aliasing for rectangles with border radius. - Fluent style: Adjust disabled scrollbar background color. - Fixed several panics in the compiler (#2312, #2274, #2319). - Winit backend: Fix rendering when moving windows between monitors with a different scale factor (#2282). ### Slint Language - The old syntax that declares component with `:=` is now deprecated - `Flickable`: don't forward events if the flickable is dragged in a direction that can be dragged, even if at the bounds. - The `TextEdit` and `LineEdit` elements now correctly handle double click and triple click to select words or lines, as well as support for the "primary" clipboard on X11 and wayland (select to copy, and middle click to paste). ### Rust - Minimum Rust version is now 1.66. - **Breaking:** Deprecated functions and enums were removed from the API. - **Breaking:** `PointerEventButton::None` was renamed `PointerEventButton::Other` - **Breaking:** More functions now return `Result`, and the return value needs to be `unwrap()`'ed. - **Breaking:** A lifetime parameter was added to `slint::ModelPeer`. - **Breaking:** `StandardListViewItem` and `TableColumn` are now marked as `#[non_exhaustive]`. - **Breaking:** Removed the `stride()` function from `slint::Image` - use `width()` instead. - **Breaking:** In `slint::platform::WindowEvent::KeyPressed` and `KeyReleased`, the `text` field is now a `slint::SharedString`. - **Breaking:** `slint::platform::WindowEvent` does not derive from `Copy` anymore. You must `clone()` it explicitly if you want to create a copy. - **Breaking:** The `MAX_BUFFER_AGE` const parameter of `slint::platform::software_renderer::MinimalSoftwareWindow` has been removed and replaced by an argument to the `new()` function. - **Breaking:** The `compat-0-3-0` mandatory cargo feature flag was renamed to `compat-1-0`. - Added a `software-renderer-systemfonts` feature to the Rust crate, to enable the use of fonts from the operating system for text rendering with the software renderer. - Fixed some clippy warnings in the generated Rust code. ### C++ - Functions that take a functor as an argument are now using C++ 20 concepts. - **Breaking:** In the C++ API, the type for row indexes in models was changed from `int` to `size_t`. This includes arguments of virtual functions in `slint::Model` that needs to be adjusted in derived classes. - **Breaking:** The order of C++ generated struct members is now preserved from the .slint source file. - Add constructors to `slint::Image` to create images from raw pixel data. - In C++ builds, support all image formats the Rust image-rs crate supports, not just png and jpeg. - Added the `SLINT_FEATURE_RENDERER_WINIT_SOFTWARE` option to enable the software renderer. ### Tooling - LSP: don't add spaces when auto-completing elements or callbacks, leads to better formatting. - The online editor was renamed to SlintPad. ## [0.3.5] - 2023-02-21 ### Fixed - FemtoVG: Fix cursor placement at end of lines - Windows: Fix panic when minimizing windows - Fixed some panics in the software renderer when using text - Fixed panic when parsing invalid linear gradient syntax - Qt: Fixed mapping of the control key - Linux: Fix panic when using GLX - Fixed const detection when using two-way bindings - Fix run-time panic when combining forward-focus with text rendering in no_std environments - Rust: Fix compile issues when generated code clashes with user types - C++: Fix compilation with structs that have a field with the same name as the struct - Fix visible property on the root of a component - Fix compiler panic when an component's base is not inlined - C++: fix including the generated file in several translation units - C++: Fix "make install" on Windows ### Added - The StandardListView provides now a set-current-item function to set the selected item and bring it into view. ## [0.3.4] - 2023-01-25 ### Changed - A new syntax is available that declares the components with `component Xxx { ... }` instead of `Xxx := ...`. The old syntax continues to work in this release. - A warning is shown when calling non-pure function or callback from pure context (eg: a property binding). Callbacks and functions can be annotated with `pure`. - On an `Image`, the default value of `source-clip-width` and `source-clip-height` is now set to the size of the image minus the `source-clip-{x,y}`. The source clip size is now used to compute the default aspect ratio of the image. - Deprecated `invoke_callback` functions in the slint interpreter in favor of `invoke`, which can also invoke functions in addition to callbacks. - Warn if the last component or singleton in a file is implicitly marked for exporting. ### Added - `StandardTableView` widget. - LSP: support of Slint features (auto-complete, preview, ...) in `slint!{}` macro in Rust files. - The software renderer can now load fonts at run-time, without the need to pre-render glyphs at compile time. - The preview in the online editor, VS Code extension, and the VS Code web extension highlight components while the cursor is on the component type name. ### Fixed - Compiler panic for files containing no component (#2005). - Calling public functions from native code. - Fixed crash when using repeaters in C++ on 32-bit architectures. - Conversion of array literal containing struct with array literal (#2023). - Structs exported by the main file are always generated in native code (#594). ## [0.3.3] - 2022-12-16 ### Changed - Minimum rust version is now 1.64. ### Added - Added support for declaring functions in the language. - Added support for re-exporting types from other files using `export * from "./other_file.slint";`. - LSP: highlight and renaming of element id. - online editor: Add zoom to the preview. - VSCode and online editor: Added UI to edit and remove property bindings. ### Fixed - Fixed privacy rules in global properties. #1951 - Fixed drawing of images with the software renderer. - Fixed `visible` property on element with drop shadow. #1460 - Warn on bad use of ListView. #860 - Fixed two way bindings between globals. - Fixed scrolling of preview area in online editor. - Fixed the CMake build to respect `CMAKE_*_OUTPUT_DIRECTORY` variables. #1979 - Fixed build when using `renderer-winit-skia-opengl` on macOS or Windows. ## [0.3.2] - 2022-11-28 ### Changed - When using a two way binding `foo <=> bar`, the default value will always be the one of `bar`. There was a warning about this change in previous versions (#1394). - Disallow overrides or duplicated declarations of callbacks. Previously they were silently overwritten, now an error is produced. - The name `init` is now a reserved name in callbacks and properties. - In the interpreter, calling `set_property` or `get_property` on properties of the base returns an error. - Deprecated `slint::WindowEvent` and `slint::PointerEventButton` and moved them to the `slint::platform` module. - Renamed the `Keys` namespace for use in `key-pressed`/`key-released` callbacks to `Key`. The old name continues to work. - The style or the backend now always set a value for the `Window`'s `default-font-size` property. - In the Rust API, the `GraphicsAPI`'s `NativeOpenGL` variant uses a function signature for `get_proc_address` that takes a `&std::ffi::CStr` instead of a `&str` to match the native APIs and avoid unnecessary conversions. ### Added - Added new `material` style based on Material Design, with `material-light` and `material-dark` as variants. - Added `Window::is_visible` in Rust and C++. - Added `From` for `SharedString` in Rust. - Added `KeyPressed` and `KeyReleased` variants to `slint::WindowEvent` in Rust, along with `slint::platform::Key`, for use by custom platform backends. - Added support for the implicitly declared `init` callback that can be used to run code when an element or component is instantiated. - Properties can be annotated with `in`, `out`, `in-out`, or `private`. - Transitions can now be declared directly within the state. - Online editor: The property view can now edit properties. - LSP preview: When the cursor is on the name of an element, the element's location is highlighted in the preview. - LSP: Added a setting to change the style and the include paths. - VSCode extension: added the property view. - Added `rem` as unit that represents a relative font size and is multiplied with the `Window.default-font-size` when used. - Added `relative-font-size` as type that holds values of `rem`. ### Fixed - Fluent style: SpinBox gets focus when buttons are clicked. - Software renderer: Fix drawing the background color. - Fix Rust code generation when comparing percentages (#1837). - Fix `visible` property in `for` and `if` (#1846). ## [0.3.1] - 2022-10-28 ### Changed - The property `Window::background` is now a brush instead of a color (allowing gradients). - Switch to yeslogic-fontconfig-sys from servo-fontconfig dependency. This allows for fontconfig to be a run-time dependency via dlopen. - Skia renderer: Improvements to text input. ### Added - Added `slint::FilterModel`, `slint::MapModel` to the C++ API. - Added `slint::SortModel` to Rust and C++ API. - Added `VecModel::extend` and `VecModel::extend_from_slice`. - Online editor: Added "Properties" and "Outline" tabs. - Added initial support for input methods with pre-edit text. - Added a dark theme for the Fluent style, which is automatically selected if the system color scheme is dark. - Added `fluent-light` and `fluent-dark` as explicit styles to select a light/dark variant, regardless of the system color scheme setting. ### Fixed - TextInput now shows the text mouse cursor. - In Flickable, added a small delay before passing the Press pointer event to the children. - Online editor: Fixed "go to definition" across files. - Fixed a panic in the slint compiler when visiting layout properties for loop analysis (#1659). - Fixed compilation error in the generated code (#1733, #1735). ## [0.3.0] - 2022-09-14 ### Breaking Changes - `mod` now works on any numeric type, not only integers. - Minimum rust version is now 1.60. - The "backend-gl-*" Rust crate features for configuring the GL renderer have been changed and split by renderer. - `VecModel::remove` now returns the removed element. - `slint::invoke_from_event_loop` and `slint::quit_event_loop` now return a Result. ### Added - Added the `platform` module providing API to use slint on bare metal with a software renderer. - Added an experimental Skia renderer. - `Button`: Add a `checkable` property that turns the button into a toggle button. Use the new `checked` property to query whether the toggle button is pressed down or not. - Added support for `slint::Window::set_position` and `slint::Window::position` to set and get the placement of the window on the screen. - Added `slint::Window::scale_factor()` as getter to read the system device pixel ratio. - Added support for `slint::Window::set_size` and `slint::Window::size` to set and get the size of the window on the screen. - Added `slint::Window::dispatch_event` and `slint::WindowEvent` to be able to manually send a mouse or touch event to a window. - Added `animation-tick()`. - `SharedString` implements `std::fmt::Write` and added `slint::format!`. - `Image` can now be rotated with the `rotation-*` properties. - Use docking widgets and integration of slint-lsp into the [Online Code Editor](https://slint.dev/editor). ### Fixed - Fixed Ctrl+Backspace/Ctrl+Del not deleting words in text input elements. - Resizing of live-preview window in the IDE integrations. - Preferred size of the TabWidget in the fluent style take in account the size of the tabs (#1363). - Fixed cursor behavior when typing the Enter key at the end of a TextEdit (#1318). - Fixed a memory leak of images when using the GL backend. - Fixed starting and stopping `slint::Timer` from within their callback (#1532). ## [0.2.5] - 2022-07-06 ### Changed - Interpreter: Implement `TryFrom` instead of `TryInto for Value` (#1258) ### Added - Added the Model Adapters `FilterModel` and `MapModel`. - Added `@radial-gradient(circle, ...)` - Added `read-only` property to `TextInput`, `TextEdit` and `LineEdit`. - VSCode extension can be installed as a web extension. (eg, from https://vscode.dev) - LSP: completion of `@` macros - LSP: completion of element that require an import - Basic accessibility support using the `accessible-` properties ### Fixed - GL backend: Fixed animation sometimes not starting from input event (#1255) - C++ fix compilation when writing to the model data - Fix mouse exit events not triggered by scrolling a Flickable (#1107) ## [0.2.4] - 2022-05-09 - Fixed crash when opening a native (Qt) ComboBox ## [0.2.3] - 2022-05-09 ### Fixed - Fixed crashes with the Qt backend in release mode. (#1230) - Fixed panic when drop-shadow is used in a ListView (#1233) - Fixed combining a brush and a color to always convert to brush, to avoid losing gradient information (#1235) - Fixed properties not having the right default value when set by some states (#1237) - Fixed properties with multiples aliases, and default values. - Enable fontdb's fontconfig feature to fix finding some fonts (#1240) ## [0.2.2] - 2022-05-04 ### Changed - On wasm, the input event are handled via a hidden `` element, allowing the keyboard to show on mobile platform - The size of the window is kept when reloading a window in the preview (instead of being reset to the preferred size) - Minimum rust version is now 1.59 ### Added - Support for keyboard focus with the tab key - Support more keyboard shortcut in the editing element - Added `From<&str>` and `From` to `StandardListViewItem` to make creation and modification of `StandardListView`'s models easier. - Added `on_close_requested` function to `Window` to register callbacks that are emitted when the user tries to close a window. - Added `VecModel::set_vec` to replace the entire contents with new data. - Added a `cache-rendering-hint` boolean property that can be applied to any element, to hint to the renderer that it should cache the element and its children into a cached layer. This may speed up rendering of complex sub-trees if they rarely change. - The `brighter` and `lighter` functions also work on values of type brush. - Added a `reset` function to C++'s `Model`, Rust's `ModelNotify` and JS's `ModelPeer` - Added a `row_data_tracked` function to `ModelExt` (an extension to the Model trait) ### Fixed - Fixed application of the `opacity` property evenly to child elements (#725). - Windows: Fixed font lookup of strings including several scripts (eg, containing asian characters) - Fixed PopupWindow in a repeater (#1113, #1132) - LSP: do not always resize the preview window to its preferred each time the code is modified ## [0.2.1] - 2022-03-10 ### Added - C++ interpreter API: added a `Value::Value(int)` constructor - Global singletons in `.slint` files may now refer to other global singletons - Added `input-type` property to `TextInput` and `LineEdit` that allows for characters to be replaced in password fields - The `SpinBox` widget now handles up and down key events ### Fixed - `TouchArea::has-hover` is now properly set to false when the mouse leaves the window - Fixed some cases of panics with 'Constant property being changed' - Fixed `Flickable` animation - Better compilation error when selecting unknown styles - Fixed duplicated key event for some keys (such as tab) with the GL backend - Improved property optimizations by inlining some bindings and remove more unused properties ## [0.2.0] - 2022-02-10 This version changes some APIs in incompatible ways. For details how to migrate your application code, see the [C++ migration guide](api/cpp/docs/cpp_migration.md) as well as the [Rust migration guide for the `sixtyfps` crate](api/rs/slint/migration.md) and for the [`slint-interpreter` crate](internal/interpreter/migration.rs). ### Changed - Minimum rust version is now 1.56 - C++ compiler requires C++20 - In the C++ interpreter API `std::span` is used for callbacks arguments, instead of `sixtyfps::Slice` - `Model::row_data` will now return a `Option` / `std::optional` instead of a plain `T`. - `Model::model_tracker` no longer has a default implementation. - The deprecated methods `Model::attach_peer` and `ModelNotify::attach` were removed. - The interpreter does not differentiate anymore between `Value::Array` and `Value::Model` everything is a `Value::Model`, which now contains a `ModelRc` - In Rust, `slint::SharedPixelBuffer` and `slint::SharedImageBuffer` now use a `u32` instead of `usize` for `width`, `height` and `stride`. - In Rust and C++, `slint::Image::size()` now returns an integer size type. - `sixtyfps::interpreter::CallCallbackError` was renamed to `slint::interpreter::InvokeCallbackError` - Some deprecation warning in .60 became hard errors - Replace `ModelHandle` with `ModelRc` - `slint::interpreter::ComponentInstance` in Rust now implements `slint::ComponentHandle`. This removes `sixtyfps_interpreter::WeakComponentInstance` in favor of `slint_interpreter::Weak`. - For the Rust API crate, the Rust Interpreter API crate, the `backend-gl`, `x11`, and `wayland` features were renamed to `backend-gl-all`, `backend-gl-x11`, and `backend-gl-wayland`. - For the C++ CMake interface, the `SIXTYFPS_BACKEND_GL`, `SIXTYFPS_FEATURE_X11`, and `SIXTYFPS_FEATURE_WAYLAND` options were renamed to `SLINT_BACKEND_GL_ALL`, `SLINT_FEATURE_BACKEND_GL_X11`, and `SLINT_FEATURE_BACKEND_GL_WAYLAND`. - The animation `loop-count` property was replaced by `iteration-count` (which is the same as `loop-count` plus one) ### Added - `TextEdit::font-size` and `LineEdit::font-size` have been added to control the size of these widgets. - Added `slint::Window::set_rendering_notifier` to get a callback before and after a new frame is being rendered. - Added `slint::Window::request_redraw()` to schedule redrawing of the window contents. ### Fixed - Models initialized from arrays are now also mutable when run in the interpreter. - Fixed compilation error when accessing object members of array through the array index syntax ## [0.1.6] - 2022-01-21 ### Changed - **Breaking:** The internal key code for the keys left, right, home and end has changed. This was undocumented, but if you were handling this in the `FocusScope` event, these keys will now be ignored. Use the `Keys.LeftArrow` and other code exposed in the `Keys` namespace instead. - For `sixtyfps::Timer` (C++ and Rust), it's now possible to call `restart()` after a timer has been stopped previously by calling `stop()`. - Property access in `.60` was optimized by doing more constant propagation. ### Added - Color names can now be accessed through the `Colors` namespace (in `.60`). - Math function are now available through the `Math` namespace (in `.60`). - `TouchArea` gained a `mouse-cursor` property to change the mouse cursor. - C++: Added `SIXTYFPS_VERSION_MAJOR`/`SIXTYFPS_VERSION_MINOR`/`SIXTYFPS_VERSION_PATCH` version macros. - More special keyboard key codes are provided in the `FocusScope`, and special keys are handled - `start()`, `stop()`, `running()` and a default constructor for C++ `sixtyfps::Timer` - Added math functions `log`, and `pow`. - Property animations now have a `delay` property, which will delay the start of the animation. Use this to create sequential animations. - Rust: Added `sixtyfps::VecModel::insert(&self, index, value)`. - C++: Added `sixtyfps::VecModel::insert(index, value)`. - Added ability to access elements of a model with the `[index]` syntax. ### Fixed - Memory leak in C++. - Native style: Colors are updated automatically when the Windowing system switches to and from dark mode (#687) - Ctrl/Command+X in text fields copies the selected text to the clipboard and deletes it (cut). - Improved native ComboBox look. - Fixed panics or compilation error when using two way bindings on global properties. ## [0.1.5] - 2021-11-24 ### Changed - The sixtyfps compiler no longer "inline" all the elements, resulting in faster compilation time and smaller binaries. - Implemented basic constant propagation in the sixtyfps compiler ### Fixed - The Slider's changed callback was not being called with the fluent style (#621). - Fixed compilation error in C++'s `sixtyfps::blocking_invoke_from_main_loop` when the callable returns `void` (#623). - Improve rendering quality on High-DPI screens on Windows when using Qt. - Fixed linking errors when selecting the native style on Windows with C++. - Fixed the maximization button in the window decoration when a window has a fixed size. ## [0.1.4] - 2021-10-22 ### Changed - The TouchArea now grabs the mouse for every button instead of just the left button. - The ScrollView's default viewport size is no longer hardcoded to 1000px but depends on the contents. - In Rust, the `sixtyfps::Model` trait deprecates the `attach_peer` function in favor of `model_tracker`, where all you need to do is return a reference to your `sixtyfps::ModelNotify` field. ### Added - Enable support for compressed SVG (.svgz). - Viewer: added possibility to set shell callback handler with `--on `. - It is now possible to query the length of a model with `.length`. ### Fixed - Fixed the `PointerEvent.kind` always being down. - `LineEdit.has-hocus` with the native style ## [0.1.3] - 2021-10-06 ### Changed - Due to changes in the build system, the C++ build now requires CMake >= 3.19. - Fluent style: The Slider and ScrollBar now updates as the mouse moves. - Parentheses around callable expression is now deprecated. - Naming a callback without calling it is now a hard error instead of producing error in the generated code. ### Added - New `no-frame` property of a `Window` which changes it to borderless/frameless - sixtyfps-compiler and slint-viewer can read the .60 file content from stdin by passing `-` - slint-viewer gained ability to read or save the property values to a json file with `--save-data` and `--load-data` - New `StandardButton` widget - New `Dialog` element - `sixtyfps::Image` has now a `path()` accessor function in Rust and C++ to access the optional path of the file on disk that's backing the image. - New `moved` and `pointer-event` callback in `TouchArea` - New `AboutSixtyFPS` widget ### Fixed - Fixed panic when using `TabWidget` with `Text` elements and the native style. - Fixed panic when calling `hide()` on a `sixtyfps::Window` from within a callback triggered by keyboard/mouse input when using the GL backend. - Rust: The implementation of ModelModel::set_row_data now forward the call to the inner model ## [0.1.2] - 2021-09-09 ### Changed - Apply the default text color from the style for the `color` of `Text` and `TextInput` elements, to contrast correctly with the application of `Window`'s `background` property. - LineEdit scrolls to keep the cursor visible - The `clicked` callbacks are now only emitted if the release event is within the TouchArea's geometry - parentheses around the condition are no longer needed in `if` elements: `if condition : Foo { ... }` ### Added - One can now set an alias from the root to a global callback - It is now possible to access properties and callback of exported global objects from the native code (#96) - C++ API: `blocking_invoke_from_event_loop`: a blocking version of `invoke_from_event_loop` - TextInput can support multiple line by setting single-line to false - The CMake integration now allows enabling/disabling SixtyFPS library features, such as Wayland support or the dynamic run-time interpreter. - Added `image-rendering` property to Image to control how the image is scaled - `TextEdit` widget - Support for semantic tokens in LSP ### Fixed - The interpreter API correctly return an error instead of panicking when setting properties or calling callbacks that don't exist - The `has-hover` property is correctly reset the false when releasing the mouse outside the touch area ## [0.1.1] - 2021-08-19 ### Changed - Fixed lookup scope when resolving model expression in `for` or `if` constructs: the `self` element now refers to the correct element instead of the root. - Rust: default to the native style if Qt is available - Rust: deprecated `SharedVector::as_slice_mut()`. Use `SharedVector::make_mut_slice()` instead. - The default non-native widget style is now the new "fluent" style. - The internal normalization of identifiers is using `-` instead of `_`, this is an internal change, but it can be seen in error messages. When listing properties the identifiers are preserved. For fields in structures they are normalized. - Show a compilation error when there are duplicated element ids. - The `clip` property can now be any expression. ### Added - `ComboBox` now has a `selected` callback. - `Window` now has an `icon` property. - Added `sixtyfps::Weak::upgrade_in_event_loop` in the Rust API. - Added `sixtyfps::Model::as_any()` in the Rust API. - Added conversion between `sixtyfps::Image` and `sixtyfps::interpreter::Value` in the C++ API. - Properties of type `angle` and `duration` are allowed in the public API. - Every element now has a `visible` property. - `Slider` now has a `changed` callback. - Added `TabWidget` widget. - Rust: `sixtyfps::Image` can now be constructed from image data provided by `sixtyfps::SharedPixelBuffer`. This enables integrating with other low-level software rendering or the popular Rust image crate. - VSCode extension: added an option to specify command line arguments for the LSP. ### Fixed - GridLayout cells with colspan and rowspan respect properly their constraints. - Don't panic when replacing programmatically text in a `TextInput` and then editing it. - The default height of elements in a ListView no longer defaults to 100%. - Fixed support for `*=` and `/=` on types with unit such as length. - Don't panic when using a self assignment operator on an invalid type - this produces a compiler error instead. - Fixed angle conversion for values specified in radians, gradians and turns. - Fixed SharedVector sometimes not allocating big enough storage. ## [0.1.0] - 2021-06-30 ### Changed - Layouts are now conceptually their own elements, meaning that the `x` and `y` properties of items within layouts are relative to the layout and no longer to the parent element of layouts. - The default spacing and padding of layouts is now 0 instead of being specific to the style. There are now `HorizontalBox`, `VerticalBox`, `GridBox` widgets which have default spacing and padding. - Setting the window `height` and `width` properties results in a fixed size. The `preferred-width` and `preferred-height` property can be used to set the initial size and the window remains resizable by the user, if the window manager permits. - Binding loops are now detected at compile-time instead of panic at runtime. - The `viewer` binary was renamed to `slint-viewer` and is now available via `cargo install` from crates.io. - The layout properties `minimum-width`, `maximum-height`, etc. were renamed to a shorter version `min-width`, `max-height`. The old names are still supported as a deprecated alias. ### Added - Warnings are now shown in the online code editor. - `sixtyfps::invoke_from_event_loop` was added to the C++ and Rust APIs, to run a function in the UI thread from any thread. - `sixtyfps::run_event_loop()` and `sixtyfps::quit_event_loop()` were added to the Rust and C++ APIs to start and quit the main event loop. - `z` property on items. - The type in two-way bindings can now be omitted. - It's possible to declare aliases for callbacks (`callback clicked <=> other.clicked;`) - `abs()` function to get the absolute value - The root element of an `if` or `for` can be given a name (`if (condition) name := Rectangle {}`) - `sixtyfps::Image` is a new type in the public Rust and C++ API to load images from a path. - The width and height of images is now accessible via the `width`or `height` of an `image` type property in .60 files (`some-image.source.width`) ### Fixed - Fixed Mouse wheel to work on the `Flickable` element and derivatives. - Fixed generated C++ code on Windows. - Calling `debug(...)` no longer breaks the LSP. - `ComponentDefinition::properties` only exposes public properties as documented. - Many more bugfixes ## [0.0.6] - 2021-04-27 ### Changed - `Rectangle::color` was deprecated and replaced by `Rectangle::background`, same for `Window::color` - `Path::fill-color` was renamed to `fill`, and `Path::stroke-color` was renamed to `stroke`, which are now brushes instead of color - Many other color property became brush in order to support gradients - the `resource` type was renamed to `image` - Calling a callback is done from C++/Rust with `invoke_` instead of `call_` ### Added - `@linear-gradient` can be used to have gradients on rectangle and paths - `Image::colorize` allows to apply a color filter on image - `0` can be converted to anything with units - Support power of unit in intermediate expression. (eg: `3px * width / height` is now supported but used to be an error) - Support for `else if` - The path fill rule can now be specified using `Path::fill-rule`. - Support for `letter-spacing` in `Text`/`TextInput` elements. - `rgb()` / `rgba()` - Layout in Flickable - LSP server with Live Preview and basic auto completion - The viewer tool gained the `--auto-reload` argument - `Window.default-font-weight` - Added `opacity` property that can be applied to elements - Added `clip` property in Rectangle, including clipping for rounded rectangle - API to load dynamically .60 files from C++ and Rust, including a way to embed sixtyfps widgets in Qt applications - Preferred size in Layouts - Math functions such as `sin`, `cos`, `sqrt`, ... - New printer demo design - Ability to load custom fonts using `import` statements ### Fixed - `Image::image-fit`'s `cover` and `contains` variant are fixed to match the CSS spec - Flickable without scrollbar - Multiplying and dividing different units. - Many more bugfixes ## [0.0.5] - 2021-01-29 ### Changed - Renamed "signal" to "callback" - And calling a callback is done from C++/Rust with `call_` instead of `emit_` - Renamed "SharedArray" to "SharedVector" in the C++/Rust API - Renamed Slider min and max property to minimum and maximum - The graphics rendering backend was changed to use femtovg or Qt - Renamed `initial-focus` to `forward-focus` as focus related property on elements - The "align-" prefix was removed from `TextHorizontalAlignment` and `TextVerticalAlignment`. Either change `align-left` to `left` or qualify with `TextHorizontalAlignment.left`. - `img!"..."` is replaced by `@image-url("...")` - `$children` is replaced by `@children` ### Added - `title` property to the Window element - `color` property to the Window element - `maximum`/`minimum` properties to the `SpinBox` - strings can contain escape codes - `FocusScope` to handle key events - `return` statements - `Text` word wrap and elide - `drop-shadow-*` properties (limited to `Rectangle` at the moment) - `Color.brighter` / `Color.darker` - `Window.default-font-family` and `Window.default-font-size` ## [0.0.4] - 2020-12-14 ### Changed - Changed Rust `build.rs` API to use an opaque compiler configuration type - With Rust, image resources are embedded in the binary by default. - Updated winit version - Updated Neon Version ### Fixed - Fixed animations sometimes stopping mid-way. - Fixed rendering of C++ components - Fixed disabled widgets ## [0.0.3] - 2020-12-09 ### Changed - In C++, the generated component is now wrapped by a `ComponentHandle` smart pointer that acts like `std::shared_ptr`. New instances are created using `T::create()`. - In Rust, the generated component implements `Clone` and acts like an `Rc`. `sixtyfps::Weak` can be used to hold weak references. - `ARGBColor` was renamed `RgbaColor` - `width and `height` of some built-in elements now default to 100% of the parent element. ### Added - Allow dashes in identifiers (#52) - VerticalLayout / HorizontalLayout - Placeholder text in `LineEdit` - global components (#96) - `Clip` element - `ComboBox` element - `PopupWindow` element - `Image` element: New source-clip-{x, y, width, height} properties - `sixtyfps::Timer` in Rust API - Transitions are now implemented - `round`/`ceil`/`floor`/`mod`/`max`/`min`/`cubic-bezier` functions - Signals can have return a value - `has_hover` property in `TouchArea` - `font-weight` property on Text - `viewbox-x/y/width/height` and `clip` properties for `Path` ## [0.0.2] - 2020-10-22 ### Changed - Default to the native style in the `viewer`, if available. - Changed the name of the common logical pixel unit from `lx` to `px`. The less often used physical pixel has now the `phx` suffix. ### Added - Add support for more keyboard shortcuts to `TextInput`. - Added a `current_item` to `StandardListView`. - API cleanup in sixtyfps-node ### Fixed - Fix occasional hang when navigating in `TextInput` fields with the cursor keys. - Fix access to aliased properties from within `for` and `if` expressions. - Fix `ScrollView` being scrollable when it shouldn't. - Fix appearance of natively styled scrollbars. - Allow converting an object type to another even if it is missing some properties. - Add missing frame drawing around `ScrollView`. - Fix Clipping in scroll views in WASM builds. - Fix resizing of `ListView`. - Many more bugfixes ## [0.0.1] - 2020-10-13 - Initial release. [0.0.1]: https://github.com/slint-ui/slint/releases/tag/v0.0.1 [0.0.2]: https://github.com/slint-ui/slint/releases/tag/v0.0.2 [0.0.3]: https://github.com/slint-ui/slint/releases/tag/v0.0.3 [0.0.4]: https://github.com/slint-ui/slint/releases/tag/v0.0.4 [0.0.5]: https://github.com/slint-ui/slint/releases/tag/v0.0.5 [0.0.6]: https://github.com/slint-ui/slint/releases/tag/v0.0.6 [0.1.0]: https://github.com/slint-ui/slint/releases/tag/v0.1.0 [0.1.1]: https://github.com/slint-ui/slint/releases/tag/v0.1.1 [0.1.2]: https://github.com/slint-ui/slint/releases/tag/v0.1.2 [0.1.3]: https://github.com/slint-ui/slint/releases/tag/v0.1.3 [0.1.4]: https://github.com/slint-ui/slint/releases/tag/v0.1.4 [0.1.5]: https://github.com/slint-ui/slint/releases/tag/v0.1.5 [0.1.6]: https://github.com/slint-ui/slint/releases/tag/v0.1.6 [0.2.0]: https://github.com/slint-ui/slint/releases/tag/v0.2.0 [0.2.1]: https://github.com/slint-ui/slint/releases/tag/v0.2.1 [0.2.2]: https://github.com/slint-ui/slint/releases/tag/v0.2.2 [0.2.3]: https://github.com/slint-ui/slint/releases/tag/v0.2.3 [0.2.4]: https://github.com/slint-ui/slint/releases/tag/v0.2.4 [0.2.5]: https://github.com/slint-ui/slint/releases/tag/v0.2.5 [0.3.0]: https://github.com/slint-ui/slint/releases/tag/v0.3.0 [0.3.1]: https://github.com/slint-ui/slint/releases/tag/v0.3.1 [0.3.2]: https://github.com/slint-ui/slint/releases/tag/v0.3.2 [0.3.3]: https://github.com/slint-ui/slint/releases/tag/v0.3.3 [0.3.4]: https://github.com/slint-ui/slint/releases/tag/v0.3.4 [0.3.5]: https://github.com/slint-ui/slint/releases/tag/v0.3.5 [1.0.0]: https://github.com/slint-ui/slint/releases/tag/v1.0.0 [1.0.1]: https://github.com/slint-ui/slint/releases/tag/v1.0.1 [1.0.2]: https://github.com/slint-ui/slint/releases/tag/v1.0.2 [1.1.0]: https://github.com/slint-ui/slint/releases/tag/v1.1.0 [1.1.1]: https://github.com/slint-ui/slint/releases/tag/v1.1.1 [1.2.0]: https://github.com/slint-ui/slint/releases/tag/v1.2.0 [1.2.1]: https://github.com/slint-ui/slint/releases/tag/v1.2.1 [1.2.2]: https://github.com/slint-ui/slint/releases/tag/v1.2.2 [1.3.0]: https://github.com/slint-ui/slint/releases/tag/v1.3.0 [1.3.1]: https://github.com/slint-ui/slint/releases/tag/v1.3.1 [1.3.2]: https://github.com/slint-ui/slint/releases/tag/v1.3.2 [1.4.0]: https://github.com/slint-ui/slint/releases/tag/v1.4.0 [1.4.1]: https://github.com/slint-ui/slint/releases/tag/v1.4.1 [1.5.0]: https://github.com/slint-ui/slint/releases/tag/v1.5.0 [1.5.1]: https://github.com/slint-ui/slint/releases/tag/v1.5.1 [1.6.0]: https://github.com/slint-ui/slint/releases/tag/v1.6.0 [1.7.0]: https://github.com/slint-ui/slint/releases/tag/v1.7.0 [1.7.1]: https://github.com/slint-ui/slint/releases/tag/v1.7.1 [1.7.2]: https://github.com/slint-ui/slint/releases/tag/v1.7.2 [1.8.0]: https://github.com/slint-ui/slint/releases/tag/v1.8.0 [1.9.0]: https://github.com/slint-ui/slint/releases/tag/v1.9.0 [1.9.1]: https://github.com/slint-ui/slint/releases/tag/v1.9.1 [1.9.2]: https://github.com/slint-ui/slint/releases/tag/v1.9.2 [1.10.0]: https://github.com/slint-ui/slint/releases/tag/v1.10.0 [1.11.0]: https://github.com/slint-ui/slint/releases/tag/v1.11.0 [1.12.0]: https://github.com/slint-ui/slint/releases/tag/v1.12.0 [1.12.1]: https://github.com/slint-ui/slint/releases/tag/v1.12.1 [1.13.0]: https://github.com/slint-ui/slint/releases/tag/v1.13.0 [1.13.1]: https://github.com/slint-ui/slint/releases/tag/v1.13.1 [1.14.0]: https://github.com/slint-ui/slint/releases/tag/v1.14.0 [1.14.1]: https://github.com/slint-ui/slint/releases/tag/v1.14.1 [1.15.0]: https://github.com/slint-ui/slint/releases/tag/v1.15.0 ================================================ FILE: CMakeLists.txt ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 cmake_minimum_required(VERSION 3.21) project(Slint LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FeatureSummary) option(SLINT_BUILD_TESTING "Build tests" OFF) add_feature_info(SLINT_BUILD_TESTING SLINT_BUILD_TESTING "configure whether to build the test suite") include(CTest) set(SLINT_IS_TOPLEVEL_BUILD TRUE) # Place all compiled examples into the same bin directory # on Windows, where we'll also put the dll if (WIN32) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/debug) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/release) elseif(APPLE) # On macOS, the slint_cpp.dylib's install_name uses @rpath. CMake doesn't set BUILD_RPATH for # imported targets though, so include the directory here by hand in the rpath used to build binaries # in the build tree (such as our examples or tests). set(CMAKE_BUILD_RPATH ${CMAKE_BINARY_DIR}/api/cpp) endif() add_subdirectory(api/cpp/) option(SLINT_BUILD_EXAMPLES "Build Slint Examples" OFF) add_feature_info(SLINT_BUILD_EXAMPLES SLINT_BUILD_EXAMPLES "configure whether to build the examples and demos") if(SLINT_BUILD_EXAMPLES) add_subdirectory(examples) add_subdirectory(demos) endif() if(SLINT_BUILD_TESTING AND (SLINT_FEATURE_COMPILER OR SLINT_COMPILER)) add_subdirectory(docs/astro/src/content/code/) endif() feature_summary(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:") feature_summary(WHAT DISABLED_FEATURES DESCRIPTION "Disabled features:") ================================================ FILE: CMakePresets.json ================================================ { "version": 8, "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json", "configurePresets": [ { "name": "base", "displayName": "base preset", "generator": "Ninja", "binaryDir": "${sourceDir}/build-${presetName}", "hidden": true }, { "name": "base-debug", "displayName": "base debug preset", "generator": "Ninja", "binaryDir": "${sourceDir}/build-${presetName}", "hidden": true, "cacheVariables": { "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_BUILD_TYPE": "Debug", "SLINT_BUILD_EXAMPLES": "ON", "SLINT_BUILD_TESTING": "ON" } }, { "name": "debug-mold", "displayName": "Build as debug + using mold linker", "cacheVariables": { "CMAKE_SHARED_LINKER_FLAGS": "-fuse-ld=mold" }, "inherits": [ "base-debug" ] }, { "name": "debug", "displayName": "Build against", "binaryDir": "${sourceDir}/build", "inherits": [ "base-debug" ] }, { "name": "release", "displayName": "Build as release mode.", "cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" }, "inherits": [ "base" ] } ], "buildPresets": [ { "name": "debug-mold", "configurePreset": "debug-mold" }, { "name": "debug", "configurePreset": "debug" }, { "name": "release", "configurePreset": "release" } ] } ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing We warmly welcome contributions to the project. Let's discuss ideas or questions in [Github discussions](https://github.com/slint-ui/slint/discussions) or on our [public chat](https://chat.slint.dev). Please feel welcome to open GitHub issues or pull requests. Use 👍 reaction on issue that you consider important. Issues which we think are suitable for new contributors are tagged with https://github.com/slint-ui/slint/labels/good%20first%20issue. ## Internal documentation - [Development guide](docs/development.md) - [Building Slint from sources in this repository](docs/building.md) - [Testing](docs/testing.md) - [GitHub issues triage and labels](docs/internal/triage.md) ## License By contributing to this project, you agree to license your contributions under the [MIT No Attribution License (MIT-0)](https://opensource.org/license/mit-0). To confirm this, you'll be asked to sign a simple [Contributor License Agreement (CLA)](https://cla-assistant.io/slint-ui/slint) when you open a pull request. The CLA does not assign copyright or transfer ownership, it simply confirms that you wrote the code yourself and are licensing it under MIT-0. ## Coding Style For the Rust portion of the code base, the CI enforces the coding style via rustfmt. For the C++ portion of the code base, the CI enforces the coding style via `clang-format`. ================================================ FILE: Cargo.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 [workspace] members = [ 'api/cpp', 'api/node', 'api/rs/build', 'api/rs/macros', 'api/rs/slint', 'api/python/slint', 'api/wasm-interpreter', 'editors/zed', 'examples/7guis', 'examples/gallery', 'examples/imagefilter/rust', 'examples/maps', 'examples/memory', 'examples/native-gestures', 'examples/opengl_underlay', 'examples/opengl_texture', 'examples/wgpu_texture', 'examples/ffmpeg', 'examples/gstreamer-player', 'examples/plotter', 'demos/printerdemo/rust', 'demos/printerdemo_mcu', 'examples/slide_puzzle', 'examples/speedometer/rust', 'examples/todo/rust', 'examples/todo-mvc/rust', 'examples/virtual_keyboard/rust', 'examples/carousel/rust', 'demos/energy-monitor', 'demos/home-automation/rust', 'examples/mcu-board-support', 'examples/mcu-embassy', 'examples/uefi-demo', 'examples/async-io', 'demos/weather-demo', 'demos/usecases/rust', 'helper_crates/const-field-offset', 'helper_crates/vtable', 'helper_crates/vtable/macro', 'internal/backends/winit', 'internal/backends/android-activity', 'internal/backends/qt', 'internal/backends/selector', 'internal/backends/testing', 'internal/backends/linuxkms', 'internal/renderers/software', 'internal/renderers/skia', 'internal/renderers/femtovg', 'internal/common', 'internal/compiler', 'internal/compiler/parser-test-macro', 'internal/core', 'internal/core-macros', 'internal/interpreter', 'tests/doctests', 'tests/driver/cpp', 'tests/driver/driverlib', 'tests/driver/interpreter', 'tests/driver/nodejs', 'tests/driver/python', 'tests/driver/rust', 'tests/screenshots', 'tests/manual/windowattributes', 'tests/manual/module-builds/blogica', 'tests/manual/module-builds/blogicb', 'tests/manual/module-builds/app', 'tools/compiler', 'tools/docsnapper', 'tools/figma_import', 'tools/lsp', 'tools/updater', 'tools/viewer', 'tools/tr-extractor', "ui-libraries/material/examples/gallery", 'xtask', ] default-members = [ 'api/rs/build', 'api/rs/slint', 'examples/gallery', 'examples/memory', 'demos/printerdemo/rust', 'examples/slide_puzzle', 'examples/speedometer/rust', 'examples/todo/rust', 'examples/virtual_keyboard/rust', 'examples/carousel/rust', 'demos/energy-monitor', 'internal/backends/winit', 'internal/backends/qt', 'internal/backends/selector', 'internal/compiler', 'internal/core', 'internal/interpreter', 'tests/doctests', 'tests/driver/interpreter', 'tests/driver/rust', 'tests/screenshots', 'tools/compiler', 'tools/figma_import', 'tools/lsp', 'tools/viewer', ] resolver = "3" [workspace.package] description = "GUI toolkit to efficiently develop fluid graphical user interfaces for embedded devices and desktop applications" authors = ["Slint Developers "] documentation = "https://slint.dev/docs" edition = "2024" homepage = "https://slint.dev" keywords = ["gui", "toolkit", "graphics", "design", "ui"] license = "GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0" repository = "https://github.com/slint-ui/slint" rust-version = "1.88" version = "1.16.0" [workspace.dependencies] i-slint-backend-android-activity = { version = "=1.16.0", path = "internal/backends/android-activity", default-features = false } i-slint-backend-linuxkms = { version = "=1.16.0", path = "internal/backends/linuxkms", default-features = false } i-slint-backend-qt = { version = "=1.16.0", path = "internal/backends/qt", default-features = false } i-slint-backend-selector = { version = "=1.16.0", path = "internal/backends/selector", default-features = false } i-slint-backend-testing = { version = "=1.16.0", path = "internal/backends/testing", default-features = false } i-slint-backend-winit = { version = "=1.16.0", path = "internal/backends/winit", default-features = false } i-slint-common = { version = "=1.16.0", path = "internal/common", default-features = false } i-slint-compiler = { version = "=1.16.0", path = "internal/compiler", default-features = false } i-slint-core = { version = "=1.16.0", path = "internal/core", default-features = false } i-slint-core-macros = { version = "=1.16.0", path = "internal/core-macros", default-features = false } i-slint-renderer-femtovg = { version = "=1.16.0", path = "internal/renderers/femtovg", default-features = false } i-slint-renderer-skia = { version = "=1.16.0", path = "internal/renderers/skia", default-features = false } i-slint-renderer-software = { version = "=1.16.0", path = "internal/renderers/software", default-features = false } slint = { version = "=1.16.0", path = "api/rs/slint", default-features = false } slint-build = { version = "=1.16.0", path = "api/rs/build", default-features = false } slint-cpp = { version = "=1.16.0", path = "api/cpp", default-features = false } slint-interpreter = { version = "=1.16.0", path = "internal/interpreter", default-features = false } slint-macros = { version = "=1.16.0", path = "api/rs/macros", default-features = false } vtable = { version = "0.3", path = "helper_crates/vtable" } by_address = { version = "1.0.4" } bytemuck = { version = "1.13.1" } cbindgen = { version = "0.29", default-features = false } cfg_aliases = { version = "0.2.0" } clap = { version = "4.0", features = ["derive", "wrap_help"] } clru = { version = "0.6.0" } css-color-parser2 = { version = "1.0.1" } derive_more = { version = "2.0.0", default-features = false, features = ["deref", "deref_mut", "into", "from", "add", "add_assign", "mul", "not", "display"] } euclid = { version = "0.22.1", default-features = false } swash = { version = "0.2.6", default-features = false } glutin = { version = "0.32.0", default-features = false } image = { version = "0.25", default-features = false, features = ["png", "jpeg"] } itertools = { version = "0.14" } log = { version = "0.4.17" } tracing = { version = "0.1" } tracing-subscriber = { version = "0.3" } resvg = { version = "0.46.0", default-features = false, features = ["text", "raster-images"] } rowan = { version = "0.16.1" } rustybuzz = { version = "0.20.0" } send_wrapper = { version = "0.6.0" } serde = { version = "1.0.163", default-features = false, features = ["derive"] } serde_json = { version = "1.0.96" } softbuffer = { version = "0.4.4", default-features = false } spin_on = { version = "0.1" } strum = { version = "0.27.1", default-features = false, features = ["derive"] } toml_edit = { version = "0.24.0" } ttf-parser = { version = "0.25" } typed-index-collections = "3.2" web-sys = { version = "0.3.72", default-features = false } smol_str = { version = "0.3.1" } rayon = { version = "1.10.0", default-features = false } raw-window-handle-06 = { package = "raw-window-handle", version = "0.6", features = ["alloc"] } # Note: We're migrating our unicode handling over to the icu family of crates. # Prefer using icu crates over adding more unicode-* dependencies # parley uses the icu crates internally as well, so we'll share the dependency there. unicode-segmentation = { version = "1.12.0" } icu_normalizer = { version = "2", default-features = false, features = ["compiled_data"] } glow = { version = "0.16" } tikv-jemallocator = { version = "0.6" } wgpu-27 = { package = "wgpu", version = "27", default-features = false } wgpu-28 = { package = "wgpu", version = "28", default-features = false } input = { version = "0.9.0", default-features = false } tr = { version = "0.1", default-features = false } fontique = { version = "0.7.0" } read-fonts = { version = "0.35" } skrifa = { version = "0.37" } windows = { version = "0.62" } windows-core = { version = "0.62.0" } lyon_path = { version = "1.0", default-features = false } [profile.release] lto = true panic = "abort" [profile.dev] panic = "abort" ================================================ FILE: Cross.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 [target.aarch64-unknown-linux-gnu] image = "ghcr.io/slint-ui/slint/aarch64-unknown-linux-gnu" [target.armv7-unknown-linux-gnueabihf] image = "ghcr.io/slint-ui/slint/armv7-unknown-linux-gnueabihf" [target.riscv64gc-unknown-linux-gnu] image = "ghcr.io/slint-ui/slint/riscv64gc-unknown-linux-gnu" [target.x86_64-unknown-linux-gnu] image = "ghcr.io/slint-ui/slint/x86_64-unknown-linux-gnu" [build.env] passthrough = ["SLINT_NO_QT", "SLINT_STYLE", "SLINT_TEST_FILTER", "SLINT_INTERPRETER_ERROR_WHITELIST"] ================================================ FILE: FAQ.md ================================================ # Frequently Asked Questions: - [General](#general) - [Why does Slint use a domain specific language?](#why-does-slint-use-a-domain-specific-language) - [Will there be API bindings to integrate with my favorite programming language?](#will-there-be-api-bindings-to-integrate-with-my-favorite-programming-language) - [Licensing](#licensing) - [Royalty-free license](#royalty-free-license) - [Who can use the Royalty-free license?](#who-can-use-the-royalty-free-license) - [What obligations do I need to fulfil to use the Royalty-free license?](#what-obligations-do-i-need-to-fulfil-to-use-the-royalty-free-license) - [Are there any limitations with the Royalty-free license?](#are-there-any-limitations-with-the-royalty-free-license) - [Scenario: What happens if my application is open-source (e.g. under MIT), forked by a different person and then redistributed?](#scenario-what-happens-if-my-application-is-open-source-eg-under-mit-forked-by-a-different-person-and-then-redistributed) - [How are modifications to Slint itself covered under this license?](#how-are-modifications-to-slint-itself-covered-under-this-license) - [If Slint were to be taken over by a larger company or the current owners were to have a change of heart, can they revoke existing licenses?](#if-slint-were-to-be-taken-over-by-a-larger-company-or-the-current-owners-were-to-have-a-change-of-heart-can-they-revoke-existing-licenses) - [GPLv3](#gplv3) - [If I link my program with Slint GPLv3, does it mean that I have to license my program under the GPLv3, too?](#if-i-link-my-program-with-slint-gplv3-does-it-mean-that-i-have-to-license-my-program-under-the-gplv3-too) - [My MIT-licensed program links to Slint GPLv3. Can someone fork my program to build and distribute a proprietary program?](#my-mit-licensed-program-links-to-slint-gplv3-can-someone-fork-my-program-to-build-and-distribute-a-proprietary-program) - [My MIT-licensed program links to Slint GPLv3. How can I convey to someone that they can distribute my program as part of a proprietary licensed program?](#my-mit-licensed-program-links-to-slint-gplv3-how-can-i-convey-to-someone-that-they-can-distribute-my-program-as-part-of-a-proprietary-licensed-program) - [My MIT-licensed program links to Slint GPLv3. Under what license can I release the binary of my program?](#my-mit-licensed-program-links-to-slint-gplv3-under-what-license-can-i-release-the-binary-of-my-program) - [Scenario: Alice is a software developer, she wants her code to be licensed under MIT. She is developing an application "AliceApp" that links to Slint GPLv3. Alice also wants to allow that Bob, a user of AliceApp, can fork AliceApp into a proprietary application called BobApp](#scenario-alice-is-a-software-developer-she-wants-her-code-to-be-licensed-under-mit-she-is-developing-an-application-aliceapp-that-links-to-slint-gplv3-alice-also-wants-to-allow-that-bob-a-user-of-aliceapp-can-fork-aliceapp-into-a-proprietary-application-called-bobapp) - [Can Alice use the MIT license header to the source code of AliceApp application?](#can-alice-use-the-mit-license-header-to-the-source-code-of-aliceapp-application) - [Under what license should she distribute the AliceApp binary?](#under-what-license-should-she-distribute-the-aliceapp-binary) - [How can Alice make it clear to Bob that he can distribute BobApp under a proprietary license?](#how-can-alice-make-it-clear-to-bob-that-he-can-distribute-bobapp-under-a-proprietary-license) - [Miscellaneous](#miscellaneous) - [Do you provide Support?](#do-you-provide-support) ## General ### Why does Slint use a domain specific language? From our long experience of building UI toolkits, we have learnt that a domain specific, declarative language is best suited to describe UIs. The Slint language is easy and intuitive to use while being strict enough for our tools to analyze and optimize to provide high graphics performance. Strictly typed binding expressions offer a powerful and robust way for humans to declare relationships between properties, even in complex user interfaces. ### Will there be API bindings to integrate with my favorite programming language? We want to make it possible to use Slint with any programming language. We do not favor one programming language over another. We have chosen to start with three languages: - Rust, our implementation language. - C++, another systems programming language we have a lot of experience with. - JavaScript, a popular dynamically typed language. This choice builds the foundation that allows us to create bindings for most types of programming languages. ## Licensing You can use Slint under ***any*** of the following licenses, at your choice: 1. [Royalty-free license](LICENSES/LicenseRef-Slint-Royalty-free-2.0.md), 2. [GNU GPLv3](LICENSES/GPL-3.0-only.txt), 3. [Paid license](LICENSES/LicenseRef-Slint-Software-3.0.md). ### Royalty-free license #### Who can use the Royalty-free license? This license is suitable for those who develop desktop, mobile, or web applications and do not want to use open-source components under copyleft licenses. #### What obligations do I need to fulfil to use the Royalty-free license? You need to do one of the following: 1. Display the [`AboutSlint`](https://slint.dev/snapshots/master/docs/slint/src/language/widgets/aboutslint.html) widget in an "About" screen or dialog that is accessible from the top level menu of the Application. In the absence of such a screen or dialog, display the widget in the "Splash Screen" of the Application. 2. Display the [Slint attribution badge](https://github.com/slint-ui/slint/tree/master/logo/MadeWithSlint-logo-whitebg.png) on a public webpage, preferably where the binaries of your Application can be downloaded from, in such a way that it can be easily found by any visitor to that page. #### Are there any limitations with the Royalty-free license? 1. You are not permitted to distribute or make Slint publicly available alone and without integration into an application. For this purpose you may use the Software under the GNU General Public License, version 3. 2. You are not permitted to use Slint within Embedded Systems. An Embedded System is a computer system designed to perform a specific task within a larger mechanical or electrical system. 3. You are not permitted to distribute an Application that exposes the APIs, in part or in total, of Slint. 4. You are not permitted to remove or alter any license notices (including copyright notices, disclaimers of warranty, or limitations of liability) contained within the source code form of Slint. #### Scenario: What happens if my application is open-source (e.g. under MIT), forked by a different person and then redistributed? The license does not restrict users on how they license their application. In the above scenario, the user may choose to use MIT-license for their application, which can be forked by a different person and then redistributed. If the forked application also uses Slint, then the person forking the application can choose to use Slint under any one of the licenses - Royalty-free, GPLv3, or paid license. #### How are modifications to Slint itself covered under this license? The license does not restrict 'if' and 'how' the modifications to Slint should be distributed. Say for example, Alice uses Slint under this new license to develop application A and modifies Slint in some way. She may choose to release the modifications to Slint under any license of her choice including any of the open source licenses. Alternatively she may decide not to release the modifications. #### If Slint were to be taken over by a larger company or the current owners were to have a change of heart, can they revoke existing licenses? We have a commitment to the larger Slint community to provide Slint under a Royalty-free license. This commitment is included in the [Contributors License Agreement (CLA)](http://cla-assistant.io/slint-ui/slint). ### GPLv3 #### If I link my program with Slint GPLv3, does it mean that I have to license my program under the GPLv3, too? No. You can license your program under any license compatible with the GPLv3 such as [https://www.gnu.org/licenses/license-list.en.html#GPLCompatibleLicenses](https://www.gnu.org/licenses/license-list.en.html#GPLCompatibleLicenses). Refer to GPL FAQ [https://www.gnu.org/licenses/gpl-faq.en.html#LinkingWithGPL](https://www.gnu.org/licenses/gpl-faq.en.html#LinkingWithGPL). #### My MIT-licensed program links to Slint GPLv3. Can someone fork my program to build and distribute a proprietary program? Yes, provided the person distributing the proprietary program acquired a Slint proprietary license, such as the Slint Royalty-free license or a paid license, instead of using Slint under GPLv3. The other option would be to remove the dependency to Slint altogether. #### My MIT-licensed program links to Slint GPLv3. How can I convey to someone that they can distribute my program as part of a proprietary licensed program? You can add a note as part of your license that to distribute a proprietary licensed program, one can acquire a Slint proprietary license or the dependency to Slint should be removed. #### My MIT-licensed program links to Slint GPLv3. Under what license can I release the entire work i.e my Program combined with Slint? While your software modules can remain under the MIT-license, the work as a whole must be licensed under the GPL. #### Scenario: Alice is a software developer, she wants her code to be licensed under MIT. She is developing an application "AliceApp" that links to Slint GPLv3. Alice also wants to allow that Bob, a user of AliceApp, can fork AliceApp into a proprietary application called BobApp - Can Alice use the MIT license header to the source code of AliceApp application? Yes. Alice can license her copyrighted source code under any license compatible with GPLv3. Refer FAQ [If I link my program with Slint GPLv3, does it mean that I have to license my program under the GPLv3, too?](#if-i-link-my-program-with-slint-gplv3-does-it-mean-that-i-have-to-license-my-program-under-the-gplv3-too) - Under what license should she distribute the AliceApp binary? Under GPLv3. While the different software modules can remain under any license compatible with GPLv3, the work as a whole must be licensed under the GPL. Refer FAQ [My MIT-licensed program links to Slint GPLv3. Under what license can I release the binary of my program?](#my-mit-licensed-program-links-to-slint-gplv3-under-what-license-can-i-release-the-binary-of-my-program) - How can Alice make it clear to Bob that he can distribute BobApp under a proprietary license? Alice can add a note that Bob can distribute BobApp under a proprietary license if he either acquires a Slint proprietary license or removes the dependency to Slint. ### Paid License #### What are the paid license options? Check out the pricing plans on our website . ## Miscellaneous ### Do you provide Support? Yes, check out our support options on our website . ================================================ FILE: LICENSE.md ================================================ # Slint License You can use Slint under ***any*** of the following licenses, at your choice: 1. [Royalty-free License](LICENSES/LicenseRef-Slint-Royalty-free-2.0.md) - Permits use in **proprietary** desktop, mobile, and web applications **at no cost**. Use in embedded systems is excluded. 2. [GNU GPLv3](LICENSES/GPL-3.0-only.txt) - Permits use in **open source software** under GPL-compatible terms, **at no cost**, for desktop, mobile, and web applications, as well as for embedded systems. 3. [Commercial license](LICENSES/LicenseRef-Slint-Software-3.0.md) - Permits use in **proprietary** applications, including desktop, mobile, web, and embedded systems. Third party licenses listed in the `LICENSES` folder also apply to parts of the product. ## Definitions A ***Desktop Application*** is a computer program that is designed to run on a general-purpose computer (PC or notebook), typically installed and executed locally on the computer's operating system. A ***Mobile Application*** is a computer program that is designed to run on a general-purpose mobile computer (mobile phone or tablet), typically installed and executed locally on the computer's operating system. A ***Web Application*** is a computer program that is designed to run in the sandbox environment provided by a web browser. An ***Embedded System*** is a computer system designed to perform a specific task within a larger mechanical or electrical system. ## Additional Info See the [Slint licensing options on the website](https://slint.dev/pricing.html) and the [Licensing FAQ](FAQ.md#licensing). Contact us at [info@slint.dev](mailto:info@slint.dev) if you have any questions regarding licensing. ================================================ FILE: LICENSES/Apache-2.0.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSES/CC-BY-2.0.txt ================================================ Creative Commons Attribution 2.0 CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. License THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 1. Definitions a. "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the Work in its entirety in unmodified form, along with a number of other contributions, constituting separate and independent works in themselves, are assembled into a collective whole. A work that constitutes a Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this License. b. "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which the Work may be recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered a Derivative Work for the purpose of this License. c. "Licensor" means the individual or entity that offers the Work under the terms of this License. d. "Original Author" means the individual or entity who created the Work. e. "Work" means the copyrightable work of authorship offered under the terms of this License. f. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. 2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws. 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: a. to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the Work as incorporated in the Collective Works; b. to create and reproduce Derivative Works; c. to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission the Work including as incorporated in Collective Works; d. to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission Derivative Works. e. For the avoidance of doubt, where the work is a musical composition: i. Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to collect, whether individually or via a performance rights society (e.g. ASCAP, BMI, SESAC), royalties for the public performance or public digital performance (e.g. webcast) of the Work. ii. Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to collect, whether individually or via a music rights agency or designated agent (e.g. Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover version") and distribute, subject to the compulsory license created by 17 USC Section 115 of the US Copyright Act (or the equivalent in other jurisdictions). f. Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is a sound recording, Licensor waives the exclusive right to collect, whether individually or via a performance-rights society (e.g. SoundExchange), royalties for the public digital performance (e.g. webcast) of the Work, subject to the compulsory license created by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions). The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved. 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: a. You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any reference to such Licensor or the Original Author, as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any reference to such Licensor or the Original Author, as requested. b. If you distribute, publicly display, publicly perform, or publicly digitally perform the Work or any Derivative Works or Collective Works, You must keep intact all copyright notices for the Work and give the Original Author credit reasonable to the medium or means You are utilizing by conveying the name (or pseudonym if applicable) of the Original Author if supplied; the title of the Work if supplied; to the extent reasonably practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). Such credit may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear where any other comparable authorship credit appears and in a manner at least as prominent as such other comparable authorship credit. 5. Representations, Warranties and Disclaimer UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. Termination a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works or Collective Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 8. Miscellaneous a. Each time You distribute or publicly digitally perform the Work or a Collective Work, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. b. Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, neither party will use the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. Creative Commons may be contacted at http://creativecommons.org/. ================================================ FILE: LICENSES/CC-BY-4.0.txt ================================================ Creative Commons Attribution 4.0 International Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. Using Creative Commons Public Licenses Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors. Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public. Creative Commons Attribution 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 – Definitions. a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 – Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: A. reproduce and Share the Licensed Material, in whole or in part; and B. produce, reproduce, and Share Adapted Material. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section 6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 5. Downstream recipients. A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. Section 3 – License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Material (including in modified form), You must: A. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. Section 4 – Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. Section 5 – Disclaimer of Warranties and Limitation of Liability. a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. Section 6 – Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. Section 7 – Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. Section 8 – Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org. ================================================ FILE: LICENSES/CC-BY-ND-4.0.txt ================================================ Creative Commons Attribution-NoDerivatives 4.0 International Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. Using Creative Commons Public Licenses Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors. Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public. Creative Commons Attribution-NoDerivatives 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NoDerivatives 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 – Definitions. a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. c. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. d. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. e. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. f. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. g. Licensor means the individual(s) or entity(ies) granting rights under this Public License. h. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. i. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. j. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 – Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: A. reproduce and Share the Licensed Material, in whole or in part; and B. produce and reproduce, but not Share, Adapted Material. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section 6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 5. Downstream recipients. A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. Section 3 – License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Material, You must: A. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. For the avoidance of doubt, You do not have permission under this Public License to Share Adapted Material. 3. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 4. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. Section 4 – Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database, provided You do not Share Adapted Material; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. Section 5 – Disclaimer of Warranties and Limitation of Liability. a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. Section 6 – Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. Section 7 – Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. Section 8 – Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org. ================================================ FILE: LICENSES/CC-BY-SA-3.0.txt ================================================ Creative Commons Attribution-ShareAlike 3.0 Unported CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. License THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 1. Definitions a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License. c. "Creative Commons Compatible License" means a license that is listed at http://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. d. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. e. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. f. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. g. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. h. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. i. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. j. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. k. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, d. to Distribute and Publicly Perform Adaptations. e. For the avoidance of doubt: i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. b. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. d. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 5. Representations, Warranties and Disclaimer UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. Termination a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 8. Miscellaneous a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. Creative Commons Notice Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License. Creative Commons may be contacted at http://creativecommons.org/. ================================================ FILE: LICENSES/CC-BY-SA-4.0.txt ================================================ Attribution-ShareAlike 4.0 International ======================================================================= Creative Commons Corporation ("Creative Commons") is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an "as-is" basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. Using Creative Commons Public Licenses Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC- licensed material, or material used under an exception or limitation to copyright. More considerations for licensors: wiki.creativecommons.org/Considerations_for_licensors Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor's permission is not necessary for any reason--for example, because of any applicable exception or limitation to copyright--then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More_considerations for the public: wiki.creativecommons.org/Considerations_for_licensees ======================================================================= Creative Commons Attribution-ShareAlike 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 -- Definitions. a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. c. BY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License. d. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. e. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. f. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. g. License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike. h. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. i. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. j. Licensor means the individual(s) or entity(ies) granting rights under this Public License. k. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. l. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. m. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 -- Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: a. reproduce and Share the Licensed Material, in whole or in part; and b. produce, reproduce, and Share Adapted Material. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section 6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a) (4) never produces Adapted Material. 5. Downstream recipients. a. Offer from the Licensor -- Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. b. Additional offer from the Licensor -- Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter's License You apply. c. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. Section 3 -- License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Material (including in modified form), You must: a. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; b. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and c. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. b. ShareAlike. In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. 1. The Adapter's License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License. 2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. 3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. Section 4 -- Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. Section 5 -- Disclaimer of Warranties and Limitation of Liability. a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. Section 6 -- Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. Section 7 -- Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. Section 8 -- Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. ======================================================================= Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the "Licensor." Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark "Creative Commons" or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org. ================================================ FILE: LICENSES/CC-PDDC.txt ================================================ The person or persons who have associated work with this document (the "Dedicator" or "Certifier") hereby either (a) certifies that, to the best of his knowledge, the work of authorship identified is in the public domain of the country from which the work is published, or (b) hereby dedicates whatever copyright the dedicators holds in the work of authorship identified below (the "Work") to the public domain. A certifier, moreover, dedicates any copyright interest he may have in the associated work, and for these purposes, is described as a "dedicator" below. A certifier has taken reasonable steps to verify the copyright status of this work. Certifier recognizes that his good faith efforts may not shield him from liability if in fact the work certified is not in the public domain. Dedicator makes this dedication for the benefit of the public at large and to the detriment of the Dedicator's heirs and successors. Dedicator intends this dedication to be an overt act of relinquishment in perpetuity of all present and future rights under copyright law, whether vested or contingent, in the Work. Dedicator understands that such relinquishment of all rights includes the relinquishment of all rights to enforce (by lawsuit or otherwise) those copyrights in the Work. Dedicator recognizes that, once placed in the public domain, the Work may be freely reproduced, distributed, transmitted, used, modified, built upon, or otherwise exploited by anyone for any purpose, commercial or non-commercial, and in any way, including by methods that have not yet been invented or conceived. ================================================ FILE: LICENSES/GPL-3.0-only.txt ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright © 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. “This License” refers to version 3 of the GNU General Public License. “Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. “The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. A “covered work” means either the unmodified Program or a work based on the Program. To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. “Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. “Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: LICENSES/LicenseRef-DejaVu-Font.txt ================================================ Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) Bitstream Vera Fonts Copyright ------------------------------ Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. Arev Fonts Copyright ------------------------------ Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr. TeX Gyre DJV Math ----------------- Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski (on behalf of TeX users groups) are in public domain. Letters imported from Euler Fraktur from AMSfonts are (c) American Mathematical Society (see below). Bitstream Vera Fonts Copyright Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license (“Fonts”) and associated documentation files (the “Font Software”), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words “Bitstream” or the word “Vera”. This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the “Bitstream Vera” names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of GNOME, the GNOME Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the GNOME Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. AMSFonts (v. 2.2) copyright The PostScript Type 1 implementation of the AMSFonts produced by and previously distributed by Blue Sky Research and Y&Y, Inc. are now freely available for general use. This has been accomplished through the cooperation of a consortium of scientific publishers with Blue Sky Research and Y&Y. Members of this consortium include: Elsevier Science IBM Corporation Society for Industrial and Applied Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS) In order to assure the authenticity of these fonts, copyright will be held by the American Mathematical Society. This is not meant to restrict in any way the legitimate use of the fonts, such as (but not limited to) electronic distribution of documents containing these fonts, inclusion of these fonts into other public domain or commercial font collections or computer applications, use of the outline data to create derivative fonts and/or faces, etc. However, the AMS does require that the AMS copyright notice be removed from any derivative versions of the fonts which have been altered in any way. In addition, to ensure the fidelity of TeX documents using Computer Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces, has requested that any alterations which yield different font metrics be given a different name. $Id$ ================================================ FILE: LICENSES/LicenseRef-Slint-Royalty-free-2.0.md ================================================ # Slint Royalty-free Desktop, Mobile, and Web Applications License Version 2.0 ## Preamble Slint is a toolkit that can be used to build user interfaces for applications. Slint (hereafter referred to as **Software**) is made available under different licenses by SixtyFPS GmbH incorporated at Oranienburger Str. 44, 16540 Hohen Neuendorf, Germany (**SixtyFPS**). The **Slint Royalty-free Desktop, Mobile, and Web Applications License** is suitable for those who develop desktop, mobile, or web applications and do not want to use open source components under copyleft licenses. ## 1. Grant of Rights SixtyFPS hereby grants You a world-wide, royalty-free, non-exclusive license to use, reproduce, make available, modify, display, perform, distribute the Software as part of a Desktop, Mobile, or Web Application. A **Desktop Application** is a computer program that is designed to run on a general-purpose computer (PC or notebook), typically installed and executed locally on the computer's operating system. A **Mobile Application** is a computer program that is designed to run on a general-purpose mobile computer (mobile phone or tablet), typically installed and executed locally on the computer's operating system. A **Web Application** is a computer program that is designed to run in the sandbox environment provided by a web browser. Desktop Application, Mobile Application, and Web Application are hereafter referred to as **Application**. ## 2. License Conditions - Attribution You may distribute the Software as part of an Application, modified or unmodified, provided that You do either of the following: (a) Display the [`AboutSlint`](https://docs.slint.dev/latest/docs/slint/reference/std-widgets/misc/aboutslint/) widget in an "About" screen or dialog that is accessible from the top level menu of the Application. In the absence of such a screen or dialog, display the widget in the "Splash Screen" of the Application. (b) Display the [Slint attribution badge](https://github.com/slint-ui/slint/tree/master/logo/MadeWithSlint-logo-whitebg.png) on a public webpage, preferably where the binaries of your Application can be downloaded from, in such a way that it can be easily found by any visitor to that page. ## 3. Limitations The License does not permit to distribute or make the Software publicly available alone and without integration into an Application. For this purpose you may use the Software under the GNU General Public License, version 3. The License does not permit the use of the Software within Embedded Systems. An **Embedded System** is a computer system designed to perform a specific task within a larger mechanical or electrical system. The License does not permit the distribution of Application that exposes the APIs, in part or in total, of the Software. You may not remove or alter any license notices (including copyright notices, disclaimers of warranty, or limitations of liability) contained within the source code form of the Software. ## 4. Warranty and Liability SixtyFPS is only liable for conflicting rights of third parties if SixtyFPS was aware of these rights without informing you. Unless required by applicable law or agreed to in writing, SixtyFPS provides the Software on an "as is" basis, without warranties or conditions of any kind, either express or implied, including, without limitation, any warranties or conditions of merchantability, or fitness for a particular purpose. Unless required by law, SixtyFPS won't be liable for any direct, indirect, incidental, or consequential damages arising in any way out of the use of the Software. ================================================ FILE: LICENSES/LicenseRef-Slint-Software-3.0.md ================================================ # Slint Software License Version 3.0.5 ## Preamble Slint is a toolkit that can be used to build user interfaces for applications. Slint (hereafter referred to as **Software**) is made available under different licenses by SixtyFPS GmbH incorporated at Oranienburger Str. 44, 16540 Hohen Neuendorf, Germany (**SixtyFPS**). The **Slint Software License** is suitable for those who do not want to use open source components under copyleft licenses. ## 1. Grant of Rights SixtyFPS hereby grants You a world-wide, non-exclusive license to use, reproduce, make available, modify, display, perform, distribute the Software as part of a Desktop, Mobile, or Web Application or as part of an Embedded System (each of which is defined below). A **Desktop Application** is a computer program that is designed to run on a general-purpose computer (PC or notebook), typically installed and executed locally on the computer's operating system. A **Mobile Application** is a computer program that is designed to run on a general-purpose mobile computer (mobile phone or tablet), typically installed and executed locally on the computer's operating system. A **Web Application** is a computer program that is designed to run in the sandbox environment provided by a web browser. An **Embedded System** is a computer system designed to perform a specific task within a larger mechanical or electrical system. Desktop Application, Mobile Application, and Web Application are hereafter referred to as **Application**. ## 2. License Conditions The grant of rights in section 1 are conditional, provided that You do all of the following: (a) You have purchased an appropriate **Paid License Plan** ([see Annex 1](#annex-1-paid-license-plans)) and the required amount of seats to cover all individual users of the Software associated with the designing, developing, or testing your Application or Embedded System. For clarity, each individual user is counted as one seat. (b) In the case that You are distributing the Software as part of an Embedded System, You have purchased an appropriate quantity of **Royalties**, one Royalty for each Embedded System. Royalties become due and payable upon manufacture of the Embedded System, regardless of whether such is subsequently sold, shipped, returned, replaced under warranty, or recalled. Payment of royalties is non-refundable under any circumstances. Royalties are not necessary for non-commercial projects, personal projects, and open source projects. ## 3. Limitations The License does not permit to distribute or make the Software publicly available alone and without integration into an Application or into an Embedded System. For this purpose you may use the Software under the GNU General Public License, version 3. The License is limited to only the versions of Software that were made available to you under the Paid License Plan. For all other versions, you may use the Software under either the GNU General Public License, version 3 or the Slint Royalty-free Desktop, Mobile, and Web Applications License. The License does not permit the distribution of Application that exposes the APIs, in part or in total, of the Software. You may not remove or alter any license notices (including copyright notices, disclaimers of warranty, or limitations of liability) contained within the source code form of the Software. ## 4. Audit Rights SixtyFPS or an independent certified auditor on SixtyFPS's behalf, may, upon its reasonable request, with 30 (thirty) days written notice, and at its sole expense, examine your books and records solely with respect to your use of the Software. Any such audit shall be conducted during regular business hours at your facilities and shall not unreasonably interfere with your business activities. The auditor shall not remove, copy, or redistribute any electronic material during an audit. If an audit reveals that you are using the Software in a way that is in material violation of the terms of this License, then you shall pay SixtyFPS reasonable costs of conducting the audit. The auditor shall only be allowed to report violations of the terms of this License, with a copy to you. You shall be provided the right to provide comments to the report before it is finalized. ## 5. Termination (a) SixtyFPS may terminate this License if You materially breach any obligation hereunder, provided You have been provided notice of such breach and an opportunity to cure such breach during a period of not less than sixty (60) days following such notice. (b) You may terminate this License with or without cause upon no less than thirty (30) days advance written notice to SixtyFPS. (c) Upon termination of this License, You will immediately cease using, reproducing, making available, modifying, displaying, performing, distributing the Software and pay immediately any unpaid Fees and contractual penalties. (d) Sections 3 through 8 of this License will survive any termination of the License to the extent necessary to implement their objectives. ## 6. Assignment You may assign this License, in whole or in part (whether by operation of law or otherwise), with prior consent from SixtyFPS, which shall not be unreasonably withheld or delayed. SixtyFPS may assign any of its rights or delegate any of its obligations hereunder with prior notice to You, provided that the successor maintains at least the same level of security, confidentiality, and data protection measures as in place at the time of assignment or delegation. Any attempt to assign this License other than in accordance with this Section 6 shall be null and void. ## 7. Severability In the event that any provision of this License will, for any reason, be determined by any court of competent jurisdiction to be invalid, illegal or unenforceable in any respect, such invalidity, illegality or unenforceability will be interpreted as closely as possible so as not affect any other provision of this License, and such provision will further be modified by said court to permit its enforcement to the maximum extent permitted by law. ## 8. Governing Law This Agreement shall be construed, interpreted, and governed by the laws of the Federal Republic of Germany. ## Annex 1: Paid License Plans ### Enterprise Plan The following is included as part of the plan (a) No restriction on the number of applications that are developed with Slint. (b) Live Preview. (c) Standard Support that includes addressing technical queries, troubleshooting, and rectifying bugs or errors (faults) present in the latest official stable release. (d) Perpetual Fallback License that allows continued use of a specific Slint version, including all bugfix updates (i.e., all Z releases within the X.Y.Z version), without an active subscription. This license applies only to those versions of Slint for which at least 12 consecutive months of subscription have been paid. (e) GUI Test Framework. ### Small Enterprise Plan This plan is limited to individual companies with a staff headcount between 10 and 50 and either a turnover or balance sheet total of 10 million EUR or less. If You are a Small Enterprise, You are required to submit the self-assessment report generated from the EU SME Self-Assessment Tool (https://ec.europa.eu/info/funding-tenders/opportunities/portal/sme/public/organisation-name). The following is included as part of the plan (a) No restriction on the number of applications that are developed with Slint. (b) Live Preview. (c) Standard Support that includes addressing technical queries, troubleshooting, and rectifying bugs or errors (faults) present in the latest official stable release. The following can be purchased as an Add-On (a) Perpetual Fallback License that allows continued use of a specific Slint version, including all bugfix updates (i.e., all Z releases within the X.Y.Z version), without an active subscription. This license applies only to those versions of Slint for which at least 12 consecutive months of subscription have been paid. (b) GUI Test Framework. ### Startup & Individual Plan This plan is limited to individuals and individual companies with a staff headcount of less than 10 and either a turnover or balance sheet total of 2 million EUR or less. If You are a Startup, you are required to submit the self-assessment report generated from the EU SME Self-Assessment Tool (https://ec.europa.eu/info/funding-tenders/opportunities/portal/sme/public/organisation-name). The following is included as part of the plan (a) No restriction on the number of applications that are developed with Slint. (b) Live Preview. The following can be purchased as an Add-On (a) Standard Support that includes addressing technical queries, troubleshooting, and rectifying bugs or errors (faults) present in the latest official stable release. (b) Perpetual Fallback License that allows continued use of a specific Slint version, including all bugfix updates (i.e., all Z releases within the X.Y.Z version), without an active subscription. This license applies only to those versions of Slint for which at least 12 consecutive months of subscription have been paid. (c) GUI Test Framework. ================================================ FILE: LICENSES/LicenseRef-qskinny.txt ================================================ QSkinny License Version 1.0, November 1, 2016 QSkinny is Copyright (C) 2016 Uwe Rathmann You may use, distribute and copy QSkinny under the terms of GNU Lesser General Public License version 2.1, which is displayed below with the following exceptions: 1. The object code form of a "work that uses the Library" may incorporate material from a header file that is part of the Library. You may distribute such object code under terms of your choice, provided that: (i) the header files of the Library have not been modified; and (ii) the incorporated material is limited to numerical parameters, data structure layouts, accessors, macros, inline functions and templates; and (iii) you comply with the terms of Section 6 of the GNU Lesser General Public License version 2.1. ------------------------------------------------------------------------- GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! ================================================ FILE: LICENSES/MIT.txt ================================================ MIT License Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: LICENSES/OFL-1.1-RFN.txt ================================================ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. ================================================ FILE: LICENSES/OFL-1.1.txt ================================================ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. ================================================ FILE: LICENSES/Unlicense.txt ================================================ This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ================================================ FILE: README.md ================================================ ![Slint](./logo/slint-logo-full-light.svg#gh-light-mode-only) ![Slint](./logo/slint-logo-full-dark.svg#gh-dark-mode-only) [![Build Status](https://github.com/slint-ui/slint/workflows/CI/badge.svg)](https://github.com/slint-ui/slint/actions) [![REUSE status](https://api.reuse.software/badge/github.com/slint-ui/slint)](https://api.reuse.software/info/github.com/slint-ui/slint) [![Discussions](https://img.shields.io/github/discussions/slint-ui/slint)](https://github.com/slint-ui/slint/discussions) **Slint** is an open-source declarative GUI toolkit for building native user interfaces for embedded systems, desktops, and mobile platforms. Write your UI once in `.slint`, a simple markup language. Connect it to business logic written in Rust, C++, JavaScript, or Python. ## Why Slint? The name *Slint* is derived from our design goals: - **Scalable**: Slint should support responsive UI design, allow cross-platform usage across operating systems and processor architectures and support multiple programming languages. - **Lightweight**: Slint should require minimal resources, in terms of memory and processing power, and yet deliver a smooth, smartphone-like user experience on any device. - **Intuitive**: Designers and developers should feel productive while enjoying the GUI design and development process. The design creation tools should be intuitive to use for the designers. Similarly for the developers, the APIs should be consistent and easy to use, no matter which programming language they choose. - **Native**: GUI built with Slint should match the end users' expectations of a native application irrespective of the platform - desktop, mobile, web or embedded system. The UI design should be compiled to machine code and provide flexibility that only a native application can offer: Access full operating system APIs, utilize all CPU and GPU cores, connect to any peripheral. Beyond the design goals, here’s what makes Slint stand out: - **Independent UI Design**: Use a declarative language similar to separate your UI from business logic. Designers can work in parallel with developers. - **Tooling**: Iterate quickly with our Live Preview & editor integrations. Integrate from Figma with the [Figma to Slint plugin](https://www.figma.com/community/plugin/1474418299182276871/figma-to-slint). - **Stable APIs**: Slint follows a stable 1.x API. We evolve carefully without breaking your code. See what others have built: [#MadeWithSlint](https://madewithslint.com) ## Examples ### Embedded | RaspberryPi | STM32 | RP2040 | | ------------------------------------ | ----------------------------- | ------------------------------ | | [Video of Slint on Raspberry Pi][#1] | [Video of Slint on STM32][#2] | [Video of Slint on RP2040][#3] | ### Desktop | Windows | macOS | Linux | | ------------------------------------------- | ----------------------------------------- | ----------------------------------------- | | ![Screenshot of the Gallery on Windows][#4] | ![Screenshot of the Gallery on macOS][#5] | ![Screenshot of the Gallery on Linux][#6] | ### Web using WebAssembly | Printer Demo | Slide Puzzle | Energy Monitor | Widget Gallery | Weather demo | | ------------------------------------------- | -------------------------------------------- | ---------------------------------------------------- | --------------------------------------------- | --------------------------------------------- | | [![Screenshot of the Printer Demo][#7]][#8] | [![Screenshot of the Slide Puzzle][#9]][#10] | [![Screenshot of the Energy Monitor Demo][#11]][#12] | [![Screenshot of the Gallery Demo][#13]][#14] | [![Screenshot of the weather Demo][#29]][#30] | More examples and demos in the [examples folder](examples#examples) ## Get Started ### Hello World The UI is defined in a Domain Specific Language that is declarative, easy to use, intuitive, and provides a powerful way to describe graphical elements, their placement, their hierarchy, property bindings, and the flow of data through the different states. Here's the obligatory "Hello World": ```slint export component HelloWorld inherits Window { width: 400px; height: 400px; Text { y: parent.width / 2; x: parent.x + 200px; text: "Hello, world"; color: blue; } } ``` ### Documentation For more details, check out the [Slint Language Documentation](https://slint.dev/docs/slint). The [examples](examples) folder contains examples and demos, showing how to use the Slint markup language and how to interact with a Slint user interface from supported programming languages. The `docs` folder contains a lot more information, including [build instructions](docs/building.md), and [internal developer docs](docs/development.md). Refer to the README of each language directory in the `api` folder: - [C++](api/cpp) ([Documentation][#15] | [Getting Started Template][#17]) - [Rust](api/rs/slint) [![Crates.io][#18]][#19] ([Documentation][#20] | [Tutorial Video][#22] | [Getting Started Template][#23]) - [JavaScript/NodeJS (Beta)](api/node) [![npm][#24]][#25] ([Documentation][#26] | [Getting Started Template][#28]) - [Python (Beta)](api/python/slint) [![pypi][#31]][#32] ([Documentation][#33] | [Getting Started Template][#34]) ## Architecture An application is composed of the business logic written in Rust, C++, or JavaScript and the `.slint` user interface design markup, which is compiled to native code. ![Architecture Overview](https://slint.dev/resources/architecture.drawio.svg) ### Compiler The `.slint` files are compiled ahead of time. The expressions in the `.slint` are pure functions that the compiler can optimize. For example, the compiler could choose to "inline" properties and remove those that are constant or unchanged. The compiler uses the typical compiler phases of lexing, parsing, optimization, and finally code generation. It provides different back-ends for code generation in the target language. The C++ code generator produces a C++ header file, the Rust generator produces Rust code, and so on. An interpreter for dynamic languages is also included. ### Runtime The runtime library consists of an engine that supports properties declared in the `.slint` language. Components with their elements, items, and properties are laid out in a single memory region, to reduce memory allocations. Rendering backends and styles are configurable at compile time: - The `femtovg` renderer uses OpenGL ES 2.0 for rendering. - The `skia` renderer uses [Skia](https://skia.org) for rendering. - The `software` renderer uses the CPU with no additional dependencies. NOTE: When Qt is installed on the system, the `qt` style becomes available, using Qt's QStyle to achieve native looking widgets. ### Tooling We have a few tools to help with the development of .slint files: - A [**LSP Server**](./tools/lsp) that adds features like auto-complete and live preview of the .slint files to many editors. - It is bundled in a [**Visual Studio Code Extension**](./editors/vscode) available from the market place. - A [**slint-viewer**](./tools/viewer) tool which displays the .slint files. The `--auto-reload` argument makes it easy to preview your UI while you are working on it (when using the LSP preview is not possible). - [**SlintPad**](https://slintpad.com/), an online editor to try out .slint syntax without installing anything ([sources](./tools/slintpad)). - A [**Figma to Slint**](https://www.figma.com/community/plugin/1474418299182276871/figma-to-slint) plugin. Please check our [Editors README](./editors/README.md) for tips on how to configure your favorite editor to work well with Slint. ## License You can use Slint under ***any*** of the following licenses, at your choice: 1. Build proprietary desktop, mobile, or web applications for free with the [Royalty-free License](LICENSES/LicenseRef-Slint-Royalty-free-2.0.md), 2. Build open source embedded, desktop, mobile, or web applications for free with the [GNU GPLv3](LICENSES/GPL-3.0-only.txt), 3. Build proprietary embedded, desktop, mobile, or web applications with the [Paid license](LICENSES/LicenseRef-Slint-Software-3.0.md). See the [Slint licensing options on the website](https://slint.dev/pricing.html) and the [Licensing FAQ](FAQ.md#licensing). ## Contributions We welcome your contributions: in the form of code, bug reports or feedback. For contribution guidelines see [CONTRIBUTING.md](CONTRIBUTING.md). ## Frequently Asked Questions Please see our separate [FAQ](FAQ.md). ## About us (SixtyFPS GmbH) We are passionate about software - API design, cross-platform software development and user interface components. Our aim is to make developing user interfaces fun for everyone: from Python, JavaScript, C++, or Rust developers all the way to UI/UX designers. We believe that software grows organically and keeping it open source is the best way to sustain that growth. Our team members are located remotely in Germany, Finland, and US. ### Stay up to date - Follow [@slint_ui](https://twitter.com/slint_ui) on X/Twitter. - Follow [@slint@fosstodon.org](https://mastodon.social/@slint@fosstodon.org) on Mastodon. - Follow [@slint-ui](https://www.linkedin.com/company/slint-ui/) on LinkedIn. - Follow [@slint.dev](https://bsky.app/profile/slint.dev) on Bluesky - Subscribe to our [YouTube channel](https://www.youtube.com/@Slint-UI) ### Contact us Feel free to join [Github discussions](https://github.com/slint-ui/slint/discussions) for general chat or questions. Use [Github issues](https://github.com/slint-ui/slint/issues) to report public suggestions or bugs. We chat in [our Mattermost instance](https://chat.slint.dev) where you are welcome to listen in or ask your questions. You can of course also contact us privately via email to [info@slint.dev](mailto://info@slint.dev). [#1]: https://www.youtube.com/watch?v=_BDbNHrjK7g [#2]: https://www.youtube.com/watch?v=NNNOJJsOAis [#3]: https://www.youtube.com/watch?v=dkBwNocItGs [#4]: https://slint.dev/resources/gallery_win_screenshot.png "Gallery" [#5]: https://slint.dev/resources/gallery_mac_screenshot.png "Gallery" [#6]: https://slint.dev/resources/gallery_linux_screenshot.png "Gallery" [#7]: https://slint.dev/resources/printerdemo_screenshot.png "Printer Demo" [#8]: https://slint.dev/demos/printerdemo/ [#9]: https://slint.dev/resources/puzzle_screenshot.png "Slide Puzzle" [#10]: https://slint.dev/demos/slide_puzzle/ [#11]: https://slint.dev/resources/energy-monitor-screenshot.png "Energy Monitor Demo" [#12]: https://slint.dev/demos/energy-monitor/ [#13]: https://slint.dev/resources/gallery_screenshot.png "Gallery Demo" [#14]: https://slint.dev/demos/gallery/ [#15]: https://slint.dev/latest/docs/cpp [#17]: https://github.com/slint-ui/slint-cpp-template [#18]: https://img.shields.io/crates/v/slint [#19]: https://crates.io/crates/slint [#20]: https://slint.dev/latest/docs/rust/slint/ [#22]: https://youtu.be/WBcv4V-whHk [#23]: https://github.com/slint-ui/slint-rust-template [#24]: https://img.shields.io/npm/v/slint-ui [#25]: https://www.npmjs.com/package/slint-ui [#26]: https://slint.dev/latest/docs/node [#28]: https://github.com/slint-ui/slint-nodejs-template [#29]: ./demos/weather-demo/docs/img/desktop-preview.png "Weather Demo" [#30]: https://slint.dev/demos/weather-demo/ [#31]: https://img.shields.io/pypi/v/slint [#32]: https://pypi.org/project/slint/ [#33]: http://snapshots.slint.dev/master/docs/python/ [#34]: https://github.com/slint-ui/slint-python-template ================================================ FILE: REUSE.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 version = 1 SPDX-PackageName = "slint" SPDX-PackageSupplier = "Slint Developers " SPDX-PackageDownloadLocation = "https://slint.dev/" [[annotations]] path = [ "**/MadeWithSlint-**.pdf", "**/MadeWithSlint-**.png", "**/MadeWithSlint-**.svg", "**/slint-icon-**.pdf", "**/slint-icon-**.png", "**/slint-icon-**.svg", "**/slint-logo-**.pdf", "**/slint-logo-**.png", "**/slint-logo-**.svg", "docs/astro/src/content/docs/reference/elements/slint-logo.png", "logo/slint-logo.icns", "logo/README.md", "**/public/favicon.svg", "**/public/slint-logo-small-light.svg", "**/public/slint-logo.woff", "**/public/apple-touch-icon.png", "**/public/favicon.ico", "**/public/favicon-32x32.png", "**/public/favicon-16x16.png", "**/public/tablet-material.webp", "ui-libraries/material/examples/gallery/frame-tablet.webp", ] precedence = "aggregate" SPDX-FileCopyrightText = "Copyright © SixtyFPS GmbH " SPDX-License-Identifier = "CC-BY-ND-4.0" [[annotations]] path = [ "**/.eslintrc.yml", "**/.gitignore", "**/.npmignore", "**/.vscodeignore", ".dockerignore", ".gitattributes", ".gitignore", ".mailmap", ".vscode/**", "editors/vscode/tests/grammar/*.slint", "Cargo.lock", "flake.nix", "flake.lock", "examples/bevy/Cargo.lock", "examples/servo/Cargo.lock", "REUSE.toml", "biome.json", "knip.json", "cspell.json", "docs/search/scraper-config.json", "internal/core-macros/link-data.json", "internal/**.md", "package.json", "pnpm-lock.yaml", "rustfmt.toml", "*.md", ] precedence = "aggregate" SPDX-FileCopyrightText = "Copyright © SixtyFPS GmbH " SPDX-License-Identifier = "GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0" [[annotations]] path = [ ".github/ISSUE_TEMPLATE/4-blank.md", ".github/ISSUE_TEMPLATE/3-tracking-issue.md", ".github/pull_request_template.md", "api/cpp/docs/**/**.css", "api/cpp/docs/**/**.md", "api/cpp/docs/**/**.html", "api/cpp/tests/redpixel.png", "demos/**.md", "demos/**.json", "demos/**/LC_MESSAGES/**.mo", "demos/**/README", "demos/**/esp-idf/partitions.csv", "demos/**/sdkconfig**", "demos/energy-monitor/ui/assets/arrow-left.svg", "demos/energy-monitor/ui/assets/arrow-right.svg", "demos/energy-monitor/ui/assets/check.svg", "demos/energy-monitor/ui/assets/cloud.svg", "demos/energy-monitor/ui/assets/cloudy.svg", "demos/energy-monitor/ui/assets/dashboard.svg", "demos/energy-monitor/ui/assets/information.svg", "demos/energy-monitor/ui/assets/settings.svg", "demos/energy-monitor/ui/assets/sunny.svg", "demos/home-automation/ui/images/**.jpg", "demos/home-automation/ui/images/**.png", "demos/home-automation/ui/images/**.svg", "demos/**/zephyr/VERSION", "demos/weather-demo/docs/**.png", "demos/weather-demo/index.html", "docs/**.json", "docs/**.mjs", "docs/**.md", "docs/**.mdx", "docs/**/**.css", "docs/**/**.html", "docs/**/book.toml", "docs/**/package.json", "docs/public/**.svg", "docs/astro/src/assets/**.svg", "docs/astro/src/assets/**.webp", "docs/astro/src/assets/getting-started/**.webp", "docs/astro/src/assets/guide/tooling/**.webp", "docs/astro/src/content/docs/reference/elements/mini-banner.png", "docs/astro/src/misc/**.json", "docs/astro/src/misc/**.jsonc", "examples/**.json", "examples/**.md", "examples/**/LC_MESSAGES/**.mo", "examples/**/README", "examples/**/README.txt", "examples/**/esp-idf/**/partitions.csv", "examples/**/esp-idf/partitions.csv", "examples/**/sdkconfig**", "examples/cpp/qt_viewer/interface.ui", "examples/dial/images/**.png", "examples/fancy-switches/images/**.png", "examples/fancy-switches/images/**.svg", "examples/orbit-animation/images/**.png", "examples/orbit-animation/images/**.svg", "examples/sprite-sheet/images/**.png", "examples/speedometer/needle.png", "demos/printerdemo/ui/images/action-button-frame.png", "demos/printerdemo/ui/images/mcu/combo-active-background.png", "demos/printerdemo/ui/images/mcu/copy-button.jpg", "demos/printerdemo/ui/images/mcu/place-document.jpg", "demos/printerdemo/ui/images/mcu/popup.jpg", "demos/printerdemo/ui/images/mcu/scan-button.jpg", "demos/printerdemo/ui/images/mcu/sub-action-button.jpg", "demos/printerdemo/ui/images/mcu/switch-active.png", "helper_crates/**.md", "ui-libraries/material/**.md", "ui-libraries/material/**.mdx", "ui-libraries/material/docs/**.json", "ui-libraries/material/docs/src/assets/styles/**.css", "ui-libraries/material/docs/src/assets/**.svg", "ui-libraries/material/docs/src/content/docs/components/icons/**.svg", "ui-libraries/material/docs/src/assets/**.webp", "ui-libraries/material/.gitignore", "ui-libraries/material/docs/src/assets/images/Components.png", "ui-libraries/material/docs/src/assets/images/phone-dark.webp", "ui-libraries/material/docs/src/assets/images/material-tablet.webp", "ui-libraries/material/docs/src/assets/images/material-award.jpg", "ui-libraries/material/docs/src/assets/images/material-design.jpg", "ui-libraries/material/docs/src/config.yaml", "ui-libraries/material/examples/gallery/ui/themes/**.json", ] precedence = "aggregate" SPDX-FileCopyrightText = "Copyright © SixtyFPS GmbH " SPDX-License-Identifier = "MIT" [[annotations]] path = [ "api/**.md", "api/**.hbs", "api/**/**.json", "api/node/__test__/resources/**.png", "CMakePresets.json", "editors/**.md", "editors/sublime/LSP.sublime-settings", "editors/tree-sitter-slint/corpus/**.txt", "editors/tree-sitter-slint/tree-sitter.json", "editors/vscode/**.json", "editors/vscode/css/**.css", "editors/vscode/tests/grammar/**.slint", "tests/screenshots/**/**.png", "tools/**.md", "tools/slintpad/**.html", "tools/slintpad/**.json", "tools/slintpad/styles/**.css", "tools/figma-inspector/**.json", "tools/figma-inspector/**.html", "tools/figma-inspector/**.css", ] precedence = "aggregate" SPDX-FileCopyrightText = "Copyright © SixtyFPS GmbH " SPDX-License-Identifier = "GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0" [[annotations]] path = ["demos/weather-demo/ui/assets/icons/**.svg", "examples/memory/icons/**.png", "examples/memory/icons/**.svg"] precedence = "aggregate" SPDX-FileCopyrightText = "Fontawesome project " SPDX-License-Identifier = "CC-BY-4.0" [[annotations]] path = "demos/printerdemo/ui/images/**.svg" precedence = "aggregate" SPDX-FileCopyrightText = "CoreyGinnivan " SPDX-License-Identifier = "Unlicense" [[annotations]] path = ["examples/iot-dashboard/images/**.svg", "examples/iot-dashboard/images/**.png"] precedence = "aggregate" SPDX-FileCopyrightText = "Uwe Rathmann " SPDX-License-Identifier = "LicenseRef-qskinny" [[annotations]] path = [ "tools/lsp/ui/assets/chevron-down.svg", "tools/lsp/ui/assets/filter.svg", "tools/lsp/ui/assets/inspect.svg", "tools/lsp/ui/assets/layout-sidebar**.svg", "tools/lsp/ui/assets/search.svg", "tools/lsp/ui/assets/sync.svg", "tools/lsp/ui/assets/black-square.png", "tools/lsp/ui/assets/close.svg", "tools/lsp/ui/assets/plus.svg", "tools/lsp/ui/assets/info.svg", "tools/lsp/ui/assets/clear.svg", "tools/lsp/ui/assets/beaker.svg", "tools/lsp/ui/assets/library.svg", "tools/lsp/ui/assets/list-tree.svg", "tools/lsp/ui/assets/settings.svg", ] precedence = "aggregate" SPDX-FileCopyrightText = "Codicon Icons " SPDX-License-Identifier = "CC-BY-4.0" [[annotations]] path = "examples/carousel/fonts/**.ttf" precedence = "aggregate" SPDX-FileCopyrightText = "Roboto " SPDX-License-Identifier = "Apache-2.0" [[annotations]] path = "internal/compiler/widgets/fluent/_**.svg" precedence = "aggregate" SPDX-FileCopyrightText = "Copyright © Microsoft Corporation " SPDX-License-Identifier = "MIT" [[annotations]] path = [ "internal/compiler/widgets/material/_**.svg", "component-sets/material/src/ui/icons/**.svg", "component-sets/material/examples/gallery/ui/icons/**.svg", "examples/carousel/icons/**.svg", "internal/compiler/widgets/cupertino/_**.svg", "internal/compiler/widgets/qt/_**.svg", "examples/todo-mvc/assets/**.svg", "demos/usecases/ui/assets/**.svg", ] precedence = "aggregate" SPDX-FileCopyrightText = "Material Icons " SPDX-License-Identifier = "Apache-2.0" [[annotations]] path = "internal/compiler/widgets/cosmic/_**.svg" precedence = "aggregate" SPDX-FileCopyrightText = "\"Cosmic Icons\" by System76 " SPDX-License-Identifier = "CC-BY-SA-4.0" [[annotations]] path = [ "internal/backends/linuxkms/mouse-pointer.svg", "examples/virtual_keyboard/ui/assets/**.svg", "examples/ffmpeg/pause.svg", "examples/ffmpeg/play.svg", "examples/gstreamer-player/**.svg", "examples/uefi-demo/resource/cursor.png", ] precedence = "aggregate" SPDX-FileCopyrightText = "Copyright © 2018 Dave Gandy & Fork Awesome" SPDX-License-Identifier = "MIT" [[annotations]] path = "demos/energy-monitor/ui/assets/spyrosoft-logo.svg" precedence = "aggregate" SPDX-FileCopyrightText = "Copyright © Spyrosoft Solutions GmbH " SPDX-License-Identifier = "CC-BY-4.0" [[annotations]] path = "demos/weather-demo/ui/assets/weathericons-font.ttf" precedence = "aggregate" SPDX-FileCopyrightText = "Weather Icons " SPDX-License-Identifier = "OFL-1.1" [[annotations]] path = ["demos/weather-demo/android-res/**/ic_launcher.png", "demos/weather-demo/ui/assets/felgo-logo.svg"] precedence = "aggregate" SPDX-FileCopyrightText = "Copyright © Felgo GmbH " SPDX-License-Identifier = "CC-BY-ND-4.0" [[annotations]] path = ["ui-libraries/material/docs/src/assets/images/icons/**.svg"] precedence = "aggregate" SPDX-FileCopyrightText = "Copyright (c) 2020-2024 Paweł Kuna " SPDX-License-Identifier = "MIT" [[annotations]] path = ["ui-libraries/material/src/ui/icons/**.svg", "ui-libraries/material/examples/gallery/ui/icons/**.svg"] precedence = "aggregate" SPDX-FileCopyrightText = "Material Icons " SPDX-License-Identifier = "Apache-2.0" [[annotations]] path = ["docs/astro/src/assets/android/**.png"] precedence = "aggregate" SPDX-FileCopyrightText = "Copyright © SixtyFPS GmbH " SPDX-License-Identifier = "CC-BY-4.0" [[annotations]] path = ["examples/bevy/bevy-hosts-slint/**.gltf"] precedence = "aggregate" SPDX-FileCopyrightText = "Copyright (c) 2024 e-verse" SPDX-License-Identifier = "MIT" ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Supported Versions We commit to publishing security updates for the last release of Slint: | Name | Description | URL | | ---- | ----------- | --- | | `slint` Rust crate | Rust crate for Slint API | https://crates.io/crates/slint | | `slint-interpreter` Rust crate | Rust crate for Slint interpreter API | https://crates.io/crates/slint-interpreter | | Slint for C++ | CMake package for Slint | https://github.com/slint-ui/slint/releases/latest | | `slint-ui` NPM package | Node.js API for Slint | https://www.npmjs.com/package/slint-ui | | `slint` Python Package | Python API for Slint | https://pypi.org/project/slint/ | | Slint Visual Studio Code Extension | IDE Integration into VS Code | https://marketplace.visualstudio.com/items?itemName=Slint.slint | | `slint-viewer` | Convenience tool to view `.slint` files from the command line | https://crates.io/crates/slint-viewer \| https://github.com/slint-ui/slint/releases/latest | | `slint-lsp` | Language Server Protocol implementation for `.slint` files | https://crates.io/crates/slint-lsp \| https://github.com/slint-ui/slint/releases/latest | Paid license holders may be eligible for support on additional versions, depending on their specific terms of agreement. ## Reporting a Vulnerability Please report any vulnerabilities via the GitHub Private Vulnerability Disclosure functionality. Check out the [GitHub Instructions](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) on how to submit a report. ================================================ FILE: about.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 accepted = [ "MIT", "Apache-2.0", "Apache-2.0 WITH LLVM-exception", "MPL-2.0", "Zlib", "BSD-2-Clause", "BSD-3-Clause", "CC0-1.0", "BSL-1.0", "ISC", "Unicode-DFS-2016", "Unicode-3.0", "OpenSSL", "WTFPL", "LicenseRef-Slint-Software-3.0", ] targets = ["x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc", "x86_64-apple-darwin"] ignore-dev-dependencies = true filter-noassertion = true # See https://embarkstudios.github.io/cargo-about/cli/generate/workarounds.html workarounds = ["ring"] [femtovg.clarify] # See https://github.com/femtovg/femtovg/commit/53bf79e720767354486affced9e2c7ad204f0f85 license = "MIT OR Apache-2.0" [[femtovg.clarify.files]] path = "LICENSE-MIT" license = "MIT" checksum = "cd4eb10b20da1d85916624e0e15a8e76b0a66406996a4f9aa5afee909fecfb7e" ================================================ FILE: api/cpp/CMakeLists.txt ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 cmake_minimum_required(VERSION 3.21) # Select C++ and C as languages, as Corrosion needs ${CMAKE_C_COMPILER} # for linking project(Slint HOMEPAGE_URL "https://slint.dev/" LANGUAGES C CXX VERSION 1.16.0) include(FeatureSummary) include(CMakeDependentOption) find_package(Corrosion QUIET 0.6) if (NOT Corrosion_FOUND) include(FetchContent) FetchContent_Declare( Corrosion GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git GIT_TAG v0.6.1 ) FetchContent_MakeAvailable(Corrosion) endif () list(PREPEND CMAKE_MODULE_PATH ${Corrosion_SOURCE_DIR}/cmake) find_package(Rust 1.88 REQUIRED MODULE) option(BUILD_SHARED_LIBS "Build Slint as shared library" ON) option(SLINT_FEATURE_COMPILER "Enable support for compiling .slint files to C++ ahead of time" ON) add_feature_info(SLINT_FEATURE_COMPILER SLINT_FEATURE_COMPILER "Enable support for compiling .slint files to C++ ahead of time") option(SLINT_BUILD_RUNTIME "Actually build the Slint runtime libraries (Disable that to only build the compiler)" ON) add_feature_info(SLINT_BUILD_RUNTIME SLINT_BUILD_RUNTIME "Actually build the Slint runtime libraries (Disable that to only build the compiler)") option(SLINT_FEATURE_SDF_FONTS "Enable support for pre-rendering fonts as Signed Distance Fields (for the software renderer)" OFF) add_feature_info(SLINT_FEATURE_SDF_FONTS SLINT_FEATURE_SDF_FONTS "Enable support for pre-rendering fonts as Signed Distance Fields (for the software renderer)") set(SLINT_COMPILER "" CACHE STRING "Path to the slint-compiler executable. When unset, it the compiler will be build as part of the build process. When set to 'download', the compiler will be downloaded from GitHub.") set(SLINT_LIBRARY_CARGO_FLAGS "" CACHE STRING "Flags to pass to cargo when building the Slint runtime library") function(define_cargo_feature cargo_feature description default) # turn foo-bar into SLINT_FEATURE_FOO_BAR string(TOUPPER "${cargo_feature}" cmake_option) string(REPLACE "-" "_" cmake_option "${cmake_option}") list(APPEND public_cmake_features ${cmake_option}) set(cmake_option "SLINT_FEATURE_${cmake_option}") option("${cmake_option}" "${description}" ${default}) if(${cmake_option}) list(APPEND features ${cargo_feature}) endif() set(features "${features}" PARENT_SCOPE) set(public_cmake_features "${public_cmake_features}" PARENT_SCOPE) add_feature_info(${cmake_option} ${cmake_option} ${description}) endfunction() function(define_cargo_dependent_feature cargo_feature description default depends_condition) # turn foo-bar into SLINT_FEATURE_FOO_BAR string(TOUPPER "${cargo_feature}" cmake_option) string(REPLACE "-" "_" cmake_option "${cmake_option}") list(APPEND public_cmake_features ${cmake_option}) set(cmake_option "SLINT_FEATURE_${cmake_option}") cmake_dependent_option("${cmake_option}" "${description}" ${default} ${depends_condition} OFF) if(${cmake_option}) list(APPEND features ${cargo_feature}) endif() set(features "${features}" PARENT_SCOPE) set(public_cmake_features "${public_cmake_features}" PARENT_SCOPE) add_feature_info(${cmake_option} ${cmake_option} ${description}) endfunction() # Features that are mapped to features in the Rust crate. These and their # defaults need to be kept in sync with the Rust bit (cpp/Cargo.toml and cbindgen.rs) define_cargo_feature(freestanding "Enable use of freestanding environment. This is only for bare-metal systems. Most other features are incompatible with this one" OFF) define_cargo_feature(libm "This feature enables floating point arithmetic emulation using the libm crate. Use this in MCU environments where the processor does not support floating point arithmetic." OFF) # Compat options (must be declared after the STD feature, but before the other renderer features) function(define_renderer_winit_compat_option renderer) string(TOUPPER "${renderer}" cmake_option) string(REPLACE "-" "_" cmake_option "${cmake_option}") cmake_dependent_option("SLINT_FEATURE_RENDERER_WINIT_${cmake_option}" "Compat option equivalent to SLINT_FEATURE_RENDERER_${cmake_option}" OFF "NOT SLINT_FEATURE_FREESTANDING" OFF) if(SLINT_FEATURE_RENDERER_WINIT_${cmake_option}) set("SLINT_FEATURE_RENDERER_${cmake_option}" ON PARENT_SCOPE) message("SLINT_FEATURE_RENDERER_WINIT_${cmake_option} is deprecated, use SLINT_FEATURE_RENDERER_${cmake_option} instead") endif() endfunction() define_renderer_winit_compat_option(femtovg) define_renderer_winit_compat_option(skia) define_renderer_winit_compat_option(skia-opengl) define_renderer_winit_compat_option(skia-vulkan) define_renderer_winit_compat_option(software) define_cargo_dependent_feature(interpreter "Enable support for the Slint interpreter to load .slint files at run-time" ON "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(live-preview "Enable support for the Slint live-preview to re-load changed .slint files at run-time" OFF "SLINT_FEATURE_INTERPRETER") define_cargo_dependent_feature(backend-winit "Enable support for the winit crate to interaction with all windowing systems." ON "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(backend-winit-x11 "Enable support for the winit create to interact only with the X11 windowing system on Unix. Enable this option and turn off SLINT_FEATURE_BACKEND_WINIT for a smaller build with just X11 support on Unix." OFF "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(backend-winit-wayland "Enable support for the winit create to interact only with the wayland windowing system on Unix. Enable this option and turn off SLINT_FEATURE_BACKEND_WINIT for a smaller build with just wayland support." OFF "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(renderer-femtovg "Enable support for the OpenGL ES 2.0 based FemtoVG rendering engine." ON "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(renderer-femtovg-wgpu "Enable support for the WGPU based FemtoVG rendering engine." OFF "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(renderer-skia "Enable support for the Skia based rendering engine." OFF "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(renderer-skia-opengl "Enable support for the Skia based rendering engine with its OpenGL backend." OFF "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(renderer-skia-vulkan "Enable support for the Skia based rendering engine with its Vulkan backend." OFF "NOT SLINT_FEATURE_FREESTANDING") define_cargo_feature(renderer-software "Enable support for the software renderer" ON) define_cargo_feature(software-renderer-path "Enable support for Path element rendering with the software renderer. This is implicitly enabled when SLINT_FEATURE_FREESTANDING is OFF. Enable this in bare-metal environments if you need support for Path elements" OFF) set(SLINT_BACKEND_QT_DEFAULT OFF) if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") set(SLINT_BACKEND_QT_DEFAULT ON) endif() define_cargo_dependent_feature(backend-qt "Enable Qt based rendering backend" ${SLINT_BACKEND_QT_DEFAULT} "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(backend-linuxkms "Enable support for the backend that renders a single window fullscreen on Linux. Requires libseat. If you don't have libseat, select `backend-linuxkms-noseat` instead." OFF "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(backend-linuxkms-noseat "Enable support for the backend that renders a single window fullscreen on Linux" OFF "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(gettext "Enable support of translations using gettext" OFF "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(accessibility "Enable integration with operating system provided accessibility APIs" ON "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(testing "Enable support for testing API (experimental)" ON "NOT SLINT_FEATURE_FREESTANDING") define_cargo_feature(experimental "Enable experimental features. (No backward compatibility guarantees)" OFF) define_cargo_dependent_feature(system-testing "Enable system testing support (experimental)" OFF "SLINT_FEATURE_EXPERIMENTAL AND NOT SLINT_FEATURE_FREESTANDING") if (SLINT_BUILD_RUNTIME) if(SLINT_FEATURE_COMPILER AND NOT SLINT_COMPILER) set(slint_compiler_crate "slint-compiler") endif() if(BUILD_SHARED_LIBS) set(rustc_lib_type "cdylib") set(slint_cpp_impl "slint_cpp-shared") set(cmake_lib_type "SHARED") else() set(rustc_lib_type "staticlib") set(slint_cpp_impl "slint_cpp-static") set(cmake_lib_type "STATIC") endif() corrosion_import_crate(MANIFEST_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../Cargo.toml" CRATES slint-cpp ${slint_compiler_crate} CRATE_TYPES bin ${rustc_lib_type}) elseif(SLINT_FEATURE_COMPILER AND NOT SLINT_COMPILER) corrosion_import_crate(MANIFEST_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../Cargo.toml" CRATES slint-compiler CRATE_TYPES bin) endif() if(SLINT_FEATURE_COMPILER AND NOT SLINT_COMPILER) corrosion_set_hostbuild(slint-compiler) set_property( TARGET slint-compiler PROPERTY CORROSION_NO_DEFAULT_FEATURES ON ) list(APPEND slint_compiler_features "software-renderer") # Switch to dlopening fontconfig in the slint-compiler to avoid the issue that # for example in Yocto environments, PKG_CONFIG_PATH will be set to the target only # and pkg-config in PATH doesn't permit lookups in the hots. if(CMAKE_CROSSCOMPILING) list(APPEND slint_compiler_features "fontconfig-dlopen") endif() if (SLINT_FEATURE_SDF_FONTS) list(APPEND slint_compiler_features "sdf-fonts") endif() # Enable jemalloc, except when cross-compiling. (#7463) if(NOT CMAKE_CROSSCOMPILING) list(APPEND slint_compiler_features "jemalloc") endif() set_property( TARGET slint-compiler PROPERTY CORROSION_FEATURES ${slint_compiler_features} ) endif() if (SLINT_BUILD_RUNTIME) # When doing "make install" package builds, set install_name to rpath, so that the installed # binaries don't have a load-command pointing back to their build directory. # Don't do this when Slint is used via FetchContent. While we could set CMAKE_BUILD_RPATH to # include the binary dir and thus our examples would have the correct rpath set, binaries # outside (i.e. applications using Slint via FetchContent) would not and the BUILD_RPATH # target property doesn't propagate :( if (APPLE AND SLINT_IS_TOPLEVEL_BUILD AND BUILD_SHARED_LIBS) # corrosion could provide the Cargo.toml package version as a CMake target property. corrosion_add_target_local_rustflags(slint_cpp -Clink-arg=-Wl,-install_name,@rpath/libslint_cpp.dylib,-current_version,${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR},-compatibility_version,${PROJECT_VERSION_MAJOR}.0) # Set this one to false again explicitly because Corrosion will starting setting this property to true by default. set_target_properties(slint_cpp-shared PROPERTIES IMPORTED_NO_SONAME 0) set_target_properties(slint_cpp-shared PROPERTIES IMPORTED_SONAME libslint_cpp.dylib) endif() add_library(Slint INTERFACE) add_library(Slint::Slint ALIAS Slint) target_link_libraries(Slint INTERFACE slint_cpp) target_compile_features(Slint INTERFACE cxx_std_20) if (WIN32) target_compile_options(Slint INTERFACE $<$:/bigobj> $<$:-Wa,-mbig-obj> $<$:-Wa,-mbig-obj> ) endif() foreach(feature ${public_cmake_features}) set(cmake_option "SLINT_FEATURE_${feature}") if(${cmake_option}) list(APPEND enabled_features ${feature}) else() list(APPEND disabled_features ${feature}) endif() endforeach() set_property(TARGET Slint APPEND PROPERTY SLINT_ENABLED_FEATURES "${enabled_features}") set_property(TARGET Slint APPEND PROPERTY SLINT_DISABLED_FEATURES "${disabled_features}") set_property(TARGET Slint APPEND PROPERTY EXPORT_PROPERTIES "SLINT_ENABLED_FEATURES") set_property(TARGET Slint APPEND PROPERTY EXPORT_PROPERTIES "SLINT_DISABLED_FEATURES") if (SLINT_FEATURE_FREESTANDING) if (ESP_PLATFORM AND NOT IDF_TARGET STREQUAL "esp32p4") list(APPEND features "esp-backtrace/${IDF_TARGET}") list(APPEND features "esp-println/${IDF_TARGET}") endif() else (SLINT_FEATURE_FREESTANDING) list(APPEND features std) endif() set_property( TARGET slint_cpp APPEND PROPERTY CORROSION_ENVIRONMENT_VARIABLES "SLINT_GENERATED_INCLUDE_DIR=${CMAKE_CURRENT_BINARY_DIR}/generated_include/" ) set_property( TARGET slint_cpp PROPERTY CORROSION_FEATURES ${features} ) set_property( TARGET slint_cpp PROPERTY CORROSION_NO_DEFAULT_FEATURES ON ) if(SLINT_LIBRARY_CARGO_FLAGS) corrosion_set_cargo_flags(slint_cpp ${SLINT_LIBRARY_CARGO_FLAGS}) endif() if(SLINT_FEATURE_BACKEND_QT) # For the CMake build don't rely on qmake being in PATH but use CMake to locate Qt. This # means usually CMAKE_PREFIX_PATH is set. find_package(Qt6 6.2 QUIET COMPONENTS Core Widgets) if(NOT TARGET Qt::qmake) find_package(Qt5 5.15 QUIET COMPONENTS Core Widgets) endif() endif(SLINT_FEATURE_BACKEND_QT) if(SLINT_FEATURE_BACKEND_QT AND TARGET Qt::qmake) set_property( TARGET slint_cpp APPEND PROPERTY CORROSION_ENVIRONMENT_VARIABLES QMAKE=$ ) else() set_property( TARGET slint_cpp APPEND PROPERTY CORROSION_ENVIRONMENT_VARIABLES SLINT_NO_QT=1 ) endif() if(SLINT_FEATURE_FREESTANDING) set(SLINT_STYLE_DEFAULT "fluent") elseif(ANDROID) set(SLINT_STYLE_DEFAULT "material") elseif(WIN32) set(SLINT_STYLE_DEFAULT "fluent") elseif(APPLE) set(SLINT_STYLE_DEFAULT "cupertino") elseif (SLINT_FEATURE_BACKEND_QT AND TARGET Qt::qmake) set(SLINT_STYLE_DEFAULT "qt") else () set(SLINT_STYLE_DEFAULT "fluent") endif() set(SLINT_STYLE "" CACHE STRING "The Slint Widget Style") if(SLINT_STYLE AND NOT SLINT_STYLE STREQUAL "native") set_property(GLOBAL PROPERTY SLINT_STYLE ${SLINT_STYLE}) else() set_property(GLOBAL PROPERTY SLINT_STYLE ${SLINT_STYLE_DEFAULT}) endif() # Build environments such as buildroot set PKG_CONFIG_SYSROOT_DIR in their toolchain file # and thus it's only available at cmake configure time. Our build dependencies that use pkg-config # however invoke pkg-config in build.rs, which means we need to defer/propagate the environment variable # to build time. if(DEFINED ENV{PKG_CONFIG_SYSROOT_DIR}) set_property( TARGET slint_cpp APPEND PROPERTY CORROSION_ENVIRONMENT_VARIABLES "PKG_CONFIG_SYSROOT_DIR=$ENV{PKG_CONFIG_SYSROOT_DIR}" ) endif() if(SLINT_FEATURE_RENDERER_SKIA OR SLINT_FEATURE_RENDERER_SKIA_OPENGL OR SLINT_FEATURE_RENDERER_SKIA_VULKAN) find_program(CLANGCC clang) find_program(CLANGCXX clang++) if(CLANGCC AND NOT DEFINED ENV{CLANGCC}) set_property( TARGET slint_cpp APPEND PROPERTY CORROSION_ENVIRONMENT_VARIABLES CLANGCC=${CLANGCC} ) endif() if(CLANGCXX AND NOT DEFINED ENV{CLANGCXX}) set_property( TARGET slint_cpp APPEND PROPERTY CORROSION_ENVIRONMENT_VARIABLES CLANGCXX=${CLANGCXX} ) endif() # The Skia cross-build requires a host C compiler (due to some build dependencies of rust-skia), # so cc.rs will first look for CC_ and then HOST_CC. # When cross-compiling, CMake doesn't really know what the host compiler is. Corrosion will set # HOST_CC to $CC, which is a good bet. Unfortunately in Yocto environments, CC will be set to # the cross-compiler. The same applies to CFLAGS, which may contain target specific options. # So the hack to solve this is two-fold: # * We look for clang or gcc in PATH - unprefixed those are usually host compilers. # * Through corrosion we know the correct host value of CC_. # Finally, we set CC_ to clang or gcc and empty CFLAGS_ if(CMAKE_CROSSCOMPILING AND Rust_CARGO_HOST_TARGET) if(CLANGCC) set(host_cc "${CLANGCC}") else() find_program(GCC gcc) if (GCC) set(host_cc "${GCC}") endif() endif() if(host_cc) string(REPLACE "-" "_" cargo_host_target_underscore "${Rust_CARGO_HOST_TARGET}") set_property( TARGET slint_cpp APPEND PROPERTY CORROSION_ENVIRONMENT_VARIABLES CC_${cargo_host_target_underscore}=${host_cc} ) set_property( TARGET slint_cpp APPEND PROPERTY CORROSION_ENVIRONMENT_VARIABLES CFLAGS_${cargo_host_target_underscore}= ) endif() endif() endif() file(GLOB api_headers RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/include/" "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h") foreach(header IN LISTS api_headers) set_property(TARGET Slint APPEND PROPERTY PUBLIC_HEADER include/${header}) endforeach() set(generated_headers ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_brush_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_builtin_structs.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_builtin_structs_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_color_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_enums.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_enums_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_generated_public.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_image_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_pathdata_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_platform_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_properties_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_qt_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_sharedvector_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_string_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_timer_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_events_internal.h ) if(SLINT_FEATURE_INTERPRETER) list(APPEND generated_headers ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_interpreter_internal.h ${CMAKE_CURRENT_BINARY_DIR}/generated_include/slint_interpreter_generated_public.h ) endif() foreach(header IN LISTS generated_headers) set_property(TARGET Slint APPEND PROPERTY PUBLIC_HEADER ${header}) endforeach() target_include_directories(Slint INTERFACE $ $ $ ) if(SLINT_FEATURE_COMPILER AND NOT SLINT_COMPILER) add_executable(Slint::slint-compiler ALIAS slint-compiler) include(${CMAKE_CURRENT_LIST_DIR}/cmake/SlintMacro.cmake) endif() export(TARGETS Slint slint_cpp NAMESPACE Slint:: FILE "${CMAKE_BINARY_DIR}/lib/cmake/Slint/SlintTargets.cmake") install(EXPORT SlintTargets NAMESPACE Slint:: DESTINATION lib/cmake/Slint) install(TARGETS Slint slint_cpp EXPORT SlintTargets LIBRARY DESTINATION lib PUBLIC_HEADER DESTINATION include/slint) install(FILES $ TYPE LIB) if(WIN32) install(FILES $ TYPE LIB) endif() endif(SLINT_BUILD_RUNTIME) include(CMakePackageConfigHelpers) include(GNUInstallDirs) if(SLINT_FEATURE_COMPILER AND NOT SLINT_COMPILER) install(PROGRAMS $ TYPE BIN) endif() if(SLINT_BUILD_RUNTIME) # Corrosion sets the `IMPORTED` locations late to allow us to set OUTPUT_DIRECTORY # target properties. This function must be deferred until after Corrosion set the # locations. Since we are writing to a config file Generator expressions are not # an option. function(_slint_write_configure_file) foreach(prop IMPORTED_LOCATION IMPORTED_LOCATION_DEBUG IMPORTED_LOCATION_RELEASE IMPORTED_LOCATION_RELWITHDEBINFO IMPORTED_LOCATION_MINSIZEREL IMPORTED_IMPLIB IMPORTED_IMPLIB_DEBUG IMPORTED_IMPLIB_RELEASE IMPORTED_IMPLIB_RELWITHDEBINFO IMPORTED_IMPLIB_MINSIZEREL) get_target_property(value ${slint_cpp_impl} ${prop}) if(value) get_filename_component(value ${value} NAME) list(APPEND SLINT_LIB_PROPERTIES ${prop} "\${_IMPORT_PREFIX}/${CMAKE_INSTALL_LIBDIR}/${value}") endif() endforeach() # Corrosion sets IMPORTED_NO_SONAME and for macOS we reset that # and set IMPORTED_SONAME. Make sure to propagate these locally # set properties also to the installed cmake target. foreach(prop IMPORTED_NO_SONAME IMPORTED_SONAME) get_target_property(value ${slint_cpp_impl} ${prop}) if(value) list(APPEND SLINT_LIB_PROPERTIES ${prop} ${value}) endif() endforeach() get_property(_SLINT_STYLE GLOBAL PROPERTY SLINT_STYLE) if (TARGET slint-compiler) get_target_property(_slint_compiler_location slint-compiler IMPORTED_LOCATION) cmake_path(GET _slint_compiler_location FILENAME SLINT_COMPILER_FILE_NAME) endif() configure_package_config_file("cmake/SlintConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/Slint/SlintConfig.cmake" INSTALL_DESTINATION lib/cmake/Slint) endfunction() cmake_language(DEFER CALL _slint_write_configure_file) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/Slint/SlintConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/Slint/SlintConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/Slint/SlintConfigVersion.cmake" "${CMAKE_CURRENT_LIST_DIR}/cmake/SlintMacro.cmake" DESTINATION lib/cmake/Slint ) endif(SLINT_BUILD_RUNTIME) set(CPACK_PACKAGE_NAME "Slint-cpp") set(CPACK_PACKAGE_VENDOR "Slint") set(CPACK_VERBATIM_VARIABLES true) set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) set(CPACK_PACKAGE_HOMEPAGE_URL "https://slint.dev") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_LIST_DIR}/../../LICENSE.md") set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_LIST_DIR}/README.md") set(CPACK_STRIP_FILES ON) set(CPACK_NSIS_DEFINES "ManifestDPIAware true") # Include a third-party license attribution folder, if generated by the CI. if(EXISTS "${CMAKE_BINARY_DIR}/licenses") list(APPEND CPACK_INSTALLED_DIRECTORIES "${CMAKE_BINARY_DIR}/licenses" licenses) endif() if(WIN32) if(MSVC) set(compiler_suffix "-MSVC") elseif(MINGW) set(compiler_suffix "-MinGW") endif() if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(CPACK_SYSTEM_NAME win64) else() set(CPACK_SYSTEM_NAME win32) endif() set(CPACK_SYSTEM_NAME "${CPACK_SYSTEM_NAME}${compiler_suffix}-${CMAKE_SYSTEM_PROCESSOR}") else() set(CPACK_GENERATOR "TGZ") set(CPACK_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") endif() if (NOT Rust_CARGO_TARGET STREQUAL Rust_CARGO_HOST_TARGET) # If the package contains host dependent content (the slint-compiler), append the target suffix. # For cross-builds that target bare-metal, strip the host suffix if (TARGET Slint::slint-compiler) string(APPEND CPACK_SYSTEM_NAME "-${Rust_CARGO_TARGET}") elseif(SLINT_FEATURE_FREESTANDING) set(CPACK_SYSTEM_NAME "${Rust_CARGO_TARGET}") endif() endif() include(CPack) if(SLINT_BUILD_TESTING) add_subdirectory(tests) endif() ================================================ FILE: api/cpp/Cargo.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 [package] name = "slint-cpp" description = "Slint C++ integration" authors.workspace = true documentation.workspace = true edition = "2024" homepage.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true version.workspace = true build = "build.rs" publish = false # prefix used to convey path to generated includes to the C++ test driver links = "slint_cpp" [lib] path = "lib.rs" crate-type = ["lib", "cdylib", "staticlib"] name = "slint_cpp" # Note, these features need to be kept in sync (along with their defaults) in # the C++ crate's CMakeLists.txt as well as cbindgen.rs [features] interpreter = ["slint-interpreter", "std"] live-preview = ["interpreter", "slint-interpreter/internal-live-preview"] # Enable some function used by the integration tests testing = ["dep:i-slint-backend-testing"] backend-qt = ["i-slint-backend-selector/backend-qt", "std"] backend-winit = ["i-slint-backend-selector/backend-winit", "std"] backend-winit-x11 = ["i-slint-backend-selector/backend-winit-x11", "std"] backend-winit-wayland = ["i-slint-backend-selector/backend-winit-wayland", "std"] backend-linuxkms = ["i-slint-backend-selector/backend-linuxkms", "std"] backend-linuxkms-noseat = ["i-slint-backend-selector/backend-linuxkms-noseat", "std"] renderer-femtovg = ["i-slint-backend-selector/renderer-femtovg"] renderer-femtovg-wgpu = ["i-slint-backend-selector/renderer-femtovg-wgpu"] renderer-skia = ["i-slint-backend-selector/renderer-skia", "i-slint-renderer-skia", "raw-window-handle"] renderer-skia-opengl = ["i-slint-backend-selector/renderer-skia-opengl", "renderer-skia"] renderer-skia-vulkan = ["i-slint-backend-selector/renderer-skia-vulkan", "renderer-skia"] renderer-software = ["i-slint-backend-selector/renderer-software", "dep:i-slint-renderer-software"] software-renderer-path = ["i-slint-renderer-software/path"] gettext = ["i-slint-core/gettext-rs"] accessibility = ["i-slint-backend-selector/accessibility"] system-testing = ["i-slint-backend-selector/system-testing"] libm = ["i-slint-core/libm", "i-slint-renderer-software?/libm"] std = [ "i-slint-core/default", "i-slint-core/image-default-formats", "i-slint-backend-selector", "i-slint-renderer-software?/std", "i-slint-core/raw-window-handle-06", "i-slint-backend-selector/raw-window-handle-06", "i-slint-core/tr", ] freestanding = ["i-slint-core/libm", "i-slint-core/unsafe-single-threaded"] experimental = ["i-slint-renderer-software?/experimental"] default = ["std", "backend-winit", "renderer-femtovg", "backend-qt"] [dependencies] i-slint-backend-selector = { workspace = true, optional = true } i-slint-backend-testing = { workspace = true, optional = true, features = ["ffi", "internal"] } i-slint-renderer-skia = { workspace = true, features = ["default", "x11", "wayland"], optional = true } i-slint-renderer-software = { workspace = true, optional = true } i-slint-core = { workspace = true, features = ["ffi"] } slint-interpreter = { workspace = true, features = ["ffi", "compat-1-2"], optional = true } raw-window-handle = { version = "0.6", optional = true } esp-backtrace = { version = "0.17.0", features = ["panic-handler", "println"], optional = true } esp-println = { version = "0.15.0", default-features = false, features = ["auto", "log-04"], optional = true } unicode-segmentation = { workspace = true } [build-dependencies] anyhow = "1.0" cbindgen = { workspace = true } i-slint-common = { workspace = true, features = ["default"] } proc-macro2 = "1.0.11" ================================================ FILE: api/cpp/README.md ================================================ # Slint-cpp ## A C++ UI toolkit [Slint](https://slint.dev/) is a UI toolkit that supports different programming languages. Slint.cpp is the C++ API to interact with a Slint UI from C++. The complete C++ documentation can be viewed online at https://slint.dev/docs/cpp/. ## Installing or Building Slint Slint comes with a CMake integration that automates the compilation step of the `.slint` markup language files and offers a CMake target for convenient linkage. *Note*: We recommend using the Ninja generator of CMake for the most efficient build and `.slint` dependency tracking. Install [Ninja](https://ninja-build.org) and select the CMake Ninja backend by passing `-GNinja` or setting the `CMAKE_GENERATOR` environment variable to `Ninja`. ### Building from Sources The recommended and most flexible way to use the C++ API is to build Slint from sources. First you need to install the prerequisites: * Install Rust by following the [Rust Getting Started Guide](https://www.rust-lang.org/learn/get-started). Once this is done, you should have the ```rustc``` compiler and the ```cargo``` build system installed in your path. * **[cmake](https://cmake.org/download/)** (3.21 or newer) * A C++ compiler that supports C++20 (e.g., **MSVC 2019 16.6** on Windows) You can include Slint in your CMake project using CMake's `FetchContent` feature. Insert the following snippet into your `CMakeLists.txt` to make CMake download the latest release, compile it and make the CMake integration available: ```cmake include(FetchContent) FetchContent_Declare( Slint GIT_REPOSITORY https://github.com/slint-ui/slint.git # `release/1` will auto-upgrade to the latest Slint >= 1.0.0 and < 2.0.0 # `release/1.0` will auto-upgrade to the latest Slint >= 1.0.0 and < 1.1.0 GIT_TAG release/1 SOURCE_SUBDIR api/cpp ) FetchContent_MakeAvailable(Slint) ``` If you prefer to treat Slint as an external CMake package, then you can also build Slint from source like a regular CMake project, install it into a prefix directory of your choice and use `find_package(Slint)` in your `CMakeLists.txt`. #### Cross-compiling It is possible to cross-compile Slint to a different target architecture when building with CMake. In order to complete that, you need to make sure that your CMake setup is ready for cross-compilation. You can find more information about how to set this up in the [upstream CMake documentation](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling). If you are building against a Yocto SDK, it is sufficient to source the SDK's environment setup file. Since Slint is implemented using the Rust programming language, you need to determine which Rust target matches the target architecture that you're compiling to. Please consult the [upstream Rust documentation](https://doc.rust-lang.org/nightly/rustc/platform-support.html) to find the correct target name. Now you need to install the Rust toolchain: ```sh rustup target add ``` Then you're ready to invoke CMake and you need to add `-DRust_CARGO_TARGET=` to the CMake command line. This ensures that the Slint library is built for the correct architecture. For example if you are building against an embedded Linux Yocto SDK targeting an ARM64 board, the following commands show how to compile: Install the Rust targe toolchain once: ```sh rustup target add aarch64-unknown-linux-gnu ``` Set up the environment and build: ```sh . /path/to/yocto/sdk/environment-setup-cortexa53-crypto-poky-linux cd slint mkdir build cd build cmake -DRust_CARGO_TARGET=aarch64-unknown-linux-gnu -DCMAKE_INSTALL_PREFIX=/slint/install/path .. cmake --build . cmake --install . ``` ### Binary Packages We also provide binary packages of Slint for use with C++, which eliminates the need to have Rust installed in your development environment. You can download one of our pre-built binaries for Linux or Windows on x86-64 architectures: 1. Open 2. Click on the latest release 3. From "Assets" download either `slint-cpp-XXX-Linux-x86_64.tar.gz` for a Linux x86-64 archive or `slint-cpp-XXX-win64.exe` for a Windows x86-64 installer. ("XXX" refers to the version of the latest release) 4. Uncompress the downloaded archive or run the installer. After extracting the artifact or running the installer, you can place the installation directory into your `CMAKE_PREFIX_PATH` and `find_package(Slint)` should succeed in locating the package. ## Usage via CMake A typical example looks like this: ```cmake cmake_minimum_required(VERSION 3.21) project(my_application LANGUAGES CXX) # Note: Use find_package(Slint) instead of the following three commands, if you prefer the package # approach. include(FetchContent) FetchContent_Declare( Slint GIT_REPOSITORY https://github.com/slint-ui/slint.git # `release/1` will auto-upgrade to the latest Slint >= 1.0.0 and < 2.0.0 # `release/1.0` will auto-upgrade to the latest Slint >= 1.0.0 and < 1.1.0 GIT_TAG release/1 SOURCE_SUBDIR api/cpp ) FetchContent_MakeAvailable(Slint) add_executable(my_application main.cpp) target_link_libraries(my_application PRIVATE Slint::Slint) slint_target_sources(my_application my_application_ui.slint) # On Windows, copy the Slint DLL next to the application binary so that it's found. if (WIN32) add_custom_command(TARGET my_application POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ $ COMMAND_EXPAND_LISTS) endif() ``` The `slint_target_sources` cmake command allows you to add .slint files to your build. Finally it is necessary to link your executable or library against the `Slint::Slint` target. ## Tutorial Let's make a UI for a todo list application using the Slint UI description language. Hopefully this should be self explanatory. Check out the documentation of the language for help ```slint // file: my_application_ui.slint import { CheckBox, Button, ListView, LineEdit } from "std-widgets.slint"; export struct TodoItem { title: string, checked: bool, } export component MainWindow { callback todo_added(string); property <[TodoItem]> todo_model; GridLayout { Row { text_edit := LineEdit { accepted(text) => { todo_added(text); } } Button { text: "Add Todo"; clicked => { todo_added(text_edit.text); } } } list_view := ListView { rowspan: 2; row: 2; for todo in todo_model: Rectangle { height: 20px; GridLayout { CheckBox { text: todo.title; checked: todo.checked; toggled => { todo.checked = self.checked; } } } } } } } ``` We can compile this code using the `slint-compiler` binary: ```sh slint-compiler my_application_ui.slint > my_application_ui.h ``` Note: You would usually not type this command yourself, this is done automatically by the build system. (that's what the `slint_target_sources` cmake function does) This will generate a `my_application_ui.h` header file. It basically contains the following code (edited for brevity) ```C++ #include struct TodoItem { slint::SharedString title; bool checked; }; struct MainWindow { public: inline auto create () -> slint::ComponentHandle; inline auto get_todo_model () const -> std::shared_ptr>; inline void set_todo_model (const std::shared_ptr> &value) const; inline void invoke_todo_added (slint::SharedString arg_0) const; template inline void on_todo_added (Functor && callback_handler) const; //... } ``` We can then use this from out .cpp file ```C++ // include the generated file #include "my_application_ui.h" int main() { // Let's instantiate our window auto todo_app = MainWindow::create(); // let's create a model: auto todo_model = std::make_shared>(std::vector { TodoItem { false, "Write documentation" }, }); // set the model as the model of our view todo_app->set_todo_model(todo_model); // let's connect our "add" button to add an item in the model todo_app->on_todo_added([todo_model](const slint::SharedString &s) { todo_model->push_back(TodoItem { false, s} ); }); // Show the window and run the event loop todo_app->run(); } ``` That's it. For more details, check the [Online documentation](https://slint.dev/docs/cpp). We also have a [Getting Started Template](https://github.com/slint-ui/slint-cpp-template) repository with the code of a minimal C++ application using Slint that can be used as a starting point to your program. ================================================ FILE: api/cpp/build.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use crate::cbindgen::EnabledFeatures; use std::path::Path; mod cbindgen; fn main() -> Result<(), anyhow::Error> { let manifest_dir = std::env::var_os("CARGO_MANIFEST_DIR").unwrap(); // Go from $root/api/cpp down to $root let root_dir = Path::new(&manifest_dir).ancestors().nth(2).unwrap_or_else(|| { panic!("Failed to locate root directory, relative to {}", manifest_dir.to_string_lossy()) }); println!("cargo:rerun-if-env-changed=SLINT_GENERATED_INCLUDE_DIR"); let output_dir = std::env::var_os("SLINT_GENERATED_INCLUDE_DIR").unwrap_or_else(|| { Path::new(&std::env::var_os("OUT_DIR").unwrap()).join("generated_include").into() }); let output_dir = Path::new(&output_dir); println!("cargo:GENERATED_INCLUDE_DIR={}", output_dir.display()); let enabled_features = EnabledFeatures::from_env(); let dependencies = cbindgen::gen_all(root_dir, output_dir, enabled_features)?; for path in dependencies { println!("cargo:rerun-if-changed={}", path.display()); } Ok(()) } ================================================ FILE: api/cpp/cbindgen.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use anyhow::Context; use std::io::{BufWriter, Write}; use std::path::{Path, PathBuf}; // cSpell: ignore compat constexpr corelib deps sharedvector pathdata fn enums(path: &Path) -> anyhow::Result<()> { let mut enums_priv = BufWriter::new( std::fs::File::create(path.join("slint_enums_internal.h")) .context("Error creating slint_enums_internal.h file")?, ); writeln!(enums_priv, "#pragma once")?; writeln!(enums_priv, "// This file is auto-generated from {}", file!())?; writeln!(enums_priv, "#include \"slint_enums.h\"")?; writeln!(enums_priv, "namespace slint::cbindgen_private {{")?; let mut enums_pub = BufWriter::new( std::fs::File::create(path.join("slint_enums.h")) .context("Error creating slint_enums.h file")?, ); writeln!(enums_pub, "#pragma once")?; writeln!(enums_pub, "// This file is auto-generated from {}", file!())?; writeln!(enums_pub, "namespace slint {{")?; macro_rules! enum_file { (PointerEventButton) => {{ writeln!(enums_priv, "using slint::PointerEventButton;")?; &mut enums_pub }}; (AccessibleRole) => {{ writeln!(enums_priv, "using slint::testing::AccessibleRole;")?; &mut enums_pub }}; ($_:ident) => { &mut enums_priv }; } macro_rules! enum_sub_namespace { (AccessibleRole) => {{ Some("testing") }}; ($_:ident) => { None }; } macro_rules! print_enums { ($( $(#[doc = $enum_doc:literal])* $(#[non_exhaustive])? enum $Name:ident { $( $(#[doc = $value_doc:literal])* $Value:ident,)* })*) => { $( let file = enum_file!($Name); let namespace: Option<&'static str> = enum_sub_namespace!($Name); if let Some(ns) = namespace { writeln!(file, "namespace {} {{", ns)?; } $(writeln!(file, "///{}", $enum_doc)?;)* writeln!(file, "enum class {} {{", stringify!($Name))?; $( $(writeln!(file, " ///{}", $value_doc)?;)* writeln!(file, " {},", stringify!($Value).trim_start_matches("r#"))?; )* writeln!(file, "}};")?; if namespace.is_some() { writeln!(file, "}}")?; } )* } } i_slint_common::for_each_enums!(print_enums); writeln!(enums_pub, "}}")?; writeln!(enums_priv, "}}")?; // Print the key codes constants // This is not an enum, but fits well in that file writeln!( enums_pub, r#" /// This namespace contains constants for each special non-printable key. /// /// Each constant can be converted to SharedString. /// The constants are meant to be used with the slint::Window::dispatch_key_press_event() and /// slint::Window::dispatch_key_release_event() functions. /// /// Example: /// ``` /// window.dispatch_key_press_event(slint::platform::key_codes::Tab); /// ``` namespace slint::platform::key_codes {{ "# )?; macro_rules! print_key_codes { ($($char:literal # $name:ident # $($shifted:expr)? $(=> $($qt:ident)|* # $($winit:ident $(($_pos:ident))?)|* # $($_xkb:ident)|*)? ;)*) => { $( writeln!(enums_pub, "/// A constant that represents the key code to be used in slint::Window::dispatch_key_press_event()")?; writeln!(enums_pub, r#"constexpr std::u8string_view {} = u8"\u{:04x}";"#, stringify!($name), $char as u32)?; )* }; } i_slint_common::for_each_keys!(print_key_codes); writeln!(enums_pub, "}}")?; enums_priv.flush()?; enums_pub.flush()?; Ok(()) } fn builtin_structs(path: &Path) -> anyhow::Result<()> { let mut structs_pub = BufWriter::new( std::fs::File::create(path.join("slint_builtin_structs.h")) .context("Error creating slint_builtin_structs.h file")?, ); writeln!(structs_pub, "#pragma once")?; writeln!(structs_pub, "// This file is auto-generated from {}", file!())?; writeln!(structs_pub, "namespace slint::language {{")?; let mut structs_priv = BufWriter::new( std::fs::File::create(path.join("slint_builtin_structs_internal.h")) .context("Error creating slint_builtin_structs_internal.h file")?, ); writeln!(structs_priv, "#pragma once")?; writeln!(structs_priv, "// This file is auto-generated from {}", file!())?; writeln!(structs_priv, "#include \"slint_builtin_structs.h\"")?; writeln!(structs_priv, "#include \"slint_enums_internal.h\"")?; writeln!(structs_priv, "#include \"slint_point.h\"")?; writeln!(structs_priv, "#include \"slint_image.h\"")?; writeln!(structs_priv, "namespace slint::cbindgen_private {{")?; writeln!(structs_priv, "enum class KeyEventType : uint8_t;")?; macro_rules! struct_file { (BuiltinPublicStruct, $Name:ident) => {{ writeln!(structs_priv, "using slint::language::{};", stringify!($Name))?; &mut structs_pub }}; (BuiltinPrivateStruct, $_:ident) => { &mut structs_priv }; } macro_rules! print_structs { ($( $(#[doc = $struct_doc:literal])* $(#[non_exhaustive])? $(#[derive(Copy, Eq)])? struct $Name:ident { @name = $NameTy:ident :: $NameVariant:ident, export { $( $(#[doc = $pub_doc:literal])* $pub_field:ident : $pub_type:ty, )* } private { $( $(#[doc = $pri_doc:literal])* $pri_field:ident : $pri_type:ty, )* } } )*) => { $( let file = struct_file!($NameTy, $Name); $(writeln!(file, "///{}", $struct_doc)?;)* writeln!(file, "struct {} {{", stringify!($Name))?; $( $(writeln!(file, " ///{}", $pub_doc)?;)* let pub_type = match stringify!($pub_type) { "i32" => "int32_t", "f32" | "Coord" => "float", other => other, }; writeln!(file, " {} {};", pub_type, stringify!($pub_field))?; )* $( $(writeln!(file, " ///{}", $pri_doc)?;)* let pri_type = stringify!($pri_type).replace(' ', ""); let pri_type = match pri_type.as_str() { "usize" => "uintptr_t", "crate::animations::Instant" => "uint64_t", // This shouldn't be accessed by the C++ anyway, just need to have the same ABI in a struct "Option" => "std::pair", "Option>" => "std::tuple", other => other, }; writeln!(file, " {} {};", pri_type, stringify!($pri_field))?; )* writeln!(file, " /// \\private")?; writeln!(file, " {}", format!("friend bool operator==(const {name}&, const {name}&) = default;", name = stringify!($Name)))?; writeln!(file, " /// \\private")?; writeln!(file, " {}", format!("friend bool operator!=(const {name}&, const {name}&) = default;", name = stringify!($Name)))?; writeln!(file, "}};")?; )* }; } i_slint_common::for_each_builtin_structs!(print_structs); writeln!(structs_priv, "}}")?; writeln!(structs_pub, "}}")?; // Backward-compatible alias: StandardListViewItem was previously exposed directly under slint:: writeln!(structs_pub, "namespace slint {{ using slint::language::StandardListViewItem; }}")?; structs_priv.flush()?; structs_pub.flush()?; Ok(()) } fn ensure_cargo_rerun_for_crate( crate_dir: &Path, dependencies: &mut Vec, ) -> anyhow::Result<()> { dependencies.push(crate_dir.to_path_buf()); for entry in std::fs::read_dir(crate_dir)? { let entry = entry?; if entry.path().extension().is_some_and(|e| e == "rs") { dependencies.push(entry.path()); } } Ok(()) } fn default_config() -> cbindgen::Config { let mut config = cbindgen::Config::default(); config.macro_expansion.bitflags = true; config.pragma_once = true; config.include_version = true; config.namespaces = Some(vec!["slint".into(), "cbindgen_private".into()]); config.line_length = 100; config.tab_width = 4; // Note: we might need to switch to C if we need to generate bindings for language that needs C headers config.language = cbindgen::Language::Cxx; config.cpp_compat = true; config.documentation = true; config.export = cbindgen::ExportConfig { rename: [ ("Callback".into(), "private_api::CallbackHelper".into()), ("VoidArg".into(), "void".into()), ("FocusReasonArg".into(), "FocusReason".into()), ("StringArg".into(), "slint::SharedString".into()), ("KeyEventArg".into(), "KeyEvent".into()), ("PointerEventArg".into(), "PointerEvent".into()), ("DropEventArg".into(), "DropEvent".into()), ("PointerScrollEventArg".into(), "PointerScrollEvent".into()), ("PointArg".into(), "slint::LogicalPosition".into()), ("FloatArg".into(), "float".into()), ("IntArg".into(), "int".into()), ("MenuEntryArg".into(), "MenuEntry".into()), // Note: these types are not the same, but they are only used in callback return types that are only used in C++ (set and called) // therefore it is ok to reinterpret_cast ("MenuEntryModel".into(), "std::shared_ptr>".into()), ("Coord".into(), "float".into()), ("Channel".into(), "uint8_t".into()), ] .iter() .cloned() .collect(), ..Default::default() }; config.defines = [ ("target_pointer_width = 64".into(), "SLINT_TARGET_64".into()), ("target_pointer_width = 32".into(), "SLINT_TARGET_32".into()), // Disable any wasm guarded code in C++, too - so that there are no gaps in enums. ("target_arch = wasm32".into(), "SLINT_TARGET_WASM".into()), ("target_os = android".into(), "__ANDROID__".into()), // Disable Rust WGPU specific API feature ("feature = unstable-wgpu-27".into(), "SLINT_DISABLED_CODE".into()), ("feature = unstable-wgpu-28".into(), "SLINT_DISABLED_CODE".into()), ] .iter() .cloned() .collect(); config.structure.associated_constants_in_body = true; config.constant.allow_constexpr = true; config } fn gen_item_declarations(items: &[&str]) -> String { format!( r#" namespace slint::private_api {{ #define SLINT_DECL_ITEM(ItemName) \ extern const cbindgen_private::ItemVTable ItemName##VTable; \ extern SLINT_DLL_IMPORT const cbindgen_private::ItemVTable* slint_get_##ItemName##VTable(); extern "C" {{ {} }} #undef SLINT_DECL_ITEM }} "#, items .iter() .map(|item_name| format!("SLINT_DECL_ITEM({item_name});")) .collect::>() .join("\n") ) } fn gen_corelib( root_dir: &Path, include_dir: &Path, dependencies: &mut Vec, enabled_features: EnabledFeatures, ) -> anyhow::Result<()> { let mut config = default_config(); let items = [ "Empty", "Rectangle", "BasicBorderRectangle", "BorderRectangle", "DragArea", "DropArea", "ImageItem", "ClippedImage", "TouchArea", "FocusScope", "KeyBinding", "SwipeGestureHandler", "PinchGestureHandler", "Flickable", "SimpleText", "StyledTextItem", "ComplexText", "Path", "WindowItem", "TextInput", "Clip", "BoxShadow", "Transform", "Opacity", "Layer", "ContextMenu", "MenuItem", ]; config.export.include = [ "Clipboard", "ItemTreeVTable", "Slice", "WindowAdapterRcOpaque", "PropertyAnimation", "AnimationDirection", "EasingCurve", "TextHorizontalAlignment", "TextVerticalAlignment", "TextOverflow", "TextWrap", "ImageFit", "FillRule", "MouseCursor", "InputType", "StandardButtonKind", "DialogButtonRole", "FocusReason", "PointerEventKind", "PointerEventButton", "PointerEvent", "PointerScrollEvent", "Rect", "SortOrder", "BitmapFont", ] .iter() .chain(items.iter()) .map(|x| x.to_string()) .collect(); let mut private_exported_types: std::collections::HashSet = config.export.include.iter().cloned().collect(); // included in generated_public.h let public_exported_types = [ "RenderingState", "SetRenderingNotifierError", "GraphicsAPI", "CloseRequestResponse", "StandardListViewItem", "Rgb8Pixel", "Rgba8Pixel", ]; config.export.exclude = [ "SharedString", "StyledText", "SharedVector", "ImageInner", "ImageCacheKey", "Image", "Color", "PathData", "PathElement", "Brush", "slint_new_path_elements", "slint_new_path_events", "Property", "Slice", "Timer", "PropertyHandleOpaque", "Callback", "slint_property_listener_scope_evaluate", "slint_property_listener_scope_is_dirty", "PropertyTrackerOpaque", "CallbackOpaque", "WindowAdapterRc", "VoidArg", "StringArg", "DropEventArg", "FocusReasonArg", "KeyEventArg", "PointerEventArg", "PointerScrollEventArg", "PointArg", "Point", "MenuEntryModel", "MenuEntryArg", "Coord", "Channel", "LogicalRect", "LogicalPoint", "LogicalPosition", "LogicalLength", ] .iter() .chain(public_exported_types.iter()) .map(|x| x.to_string()) .collect(); let mut crate_dir = root_dir.to_owned(); crate_dir.extend(["internal", "core"].iter()); ensure_cargo_rerun_for_crate(&crate_dir, dependencies)?; let mut string_config = config.clone(); string_config.export.exclude = vec!["SharedString".into(), "StyledText".into()]; string_config.export.body.insert( "Slice".to_owned(), " const T &operator[](int i) const { return ptr[i]; }".to_owned(), ); cbindgen::Builder::new() .with_config(string_config) .with_src(crate_dir.join("string.rs")) .with_src(crate_dir.join("styled_text.rs")) .with_src(crate_dir.join("slice.rs")) .with_after_include("namespace slint { struct SharedString; namespace private_api { struct StyledText; } namespace cbindgen_private { using private_api::StyledText; }}") .generate() .context("Unable to generate bindings for slint_string_internal.h")? .write_to_file(include_dir.join("slint_string_internal.h")); cbindgen::Builder::new() .with_config(config.clone()) .with_src(crate_dir.join("sharedvector.rs")) .with_after_include("namespace slint { template struct SharedVector; }") .generate() .context("Unable to generate bindings for slint_sharedvector_internal.h")? .write_to_file(include_dir.join("slint_sharedvector_internal.h")); let mut properties_config = config.clone(); properties_config.export.exclude.clear(); properties_config.structure.derive_eq = true; properties_config.structure.derive_neq = true; private_exported_types.extend(properties_config.export.include.iter().cloned()); cbindgen::Builder::new() .with_config(properties_config) .with_src(crate_dir.join("properties.rs")) .with_src(crate_dir.join("properties/ffi.rs")) .with_src(crate_dir.join("callbacks.rs")) .with_after_include("namespace slint { class Color; class Brush; }") .generate() .context("Unable to generate bindings for slint_properties_internal.h")? .write_to_file(include_dir.join("slint_properties_internal.h")); // slint_timer_internal.h: let timer_config = { let mut tmp = config.clone(); tmp.export.include = [ "TimerMode", "slint_timer_start", "slint_timer_singleshot", "slint_timer_destroy", "slint_timer_stop", "slint_timer_restart", "slint_timer_running", ] .iter() .map(|s| s.to_string()) .collect(); tmp }; config.export.exclude.extend(timer_config.export.include.iter().cloned()); cbindgen::Builder::new() .with_config(timer_config) .with_src(crate_dir.join("timers.rs")) .generate() .context("Unable to generate bindings for slint_timer_internal.h")? .write_to_file(include_dir.join("slint_timer_internal.h")); for (rust_types, internal_header, prelude) in [ ( vec![ "ImageInner", "Image", "ImageCacheKey", "Size", "slint_image_size", "slint_image_path", "slint_image_load_from_path", "slint_image_load_from_embedded_data", "slint_image_from_embedded_textures", "slint_image_compare_equal", "slint_image_set_nine_slice_edges", "slint_image_to_rgb8", "slint_image_to_rgba8", "slint_image_to_rgba8_premultiplied", "SharedPixelBuffer", "SharedImageBuffer", "StaticTextures", "BorrowedOpenGLTextureOrigin", "PhysicalRegion", "PHYSICAL_REGION_MAX_SIZE", ], "slint_image_internal.h", "#include \"slint_color.h\"\nnamespace slint::cbindgen_private { struct ParsedSVG{}; struct HTMLImage{}; struct PhysicalPx; using namespace vtable; namespace types{ struct NineSliceImage{}; } }", ), ( vec!["Color", "slint_color_brighter", "slint_color_darker", "slint_color_transparentize", "slint_color_mix", "slint_color_with_alpha", "slint_color_to_hsva", "slint_color_from_hsva", "slint_color_from_oklch", "slint_color_to_oklch",], "slint_color_internal.h", "", ), ( vec!["PathData", "PathElement", "slint_new_path_elements", "slint_new_path_events", "Point"], "slint_pathdata_internal.h", "#include \"slint_sharedvector.h\"\n#include \"slint_point.h\"", ), ( vec!["Brush", "LinearGradient", "GradientStop", "RadialGradient", "ConicGradientBrush", "slint_conic_gradient_normalize_stops", "slint_conic_gradient_apply_rotation"], "slint_brush_internal.h", "", ), ( vec!["MouseEvent", "TouchPhase", "Keys"], "slint_events_internal.h", "#include \"slint_point.h\" namespace slint::cbindgen_private { struct KeyEvent; struct PointerEvent; struct Rect; using LogicalRect = Rect; using LogicalPoint = Point2D; using LogicalLength = float; }", ) ] .iter() { let mut special_config = config.clone(); special_config.export.include = rust_types.iter().map(|s| s.to_string()).collect(); special_config.export.exclude = [ "slint_keys_debug_string", "slint_keys_to_string", "slint_keys", "slint_visit_item_tree", "slint_windowrc_drop", "slint_windowrc_clone", "slint_windowrc_show", "slint_windowrc_hide", "slint_windowrc_is_visible", "slint_windowrc_get_scale_factor", "slint_windowrc_set_const_scale_factor", "slint_windowrc_get_text_input_focused", "slint_windowrc_set_text_input_focused", "slint_windowrc_set_focus_item", "slint_windowrc_set_component", "slint_windowrc_show_popup", "slint_windowrc_close_popup", "slint_windowrc_set_rendering_notifier", "slint_windowrc_request_redraw", "slint_windowrc_on_close_requested", "slint_windowrc_position", "slint_windowrc_set_logical_position", "slint_windowrc_set_physical_position", "slint_windowrc_size", "slint_windowrc_set_logical_size", "slint_windowrc_set_physical_size", "slint_windowrc_color_scheme", "slint_windowrc_supports_native_menu_bar", "slint_windowrc_setup_native_menu_bar", "slint_windowrc_show_native_popup_menu", "slint_windowrc_resolved_default_font_size", "slint_windowrc_dispatch_pointer_event", "slint_windowrc_dispatch_key_event", "slint_windowrc_dispatch_event", "slint_windowrc_set_fullscreen", "slint_windowrc_set_minimized", "slint_windowrc_set_maximized", "slint_windowrc_is_fullscreen", "slint_windowrc_is_minimized", "slint_windowrc_is_maximized", "slint_windowrc_take_snapshot", "slint_windowrc_hwnd_win32", "slint_windowrc_hinstance_win32", "slint_windowrc_wlsurface_wayland", "slint_windowrc_wldisplay_wayland", "slint_windowrc_nsview_appkit", "GradientStop", "ConicGradientBrush", "slint_conic_gradient_normalize_stops", "slint_conic_gradient_apply_rotation", "PHYSICAL_REGION_MAX_SIZE", ] .into_iter() .chain(config.export.exclude.iter().map(|s| s.as_str())) .filter(|exclusion| !rust_types.iter().any(|inclusion| inclusion == exclusion)) .map(|s| s.to_string()) .collect(); config.export.exclude.extend(rust_types.iter().map(|s| s.to_string())); special_config.enumeration = cbindgen::EnumConfig { derive_tagged_enum_copy_assignment: true, derive_tagged_enum_copy_constructor: true, derive_tagged_enum_destructor: true, derive_helper_methods: true, private_default_tagged_enum_constructor: true, ..Default::default() }; special_config.structure.derive_eq = true; special_config.structure.derive_neq = true; // Put the rust type in a deeper "types" namespace, so the use of same type in for example generated // Property<> fields uses the public `slint::Blah` type special_config.namespaces = Some(vec!["slint".into(), "cbindgen_private".into(), "types".into()]); private_exported_types.extend(special_config.export.include.iter().cloned()); special_config.after_includes = (!prelude.is_empty()).then(|| prelude.to_string()); cbindgen::Builder::new() .with_config(special_config) .with_src(crate_dir.join("graphics.rs")) .with_src(crate_dir.join("graphics/color.rs")) .with_src(crate_dir.join("graphics/path.rs")) .with_src(crate_dir.join("graphics/brush.rs")) .with_src(crate_dir.join("graphics/image.rs")) .with_src(crate_dir.join("graphics/image/cache.rs")) .with_src(crate_dir.join("animations.rs")) .with_src(crate_dir.join("input.rs")) .with_src(crate_dir.join("item_rendering.rs")) .with_src(crate_dir.join("window.rs")) .with_src(crate_dir.join("../renderers/software/lib.rs")) .with_include("slint_enums_internal.h") .generate() .with_context(|| format!("Unable to generate bindings for {internal_header}"))? .write_to_file(include_dir.join(internal_header)); } // Generate a header file with some public API (enums, etc.) let mut public_config = config.clone(); public_config.namespaces = Some(vec!["slint".into()]); public_config.export.item_types = vec![cbindgen::ItemType::Enums, cbindgen::ItemType::Structs]; // Previously included types are now excluded (to avoid duplicates) public_config.export.exclude = private_exported_types.into_iter().collect(); public_config.export.exclude.push("LogicalPosition".into()); public_config.export.exclude.push("MenuVTable".into()); public_config.export.include = public_exported_types.into_iter().map(str::to_string).collect(); public_config.export.body.insert( "Rgb8Pixel".to_owned(), "/// \\private\nfriend bool operator==(const Rgb8Pixel&, const Rgb8Pixel&) = default;" .into(), ); public_config.export.body.insert( "Rgba8Pixel".to_owned(), "/// \\private\nfriend bool operator==(const Rgba8Pixel&, const Rgba8Pixel&) = default;" .into(), ); cbindgen::Builder::new() .with_config(public_config) .with_src(crate_dir.join("graphics.rs")) .with_src(crate_dir.join("window.rs")) .with_src(crate_dir.join("api.rs")) .with_src(crate_dir.join("model.rs")) .with_src(crate_dir.join("graphics/image.rs")) .with_src(crate_dir.join("lengths.rs")) .with_include("slint_string.h") .with_after_include(format!( r#" /// This macro expands to the to the numeric value of the major version of Slint you're /// developing against. For example if you're using version 1.5.2, this macro will expand to 1. #define SLINT_VERSION_MAJOR {x} /// This macro expands to the to the numeric value of the minor version of Slint you're /// developing against. For example if you're using version 1.5.2, this macro will expand to 5. #define SLINT_VERSION_MINOR {y} /// This macro expands to the to the numeric value of the patch version of Slint you're /// developing against. For example if you're using version 1.5.2, this macro will expand to 2. #define SLINT_VERSION_PATCH {z} /// This macro expands to the string representation of the version of Slint you're developing against. /// For example if you're using version 1.5.2, this macro will expand to "1.5.2". #define SLINT_VERSION_STRING "{x}.{y}.{z}" {features} "#, x = env!("CARGO_PKG_VERSION_MAJOR"), y = env!("CARGO_PKG_VERSION_MINOR"), z = env!("CARGO_PKG_VERSION_PATCH"), features = enabled_features.defines() )) .generate() .context("Unable to generate bindings for slint_generated_public.h")? .write_to_file(include_dir.join("slint_generated_public.h")); config.export.body.insert( "ItemTreeNode".to_owned(), " constexpr ItemTreeNode(Item_Body x) : item {x} {} constexpr ItemTreeNode(DynamicTree_Body x) : dynamic_tree{x} {}" .to_owned(), ); config.export.body.insert( "CachedRenderingData".to_owned(), " constexpr CachedRenderingData() : cache_index{}, cache_generation{} {}".to_owned(), ); config.export.body.insert( "EasingCurve".to_owned(), " constexpr EasingCurve(EasingCurve::Tag tag = Tag::Linear, float a = 0, float b = 0, float c = 1, float d = 1) : tag(tag), cubic_bezier{{a,b,c,d}} {}".into() ); config.export.body.insert( "LayoutInfo".to_owned(), " inline LayoutInfo merge(const LayoutInfo &other) const; friend inline LayoutInfo operator+(const LayoutInfo &a, const LayoutInfo &b) { return a.merge(b); } friend bool operator==(const LayoutInfo&, const LayoutInfo&) = default;".into(), ); config.export.body.insert( "WindowEvent".to_owned(), "/* Some members of the WindowEvent enum have destructors (with SharedString), but thankfully we don't use these so we can have an empty constructor */ ~WindowEvent() {}" .into(), ); config .export .body .insert("FocusScope".to_owned(), " inline FocusScope(); inline ~FocusScope();".into()); config .export .pre_body .insert("MaybeKeyBindingList".to_owned(), "struct KeyBindingList;".into()); config .export .body .insert("Flickable".to_owned(), " inline Flickable(); inline ~Flickable();".into()); config.export.pre_body.insert("FlickableDataBox".to_owned(), "struct FlickableData;".into()); cbindgen::Builder::new() .with_config(config) .with_src(crate_dir.join("lib.rs")) .with_include("slint_config.h") .with_include("vtable.h") .with_include("slint_string.h") .with_include("slint_sharedvector.h") .with_include("slint_properties.h") .with_include("slint_callbacks.h") .with_include("slint_color.h") .with_include("slint_image.h") .with_include("slint_pathdata.h") .with_include("slint_brush.h") .with_include("slint_generated_public.h") .with_include("slint_enums_internal.h") .with_include("slint_point.h") .with_include("slint_timer.h") .with_include("slint_builtin_structs_internal.h") .with_include("slint_events_internal.h") .with_after_include( r" namespace slint { namespace private_api { class WindowAdapterRc; } namespace cbindgen_private { using slint::private_api::WindowAdapterRc; using namespace vtable; using private_api::Property; using private_api::PathData; using private_api::Point; struct ItemTreeVTable; struct ItemVTable; using types::IntRect; using types::Size; using types::MouseEvent; using types::Keys; } template class Model; }", ) .with_trailer(gen_item_declarations(&items)) .generate() .expect("Unable to generate bindings") .write_to_file(include_dir.join("slint_internal.h")); Ok(()) } fn gen_backend_qt( root_dir: &Path, include_dir: &Path, dependencies: &mut Vec, ) -> anyhow::Result<()> { let mut config = default_config(); let items = [ "NativeButton", "NativeSpinBox", "NativeCheckBox", "NativeSlider", "NativeProgressIndicator", "NativeGroupBox", "NativeLineEdit", "NativeScrollView", "NativeStandardListViewItem", "NativeTableHeaderSection", "NativeComboBox", "NativeComboBoxPopup", "NativeTabWidget", "NativeTab", "NativeStyleMetrics", "NativePalette", ]; config.export.include = items.iter().map(|x| x.to_string()).collect(); config.export.exclude = vec!["FloatArg".into(), "IntArg".into()]; config.export.body.insert( "NativeStyleMetrics".to_owned(), " inline explicit NativeStyleMetrics(void* = nullptr); inline ~NativeStyleMetrics();" .to_owned(), ); config.export.body.insert( "NativePalette".to_owned(), " inline explicit NativePalette(void* = nullptr); inline ~NativePalette();".to_owned(), ); let mut crate_dir = root_dir.to_owned(); crate_dir.extend(["internal", "backends", "qt"].iter()); ensure_cargo_rerun_for_crate(&crate_dir, dependencies)?; cbindgen::Builder::new() .with_config(config) .with_crate(crate_dir) .with_include("slint_internal.h") .with_after_include( r" namespace slint::cbindgen_private { // HACK ALERT: This struct declaration is duplicated in internal/backend/qt/qt_widgets.rs - keep in sync. struct SlintTypeErasedWidget { virtual ~SlintTypeErasedWidget() = 0; SlintTypeErasedWidget(const SlintTypeErasedWidget&) = delete; SlintTypeErasedWidget& operator=(const SlintTypeErasedWidget&) = delete; virtual void *qwidget() const = 0; }; using SlintTypeErasedWidgetPtr = std::unique_ptr; } ", ) .with_trailer(gen_item_declarations(&items)) .generate() .context("Unable to generate bindings for slint_qt_internal.h")? .write_to_file(include_dir.join("slint_qt_internal.h")); Ok(()) } fn gen_testing( root_dir: &Path, include_dir: &Path, dependencies: &mut Vec, ) -> anyhow::Result<()> { let config = default_config(); let mut crate_dir = root_dir.to_owned(); crate_dir.extend(["internal", "backends", "testing"].iter()); ensure_cargo_rerun_for_crate(&crate_dir, dependencies)?; cbindgen::Builder::new() .with_config(config) .with_crate(crate_dir) .with_include("slint_testing_internal.h") .generate() .context("Unable to generate bindings for slint_testing_internal.h")? .write_to_file(include_dir.join("slint_testing_internal.h")); Ok(()) } fn gen_platform( root_dir: &Path, include_dir: &Path, dependencies: &mut Vec, ) -> anyhow::Result<()> { let config = default_config(); let mut crate_dir = root_dir.to_owned(); crate_dir.extend(["api", "cpp"].iter()); ensure_cargo_rerun_for_crate(&crate_dir, dependencies)?; cbindgen::Builder::new() .with_config(config) .with_crate(crate_dir) .with_include("slint_image_internal.h") .with_include("slint_internal.h") .with_after_include( r" namespace slint::platform { struct Rgb565Pixel; } namespace slint::cbindgen_private { struct WindowProperties; using slint::platform::Rgb565Pixel; using slint::cbindgen_private::types::TexturePixelFormat; struct DrawTextureArgs; struct DrawRectangleArgs; using types::PhysicalRegion; } ", ) .generate() .context("Unable to generate bindings for slint_platform_internal.h")? .write_to_file(include_dir.join("slint_platform_internal.h")); Ok(()) } fn gen_interpreter( root_dir: &Path, include_dir: &Path, dependencies: &mut Vec, ) -> anyhow::Result<()> { let mut config = default_config(); config.export.exclude = IntoIterator::into_iter([ "Value", "ValueType", "PropertyDescriptor", "Diagnostic", "PropertyDescriptor", "Box", "LiveReloadingComponentInner", ]) .map(String::from) .collect(); let mut crate_dir = root_dir.to_owned(); crate_dir.extend(["internal", "interpreter"].iter()); ensure_cargo_rerun_for_crate(&crate_dir, dependencies)?; // Generate a header file with some public API (enums, etc.) let mut public_config = config.clone(); public_config.namespaces = Some(vec!["slint".into(), "interpreter".into()]); public_config.export.item_types = vec![cbindgen::ItemType::Enums, cbindgen::ItemType::Structs]; public_config.export.exclude = IntoIterator::into_iter([ "ComponentCompilerOpaque", "ComponentDefinitionOpaque", "ModelAdaptorVTable", "StructIteratorOpaque", "ComponentInstance", "StructIteratorResult", "Value", "StructOpaque", "ModelNotifyOpaque", ]) .map(String::from) .collect(); cbindgen::Builder::new() .with_config(public_config) .with_crate(crate_dir.clone()) .generate() .context("Unable to generate bindings for slint_interpreter_generated_public.h")? .write_to_file(include_dir.join("slint_interpreter_generated_public.h")); cbindgen::Builder::new() .with_config(config) .with_crate(crate_dir) .with_include("slint_internal.h") .with_include("slint_interpreter_generated_public.h") .with_after_include( r" namespace slint::cbindgen_private { struct Value; using slint::interpreter::ValueType; using slint::interpreter::PropertyDescriptor; using slint::interpreter::Diagnostic; struct LiveReloadingComponentInner; template using Box = T*; }", ) .generate() .context("Unable to generate bindings for slint_interpreter_internal.h")? .write_to_file(include_dir.join("slint_interpreter_internal.h")); Ok(()) } macro_rules! declare_features { ($($f:ident)+) => { #[derive(Clone, Copy)] pub struct EnabledFeatures { $(pub $f: bool,)* } impl EnabledFeatures { /// Generate the `#define` pub fn defines(self) -> String { let mut defines = String::new(); $( if self.$f { defines = format!("{defines}///This macro is defined when Slint was configured with the SLINT_FEATURE_{0} flag enabled\n#define SLINT_FEATURE_{0}\n", stringify!($f).to_ascii_uppercase()); }; )* defines } /// Get the feature from the environment variable set by cargo when building running the slint-cpp's build script #[allow(unused)] pub fn from_env() -> Self { Self { $( $f: std::env::var(format!("CARGO_FEATURE_{}", stringify!($f).to_ascii_uppercase())).is_ok(), )* } } } }; } declare_features! { interpreter live_preview testing backend_qt backend_winit backend_winit_x11 backend_winit_wayland backend_linuxkms backend_linuxkms_noseat renderer_femtovg renderer_skia renderer_skia_opengl renderer_skia_vulkan renderer_software gettext accessibility system_testing freestanding experimental } /// Generate the headers. /// `root_dir` is the root directory of the slint git repo /// `include_dir` is the output directory /// Returns the list of all paths that contain dependencies to the generated output. If you call this /// function from build.rs, feed each entry to stdout prefixed with `cargo:rerun-if-changed=`. pub fn gen_all( root_dir: &Path, include_dir: &Path, enabled_features: EnabledFeatures, ) -> anyhow::Result> { proc_macro2::fallback::force(); // avoid a abort if panic=abort is set std::fs::create_dir_all(include_dir).context("Could not create the include directory")?; let mut deps = Vec::new(); enums(include_dir)?; builtin_structs(include_dir)?; gen_corelib(root_dir, include_dir, &mut deps, enabled_features)?; gen_backend_qt(root_dir, include_dir, &mut deps)?; gen_platform(root_dir, include_dir, &mut deps)?; if enabled_features.testing { gen_testing(root_dir, include_dir, &mut deps)?; } if enabled_features.interpreter { gen_interpreter(root_dir, include_dir, &mut deps)?; } Ok(deps) } ================================================ FILE: api/cpp/cmake/SlintConfig.cmake.in ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 @PACKAGE_INIT@ get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) if(_IMPORT_PREFIX STREQUAL "/") set(_IMPORT_PREFIX "") endif() add_library(@slint_cpp_impl@ @cmake_lib_type@ IMPORTED) set_target_properties(@slint_cpp_impl@ PROPERTIES @SLINT_LIB_PROPERTIES@) function(_slint_download_compiler_and_cache) set(SLINT_GITHUB_RELEASE "v@PROJECT_VERSION@" CACHE STRING "GitHub Release to use for Slint Binary Artifact Downloads") if (CMAKE_HOST_WIN32) set(compiler_exe_suffix ".exe") set(platform_suffix "windows-${CMAKE_HOST_SYSTEM_PROCESSOR}") else() set(platform_suffix "${CMAKE_HOST_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}") endif() set(prebuilt_archive_filename "slint-compiler-${platform_suffix}.tar.gz") set(download_url "https://github.com/slint-ui/slint/releases/download/${SLINT_GITHUB_RELEASE}/${prebuilt_archive_filename}") set(download_directory "${CMAKE_BINARY_DIR}/slint-prebuilt") message(STATUS "Downloading pre-built Slint compiler binary from ${download_url}") file(DOWNLOAD "${download_url}" "${download_directory}/${prebuilt_archive_filename}" STATUS download_status) list(GET download_status 0 download_code) if (NOT download_code EQUAL 0) list(GET download_status 1 download_message) message(FATAL_ERROR "Download of Slint compiler binary package failed: ${download_message}") else() file(ARCHIVE_EXTRACT INPUT "${download_directory}/${prebuilt_archive_filename}" DESTINATION "${download_directory}") file(REMOVE "${download_directory}/${prebuilt_archive_filename}") set(SLINT_COMPILER "${download_directory}/slint-compiler${compiler_exe_suffix}") set(SLINT_COMPILER "${SLINT_COMPILER}" CACHE STRING "Path to the slint-compiler executable" FORCE) set(SLINT_COMPILER "${SLINT_COMPILER}" PARENT_SCOPE) endif() endfunction() set(SLINT_COMPILER @SLINT_COMPILER@ CACHE STRING "Path to the slint-compiler executable") if (SLINT_COMPILER) if (SLINT_COMPILER STREQUAL "download") _slint_download_compiler_and_cache(_) endif() add_executable(Slint::slint-compiler IMPORTED GLOBAL) set_target_properties(Slint::slint-compiler PROPERTIES IMPORTED_LOCATION ${SLINT_COMPILER}) include("${CMAKE_CURRENT_LIST_DIR}/SlintMacro.cmake") elseif (@SLINT_FEATURE_COMPILER@) add_executable(Slint::slint-compiler IMPORTED GLOBAL) set_target_properties(Slint::slint-compiler PROPERTIES IMPORTED_LOCATION "${_IMPORT_PREFIX}/@CMAKE_INSTALL_BINDIR@/@SLINT_COMPILER_FILE_NAME@") include("${CMAKE_CURRENT_LIST_DIR}/SlintMacro.cmake") endif() set(_IMPORT_PREFIX) include("${CMAKE_CURRENT_LIST_DIR}/SlintTargets.cmake") set(SLINT_STYLE @_SLINT_STYLE@ CACHE STRING "The Slint widget style") set_property(GLOBAL PROPERTY SLINT_STYLE ${SLINT_STYLE}) ================================================ FILE: api/cpp/cmake/SlintMacro.cmake ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # Set up machinery to handle SLINT_EMBED_RESOURCES target property set(DEFAULT_SLINT_EMBED_RESOURCES as-absolute-path CACHE STRING "The default resource embedding option to pass to the Slint compiler") set_property(CACHE DEFAULT_SLINT_EMBED_RESOURCES PROPERTY STRINGS "as-absolute-path" "embed-files" "embed-for-software-renderer" "embed-for-software-renderer-with-sdf") ## This requires CMake 3.23 and does not work in 3.26 AFAICT. # define_property(TARGET PROPERTY SLINT_EMBED_RESOURCES # INITIALIZE_FROM_VARIABLE DEFAULT_SLINT_EMBED_RESOURCES) function(SLINT_TARGET_SOURCES target) # Parse the NAMESPACE argument cmake_parse_arguments(SLINT_TARGET_SOURCES "" "NAMESPACE;COMPILATION_UNITS" "LIBRARY_PATHS" ${ARGN}) get_target_property(enabled_features Slint::Slint SLINT_ENABLED_FEATURES) if ("EXPERIMENTAL" IN_LIST enabled_features) set(SLINT_COMPILER_ENV ${CMAKE_COMMAND} -E env) set(SLINT_COMPILER_ENV ${SLINT_COMPILER_ENV} SLINT_ENABLE_EXPERIMENTAL_FEATURES=1) if ("SYSTEM_TESTING" IN_LIST enabled_features) set(SLINT_COMPILER_ENV ${SLINT_COMPILER_ENV} SLINT_EMIT_DEBUG_INFO=1) endif() endif() if (DEFINED SLINT_TARGET_SOURCES_NAMESPACE) # Remove the NAMESPACE argument from the list list(FIND ARGN "NAMESPACE" _index) list(REMOVE_AT ARGN ${_index}) list(FIND ARGN "${SLINT_TARGET_SOURCES_NAMESPACE}" _index) list(REMOVE_AT ARGN ${_index}) # If the namespace is not empty, add the --cpp-namespace argument set(_SLINT_CPP_NAMESPACE_ARG "--cpp-namespace=${SLINT_TARGET_SOURCES_NAMESPACE}") endif() if (DEFINED SLINT_TARGET_SOURCES_COMPILATION_UNITS) if (NOT SLINT_TARGET_SOURCES_COMPILATION_UNITS MATCHES "^[0-9]+$") message(FATAL_ERROR "Expected number, got '${SLINT_TARGET_SOURCES_COMPILATION_UNITS}' for COMPILATION_UNITS argument") endif() set(compilation_units ${SLINT_TARGET_SOURCES_COMPILATION_UNITS}) else() set(compilation_units 1) endif() while (SLINT_TARGET_SOURCES_LIBRARY_PATHS) list(POP_FRONT SLINT_TARGET_SOURCES_LIBRARY_PATHS name_and_path) list(APPEND _SLINT_CPP_LIBRARY_PATHS_ARG "-L") list(APPEND _SLINT_CPP_LIBRARY_PATHS_ARG "${name_and_path}") endwhile() foreach (it IN ITEMS ${SLINT_TARGET_SOURCES_UNPARSED_ARGUMENTS}) get_filename_component(_SLINT_BASE_NAME ${it} NAME_WE) get_filename_component(_SLINT_ABSOLUTE ${it} REALPATH BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) get_property(_SLINT_STYLE GLOBAL PROPERTY SLINT_STYLE) set(t_prop "$>") set(global_fallback "${DEFAULT_SLINT_EMBED_RESOURCES}") set(embed "$,${global_fallback},${t_prop}>") set(scale_factor_target_prop "$>") set(scale_factor_arg "$,,--scale-factor=${scale_factor_target_prop}>") set(bundle_translations_prop "$>") set(bundle_translations_arg "$,,--bundle-translations=${bundle_translations_prop}>") set(translation_domain_prop "$>") set(translation_domain_arg "$,${target},${translation_domain_prop}>") set(no_default_translation_context_bool "$>") set(no_default_translation_context_arg "$") if (compilation_units GREATER 0) # We need to set this to empty, as this variable is reused in every foreach iteration. set(cpp_files "") foreach(cpp_num RANGE 1 ${compilation_units}) list(APPEND cpp_files "${CMAKE_CURRENT_BINARY_DIR}/slint_generated_${_SLINT_BASE_NAME}_${cpp_num}.cpp") endforeach() list(TRANSFORM cpp_files PREPEND "--cpp-file=" OUTPUT_VARIABLE cpp_files_arg) endif() add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_SLINT_BASE_NAME}.h ${cpp_files} COMMAND ${SLINT_COMPILER_ENV} $ ${_SLINT_ABSOLUTE} -f cpp -o ${CMAKE_CURRENT_BINARY_DIR}/${_SLINT_BASE_NAME}.h --depfile ${CMAKE_CURRENT_BINARY_DIR}/${_SLINT_BASE_NAME}.d --style ${_SLINT_STYLE} --embed-resources=${embed} --translation-domain=${translation_domain_arg} ${no_default_translation_context_arg} ${_SLINT_CPP_NAMESPACE_ARG} ${_SLINT_CPP_LIBRARY_PATHS_ARG} ${scale_factor_arg} ${bundle_translations_arg} ${cpp_files_arg} DEPENDS Slint::slint-compiler ${_SLINT_ABSOLUTE} COMMENT "Generating ${_SLINT_BASE_NAME}.h" DEPFILE ${CMAKE_CURRENT_BINARY_DIR}/${_SLINT_BASE_NAME}.d WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) target_sources(${target} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/${_SLINT_BASE_NAME}.h ${cpp_files}) endforeach() target_include_directories(${target} PUBLIC "$") endfunction() ================================================ FILE: api/cpp/docs/_static/theme_tweak.css ================================================ .wy-table-responsive table td, .wy-table-responsive table th { white-space: normal; } .wy-table-responsive { margin-bottom: 24px; max-width: 100%; overflow: visible; } img.logo { max-height: 70px; border-radius: 12% !important; } ================================================ FILE: api/cpp/docs/_templates/base.html ================================================ {% extends "!base.html" %} {% block scripts %} {{ super() }} {% include "../../../../docs/astro/src/utils/slint-docs-preview.html" %} {% include "../../../../docs/astro/src/utils/slint-docs-highlight.html" %} {% endblock %} ================================================ FILE: api/cpp/docs/cmake.md ================================================ # Set Up Development Environment ## Prerequisites * A C++ compiler that supports C++20 (e.g., **MSVC 2019 16.6** on Windows) * **[cmake](https://cmake.org/download/)** (3.21 or newer) * Slint comes with a CMake integration that automates the compilation step of the `.slint` markup language files and offers a CMake target for convenient linkage. * *Note*: We recommend using the Ninja generator of CMake for the most efficient build and `.slint` dependency tracking. Install [Ninja](https://ninja-build.org) and select the CMake Ninja backend by passing `-GNinja` or set the `CMAKE_GENERATOR` environment variable to `Ninja`. ## Install Slint To install Slint, either download the [binary packages](#install-binary-packages) or [build from sources](#build-from-sources). *Note*: Binary packages are available for only Linux and Windows on x86-64 architecture. The recommended and most flexible way to use the C++ API is to build Slint from sources. ### Install Binary Packages The Slint binary packages work without any Rust development environment. Steps: 1. Open 2. Click on the latest release 3. From "Assets" ("XXX" refers to the version of the latest release), * for Linux x86-64 architecture - download `slint-cpp-XXX-Linux-x86_64.tar.gz` * for Windows x86-64 architecture - download `slint-cpp-XXX-win64-MSVC.exe` 4. Unpack the downloaded archive (Linux) or run the installer executable (Windows). 5. Set environment variables * set `CMAKE_PREFIX_PATH` to the installation directory of Slint. Alternatively you can pass `-DCMAKE_PREFIX_PATH=/path/to/installed/slint` argument when invoking cmake. This helps `find_package(Slint)` to find Slint from within a `CMakeLists.txt` file. * add the `lib` sub-directory in the installation directory of Slint to `LD_LIBRARY_PATH` (Linux) or to the `PATH` environment variable (Windows). This is necessary to find the Slint libraries when running a Slint program. In the next section you will learn how to use the installed library in your application and how to work with `.slint` UI files. ### Build From Sources First you need to install the prerequisites: * Install Rust by following the [Rust Getting Started Guide](https://www.rust-lang.org/learn/get-started). If you already have Rust installed, make sure that it's at least version 1.88 or newer. You can check which version you have installed by running `rustc --version`. Once this is done, you should have the `rustc` compiler and the `cargo` build system installed in your path. You can either choose to compile Slint from source along with your application or include Slint as an external CMake package. * To compile Slint along with your application, include Slint into your CMake project using CMake's [`FetchContent`](https://cmake.org/cmake/help/latest/module/FetchContent.html) feature. Insert the following snippet into your `CMakeLists.txt` to make CMake download the latest released 1.x version, compile it, and make the CMake integration available: ```cmake include(FetchContent) FetchContent_Declare( Slint GIT_REPOSITORY https://github.com/slint-ui/slint.git # `release/1` will auto-upgrade to the latest Slint >= 1.0.0 and < 2.0.0 # `release/1.0` will auto-upgrade to the latest Slint >= 1.0.0 and < 1.1.0 GIT_TAG release/1 SOURCE_SUBDIR api/cpp ) FetchContent_MakeAvailable(Slint) ``` * To include Slint as an external CMake package, build Slint from source like a regular CMake project, install it into a prefix directory of your choice and use `find_package(Slint)` in your `CMakeLists.txt`. ### Features The Slint library supports a set of features, not all of them enabled by default. You might want to adapt the set of enabled features to optimize your binary size. For example you might want to support only the wayland stack on Linux. Enable the `backend-winit-wayland` feature while turning off the `backend-winit-x11` feature to do so. Slint's CMake configuration uses CMake options prefixed with `SLINT_FEATURE_` to expose Slint's feature flags at compile time. To have a wayland-only stack with the CMake setup you would for example use: `cmake -DSLINT_FEATURE_BACKEND_WINIT=OFF -DSLINT_FEATURE_BACKEND_WINIT_WAYLAND=ON ...` Alternatively, you can use `cmake-gui` or `ccmake` for a more interactive way to discover and toggle features. This works when compiling Slint as a package, using `cmake --build` and `cmake --install`, or when including Slint using `FetchContent`. If you need to check in your application's `CMakeLists.txt` whether a feature is enabled or disabled, read the `SLINT_ENABLED_FEATURES` and `SLINT_DISABLED_FEATURES` target properties from the `Slint::Slint` cmake target: ```cmake get_target_property(slint_enabled_features Slint::Slint SLINT_ENABLED_FEATURES) if ("BACKEND_WINIT" IN_LIST slint_enabled_features) ... endif() ``` Similarly, if you need to check for features at compile-time, check for the existence of `SLINT_FEATURE_` pre-processor macros: ``` #include #if defined(SLINT_FEATURE_BACKEND_WINIT) // ... #endif ``` ### Rust Flags Slint uses [Corrosion](https://github.com/corrosion-rs/corrosion) to build Slint, which is developed in Rust. You can utilize [Corrosion's global CMake variables](https://corrosion-rs.github.io/corrosion/usage.html#global-corrosion-options) to control certain aspects of the Rust build process. Furthermore, you can set the `SLINT_LIBRARY_CARGO_FLAGS` cache variable to specify additional flags for the Slint runtime during the build. ### Platform Backends In Slint, a backend is the module that encapsulates the interaction with the operating system, in particular the windowing sub-system. Multiple backends can be compiled into Slint and one backend is selected for use at run-time on application start-up. You can configure Slint without any built-in backends, and instead develop your own backend by implementing Slint's platform abstraction and window adapter interfaces. For more information about the available backends, their system requirements, and configuration options, see the {{ '[Backend & Renderers Documentation]({})'.format(slint_href_backends_and_renderers) }}. By default Slint will include both the Qt and [winit](https://crates.io/crates/winit) back-ends -- if both are detected at compile time. You can enable or disable back-ends using the `SLINT_FEATURE_BACKEND_` features. For example, to exclude the winit back-end, you would disable the `SLINT_FEATURE_BACKEND_WINIT` option in your CMake project configuration. ### Cross-compiling It's possible to cross-compile Slint to a different target architecture when building with CMake. You need to make sure your CMake setup is ready for cross-compilation, as documented in the [upstream CMake documentation](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling). If you are building against a Yocto SDK, it is sufficient to source the SDK's environment setup file. Since Slint is implemented using the Rust programming language, you need to determine which Rust target matches the target architecture that you're compiling for. Please consult the [upstream Rust documentation](https://doc.rust-lang.org/nightly/rustc/platform-support.html) to find the correct target name. Now you need to install the Rust toolchain: ```sh rustup target add ``` Then you're ready to iconfigure your CMake project you need to add `-DRust_CARGO_TARGET=` to the CMake command line. This ensures that the Slint library is built for the correct architecture. For example if you are building against an embedded Linux Yocto SDK targeting an ARM64 board, the following commands show how to compile: Install the Rust target toolchain once: ```sh rustup target add aarch64-unknown-linux-gnu ``` Set up the environment and build: ```sh . /path/to/yocto/sdk/environment-setup-cortexa53-crypto-poky-linux cd mkdir build cd build cmake -DRust_CARGO_TARGET=aarch64-unknown-linux-gnu -DCMAKE_INSTALL_PREFIX=/slint/install/path .. cmake --build . cmake --install . ``` ================================================ FILE: api/cpp/docs/cmake_reference.md ================================================ # CMake Reference ## `slint_target_sources` ``` slint_target_sources( .... [NAMESPACE namespace] [LIBRARY_PATHS name1=lib1 name2=lib2 ...] [COMPILATION_UNITS num]) ``` Use this function to tell CMake about the .slint files of your application, similar to the builtin cmake [target_sources](https://cmake.org/cmake/help/latest/command/target_sources.html) function. The function takes care of running the slint-compiler to convert `.slint` files to `.h` files in the build directory, and extend the include directories of your target so that the generated file is found when including it in your application. The optional `NAMESPACE` argument will put the generated components in the given C++ namespace. Use the `LIBRARY_PATHS` argument to specify the name and paths to {{ '[component libraries]({})'.format(slint_href_ComponentLibraries) }}, separated by an equals sign (`=`). Given a file called `the_window.slint`, the following example will create a file called `the_window.h` that can be included from your .cpp file. Assuming the `the_window.slint` contains a component `TheWindow`, the output C++ class will be put in the namespace `ui`, resulting to `ui::TheWindow`. Any import from `@mycomponentlib/` will be redirected to the specified path. ```cmake add_executable(my_application main.cpp) target_link_libraries(my_application PRIVATE Slint::Slint) slint_target_sources(my_application the_window.slint NAMESPACE ui LIBRARY_PATHS mycomponentlib=/path/to/customcomponents ) ``` By default, a `.slint` file is compiled to a `.h` file for inclusion in your application's business logic code, and a `.cpp` file with code generated by the slint-compiler. If you want to speed up compilation of the generated `.cpp` file, then you can pass the `COMPILATION_UNITS` argument with a value greater than 1 to create multiple `.cpp` files. These can be compiled in parallel, which might speed up overall build times. However, splitting the generated code across multiple `.cpp` files decreases the compiler's visibility and thus ability to perform optimizations. You can also pass `COMPILATION_UNITS 0` to generate only one single `.h` file. ```{caution} Compiling multiple .slint files with the same namespace may create conflicting symbols. Avoid this by putting each .slint file in its own namespace. ``` ## Resource Embedding By default, images from {{ '[`@image-url()`]({})'.format(slint_href_ImageType) }} or fonts that your Slint files reference are loaded from disk at run-time. This minimises build times, but requires that the directory structure with the files remains stable. If you want to build a program that runs anywhere, then you can configure the Slint compiler to embed such sources into the binary. Set the `SLINT_EMBED_RESOURCES` target property on your CMake target to one of the following values: * `embed-files`: The raw files are embedded in the application binary. * `embed-for-software-renderer`: The files will be loaded by the Slint compiler, optimized for use with the software renderer and embedded in the application binary. * `embed-for-software-renderer-with-sdf`: Same as `embed-for-software-renderer`, but use [Signed Distance Fields (SDF)](https://en.wikipedia.org/wiki/Signed_distance_function) to render fonts. This produces smaller binaries, but may result in slightly inferior visual output and slower rendering. (Requires the `SLINT_FEATURE_SDF_FONTS` feature to be enabled.) * `as-absolute-path`: The paths of files are made absolute and will be used at run-time to load the resources from the file system. This is the default. This target property is initialised from the global `DEFAULT_SLINT_EMBED_RESOURCES` cache variable. Set it to configure the default for all CMake targets. ```cmake # Example: when building my_application, specify that the compiler should embed the resources in the binary set_property(TARGET my_application PROPERTY SLINT_EMBED_RESOURCES embed-files) ``` ## Scale Factor for Microcontrollers When targeting a Microcontroller, there exists no windowing system that provides a device pixel ratio to map logical lengths in Slint (`px`) to physical pixels (`phx`). If desired, you can provide this ratio at compile time by setting the `SLINT_SCALE_FACTOR` target property on your CMake target. ```cmake # Example: when building my_application, specify that the scale factor shall be 2 set_property(TARGET my_application PROPERTY SLINT_SCALE_FACTOR 2.0) ``` A scale factor specified this way will also be used to pre-scale images and glyphs when used in combination with [Resource Embedding](#resource-embedding). ## Bundle Translations Translations can either be done using `gettext` at runtime, or by bundling all the translated strings directly into the binary, by embedding them in the generated C++ code. If you want to bundle translations, you need to set the `SLINT_BUNDLE_TRANSLATIONS` target property to point to a directory containing translations. The translations must be in the gettext `.po` format. In the following example, the translation files will be bundled from `lang//LC_MESSAGES/my_application.po` ```cmake set_property(TARGET my_application PROPERTY SLINT_BUNDLE_TRANSLATIONS "${CMAKE_CURRENT_SOURCE_DIR}/lang") ``` ## Translation Domain By default, the domain used for translations is the name of the CMake target the `.slint` files are targeted with. Use the `SLINT_TRANSLATION_DOMAIN` target property to override this and use the specified value as domain, instead. This is useful in build environments where the target name is given and not suitable, such as esp-idf. ## Disable Default Translation Context Unless explicitly specified with the `@tr("context" => ...)`, the default translation context is the component name. Set the `SLINT_NO_DEFAULT_TRANSLATION_CONTEXT` target property to disable the default translation context. When doing that, the `--no-default-translation-context` flag must be passed to `slint-tr-extractor`. ================================================ FILE: api/cpp/docs/conf.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Software-3.0 # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import sys # sys.path.insert(0, os.path.abspath('.')) import textwrap import os import json # -- Project information ----------------------------------------------------- # The full version, including alpha/beta/rc tags version = "1.16.0" project = f"Slint {version} C++ API" copyright = "SixtyFPS GmbH" author = "Slint Developers " cpp_index_common_prefix = ["slint::", "slint::interpreter::"] # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "breathe", "myst_parser", "exhale", "sphinx_markdown_tables", "sphinxcontrib.jquery", ] breathe_projects = {"Slint": "./docs/xml"} breathe_default_project = "Slint" exhale_args = { "containmentFolder": "./api", "rootFileName": "library_root.rst", "rootFileTitle": "C++ API Reference", "afterTitleDescription": textwrap.dedent( """ The following sections present the C++ API Reference. All types are within the :ref:`slint` namespace and are accessible by including the :code:`slint.h` header file. If you choose to load :code:`.slint` files dynamically at run-time, then you can use the classes in :ref:`slint::interpreter`, starting at :cpp:class:`slint::interpreter::ComponentCompiler`. You need to include the :code:`slint-interpreter.h` header file. """ ), "doxygenStripFromPath": "..", "createTreeView": True, "kindsWithContentsDirectives": [], "exhaleExecutesDoxygen": True, "exhaleDoxygenStdin": """INPUT = ../../api/cpp/include generated_include EXCLUDE_SYMBOLS = slint::cbindgen_private* slint::private_api* vtable* SLINT_DECL_ITEM EXCLUDE = ../../api/cpp/include/vtable.h ../../api/cpp/include/slint_tests_helper.h ../../api/cpp/include/slint-stm32.h ENABLE_PREPROCESSING = YES PREDEFINED += DOXYGEN INCLUDE_PATH = generated_include WARN_AS_ERROR = YES""", } # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [ "_build", "html/_static/collapsible-lists/LICENSE.md", "Thumbs.db", ".DS_Store", "markdown/tutorial", "markdown/building.md", "markdown/development.md", "markdown/install_qt.md", "markdown/README.md", "README.md", ] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "furo" html_theme_options = {"collapse_navigation": False} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] html_show_sourcelink = False html_logo = "https://slint.dev/logo/slint-logo-small-light.svg" myst_enable_extensions = ["html_image", "colon_fence", "substitution"] # Annotate h1/h2 elements with anchors myst_heading_anchors = 2 myst_url_schemes = { "slint-reference": f"https://slint.dev/releases/{version}/docs/slint/{{{{path}}}}", "http": None, "https": None, "mailto": None, } rst_epilog = "" myst_substitutions = {} with open( os.path.join( os.path.dirname(__file__), "..", "..", "internal", "core-macros", "link-data.json", ) ) as link_data: links = json.load(link_data) for key in links.keys(): href = links[key]["href"] url = f"https://slint.dev/releases/{version}/docs/slint/{href}" myst_substitutions[f"slint_href_{key}"] = url rst_epilog += f".. |{key}| replace:: :code:`{key}`\n" rst_epilog += f".. _{key}: {url}\n" def setup(app): app.add_css_file("theme_tweak.css") ================================================ FILE: api/cpp/docs/generated_code.md ================================================ # Generated Code The Slint compiler [called by the build system](cmake_reference.md#slint_target_sources) will generate a header file for the root `.slint` file. This header file will contain a `class` for every exported component from the main file that inherits from `Window` or `Dialog`. These classes have the same name as the component will have the following public member functions: * A `create` constructor function and a destructor. * A `show` function, which will show the component on the screen. You still need to spin the event loop by {cpp:func}`slint::run_event_loop()` or using the convenience `run` function in this class to render and react to user input! * A `hide` function, which de-registers the component from the windowing system. * A `window` function that provides access to the {cpp:class}`slint::Window`, to allow for further customization towards the windowing system. * A `run` convenience function, which will show the component and starts the event loop. * For each property: * A getter `get_` returning the property type. * A setter `set_` taking the new value of the property by const reference * For each callback: * `invoke_` function which takes the callback argument as parameter and call the callback. * `on_` function which takes a functor as an argument and sets the callback handler for this callback. the functor must accept the type parameter of the callback * For each public function declared in the root component, an `invoke_` function to call the function. * A `global` function to access exported global singletons. The `create` function creates a new instance of the component, which is wrapped in {cpp:class}`slint::ComponentHandle`. This is a smart pointer that owns the actual instance and keeps it alive as long as at least one {cpp:class}`slint::ComponentHandle` is in scope, similar to `std::shared_ptr`. For more complex user interfaces it's common to supply data in the form of an abstract data model, that's used with {{ '[`for` - `in`]({})'.format(slint_href_Models) }} repetitions or {{ '[ListView]({})'.format(slint_href_ListView) }} elements in the `.slint` language. All models in C++ are sub-classes of the {cpp:class}`slint::Model` and you can sub-class it yourself. For convenience, the {cpp:class}`slint::VectorModel` provides an implementation that's backed by a `std::vector`. ## Example Let's assume we've this code in our `.slint` file: ```slint,no-preview export component SampleComponent inherits Window { in-out property counter; // note that dashes will be replaced by underscores in the generated code in-out property user_name; callback hello; public function do-something(x: int) -> bool { return x > 0; } // ... maybe more elements here } ``` This generates a header with the following contents (edited for documentation purpose) ```cpp #include #include #include class SampleComponent { public: /// Constructor function inline auto create () -> slint::ComponentHandle; /// Destructor inline ~SampleComponent (); /// Show this component, and runs the event loop inline void run () const; /// Show the window that renders this component. Call `slint::run_event_loop()` /// to continuously render the contents and react to user input. inline void show () const; /// Hide the window that renders this component. inline void hide () const; /// Getter for the `counter` property inline int get_counter () const; /// Setter for the `counter` property inline void set_counter (const int &value) const; /// Getter for the `user_name` property inline slint::SharedString get_user_name () const; /// Setter for the `user_name` property inline void set_user_name (const slint::SharedString &value) const; /// Call this function to call the `hello` callback inline void invoke_hello () const; /// Sets the callback handler for the `hello` callback. template inline void on_hello (Functor && callback_handler) const; /// Call this function to call the `do-something` function. inline bool invoke_do_something (int x) const; /// Returns a reference to a global singleton that's exported. /// /// **Note:** Only globals that are exported or re-exported from the main .slint file will /// be exposed in the API inline template const T &global() const; private: /// private fields omitted }; ``` ## Global Singletons You can declare globally available singletons in your `.slint` files. If exported, these singletons are available via the `global()` getter function on the generated C++ class. Each global singleton maps to a class with getter/setter functions for properties and callbacks, similar to API that's created for your `.slint` component. For example the following `.slint` markup defines a global `Logic` singleton that's also exported: ```slint,ignore export global Logic { callback to_uppercase(string) -> string; } ``` Assuming this global is used together with the `SampleComponent` from the previous section, you can access `Logic` like this: ```cpp auto app = SampleComponent::create(); // ... app->global().on_to_uppercase([](SharedString str) -> SharedString { std::string arg(str); std::transform(arg.begin(), arg.end(), arg.begin(), toupper); return SharedString(arg); }); ``` :::{note} Global singletons are instantiated once per component. When declaring multiple components for `export` to C++, each instance will have their own instance of associated globals singletons. ::: ================================================ FILE: api/cpp/docs/genindex.rst ================================================ .. Copyright © SixtyFPS GmbH .. SPDX-License-Identifier: MIT =========== Index (C++) =========== ================================================ FILE: api/cpp/docs/getting_started.md ================================================ # Getting Started Once Slint is built, you can use it in your CMake application or library target in two steps: 1. Associate the `.slint` files that you'd like to use by calling the `slint_target_sources` CMake command. The first parameter is your application (or library) build target, and the parameters following are the names of the `.slint` files you want to include. This will compile the `.slint` files to C++ source code and included that into your built target. 2. The generated C++ source code needs the Slint run-time library. Use `target_link_libraries` to link your build target to `Slint::Slint`. A minimal CMake `CMakeLists.txt` file looks like this: ```cmake cmake_minimum_required(VERSION 3.21) project(my_application LANGUAGES CXX) # Note: Use find_package(Slint) instead of the following three commands, # if you prefer the package approach. include(FetchContent) FetchContent_Declare( Slint GIT_REPOSITORY https://github.com/slint-ui/slint.git # `release/1` will auto-upgrade to the latest Slint >= 1.0.0 and < 2.0.0 # `release/1.0` will auto-upgrade to the latest Slint >= 1.0.0 and < 1.1.0 GIT_TAG release/1 SOURCE_SUBDIR api/cpp ) FetchContent_MakeAvailable(Slint) add_executable(my_application main.cpp) slint_target_sources(my_application my_application_ui.slint) target_link_libraries(my_application PRIVATE Slint::Slint) # On Windows, copy the Slint DLL next to the application binary so that it's found. if (WIN32) add_custom_command(TARGET my_application POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ $ COMMAND_EXPAND_LISTS) endif() ``` Suppose `my_application_ui.slint` was a "Hello World" like this: ```slint,no-preview export component HelloWorld inherits Window { width: 400px; height: 400px; // Declare an alias that exposes the label's text property to C++ in property my_label <=> label.text; label := Text { y: parent.width / 2; x: parent.x + 200px; text: "Hello, world"; color: blue; } } ``` then you can use the following code in your `main` function to show the [`Window`](../slint/src/language/builtins/elements#window) and change the text: ```cpp #include "my_application_ui.h" int main(int argc, char **argv) { auto hello_world = HelloWorld::create(); hello_world->set_my_label("Hello from C++"); // Show the window and spin the event loop until the window is closed. hello_world->run(); return 0; } ``` This works because the Slint compiler translated `my_application_ui.slint` to C++ code, in the `my_application_ui.h` header file. That generated code contains a C++ class that corresponds to the `HelloWorld` element and has API to create the UI, read or write properties, and set callbacks. You can learn more about how this API looks like in general in the [](generated_code.md) section. ## Tutorial For an in-depth walk-through, read our Slint Memory Game Tutorial. It will guide you through the `.slint` mark-up language and the C++ API by building a simple memory game. ## Template You can check out the [Slint C++ Template](https://github.com/slint-ui/slint-cpp-template) with the code of a minimal C++ application using Slint. It provides a convenient starting point to a new program. Of course you can also read on: We will cover some recipes to handle common use-cases next. ================================================ FILE: api/cpp/docs/index.rst ================================================ .. Copyright © SixtyFPS GmbH .. SPDX-License-Identifier: MIT .. Slint C++ documentation master file Slint C++ documentation ======================================== .. toctree:: :maxdepth: 2 :hidden: :caption: Getting Started On Desktop cmake.md First Steps live_preview.md .. toctree:: :maxdepth: 2 :hidden: :caption: Getting Started on MCU mcu/intro.md mcu/esp_idf.md mcu/stm32.md mcu/generic.md .. toctree:: :maxdepth: 2 :hidden: :caption: C++ / .slint Integration Overview Type Mapping to C++ Example Generated Code .. toctree:: :maxdepth: 2 :hidden: :caption: Reference api/library_root cmake_reference.md genindex .. toctree:: :maxdepth: 0 :hidden: :caption: Third-Party Licenses thirdparty.md .. image:: https://github.com/slint-ui/slint/workflows/CI/badge.svg :target: https://github.com/slint-ui/slint/actions :alt: GitHub CI Build Status .. image:: https://img.shields.io/github/discussions/slint-ui/slint :target: https://github.com/slint-ui/slint/discussions :alt: GitHub Discussions `Slint `_ is a toolkit to efficiently develop fluid graphical user interfaces for any display: embedded devices and desktop applications. Slint C++ is the C++ API to interact with a Slint UI from C++. The .slint Markup Language ======================= Slint comes with a markup language that is specifically designed for user interfaces. This language provides a powerful way to describe graphical elements, their placement, and the flow of data through the different states. It is a familiar syntax to describe the hierarchy of elements and property bindings. Here's the obligatory "Hello World": .. code-block:: slint,ignore export component HelloWorld inherits Window { width: 400px; height: 400px; Text { y: parent.width / 2; x: parent.x + 200px; text: "Hello, world"; color: blue; } } Check out the `Slint Language Documentation <../slint>`_ for more details. Architecture ============ An application is composed of the business logic written in C++ and the `.slint` user interface design markup, which is compiled to native code. .. image:: https://slint.dev/resources/architecture.drawio.svg :alt: Architecture Overview Developing ========== You can create and edit `.slint` files using our `Slint Visual Studio Code Extension `_, which features syntax highlighting and live design preview. For a quick edit and preview cycle, you can also use the :code:`slint-viewer` command line tool, which can be installed using :code:`cargo install slint-viewer`, if you have `Cargo `_ installed. In the next section you will learn how to install the Slint C++ library and the CMake build system integration. ================================================ FILE: api/cpp/docs/live_preview.md ================================================ # Live-Preview `.slint` files are compiled to C++ code when using the [`slint_target_sources()`](cmake_reference.md#slint_target_sources) function. This is the default and recommended for release builds. During debugging and development, changes to `.slint` files requires re-compiling and re-starting the application. To speed up modifications to the UI while connected to the applications' business logic, you can opt into enabling Live-Preview for C++: 1. Compile Slint [from sources](cmake.md#build-from-sources). At the configure step, enable the `SLINT_FEATURE_LIVE_PREVIEW` cmake option. 2. When compiling your application, set the `SLINT_LIVE_PREVIEW=1` environment variable. 3. Start you application. The Slint run-time library will load and reload `.slint` files after you've modified them on disk. ================================================ FILE: api/cpp/docs/mcu/esp-idf/troubleshoot.md ================================================ # Troubleshooting You may run into compile or run-time issues due to Slint's requirements. The following sections track issues we're aware of and how to solve them. ## Rust Compilation Error During Slint Build You see the following error: ``` error: the `-Z` flag is only accepted on the nightly channel of Cargo, but this is the `stable` channel ``` Solution: You need to configure your Rust toolchain to use the esp channel. Either set the `RUSTUP_TOOLCHAIN` environment variable to the value `esp` or create a file called `rust-toolchain.toml` in your project directory with the following contents: ```toml [toolchain] channel = "esp" ``` ## The device crashes at boot or enter a boot loop One reason could be that you don't have enough ram for the heap or the stack. Make sure that the stack is big enough (~8KiB), and that all the RAM was made available for the heap allocator. ## Wrong colors shown If colors look inverted on your display, it may be an incompatibility between how RGB565 colors are ordered in little-endian and your display expecting a different byte order. Typically, esp32 devices are little ending and display controllers often expect big-endian or `esp_lcd` configures them accordingly. Therefore, by default Slint converts pixels to big-endian. If your display controller expects little endian, set the `byte_swap` field in `SlintPlatformConfiguration` to `false`. ## Errors about multiple symbol definitions when linking You see errors at application link time such as these: ``` compiler_builtins.4c2482f45199cb1e-cgu.05:(.text.__udivdi3+0x0): multiple definition of `__udivdi3'; .../libgcc.a(_udivdi3.o): first defined here ``` Solution: Add `-Wl,--allow-multiple-definition` to your linker flags by using the following cmake command: ```cmake target_link_options(${COMPONENT_LIB} PUBLIC -Wl,--allow-multiple-definition) ``` ================================================ FILE: api/cpp/docs/mcu/esp_idf.md ================================================ # Espressif's IoT Development Framework Slint provides a [component](https://components.espressif.com/components/slint/slint) for the [Espressif IoT Development Framework](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/index.html). It has been tested on ESP32-S3 devices. ## Prerequisites * Install the [Espressif IoT Development Framework](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/index.html) and open a terminal or command prompt with the environment set up. On Windows, follow the [Using the Command Prompt](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html#using-the-command-prompt) instructions, on macOS and Linux, follow the [Set up the Environment Variables](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/linux-macos-setup.html#step-4-set-up-the-environment-variables) instructions. By default, Slint will use pre-compiled binaries. If for some reason there are no binaries available, the build will fall back to compiling Slint from source and you need to have [Rust installed](https://esp-rs.github.io/book/installation/rust.html installed) as well as the [Rust toolchains for Espressif SoCs with Xtensa and RISC-V targets](https://esp-rs.github.io/book/installation/riscv-and-xtensa.html). ## First Steps The following steps will guide from the a bare-bones esp-idf "hello_world" to a GUI with Slint. 1. Start by creating a new project: ```bash idf.py create-project slint-hello-world cd slint-hello-world ``` 2. Select your chipset with `idf.py set-target`, for example if you're using an `ESP32S3` chipset, run ```bash idf.py set-target esp32s3 ``` 3. Add a [Board Support Package](https://github.com/espressif/esp-bsp#esp-bsp-espressifs-board-support-packages) that matches your device as a dependency. For example, if you're using an ESP-BOX, run ```bash idf.py add-dependency esp-box ``` 4. Add Slint as a dependency: ```bash idf.py add-dependency slint/slint ``` 5. Remove `main/slint-hello-world.c`. 6. Create a new file `main/slint-hello-world.cpp` with the following contents: ```cpp #include #include #include #include #include #include #if defined(BSP_LCD_DRAW_BUFF_SIZE) # define DRAW_BUF_SIZE BSP_LCD_DRAW_BUFF_SIZE #else # define DRAW_BUF_SIZE (BSP_LCD_H_RES * CONFIG_BSP_LCD_DRAW_BUF_HEIGHT) #endif #include "app-window.h" extern "C" void app_main(void) { /* Initialize display */ esp_lcd_panel_io_handle_t io_handle = NULL; esp_lcd_panel_handle_t panel_handle = NULL; const bsp_display_config_t bsp_disp_cfg = { .max_transfer_sz = DRAW_BUF_SIZE * sizeof(uint16_t), }; bsp_display_new(&bsp_disp_cfg, &panel_handle, &io_handle); /* Set display brightness to 100% */ bsp_display_backlight_on(); /* Initialize touch */ esp_lcd_touch_handle_t touch_handle = NULL; const bsp_touch_config_t bsp_touch_cfg = {}; bsp_touch_new(&bsp_touch_cfg, &touch_handle); /* Allocate a drawing buffer */ static std::vector buffer(BSP_LCD_H_RES * BSP_LCD_V_RES); /* Initialize Slint's ESP platform support*/ slint_esp_init(SlintPlatformConfiguration { .size = slint::PhysicalSize({ BSP_LCD_H_RES, BSP_LCD_V_RES }), .panel_handle = panel_handle, .touch_handle = touch_handle, .buffer1 = buffer, .byte_swap = true }); /* Instantiate the UI */ auto ui = AppWindow::create(); /* Show it on the screen and run the event loop */ ui->run(); } ``` 7. Create `main/app-window.slint` with the following contents: ``` import { VerticalBox, AboutSlint } from "std-widgets.slint"; export component AppWindow inherits Window { VerticalBox { AboutSlint {} Text { text: "Hello World"; font-size: 18px; horizontal-alignment: center; } } } ``` 8. Edit `main/CMakeLists.txt` to adjust for the new `slint-hello-world.cpp`, add `slint` as required component, and instruction the build system to compile `app-window.slint` to `app-window.h`. The file should look like this: ```cmake idf_component_register(SRCS "slint-hello-world.cpp" INCLUDE_DIRS "." REQUIRES slint) slint_target_sources(${COMPONENT_LIB} app-window.slint) ``` 9. Open the configuration editor with `idf.py menuconfig`: * Change the stack size under `Component config --> ESP System Settings --> Main task stack size` to at least `8192`. You may need to tweak this value in the future if you run into stack overflows. * You may need additional device-specific settings. For example if your device has external SPI RAM, you may need to enable that. For details for ESP32-S3 based devices see how to [Configure the PSRAM](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/flash_psram_config.html#configure-the-psram). * Quit the editor with `Q` and save the configuration. Alternatively, check in a default sdkconfig tweaked from your board that adds the right amount of ram, flash, and use `CONFIG_MAIN_TASK_STACK_SIZE=8192` 10. Build the project with `idf.py build`. 11. Connect your device, then flash and run it with `idf.py flash monitor`. 12. Observe Slint rendering "Hello World" on the screen 🎉. Congratulations, you're all set up to develop with Slint. ## Next Steps - For more details about the Slint language, check out the [Slint Language Documentation](slint-reference:). - Learn about the [](../types.md) between Slint and C++. - Study the [](../api/library_root). ```{toctree} :maxdepth: 2 :hidden: :caption: Espressif's IoT Development Framework esp-idf/troubleshoot.md ``` ================================================ FILE: api/cpp/docs/mcu/generic.md ================================================ # Generic MCU Environment Setup We aim to support many different MCUs and their respective software development environments. For those environments where we can't provide an out-of-the-box integration, we provide the following generic instructions on what's needed to compile and use Slint. ## Prerequisites * Install Rust by following the [Rust Getting Started Guide](https://www.rust-lang.org/learn/get-started). If you already have Rust installed, make sure that it's at least version 1.88 or newer. You can check which version you have installed by running `rustc --version`. Once this is done, you should have the `rustc` compiler and the `cargo` build system installed in your path. * A C++ cross-compiler compiler that supports C++20. * **[cmake](https://cmake.org/download/)** (3.21 or newer) * Slint comes with a CMake integration that automates the compilation step of the `.slint` markup language files and offers a CMake target for convenient linkage. * *Note*: We recommend using the Ninja generator of CMake for the most efficient build and `.slint` dependency tracking. Install [Ninja](https://ninja-build.org) and select the CMake Ninja backend by passing `-GNinja` or set the `CMAKE_GENERATOR` environment variable to `Ninja`. * A build environment for [cross-compilation with CMake](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling), such as a toolchain file. ## Compiling Slint To target an MCU environment, all of the following additional CMake configuration options must be set when compiling Slint: | Option | Description | |---------------------------------------------------------------|----------------------------------------------------------------------| | `-DSLINT_FEATURE_FREESTANDING=ON` | Enables building for environments without a standard library. | | `-DBUILD_SHARED_LIBS=OFF` | Disables shared library support and instead builds Slint statically. | | `-DSLINT_FEATURE_RENDERER_SOFTWARE=ON` | Enable support for the software renderer. | | `-DDEFAULT_SLINT_EMBED_RESOURCES=embed-for-software-renderer` | Default to pre-compiling images and fonts. | For example, if you're targeting an MCU with a ARM Cortex-M processor, the complete command line for CMake could look like this: ```sh cmake -DRust_CARGO_TARGET=thumbv7em-none-eabihf -DSLINT_FEATURE_FREESTANDING=ON -DBUILD_SHARED_LIBS=OFF -DSLINT_FEATURE_RENDERER_SOFTWARE=ON -DDEFAULT_SLINT_EMBED_RESOURCES=embed-for-software-renderer .. ``` ## Next Steps - Check out the [](../getting_started.md) instructions for a generic "Hello World" with C++. - Study the [](../api/library_root), in particular the `slint::platform` namespace for writing a Slint platform integration to handle touch input and render pixel, which you need to forward to your MCU's display driver. - For more details about the Slint language, check out the [Slint Language Documentation](slint-reference:). - Learn about the [](../types.md) between Slint and C++. ================================================ FILE: api/cpp/docs/mcu/intro.md ================================================ # Introduction Microcontrollers (MCUs) are highly customizable and each vendor typically provides their own development environment and toolchain. Slint aims to support any MCU provided the SDK supports a C++ 20 cross-compiler as well as CMake as build system. This documentation is divided into three sub-sections: - [ESP-IDF section](esp_idf.md), when targeting MCUs with Espressif's IoT Development Framework - [STM32 section](stm32.md), when targeting MCUs in STMicroelectronics' STM32Cube Ecosystem. - [Generic section](generic.md), providing an overview how to use Slint with other MCUs. ================================================ FILE: api/cpp/docs/mcu/stm32/generic.md ================================================ # Generic Instructions for Slint on STM32 MCUs The following instructions outline a rough, general path how to get started on an STM32 MCU with STM32 Cube tooling and Slint. Successful completion requires experience with STM32CubeMX as well as the peripherals of our board. 1. Make sure to install all the . 2. Start a new project with STM32CubeMX: - Select your base board. - Enable all peripherals needed. This includes LTDC and typically the FMC to be able to place the framebuffers in RAM. - Select CMake in the code generator options. 3. Generate the CMake project and skeleton code. 4. In the STM32 VS Code Extension, choose the command to import a CMake project. 5. In STM32CubeMX select the STM32Cube BSP that matches your board and install it. 6. Copy the BSP drivers into your project's source tree and modify `CMakeLists.txt` to add them to the build. 7. Add C++ support to the generated CMake project by adding `CXX` to the `LANGUAGES` option of the `project` command. 8. Open a web browser, navigate to , and download and extract `Slint-cpp-VERSION-thumbv7em-none-eabihf.tar.gz`, replace `VERSION` with the version you see. 10. Set `CMAKE_PREFIX_PATH` to the extracted directory. 11. Add a C++ source file to your project, for example `appmain.cpp`, with an `appmain` function and call it from the generated `main.c`. 12. Create `app-window.slint` with the following contents: ```slint,no-preview import { VerticalBox, AboutSlint } from "std-widgets.slint"; export component AppWindow inherits Window { VerticalBox { AboutSlint {} Text { text: "Hello World"; font-size: 18px; horizontal-alignment: center; } } } ``` 13. Adjust your `CMakeLists.txt` for use of Slint. Copy the follow snippets and adjust the target names as needed: ```cmake # Locate Slint find_package(Slint) # Compile app-window.slint to app-window.h and app-window.cpp slint_target_sources(your-target app-window.slint) # Embed images and fonts in the binary set_target_properties(your-target PROPERTIES SLINT_EMBED_RESOURCES embed-for-software-renderer) # Replace $BSP_NAME with the name of your concrete BSP, # for example stm32h735g_discovery. target_compile_definitions(your-target PRIVATE SLINT_STM32_BSP_NAME=$BSP_NAME ) # Link Slint run-time library target_link_libraries(your-target PRIVATE Slint::Slint ) ``` 14. In your `appmain` function, initialize the screen via `BSP_LCD_InitEx` as well as the touch screen via `BSP_TS_Init`, include `#include ` and call `slint_stm32_init(SlintPlatformConfiguration());` to initialize the Slint platform integration for STM32. Finally, include the header file for your `.slint` file, instantiate the generated class, and invoke the event loop by calling `->run()` on the instance. Use the following example as reference: ```cpp #include #include #include #include #include #include "app-window.h" // Called from main() extern "C" void appmain() { if (BSP_LCD_InitEx(0, LCD_ORIENTATION_LANDSCAPE, LCD_PIXEL_FORMAT_RGB565, LCD_DEFAULT_WIDTH, LCD_DEFAULT_HEIGHT) != 0) { Error_Handler(); } BSP_LCD_DisplayOn(0); BSP_LCD_SetActiveLayer(0, 0); TS_Init_t hTS; hTS.Width = LCD_DEFAULT_WIDTH; hTS.Height = LCD_DEFAULT_HEIGHT; hTS.Orientation = TS_SWAP_XY; hTS.Accuracy = 0; /* Touchscreen initialization */ if (BSP_TS_Init(0, &hTS) != 0) { Error_Handler(); } slint_stm32_init(SlintPlatformConfiguration()); auto app_window = AppWindow::create(); app_window->run(); return 0; } ``` ================================================ FILE: api/cpp/docs/mcu/stm32.md ================================================ # STMicroelectronics' STM32Cube Ecosystem Slint provides a platform integration with into STMicroelectronics' (STM) STM32Cube software platform. It uses the `BSP_TS` APIs to retrieve touch input and uses the `BSP_LCD` and `HAL_LTDC` APIs to render to the screen with double-buffering. ## Prerequisites To build a C++ application with Slint for STM32 MCUs, install the following tools: * **[cmake](https://cmake.org/download/)** (3.21 or newer) * **[STM32CubeCLT](https://www.st.com/en/development-tools/stm32cubeclt.html)** * **[Visual Studio Code](https://code.visualstudio.com)** * **[Slint extension](https://marketplace.visualstudio.com/items?itemName=Slint.slint)** * **[STM32 VS Code Extension](https://marketplace.visualstudio.com/items?itemName=stmicroelectronics.stm32-vscode-extension)** * **[CMake Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools)** ## First Steps We provide templates for different STM32 Discovery Kits that provide: - A pre-configured build system. - Application skeleton source code with sample Slint UI. - Example usage of callbacks, properties, and basic widgets. To get started, select a download from the following table. If your board is not included in the table below, see our [](stm32/generic.md) instructions. | STM32 Board | Download | |----------------------------------|----------| | [STM32H747I-DISCO](https://www.st.com/en/evaluation-tools/stm32h747i-disco.html): Dual-core Arm M7/M4 MCU with 4” touch LCD display module | [slint-cpp-template-stm32h747i-disco.zip](https://github.com/slint-ui/slint/releases/latest/download/slint-cpp-template-stm32h747i-disco.zip) | | [STM32H735G-DK](https://www.st.com/en/evaluation-tools/stm32h735g-dk.html): Arm M7 MCU with 4” touch LCD display module | [slint-cpp-template-stm32h735g-dk.zip](https://github.com/slint-ui/slint/releases/latest/download/slint-cpp-template-stm32h735g-dk.zip) | 1. Download and extract the archive that matches our STM32 Discovery Kit. 2. Open the extracted folder with VS Code. 3. Configure the project either via "CMake: Select Configure Preset" from the command palette or the CMake extension panel. 4. Build, Flash to Device, and debug by hitting `F5` or running the `CMake: Debug` command from the command palette. ## Next Steps - For more details about the Slint language, check out the [Slint Language Documentation](slint-reference:). - Learn about the [](../types.md) between Slint and C++. - Study the [](../api/library_root). ```{toctree} :maxdepth: 2 :hidden: :caption: STMicroelectronics' STM32Cube Ecosystem stm32/generic.md ``` ================================================ FILE: api/cpp/docs/overview.md ================================================ # Overview The following sections explain how to integrate your `.slint` designs into your C++ application. The entry point is the `.slint` file containing the primary component you need to instantiate from C++. Slint is a very flexible system and allows for different integration options. First you can compile your Slint designs ahead of time into C++ code. This code is then built into your application. This allows for the smallest possible memory footprint and the best possible performance. The second approach is to load your Slint designs at run-time, interpreting them as needed. This enables even more dynamic user interfaces that can be changed at run-time, but comes at the price of having less opportunity to apply optimizations. Either way, once your user interface is shown, you interact with it from C++, for example by setting properties, populating data models or setting up and handling callbacks to react to events triggered by the user. ## Compiled `.slint` Designs The provided CMake integration makes it easy to compile your Slint sources: The [`slint_target_sources` CMake command](cmake_reference.md#slint_target_sources) makes the translation automatic. The [generated code](generated_code.md) has an API to set and get property values, etc. This API uses types from the {ref}`slint ` namespace, for example {cpp:class}`slint::SharedString` or {cpp:class}`slint::Color`. ## Run-Time Interpreted `.slint` Designs Instead of compiling `.slint` designs to C++, you can dynamically load `.slint` files at run-time. This is slower than compiling them ahead of time and requires more memory, however it provides more flexibility in your application design. The entry point to loading a `.slint` file is the {cpp:class}`slint::interpreter::ComponentCompiler` class in the {ref}`slint::interpreter ` namespace. With the help of {cpp:class}`slint::interpreter::ComponentCompiler` you create a {cpp:class}`slint::interpreter::ComponentDefinition`, which provides information on properties and callbacks common to all instances. The {cpp:func}`slint::interpreter::ComponentDefinition::create()` function creates new instances, wrapped in a {cpp:class}`slint::ComponentHandle`. This is a smart pointer that owns the actual instance and keeps it alive as long as at least one {cpp:class}`slint::ComponentHandle` is in scope, similar to `std::shared_ptr`. All property values in `.slint` are mapped to {cpp:class}`slint::interpreter::Value` in C++. This is a polymorphic data type that can hold different kinds of values, such as numbers, strings or even data models. More complex user interfaces commonly consume data in the form of an abstract data model, that is used with `for` - `in` repetitions or `ListView` elements in the `.slint` language. All models in C++ with the interpreter API are sub-classes of the {cpp:class}`slint::Model` where the template parameter is {cpp:class}`slint::interpreter::Value`. To provide your own data model, you can subclass `slint::Model`. It's possible to declare [singletons that are globally available](../slint/src/globals.html) in `.slint` files. You can access them from to your C++ code by exporting them and using the getter and setter functions on {cpp:class}`slint::interpreter::ComponentInstance` to change properties and callbacks: 1. {cpp:func}`slint::interpreter::ComponentInstance::set_global_property()` 1. {cpp:func}`slint::interpreter::ComponentInstance::get_global_property()` 1. {cpp:func}`slint::interpreter::ComponentInstance::set_global_callback()` 1. {cpp:func}`slint::interpreter::ComponentInstance::invoke_global_callback()` ================================================ FILE: api/cpp/docs/pyproject.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: MIT [project] name = "slint-cppdocs" version = "0.1" description = "Python env for running sphinx, breathe, etc." requires-python = ">=3.10" dependencies = [ "breathe>=4.33.1", "exhale>=0.2.4", "furo>=2022.12.7", "myst-parser>=0.17.2", "sphinx==7.1.2", "sphinx-markdown-tables>=0.0.15", "sphinxcontrib-jquery>=4.1", ] ================================================ FILE: api/cpp/docs/thirdparty.hbs ================================================ # Third Party Licenses This page lists the licenses of the dependencies used by Slint. ## Overview of licenses {{#each overview}} - [{{name}}](#{{id}}) ({{count}}) {{/each}} ## All License Text {{#each licenses}} ### {{name}} #### Used by: {{#each used_by}} - [{{crate.name}} {{crate.version}}]({{#if crate.repository}} {{crate.repository}} {{else}} https://crates.io/crates/{{crate.name}} {{/if}}) {{/each}} #### License Text ``` {{text}} ``` {{/each}} ================================================ FILE: api/cpp/docs/types.md ================================================ # Type Mappings The types used for properties in `.slint` design markup each translate to specific types in C++. The follow table summarizes the entire mapping: ```{eval-rst} =========================== =================================== ======================================================================================================================================= :code:`.slint` Type C++ Type Note =========================== =================================== ======================================================================================================================================= :code:`int` :code:`int` :code:`float` :code:`float` :code:`bool` :code:`bool` :code:`string` :cpp:class:`slint::SharedString` A reference-counted string type that uses UTF-8 encoding and can be easily converted to a std::string_view or a :code:`const char *`. :code:`color` :cpp:class:`slint::Color` :code:`brush` :cpp:class:`slint::Brush` :code:`image` :cpp:class:`slint::Image` :code:`physical_length` :code:`float` The unit are physical pixels. :code:`length` :code:`float` At run-time, logical lengths are automatically translated to physical pixels using the device pixel ratio. :code:`duration` :code:`std::int64_t` At run-time, durations are always represented as signed 64-bit integers with millisecond precision. :code:`angle` :code:`float` The angle in degrees. :code:`relative-font-size` :code:`float` Relative font size factor that is multiplied with the :code:`Window.default-font-size` and can be converted to a :code:`length`. structure A :code:`class` of the same name The order of the data member are in the same as in the slint declaration anonymous object A :code:`std::tuple` The fields are in alphabetical order. enum An :code:`enum class` The values are always converted to CamelCase. The order of the values is the same as in the declaration. :code:`Point` :cpp:class:`slint::LogicalPosition` A struct with :code:`x` and :code:`y` fields, representing logical coordinates. =========================== =================================== ======================================================================================================================================= ``` ## Structures The Slint compiler generates a `class` with all data members in the same order for any user-defined, exported `struct` in the `.slint` code. For example, this `struct` in a `.slint` file ```slint,ignore export struct MyStruct { foo: int, bar: string, } ``` will generate the following type in C++: ```cpp class MyStruct { public: int foo; slint::SharedString bar; }; ``` ## Enums The Slint compiler generates an `enum class` with all values in the same order and converted to camel case for any user-defined, exported `enum` in the `.slint` code. For example, this `enum` in a `.slint` file ```slint,ignore export enum MyEnum { alpha, beta-gamma, omicron } ``` will generate the following type in C++: ```cpp enum class MyEnum { Alpha, BetaGamma, Omicron }; ``` ================================================ FILE: api/cpp/esp-idf/slint/CMakeLists.txt ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 idf_component_register( SRCS "src/slint-esp.cpp" INCLUDE_DIRS "include" REQUIRES "esp_lcd" "esp_lcd_touch") list(PREPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") if (CONFIG_IDF_TARGET_ARCH_XTENSA) set(rust_target "xtensa-${IDF_TARGET}-none-elf") elseif(CONFIG_IDF_TARGET_ARCH_RISCV) if (CONFIG_IDF_TARGET_ESP32C6 OR CONFIG_IDF_TARGET_ESP32C5 OR CONFIG_IDF_TARGET_ESP32H2) set(rust_target "riscv32imac-esp-espidf") elseif (CONFIG_IDF_TARGET_ESP32P4) set(rust_target "riscv32imafc-esp-espidf") else () set(rust_target "riscv32imc-esp-espidf") endif() else() message(FATAL_ERROR "Architecture currently not supported") endif() set(SLINT_FEATURE_FREESTANDING ON) set(SLINT_FEATURE_RENDERER_SOFTWARE ON) set(SLINT_LIBRARY_CARGO_FLAGS "-Zbuild-std=core,alloc") set(DEFAULT_SLINT_EMBED_RESOURCES "embed-for-software-renderer" CACHE STRING "") set(CMAKE_BUILD_TYPE Release) set(BUILD_SHARED_LIBS OFF) set(Rust_CARGO_TARGET ${rust_target}) if (SLINT_ESP_LOCAL_EXAMPLE) add_subdirectory(../.. slint_build) else() list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") # This variable is set when generating nightly snapshot's CMakeLists.txt if (NOT SLINT_NIGHTLY) find_package(Slint) endif() if (TARGET Slint::Slint) # Imported targets are only visible in directory scope by default, but # for use by the application, we need to make the target global. set_target_properties(Slint::Slint PROPERTIES IMPORTED_GLOBAL TRUE) else() include(FetchContent) FetchContent_Declare( Slint GIT_REPOSITORY https://github.com/slint-ui/slint GIT_TAG v1.16.0 SOURCE_SUBDIR api/cpp ) FetchContent_MakeAvailable(Slint) endif() endif() target_link_libraries(${COMPONENT_LIB} PUBLIC Slint::Slint) target_linker_script(${COMPONENT_LIB} INTERFACE "esp-println.x") ================================================ FILE: api/cpp/esp-idf/slint/README.md ================================================ # Slint [![Component Registry](https://components.espressif.com/components/slint/slint/badge.svg)](https://components.espressif.com/components/slint/slint) Slint is a declarative GUI toolkit to build native user interfaces for desktop and embedded applications written in Rust, C++, or JavaScript. This component provides the C++ version of [Slint](https://slint.dev/) for the [Espressif IoT Development Framework](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/index.html). It has been tested on ESP32-S3 devices. ![Screenshot](https://user-images.githubusercontent.com/959326/260754861-e2130cce-9d2b-4925-9536-88293818ac3e.jpeg) ## Documentation Check out our [Getting Started Guide for C++ on ESP-IDF](https://slint.dev/docs/cpp/mcu/esp_idf.html). ## Feedback If you have feedback or questions, feel free to reach out to the Slint community: - [Chat with us](https://chat.slint.dev/) on Mattermost. - [Ask questions](https://github.com/slint-ui/slint/discussions) on GitHub - Contact us on [Twitter](https://twitter.com/slint_ui) or [Mastodon](https://fosstodon.org/@slint) - [Report a bug](https://github.com/slint-ui/slint/issues) on Github ## License You can use Slint under ***any*** of the following licenses, at your choice: 1. [Royalty-free license](https://github.com/slint-ui/slint/blob/master/LICENSES/LicenseRef-Slint-Royalty-free-2.0.md), 2. [GNU GPLv3](https://github.com/slint-ui/slint/blob/master/LICENSES/GPL-3.0-only.txt), 3. [Paid license](https://github.com/slint-ui/slint/blob/master/LICENSES/LicenseRef-Slint-Software-3.0.md). See also the [Licensing FAQ](https://github.com/slint-ui/slint/blob/master/FAQ.md#licensing). ## Links [Website](https://slint.dev) · [GitHub](https://github.com/slint-ui/slint) · [Docs](https://slint.dev/docs/cpp) ================================================ FILE: api/cpp/esp-idf/slint/cmake/FindSlint.cmake ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # # FindSlint # --------- # # This modules attempts to locate an installation of Slint, as follows: # # 1. First `find_package(Slint ... CONFIG ...)` is called, to locate any packages in the `CMAKE_PREFIX_PATH`. # 2. If that failed and if `find_package` was called with a `VERSION`, then this module will attempt to download # a pre-compiled binary package for the specified Slint release, extract it into `${CMAKE_BINARY_DIR}/slint-prebuilt`, # and make it available. If version is unset, download the nightly release. # # The following variables may be set to affect the behaviour: # # `SLINT_TARGET_ARCHITECTURE`: Set this to the desired target architecture. The format of this string is matched against # the `Slint-cpp-*-$SLINT_TARGET_ARCHITECTURE.tar.gz` pre-built assets on the GitHub releases. For example, if you're targeting # STM32 ARM architectures, you'd set this to `thumbv7em-none-eabihf`. If not set, this module will attempt to detect if compilation # is happening in an ESP-IDF cross-compilation environment and detect the architecture accordingly, otherwise # `${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}` is used. find_package(Slint ${Slint_FIND_VERSION} QUIET CONFIG) if (TARGET Slint::Slint) return() endif() if (NOT SLINT_TARGET_ARCHITECTURE) if(WIN32) if(MSVC) set(compiler_suffix "-MSVC") elseif(MINGW) set(compiler_suffix "-MinGW") endif() if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(CPACK_SYSTEM_NAME win64) else() set(CPACK_SYSTEM_NAME win32) endif() set(SLINT_TARGET_ARCHITECTURE "${CPACK_SYSTEM_NAME}${compiler_suffix}-${CMAKE_SYSTEM_PROCESSOR}") elseif (CONFIG_IDF_TARGET_ARCH_XTENSA) set(SLINT_TARGET_ARCHITECTURE "xtensa-${IDF_TARGET}-none-elf") elseif(CONFIG_IDF_TARGET_ARCH_RISCV) if (CONFIG_IDF_TARGET_ESP32C6 OR CONFIG_IDF_TARGET_ESP32C5 OR CONFIG_IDF_TARGET_ESP32H2) set(SLINT_TARGET_ARCHITECTURE "riscv32imac-esp-espidf") elseif (CONFIG_IDF_TARGET_ESP32P4) set(SLINT_TARGET_ARCHITECTURE "riscv32imafc-esp-espidf") else () set(SLINT_TARGET_ARCHITECTURE "riscv32imc-esp-espidf") endif() else() set(SLINT_TARGET_ARCHITECTURE "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") endif() endif() if (NOT DEFINED Slint_FIND_VERSION) # Set this to instruct the slint-compiler download to use the same release set(SLINT_GITHUB_RELEASE "nightly" CACHE STRING "") set(github_release "nightly") set(github_filename_infix "nightly") else() set(github_release "v${Slint_FIND_VERSION}") set(github_filename_infix "${Slint_FIND_VERSION}") endif() set(prebuilt_archive_filename "Slint-cpp-${github_filename_infix}-${SLINT_TARGET_ARCHITECTURE}.tar.gz") set(download_target_path "${CMAKE_BINARY_DIR}/slint-prebuilt/") set(download_url "https://github.com/slint-ui/slint/releases/download/${github_release}/${prebuilt_archive_filename}") file(MAKE_DIRECTORY "${download_target_path}") message(STATUS "Downloading pre-built Slint binary ${download_url}") file(DOWNLOAD "${download_url}" "${download_target_path}/${prebuilt_archive_filename}" STATUS download_status) list(GET download_status 0 download_code) if (NOT download_code EQUAL 0) list(GET download_status 1 download_message) message(STATUS "Download of Slint binary package failed: ${download_message}") return() endif() file(ARCHIVE_EXTRACT INPUT "${download_target_path}/${prebuilt_archive_filename}" DESTINATION "${download_target_path}") list(PREPEND CMAKE_PREFIX_PATH "${download_target_path}") find_package(Slint CONFIG) ================================================ FILE: api/cpp/esp-idf/slint/esp-println.x ================================================ /* Copyright © SixtyFPS GmbH SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 */ /* Linker script needed to ensure that the meta-data from esp-println is included in the final binary. We use esp-println and esp-backtrace in the C++ build, where this linker section isn't automatically included. For more details, see https://github.com/esp-rs/rust/issues/266#issuecomment-3361411040 */ SECTIONS { .espressif.metadata 0 (INFO) : { KEEP(*(.espressif.metadata)); } } ================================================ FILE: api/cpp/esp-idf/slint/idf_component.yml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 description: "Slint — declarative GUI toolkit" url: "https://slint.dev" license: "GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0" version: "1.16.0" discussion: "https://github.com/slint-ui/slint/discussions" documentation: "https://slint.dev/docs" repository: "https://github.com/slint-ui/slint" dependencies: idf: ">=5.1" esp_lcd_touch: version: ">=1.0.4" public: true ================================================ FILE: api/cpp/esp-idf/slint/include/slint-esp.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include "slint-platform.h" #include "esp_lcd_touch.h" #include "esp_lcd_types.h" /** * This data structure configures the Slint platform for use with ESP-IDF, in particular * the esp_lcd component ( * https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/lcd.html ) * for touch input and on-screen rendering. * * Slint supports three different ways of rendering: * * * Single-buffering: Allocate one frame-buffer at a location of your choosing in RAM, and * set the `buffer1` field. * * Double-buffering: Call `esp_lcd_rgb_panel_get_frame_buffer` to obtain two frame buffers * allocated by the `esp_lcd` driver and set `buffer1` and `buffer2`. * * Line-by-line rendering: Set neither `buffer1` nor `buffer2` to instruct Slint to allocate * a buffer (with MALLOC_CAP_INTERNAL) big enough to hold one line, * render into it, and send it to the display. * * Use single-buffering if you can allocate a buffer in a memory region that allows the esp_lcd * driver to efficiently transfer to the display. Use double-buffering if your driver supports * calling `esp_lcd_rgb_panel_get_frame_buffer` and the buffers can be accessed directly by the * display controller. Use line-by-line rendering if you don't have sufficient memory or rendering * to internal memory (MALLOC_CAP_INTERNAL) and flushing to the display is faster than rendering * into memory buffers that may be slower to access for the CPU. * * The data structure is a template where the pixel type is configurable. * The default depends on the sdkconfig, but you can use either `slint::Rgb8Pixel` or * `slint::platform::Rgb565Pixel`, depending on how the display is configured. */ template struct SlintPlatformConfiguration { /// The size of the screen in pixels. slint::PhysicalSize size; /// The handle to the display as previously initialized by `bsp_display_new` or /// `esp_lcd_panel_init`. Must be set to a valid, non-null esp_lcd_panel_handle_t. esp_lcd_panel_handle_t panel_handle = nullptr; /// The touch screen handle, if the device is equipped with a touch screen. Set to nullptr /// otherwise; esp_lcd_touch_handle_t touch_handle = nullptr; /// The buffer Slint will render into. It must have have the size of at least one frame. Slint /// calls esp_lcd_panel_draw_bitmap to flush the buffer to the screen. std::optional> buffer1 = {}; /// If specified, this is a second buffer that will be used for double-buffering. Use this if /// your LCD panel supports double buffering: Call `esp_lcd_rgb_panel_get_frame_buffer` to /// obtain two buffers and set `buffer` and `buffer2` in this data structure. std::optional> buffer2 = {}; slint::platform::SoftwareRenderer::RenderingRotation rotation = slint::platform::SoftwareRenderer::RenderingRotation::NoRotation; /// Swap the 2 bytes of RGB 565 pixels before sending to the display, or turn 24-bit RGB into /// BGR. Use this if your CPU is little endian but the display expects big-endian. union { [[deprecated("Renamed to byte_swap")]] bool color_swap_16; bool byte_swap = false; }; }; template SlintPlatformConfiguration(Args...) -> SlintPlatformConfiguration<>; /** * Initialize the Slint platform for ESP-IDF * * This must be called before any other call to the Slint library. * * - `size` is the size of the screen * - `panel` is a handle to the display. * - `touch` is a handle to the touch screen, if the device has a touch screen * - `buffer1`, is a buffer of at least the size of the frame in which the slint scene * will be drawn. Slint will take care to flush it to the screen * - `buffer2`, if specified, is a second buffer to be used with double buffering, * both buffer1 and buffer2 should then be obtained with `esp_lcd_rgb_panel_get_frame_buffer` * * Note: For compatibility, this function overload selects RGB16 byte swapping if single-buffering * is selected as rendering method. * * \deprecated Prefer the overload taking a SlintPlatformConfiguration */ [[deprecated("Use the overload taking a SlintPlatformConfiguration")]] void slint_esp_init(slint::PhysicalSize size, esp_lcd_panel_handle_t panel, std::optional touch, std::span buffer1, std::optional> buffer2 = {}); /** * Initialize the Slint platform for ESP-IDF. * * This must be called before any other call to the Slint library. */ void slint_esp_init(const SlintPlatformConfiguration &config); void slint_esp_init(const SlintPlatformConfiguration &config); ================================================ FILE: api/cpp/esp-idf/slint/src/slint-esp.cpp ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #include #include #include "slint-esp.h" #include "slint-platform.h" #include "esp_lcd_panel_ops.h" #if __has_include("soc/soc_caps.h") # include "soc/soc_caps.h" #endif #if SOC_LCD_RGB_SUPPORTED && ESP_IDF_VERSION_MAJOR >= 5 # include "esp_lcd_panel_rgb.h" #endif #include "esp_log.h" static const char *TAG = "slint_platform"; using RepaintBufferType = slint::platform::SoftwareRenderer::RepaintBufferType; class EspWindowAdapter : public slint::platform::WindowAdapter { public: slint::platform::SoftwareRenderer m_renderer; bool needs_redraw = true; const slint::PhysicalSize m_size; explicit EspWindowAdapter(RepaintBufferType buffer_type, slint::PhysicalSize size) : m_renderer(buffer_type), m_size(size) { } slint::platform::AbstractRenderer &renderer() override { return m_renderer; } slint::PhysicalSize size() override { return m_size; } void request_redraw() override { needs_redraw = true; } }; template struct EspPlatform : public slint::platform::Platform { EspPlatform(const SlintPlatformConfiguration &config) : size(config.size), panel_handle(config.panel_handle), touch_handle(config.touch_handle), buffer1(config.buffer1), buffer2(config.buffer2), byte_swap(config.byte_swap), rotation(config.rotation) { task = xTaskGetCurrentTaskHandle(); } std::unique_ptr create_window_adapter() override; std::chrono::milliseconds duration_since_start() override; void run_event_loop() override; void quit_event_loop() override; void run_in_event_loop(Task) override; private: slint::PhysicalSize size; esp_lcd_panel_handle_t panel_handle; esp_lcd_touch_handle_t touch_handle; std::optional> buffer1; std::optional> buffer2; bool byte_swap; slint::platform::SoftwareRenderer::RenderingRotation rotation; class EspWindowAdapter *m_window = nullptr; // Need to be static because we can't pass user data to the touch interrupt callback static TaskHandle_t task; std::mutex queue_mutex; std::deque queue; // protected by queue_mutex bool quit = false; // protected by queue_mutex }; template std::unique_ptr EspPlatform::create_window_adapter() { if (m_window != nullptr) { ESP_LOGI(TAG, "FATAL: create_window_adapter called multiple times"); return nullptr; } auto buffer_type = buffer2 ? RepaintBufferType::SwappedBuffers : RepaintBufferType::ReusedBuffer; auto window = std::make_unique(buffer_type, size); m_window = window.get(); m_window->m_renderer.set_rendering_rotation(rotation); return window; } template std::chrono::milliseconds EspPlatform::duration_since_start() { auto ticks = xTaskGetTickCount(); return std::chrono::milliseconds(pdTICKS_TO_MS(ticks)); } #if SOC_LCD_RGB_SUPPORTED && ESP_IDF_VERSION_MAJOR >= 5 static SemaphoreHandle_t sem_vsync_end; static SemaphoreHandle_t sem_gui_ready; extern "C" bool on_vsync_event(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *) { BaseType_t high_task_awoken = pdFALSE; if (xSemaphoreTakeFromISR(sem_gui_ready, &high_task_awoken) == pdTRUE) { xSemaphoreGiveFromISR(sem_vsync_end, &high_task_awoken); } return high_task_awoken == pdTRUE; } #endif namespace { void byte_swap_color(slint::platform::Rgb565Pixel *pixel) { // Swap endianness to big endian auto px = reinterpret_cast(pixel); *px = (*px << 8) | (*px >> 8); } void byte_swap_color(slint::Rgb8Pixel *pixel) { std::swap(pixel->r, pixel->b); } } template void EspPlatform::run_event_loop() { esp_lcd_panel_disp_on_off(panel_handle, true); TickType_t max_ticks_to_wait = portMAX_DELAY; if (touch_handle) { if (esp_lcd_touch_register_interrupt_callback( touch_handle, [](auto) { vTaskNotifyGiveFromISR(task, nullptr); }) != ESP_OK) { // No touch interrupt assigned or supported? Fall back to polling like esp_lvgl_port. // LVGL polls in 5ms intervals, but FreeRTOS tick interval is 10ms, so go for that max_ticks_to_wait = pdMS_TO_TICKS(10); } } #if SOC_LCD_RGB_SUPPORTED && ESP_IDF_VERSION_MAJOR >= 5 if (buffer2) { sem_vsync_end = xSemaphoreCreateBinary(); sem_gui_ready = xSemaphoreCreateBinary(); esp_lcd_rgb_panel_event_callbacks_t cbs = {}; cbs.on_vsync = on_vsync_event; esp_lcd_rgb_panel_register_event_callbacks(panel_handle, &cbs, this); } #endif float last_touch_x = 0; float last_touch_y = 0; bool touch_down = false; while (true) { slint::platform::update_timers_and_animations(); std::optional event; { std::unique_lock lock(queue_mutex); if (queue.empty()) { if (quit) { quit = false; break; } } else { event = std::move(queue.front()); queue.pop_front(); } } if (event) { std::move(*event).run(); event.reset(); continue; } if (m_window) { if (touch_handle) { uint16_t touchpad_x[1] = { 0 }; uint16_t touchpad_y[1] = { 0 }; uint8_t touchpad_cnt = 0; /* Read touch controller data */ esp_lcd_touch_read_data(touch_handle); /* Get coordinates */ bool touchpad_pressed = esp_lcd_touch_get_coordinates( touch_handle, touchpad_x, touchpad_y, NULL, &touchpad_cnt, 1); if (touchpad_pressed && touchpad_cnt > 0) { auto scale_factor = m_window->window().scale_factor(); // ESP_LOGI(TAG, "x: %i, y: %i", touchpad_x[0], touchpad_y[0]); last_touch_x = float(touchpad_x[0]) / scale_factor; last_touch_y = float(touchpad_y[0]) / scale_factor; m_window->window().dispatch_pointer_move_event( slint::LogicalPosition({ last_touch_x, last_touch_y })); if (!touch_down) { m_window->window().dispatch_pointer_press_event( slint::LogicalPosition({ last_touch_x, last_touch_y }), slint::PointerEventButton::Left); } touch_down = true; } else if (touch_down) { m_window->window().dispatch_pointer_release_event( slint::LogicalPosition({ last_touch_x, last_touch_y }), slint::PointerEventButton::Left); m_window->window().dispatch_pointer_exit_event(); touch_down = false; } } if (std::exchange(m_window->needs_redraw, false)) { using slint::platform::SoftwareRenderer; auto rotated = rotation == SoftwareRenderer::RenderingRotation::Rotate90 || rotation == SoftwareRenderer::RenderingRotation::Rotate270; auto stride = rotated ? size.height : size.width; if (buffer1) { #if SOC_LCD_RGB_SUPPORTED && ESP_IDF_VERSION_MAJOR >= 5 if (buffer2) { xSemaphoreGive(sem_gui_ready); xSemaphoreTake(sem_vsync_end, portMAX_DELAY); } #endif auto region = m_window->m_renderer.render(buffer1.value(), stride); if (byte_swap) { for (auto [o, s] : region.rectangles()) { for (int y = o.y; y < o.y + s.height; y++) { for (int x = o.x; x < o.x + s.width; x++) { byte_swap_color(&buffer1.value()[y * stride + x]); } } } } if (buffer2) { auto s = region.bounding_box_size(); if (s.width > 0 && s.height > 0) { // Assuming that using double buffer means that the buffer comes from // the driver and we need to pass the exact pointer. // https://github.com/espressif/esp-idf/blob/53ff7d43dbff642d831a937b066ea0735a6aca24/components/esp_lcd/src/esp_lcd_panel_rgb.c#L681 esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, size.width, size.height, buffer1->data()); std::swap(buffer1, buffer2); } } else { for (auto [o, s] : region.rectangles()) { for (int y = o.y; y < o.y + s.height; y++) { esp_lcd_panel_draw_bitmap(panel_handle, o.x, y, o.x + s.width, y + 1, buffer1->data() + y * stride + o.x); } } } } else { // esp_lcd_panel_draw_bitmap is "async" so we have two buffers, one in which we // render, and one which is being transmitted with a DMA transfer in parallel. // TODO: add some synchronization code anyway to make sure that we don't // call esp_lcd_panel_draw_bitmap when an operation is still in progress // or free the buffer too early. (using `on_color_trans_done` callback). using Uniq = std::unique_ptr; auto alloc = [&] { void *ptr = heap_caps_malloc(stride * sizeof(PixelType), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); if (!ptr) { ESP_LOGE(TAG, "malloc failed to allocate line buffer"); abort(); } return Uniq(reinterpret_cast(ptr), heap_caps_free); }; Uniq lb[2] = { alloc(), alloc() }; int idx = 0; m_window->m_renderer.render_by_line( [this, &lb, &idx](std::size_t line_y, std::size_t line_start, std::size_t line_end, auto &&render_fn) { std::span view { lb[idx].get(), line_end - line_start }; render_fn(view); if (byte_swap) { // Swap endianness to big endian std::for_each(view.begin(), view.end(), [](auto &rgbpix) { byte_swap_color(&rgbpix); }); } esp_lcd_panel_draw_bitmap(panel_handle, line_start, line_y, line_end, line_y + 1, view.data()); idx = (idx + 1) % 2; }); } } if (m_window->window().has_active_animations()) { continue; } } TickType_t ticks_to_wait = max_ticks_to_wait; if (auto wait_time = slint::platform::duration_until_next_timer_update()) { ticks_to_wait = std::min(ticks_to_wait, pdMS_TO_TICKS(wait_time->count())); } ulTaskNotifyTake(/*reset to zero*/ pdTRUE, ticks_to_wait); } vTaskDelete(NULL); } template void EspPlatform::quit_event_loop() { { const std::unique_lock lock(queue_mutex); quit = true; } vTaskNotifyGiveFromISR(task, nullptr); } template void EspPlatform::run_in_event_loop(slint::platform::Platform::Task event) { { const std::unique_lock lock(queue_mutex); queue.push_back(std::move(event)); } vTaskNotifyGiveFromISR(task, nullptr); } template TaskHandle_t EspPlatform::task = {}; void slint_esp_init(slint::PhysicalSize size, esp_lcd_panel_handle_t panel, std::optional touch, std::span buffer1, std::optional> buffer2) { SlintPlatformConfiguration config { .size = size, .panel_handle = panel, .touch_handle = touch ? *touch : nullptr, .buffer1 = buffer1, .buffer2 = buffer2, // For compatibility with earlier versions of Slint, we compute the value of // byte_swap the way it was implemented in Slint (slint-esp) <= 1.6.0: .byte_swap = !buffer2.has_value() }; slint_esp_init(config); } void slint_esp_init(const SlintPlatformConfiguration &config) { slint::platform::set_platform( std::make_unique>(config)); } void slint_esp_init(const SlintPlatformConfiguration &config) { slint::platform::set_platform(std::make_unique>(config)); } ================================================ FILE: api/cpp/include/slint-interpreter.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include "slint.h" #ifndef SLINT_FEATURE_INTERPRETER # warning "slint-interpreter.h API only available when SLINT_FEATURE_INTERPRETER is activated" #else # include "slint_interpreter_internal.h" # include # ifdef SLINT_FEATURE_BACKEND_QT class QWidget; # endif namespace slint::cbindgen_private { // This has to stay opaque, but VRc don't compile if it is just forward declared struct ErasedItemTreeBox : vtable::Dyn { ~ErasedItemTreeBox() = delete; ErasedItemTreeBox() = delete; ErasedItemTreeBox(ErasedItemTreeBox &) = delete; }; } namespace slint::private_api::live_preview { class LiveReloadingComponent; class LiveReloadModelWrapperBase; } /// The types in this namespace allow you to load a .slint file at runtime and show its UI. /// /// You only need to use them if you do not want to use pre-compiled .slint code, which is /// the normal way to use Slint. /// /// The entry point for this namespace is the \ref ComponentCompiler, which you can /// use to create \ref ComponentDefinition instances with the /// \ref ComponentCompiler::build_from_source() or \ref ComponentCompiler::build_from_path() /// functions. namespace slint::interpreter { class Value; /// This type represents a runtime instance of structure in `.slint`. /// /// This can either be an instance of a name structure introduced /// with the `struct` keyword in the .slint file, or an anonymous struct /// written with the `{ key: value, }` notation. /// /// It can be constructed with the range constructor or initializer lst, /// and converted into or from a Value with the Value constructor and /// Value::to_struct(). struct Struct { public: /// Constructs a new empty struct. You can add fields with set_field() and /// read them with get_field(). Struct() { cbindgen_private::slint_interpreter_struct_new(&inner); } /// Creates a new Struct as a copy from \a other. All fields are copied as well. Struct(const Struct &other) { cbindgen_private::slint_interpreter_struct_clone(&other.inner, &inner); } /// Creates a new Struct by moving all fields from \a other into this struct. Struct(Struct &&other) { inner = other.inner; cbindgen_private::slint_interpreter_struct_new(&other.inner); } /// Assigns all the fields of \a other to this struct. Struct &operator=(const Struct &other) { if (this == &other) return *this; cbindgen_private::slint_interpreter_struct_destructor(&inner); slint_interpreter_struct_clone(&other.inner, &inner); return *this; } /// Moves all the fields of \a other to this struct. Struct &operator=(Struct &&other) { if (this == &other) return *this; cbindgen_private::slint_interpreter_struct_destructor(&inner); inner = other.inner; cbindgen_private::slint_interpreter_struct_new(&other.inner); return *this; } /// Destroys this struct. ~Struct() { cbindgen_private::slint_interpreter_struct_destructor(&inner); } /// Creates a new struct with the fields of the std::initializer_list given by args. inline Struct(std::initializer_list> args); /// Creates a new struct with the fields produced by the iterator \a it. \a it is /// advanced until it equals \a end. template(*std::declval())), std::string_view>::value && std::is_convertible(*std::declval())), Value>::value > * = nullptr # endif > Struct(InputIterator it, InputIterator end) : Struct() { for (; it != end; ++it) { auto [key, value] = *it; set_field(key, value); } } // FIXME: this probably miss a lot of iterator api /// The Struct::iterator class implements the typical C++ iterator protocol and conveniently /// provides access to the field names and values of a Struct. It is created by calling either /// Struct::begin() or Struct::end(). /// /// Make sure to compare the iterator to the iterator returned by Struct::end() before /// de-referencing it. The value returned when de-referencing is a std::pair that holds a /// std::string_view of the field name as well as a const reference of the value. Both /// references become invalid when the iterator or the Struct is changed, so make sure to make /// copies if you want to retain the name or value. /// /// Note that the order in which the iterator exposes the fields is not defined. /// /// If you're using C++ 17, you can use the convenience destructuring syntax to extract the name /// and value in one go: /// /// ``` /// Struct stru = ...; /// auto it = stru.begin(); /// ... /// ++it; // advance iterator to the next field /// ... /// // Check iterator before dereferencing it /// if (it != stru.end()) { /// // Extract a view of the name and a const reference to the value in one go. /// auto [field_name, field_value] = *it; /// } /// ``` struct iterator { /// A typedef for std::pair that's returned /// when dereferencing the iterator. using value_type = std::pair; private: cbindgen_private::StructIteratorOpaque inner; Value *v = nullptr; std::string_view k; friend Struct; explicit iterator(cbindgen_private::StructIteratorOpaque inner) : inner(inner) { next(); } // construct a end iterator iterator() = default; inline void next(); public: /// Destroys this field iterator. inline ~iterator(); // FIXME I believe iterators are supposed to be copy constructible iterator(const iterator &) = delete; iterator &operator=(const iterator &) = delete; /// Move-constructs a new iterator from \a other. iterator(iterator &&other) = default; /// Move-assigns the iterator \a other to this and returns a reference to this. iterator &operator=(iterator &&other) = default; /// The prefix ++ operator advances the iterator to the next entry and returns /// a reference to this. iterator &operator++() { if (v) next(); return *this; } /// Dereferences the iterator to return a pair of the key and value. value_type operator*() const { return { k, *v }; } /// Returns true if \a a is pointing to the same entry as \a b; false otherwise. friend bool operator==(const iterator &a, const iterator &b) { return a.v == b.v; } /// Returns false if \a a is pointing to the same entry as \a b; true otherwise. friend bool operator!=(const iterator &a, const iterator &b) { return a.v != b.v; } }; /// Returns an iterator over the fields of the struct. iterator begin() const { return iterator(cbindgen_private::slint_interpreter_struct_make_iter(&inner)); } /// Returns an iterator that when compared with an iterator returned by begin() can be /// used to detect when all fields have been visited. iterator end() const { return iterator(); } /// Returns the value of the field with the given \a name; Returns an std::optional without /// value if the field does not exist. inline std::optional get_field(std::string_view name) const; /// Sets the value of the field with the given \a name to the specified \a value. If the field /// does not exist yet, it is created; otherwise the existing field is updated to hold the new /// value. inline void set_field(std::string_view name, const Value &value); /// \private Struct(const slint::cbindgen_private::StructOpaque &other) { cbindgen_private::slint_interpreter_struct_clone(&other, &inner); } private: using StructOpaque = slint::cbindgen_private::StructOpaque; StructOpaque inner; friend class Value; }; /// This is a dynamically typed value used in the Slint interpreter. /// It can hold a value of different types, and you should use the /// different overloaded constructors and the to_xxx() functions to access the //// value within. /// /// It is also possible to query the type the value holds by calling the Value::type() /// function. /// /// Note that models are only represented in one direction: You can create a slint::Model /// in C++, store it in a std::shared_ptr and construct Value from it. Then you can set it on a /// property in your .slint code that was declared to be either an array (`property <[sometype]> /// foo;`) or an object literal (`property <{foo: string, bar: int}> my_prop;`). Such properties are /// dynamic and accept models implemented in C++. /// /// ``` /// Value v(42.0); // Creates a value that holds a double with the value 42. /// /// Value some_value = ...; /// // Check if the value has a string /// if (std::optional string_value = some_value.to_string()) /// do_something(*string_value); // Extract the string by de-referencing /// ``` class Value { public: /// Constructs a new value of type Value::Type::Void. Value() : inner(cbindgen_private::slint_interpreter_value_new()) { } /// Constructs a new value by copying \a other. Value(const Value &other) : inner(slint_interpreter_value_clone(other.inner)) { } /// Constructs a new value by moving \a other to this. Value(Value &&other) { inner = other.inner; other.inner = cbindgen_private::slint_interpreter_value_new(); } /// Assigns the value \a other to this. Value &operator=(const Value &other) { if (this == &other) return *this; cbindgen_private::slint_interpreter_value_destructor(inner); inner = slint_interpreter_value_clone(other.inner); return *this; } /// Moves the value \a other to this. Value &operator=(Value &&other) { if (this == &other) return *this; cbindgen_private::slint_interpreter_value_destructor(inner); inner = other.inner; other.inner = cbindgen_private::slint_interpreter_value_new(); return *this; } /// Destroys the value. ~Value() { cbindgen_private::slint_interpreter_value_destructor(inner); } /// A convenience alias for the value type enum. using Type = ValueType; // optional to_int() const; // optional to_float() const; /// Returns a std::optional that contains a double if the type of this Value is /// Type::Double, otherwise an empty optional is returned. std::optional to_number() const { if (auto *number = cbindgen_private::slint_interpreter_value_to_number(inner)) { return *number; } else { return {}; } } /// Returns a std::optional that contains a string if the type of this Value is /// Type::String, otherwise an empty optional is returned. std::optional to_string() const { if (auto *str = cbindgen_private::slint_interpreter_value_to_string(inner)) { return *str; } else { return {}; } } /// Returns a std::optional that contains a bool if the type of this Value is /// Type::Bool, otherwise an empty optional is returned. std::optional to_bool() const { if (auto *b = cbindgen_private::slint_interpreter_value_to_bool(inner)) { return *b; } else { return {}; } } /// Returns a std::optional that contains a vector of values if the type of this Value is /// Type::Model, otherwise an empty optional is returned. /// /// The vector will be constructed by serializing all the elements of the model. inline std::optional> to_array() const; /// Returns a std::optional that contains a brush if the type of this Value is /// Type::Brush, otherwise an empty optional is returned. std::optional to_brush() const { if (auto *brush = cbindgen_private::slint_interpreter_value_to_brush(inner)) { return *brush; } else { return {}; } } /// Returns a std::optional that contains a Struct if the type of this Value is /// Type::Struct, otherwise an empty optional is returned. std::optional to_struct() const { if (auto *opaque_struct = cbindgen_private::slint_interpreter_value_to_struct(inner)) { return Struct(*opaque_struct); } else { return {}; } } /// Returns a std::optional that contains an Image if the type of this Value is /// Type::Image, otherwise an empty optional is returned. std::optional to_image() const { if (auto *img = cbindgen_private::slint_interpreter_value_to_image(inner)) { return *reinterpret_cast(img); } else { return {}; } } // template std::optional get() const; /// Constructs a new Value that holds the double \a value. Value(double value) : inner(cbindgen_private::slint_interpreter_value_new_double(value)) { } /// Constructs a new Value that holds the int \a value. /// Internally this is stored as a double and Value::type() will return Value::Type::Number. Value(int value) : Value(static_cast(value)) { } /// Constructs a new Value that holds the string \a str. Value(const SharedString &str) : inner(cbindgen_private::slint_interpreter_value_new_string(&str)) { } /// Constructs a new Value that holds the boolean \a b. Value(bool b) : inner(cbindgen_private::slint_interpreter_value_new_bool(b)) { } /// Constructs a new Value that holds the value vector \a v as a model. inline Value(const SharedVector &v); /// Constructs a new Value that holds the value model \a m. Value(const std::shared_ptr> &m); /// Constructs a new Value that holds the brush \a b. Value(const slint::Brush &brush) : inner(cbindgen_private::slint_interpreter_value_new_brush(&brush)) { } /// Constructs a new Value that holds the Struct \a struc. Value(const Struct &struc) : inner(cbindgen_private::slint_interpreter_value_new_struct(&struc.inner)) { } /// Constructs a new Value that holds the Image \a img. Value(const Image &img) : inner(cbindgen_private::slint_interpreter_value_new_image(&img)) { } /// Returns the type the variant holds. Type type() const { return cbindgen_private::slint_interpreter_value_type(inner); } /// Returns true if \a a and \a b hold values of the same type and the underlying vales are /// equal. friend bool operator==(const Value &a, const Value &b) { return cbindgen_private::slint_interpreter_value_eq(a.inner, b.inner); } private: inline Value(const void *) = delete; // Avoid that for example Value("foo") turns to Value(bool) slint::cbindgen_private::Value *inner; friend struct Struct; friend class ComponentInstance; friend class slint::private_api::live_preview::LiveReloadingComponent; friend class slint::private_api::live_preview::LiveReloadModelWrapperBase; // Internal constructor that takes ownership of the value explicit Value(slint::cbindgen_private::Value *&&inner) : inner(inner) { } }; inline Value::Value(const slint::SharedVector &array) : inner(cbindgen_private::slint_interpreter_value_new_array_model( reinterpret_cast *>( &array))) { } inline std::optional> Value::to_array() const { slint::SharedVector array; if (cbindgen_private::slint_interpreter_value_to_array( &inner, reinterpret_cast *>( &array))) { return array; } else { return {}; } } inline Value::Value(const std::shared_ptr> &model) { using cbindgen_private::ModelAdaptorVTable; using vtable::VRef; struct ModelWrapper : private_api::ModelChangeListener { std::shared_ptr> model; cbindgen_private::ModelNotifyOpaque notify; // This kind of mean that the rust code has ownership of "this" until the drop function is // called std::shared_ptr self; ~ModelWrapper() { cbindgen_private::slint_interpreter_model_notify_destructor(¬ify); } void row_added(size_t index, size_t count) override { cbindgen_private::slint_interpreter_model_notify_row_added(¬ify, index, count); } void row_changed(size_t index) override { cbindgen_private::slint_interpreter_model_notify_row_changed(¬ify, index); } void row_removed(size_t index, size_t count) override { cbindgen_private::slint_interpreter_model_notify_row_removed(¬ify, index, count); } void reset() override { cbindgen_private::slint_interpreter_model_notify_reset(¬ify); } }; auto wrapper = std::make_shared(); wrapper->model = model; wrapper->self = wrapper; cbindgen_private::slint_interpreter_model_notify_new(&wrapper->notify); model->attach_peer(wrapper); auto row_count = [](VRef self) -> uintptr_t { return reinterpret_cast(self.instance)->model->row_count(); }; auto row_data = [](VRef self, uintptr_t row) -> slint::cbindgen_private::Value * { std::optional v = reinterpret_cast(self.instance)->model->row_data(int(row)); if (v.has_value()) { slint::cbindgen_private::Value *rval = v->inner; v->inner = cbindgen_private::slint_interpreter_value_new(); return rval; } else { return nullptr; } }; auto set_row_data = [](VRef self, uintptr_t row, slint::cbindgen_private::Value *value) { Value v(std::move(value)); reinterpret_cast(self.instance)->model->set_row_data(int(row), v); }; auto get_notify = [](VRef self) -> const cbindgen_private::ModelNotifyOpaque * { return &reinterpret_cast(self.instance)->notify; }; auto drop = [](vtable::VRefMut self) { reinterpret_cast(self.instance)->self = nullptr; }; static const ModelAdaptorVTable vt { row_count, row_data, set_row_data, get_notify, drop }; inner = cbindgen_private::slint_interpreter_value_new_model( reinterpret_cast(wrapper.get()), &vt); } inline Struct::Struct(std::initializer_list> args) : Struct(args.begin(), args.end()) { } inline std::optional Struct::get_field(std::string_view name) const { using namespace cbindgen_private; cbindgen_private::Slice name_view = slint::private_api::string_to_slice(name); if (cbindgen_private::Value *field_val = cbindgen_private::slint_interpreter_struct_get_field(&inner, name_view)) { return Value(std::move(field_val)); } else { return {}; } } inline void Struct::set_field(std::string_view name, const Value &value) { cbindgen_private::Slice name_view = slint::private_api::string_to_slice(name); cbindgen_private::slint_interpreter_struct_set_field(&inner, name_view, value.inner); } inline void Struct::iterator::next() { cbindgen_private::Slice name_slice; if (cbindgen_private::Value *nextval_inner = cbindgen_private::slint_interpreter_struct_iterator_next(&inner, &name_slice)) { k = std::string_view(reinterpret_cast(name_slice.ptr), name_slice.len); if (!v) v = new Value(); *v = Value(std::move(nextval_inner)); } else { cbindgen_private::slint_interpreter_struct_iterator_destructor(&inner); delete v; v = nullptr; } } inline Struct::iterator::~iterator() { if (v) { cbindgen_private::slint_interpreter_struct_iterator_destructor(&inner); delete v; } } class ComponentDefinition; /// The ComponentInstance represents a running instance of a component. /// /// You can create an instance with the ComponentDefinition::create() function. /// /// Properties and callback can be accessed using the associated functions. /// /// An instance can be put on screen with the ComponentInstance::show() or the /// ComponentInstance::run() class ComponentInstance : vtable::Dyn { ComponentInstance() = delete; ComponentInstance(ComponentInstance &) = delete; ComponentInstance &operator=(ComponentInstance &) = delete; friend class ComponentDefinition; // ComponentHandle is in fact a VRc const cbindgen_private::ErasedItemTreeBox *inner() const { slint::private_api::assert_main_thread(); return reinterpret_cast(this); } public: /// Marks the window of this component to be shown on the screen. This registers /// the window with the windowing system. In order to react to events from the windowing system, /// such as draw requests or mouse/touch input, it is still necessary to spin the event loop, /// using slint::run_event_loop(). void show() const { cbindgen_private::slint_interpreter_component_instance_show(inner(), true); } /// Marks the window of this component to be hidden on the screen. This de-registers /// the window from the windowing system and it will not receive any further events. void hide() const { cbindgen_private::slint_interpreter_component_instance_show(inner(), false); } /// Returns the Window associated with this component. The window API can be used /// to control different aspects of the integration into the windowing system, /// such as the position on the screen. const slint::Window &window() { const cbindgen_private::WindowAdapterRcOpaque *win_ptr = nullptr; cbindgen_private::slint_interpreter_component_instance_window(inner(), &win_ptr); return *reinterpret_cast(win_ptr); } /// This is a convenience function that first calls show(), followed by /// slint::run_event_loop() and hide(). void run() const { show(); slint::run_event_loop(); hide(); } # if defined(SLINT_FEATURE_BACKEND_QT) || defined(DOXYGEN) /// Return a QWidget for this instance. /// This function is only available if the qt graphical backend was compiled in, and /// it may return nullptr if the Qt backend is not used at runtime. QWidget *qwidget() const { const cbindgen_private::WindowAdapterRcOpaque *win_ptr = nullptr; cbindgen_private::slint_interpreter_component_instance_window(inner(), &win_ptr); auto wid = reinterpret_cast(cbindgen_private::slint_qt_get_widget( reinterpret_cast(win_ptr))); return wid; } # endif /// Set the value for a public property of this component /// /// For example, if the component has a `property hello;`, /// we can set this property /// ``` /// instance->set_property("hello", slint::SharedString("world")); /// ``` /// /// Returns true if the property was correctly set. Returns false if the property /// could not be set because it either do not exist (was not declared in .slint) or if /// the value is not of the proper type for the property's type. bool set_property(std::string_view name, const Value &value) const { using namespace cbindgen_private; return slint_interpreter_component_instance_set_property( inner(), slint::private_api::string_to_slice(name), value.inner); } /// Returns the value behind a property declared in .slint. std::optional get_property(std::string_view name) const { using namespace cbindgen_private; if (cbindgen_private::Value *prop_inner = slint_interpreter_component_instance_get_property( inner(), slint::private_api::string_to_slice(name))) { return Value(std::move(prop_inner)); } else { return {}; } } /// Invoke the specified callback or function declared in .slint with the given arguments /// /// Example: imagine the .slint file contains the given callback declaration: /// ``` /// callback foo(string, int) -> string; /// ``` /// Then one can call it with this function /// ``` /// slint::Value args[] = { SharedString("Hello"), 42. }; /// instance->invoke("foo", { args, 2 }); /// ``` /// /// Returns an null optional if the callback don't exist or if the argument don't match /// Otherwise return the returned value from the callback, which may be an empty Value if /// the callback did not return a value. std::optional invoke(std::string_view name, std::span args) const { using namespace cbindgen_private; Slice> args_view = slint::private_api::make_slice( reinterpret_cast *>(args.data()), args.size()); if (cbindgen_private::Value *rval_inner = slint_interpreter_component_instance_invoke( inner(), slint::private_api::string_to_slice(name), args_view)) { return Value(std::move(rval_inner)); } else { return {}; } } /// Set a handler for the callback with the given name. /// /// A callback with that name must be defined in the document otherwise the function /// returns false. /// /// The \a callback parameter is a functor which takes as argument a slice of Value /// and must return a Value. /// /// Example: imagine the .slint file contains the given callback declaration: /// ``` /// callback foo(string, int) -> string; /// ``` /// Then one can set the callback handler with this function /// ``` /// instance->set_callback("foo", [](auto args) { /// std::cout << "foo(" << *args[0].to_string() << ", " << *args[1].to_number() << ")\n"; /// }); /// ``` /// /// Note: Since the ComponentInstance holds the handler, the handler itself should not /// capture a strong reference to the instance. template> F> requires(std::is_convertible_v>, Value>) auto set_callback(std::string_view name, F callback) const -> bool { using namespace cbindgen_private; auto actual_cb = [](void *data, cbindgen_private::Slice> arg) { std::span args_view { reinterpret_cast(arg.ptr), arg.len }; Value r = (*reinterpret_cast(data))(args_view); auto inner = r.inner; r.inner = cbindgen_private::slint_interpreter_value_new(); return inner; }; return cbindgen_private::slint_interpreter_component_instance_set_callback( inner(), slint::private_api::string_to_slice(name), actual_cb, new F(std::move(callback)), [](void *data) { delete reinterpret_cast(data); }); } /// Set the value for a property within an exported global singleton. /// /// For example, if the main file has an exported global `TheGlobal` with a /// `property hello`, we can set this property /// ``` /// instance->set_global_property("TheGlobal", "hello", 42); /// ``` /// /// Returns true if the property was correctly set. Returns false if the property /// could not be set because it either does not exist (was not declared in .slint) or if /// the value is not of the correct type for the property's type. /// /// **Note:** Only globals that are exported or re-exported from the main .slint file will /// be accessible bool set_global_property(std::string_view global, std::string_view prop_name, const Value &value) const { using namespace cbindgen_private; return slint_interpreter_component_instance_set_global_property( inner(), slint::private_api::string_to_slice(global), slint::private_api::string_to_slice(prop_name), value.inner); } /// Returns the value behind a property in an exported global singleton. std::optional get_global_property(std::string_view global, std::string_view prop_name) const { using namespace cbindgen_private; if (cbindgen_private::Value *rval_inner = slint_interpreter_component_instance_get_global_property( inner(), slint::private_api::string_to_slice(global), slint::private_api::string_to_slice(prop_name))) { return Value(std::move(rval_inner)); } else { return {}; } } /// Like `set_callback()` but on a callback in the specified exported global singleton. /// /// Example: imagine the .slint file contains the given global: /// ```slint,no-preview /// export global Logic { /// pure callback to_uppercase(string) -> string; /// } /// ``` /// Then you can set the callback handler /// ```cpp /// instance->set_global_callback("Logic", "to_uppercase", [](auto args) { /// std::string arg1(*args[0].to_string()); /// std::transform(arg1.begin(), arg1.end(), arg1.begin(), toupper); /// return SharedString(arg1); /// }) /// ``` /// /// **Note:** Only globals that are exported or re-exported from the main .slint file will /// be accessible template> F> bool set_global_callback(std::string_view global, std::string_view name, F callback) const { using namespace cbindgen_private; auto actual_cb = [](void *data, cbindgen_private::Slice> arg) { std::span args_view { reinterpret_cast(arg.ptr), arg.len }; Value r = (*reinterpret_cast(data))(args_view); auto inner = r.inner; r.inner = cbindgen_private::slint_interpreter_value_new(); return inner; }; return cbindgen_private::slint_interpreter_component_instance_set_global_callback( inner(), slint::private_api::string_to_slice(global), slint::private_api::string_to_slice(name), actual_cb, new F(std::move(callback)), [](void *data) { delete reinterpret_cast(data); }); } /// Invoke the specified callback or function declared in an exported global singleton std::optional invoke_global(std::string_view global, std::string_view callable_name, std::span args) const { using namespace cbindgen_private; Slice> args_view = slint::private_api::make_slice( reinterpret_cast *>( args.data()), args.size()); if (cbindgen_private::Value *rval_inner = slint_interpreter_component_instance_invoke_global( inner(), slint::private_api::string_to_slice(global), slint::private_api::string_to_slice(callable_name), args_view)) { return Value(std::move(rval_inner)); } else { return {}; } } /// Return the ComponentDefinition that was used to create this instance. inline ComponentDefinition definition() const; }; /// ComponentDefinition is a representation of a compiled component from .slint markup. /// /// It can be constructed from a .slint file using the ComponentCompiler::build_from_path() or /// ComponentCompiler::build_from_source() functions. And then it can be instantiated with the /// create() function. /// /// The ComponentDefinition acts as a factory to create new instances. When you've finished /// creating the instances it is safe to destroy the ComponentDefinition. class ComponentDefinition { friend class ComponentCompiler; friend class ComponentInstance; using ComponentDefinitionOpaque = slint::cbindgen_private::ComponentDefinitionOpaque; ComponentDefinitionOpaque inner; ComponentDefinition() = delete; // Internal constructor that takes ownership of the component definition explicit ComponentDefinition(ComponentDefinitionOpaque &inner) : inner(inner) { } public: /// Constructs a new ComponentDefinition as a copy of \a other. ComponentDefinition(const ComponentDefinition &other) { slint_interpreter_component_definition_clone(&other.inner, &inner); } /// Assigns \a other to this ComponentDefinition. ComponentDefinition &operator=(const ComponentDefinition &other) { using namespace slint::cbindgen_private; if (this == &other) return *this; slint_interpreter_component_definition_destructor(&inner); slint_interpreter_component_definition_clone(&other.inner, &inner); return *this; } /// Destroys this ComponentDefinition. ~ComponentDefinition() { slint_interpreter_component_definition_destructor(&inner); } /// Creates a new instance of the component and returns a shared handle to it. ComponentHandle create() const { union CI { cbindgen_private::ComponentInstance i; ComponentHandle result; ~CI() { result.~ComponentHandle(); } CI() { } } u; cbindgen_private::slint_interpreter_component_instance_create(&inner, &u.i); return u.result; } /// Returns a vector of PropertyDescriptor instances that describe the list of /// public properties that can be read and written using ComponentInstance::set_property and /// ComponentInstance::get_property. slint::SharedVector properties() const { slint::SharedVector props; cbindgen_private::slint_interpreter_component_definition_properties(&inner, &props); return props; } /// Returns a vector of strings that describe the list of public callbacks that can be invoked /// using ComponentInstance::invoke and set using ComponentInstance::set_callback. slint::SharedVector callbacks() const { slint::SharedVector callbacks; cbindgen_private::slint_interpreter_component_definition_callbacks(&inner, &callbacks); return callbacks; } /// Returns a vector of strings that describe the list of public functions that can be invoked /// using ComponentInstance::invoke. slint::SharedVector functions() const { slint::SharedVector functions; cbindgen_private::slint_interpreter_component_definition_functions(&inner, &functions); return functions; } /// Returns the name of this Component as written in the .slint file slint::SharedString name() const { slint::SharedString name; cbindgen_private::slint_interpreter_component_definition_name(&inner, &name); return name; } /// Returns a vector of strings with the names of all exported global singletons. slint::SharedVector globals() const { slint::SharedVector names; cbindgen_private::slint_interpreter_component_definition_globals(&inner, &names); return names; } /// Returns a vector of the property descriptors of the properties of the specified /// publicly exported global singleton. An empty optional is returned if there exists no /// exported global singleton under the specified name. std::optional> global_properties(std::string_view global_name) const { slint::SharedVector properties; if (cbindgen_private::slint_interpreter_component_definition_global_properties( &inner, slint::private_api::string_to_slice(global_name), &properties)) { return properties; } return {}; } /// Returns a vector of the names of the callbacks of the specified publicly exported global /// singleton. An empty optional is returned if there exists no exported global singleton /// under the specified name. std::optional> global_callbacks(std::string_view global_name) const { slint::SharedVector names; if (cbindgen_private::slint_interpreter_component_definition_global_callbacks( &inner, slint::private_api::string_to_slice(global_name), &names)) { return names; } return {}; } /// Returns a vector of the names of the functions of the specified publicly exported global /// singleton. An empty optional is returned if there exists no exported global singleton /// under the specified name. std::optional> global_functions(std::string_view global_name) const { slint::SharedVector names; if (cbindgen_private::slint_interpreter_component_definition_global_functions( &inner, slint::private_api::string_to_slice(global_name), &names)) { return names; } return {}; } }; inline ComponentDefinition ComponentInstance::definition() const { cbindgen_private::ComponentDefinitionOpaque result; cbindgen_private::slint_interpreter_component_instance_component_definition(inner(), &result); return ComponentDefinition(result); } /// ComponentCompiler is the entry point to the Slint interpreter that can be used /// to load .slint files or compile them on-the-fly from a string /// (using build_from_source()) or from a path (using build_from_source()) class ComponentCompiler { cbindgen_private::ComponentCompilerOpaque inner; ComponentCompiler(ComponentCompiler &) = delete; ComponentCompiler &operator=(ComponentCompiler &) = delete; public: /// Constructs a new ComponentCompiler instance. ComponentCompiler() { cbindgen_private::slint_interpreter_component_compiler_new(&inner); } /// Destroys this ComponentCompiler. ~ComponentCompiler() { cbindgen_private::slint_interpreter_component_compiler_destructor(&inner); } /// Sets the include paths used for looking up `.slint` imports to the specified vector of /// paths. void set_include_paths(const slint::SharedVector &paths) { cbindgen_private::slint_interpreter_component_compiler_set_include_paths(&inner, &paths); } /// Sets the style to be used for widgets. void set_style(std::string_view style) { cbindgen_private::slint_interpreter_component_compiler_set_style( &inner, slint::private_api::string_to_slice(style)); } /// Returns the widget style the compiler is currently using when compiling .slint files. slint::SharedString style() const { slint::SharedString s; cbindgen_private::slint_interpreter_component_compiler_get_style(&inner, &s); return s; } /// Sets the domain used for translations. void set_translation_domain(std::string_view domain) { cbindgen_private::slint_interpreter_component_compiler_set_translation_domain( &inner, slint::private_api::string_to_slice(domain)); } /// Returns the include paths the component compiler is currently configured with. slint::SharedVector include_paths() const { slint::SharedVector paths; cbindgen_private::slint_interpreter_component_compiler_get_include_paths(&inner, &paths); return paths; } /// Returns the diagnostics that were produced in the last call to build_from_path() or /// build_from_source(). slint::SharedVector diagnostics() const { slint::SharedVector result; cbindgen_private::slint_interpreter_component_compiler_get_diagnostics(&inner, &result); return result; } /// Compile a .slint file into a ComponentDefinition /// /// Returns the compiled `ComponentDefinition` if there were no errors. /// /// Any diagnostics produced during the compilation, such as warnings or errors, are collected /// in this ComponentCompiler and can be retrieved after the call using the diagnostics() /// function. /// /// Diagnostics from previous calls are cleared when calling this function. std::optional build_from_source(std::string_view source_code, std::string_view path) { cbindgen_private::ComponentDefinitionOpaque result; if (cbindgen_private::slint_interpreter_component_compiler_build_from_source( &inner, slint::private_api::string_to_slice(source_code), slint::private_api::string_to_slice(path), &result)) { return ComponentDefinition(result); } else { return {}; } } /// Compile some .slint code into a ComponentDefinition /// /// The `path` argument will be used for diagnostics and to compute relative /// paths while importing. /// /// Any diagnostics produced during the compilation, such as warnings or errors, are collected /// in this ComponentCompiler and can be retrieved after the call using the /// Self::diagnostics() function. /// /// Diagnostics from previous calls are cleared when calling this function. std::optional build_from_path(std::string_view path) { cbindgen_private::ComponentDefinitionOpaque result; if (cbindgen_private::slint_interpreter_component_compiler_build_from_path( &inner, slint::private_api::string_to_slice(path), &result)) { return ComponentDefinition(result); } else { return {}; } } }; } namespace slint::private_api::testing { /// Send a key events to the given component instance inline void send_keyboard_string_sequence(const slint::interpreter::ComponentInstance *component, const slint::SharedString &str) { const cbindgen_private::WindowAdapterRcOpaque *win_ptr = nullptr; cbindgen_private::slint_interpreter_component_instance_window( reinterpret_cast(component), &win_ptr); cbindgen_private::send_keyboard_string_sequence( &str, reinterpret_cast(win_ptr)); } } #endif ================================================ FILE: api/cpp/include/slint-platform.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include "slint.h" #include #include #include #include struct xcb_connection_t; struct wl_surface; struct wl_display; #if defined(__APPLE__) && !defined(_WIN32) && !defined(_WIN64) # ifdef __OBJC__ @class NSView; @class NSWindow; # else typedef struct objc_object NSView; typedef struct objc_object NSWindow; # endif #endif namespace slint { /// Use the types in this namespace when implementing a custom Slint platform. /// /// Slint comes with built-in support for different windowing systems, called backends. A backend /// is a module that implements the Platform interface in this namespace, interacts with a /// windowing system, and uses one of Slint's renderers to display a scene to the windowing system. /// A typical Slint application uses one of the built-in backends. Implement your own Platform if /// you're using Slint in an environment without a windowing system, such as with microcontrollers, /// or you're embedding a Slint UI as plugin in other applications. /// /// Examples of custom platform implementation can be found in the Slint repository: /// - https://github.com/slint-ui/slint/tree/master/examples/cpp/platform_native /// - https://github.com/slint-ui/slint/tree/master/examples/cpp/platform_qt /// - https://github.com/slint-ui/slint/blob/master/api/cpp/esp-idf/slint/src/slint-esp.cpp /// /// The entry point to re-implement a platform is the Platform class. Derive /// from slint::platform::Platform, and call slint::platform::set_platform /// to set it as the Slint platform. /// /// Another important class to subclass is the WindowAdapter. namespace platform { /// Internal interface for a renderer for use with the WindowAdapter. /// /// This class is not intended to be re-implemented. In places where this class is required, use /// one of the existing implementations such as SoftwareRenderer or SkiaRenderer. class AbstractRenderer { private: virtual ~AbstractRenderer() { } AbstractRenderer(const AbstractRenderer &) = delete; AbstractRenderer &operator=(const AbstractRenderer &) = delete; AbstractRenderer() = default; /// \private virtual cbindgen_private::RendererPtr renderer_handle() const = 0; friend class WindowAdapter; friend class SoftwareRenderer; friend class SkiaRenderer; }; /// Base class for the layer between a slint::Window and the windowing system specific window type, /// such as a Win32 `HWND` handle or a `wayland_surface_t`. /// /// Re-implement this class to establish the link between the two, and pass messages in both /// directions: /// /// - When receiving messages from the windowing system about state changes, such as the window /// being resized, the user requested the window to be closed, input being received, etc. you /// need to call the corresponding event functions on the Window, such as /// Window::dispatch_resize_event(), Window::dispatch_mouse_press_event(), or /// Window::dispatch_close_requested_event(). /// /// - Slint sends requests to change visibility, position, size, etc. via virtual functions such as /// set_visible(), set_size(), set_position(), or update_window_properties(). /// Re-implement these functions and delegate the requests to the windowing system. /// /// If the implementation of this bi-directional message passing protocol is incomplete, the user /// may experience unexpected behavior, or the intention of the developer calling functions on the /// Window API may not be fulfilled. /// /// Your WindowAdapter subclass must hold a renderer (either a SoftwareRenderer or a SkiaRenderer). /// In the renderer() method, you must return a reference to it. /// /// # Example /// ```cpp /// class MyWindowAdapter : public slint::platform::WindowAdapter { /// slint::platform::SoftwareRenderer m_renderer; /// NativeHandle m_native_window; // a handle to the native window /// public: /// void request_redraw() override { m_native_window.refresh(); } /// slint::PhysicalSize size() const override { /// return slint::PhysicalSize({m_native_window.width, m_native_window.height}); /// } /// slint::platform::AbstractRenderer &renderer() override { return m_renderer; } /// void set_visible(bool v) override { /// if (v) { /// m_native_window.show(); /// } else { /// m_native_window.hide(); /// } /// } /// // ... /// void repaint_callback(); /// } /// ``` /// /// Rendering is typically asynchronous, and your windowing system or event loop would invoke /// a callback when it is time to render. /// ```cpp /// void MyWindowAdapter::repaint_callback() /// { /// slint::platform::update_timers_and_animations(); /// m_renderer.render(m_native_window.buffer(), m_native_window.width); /// // if animations are running, schedule the next frame /// if (window().has_active_animations()) m_native_window.refresh(); /// } /// ``` class WindowAdapter { // This is a pointer to the rust window that own us. // Note that we do not have ownership (there is no reference increase for this) // because it would otherwise be a reference loop cbindgen_private::WindowAdapterRcOpaque self {}; // Whether this WindowAdapter was already given to the slint runtime bool was_initialized = false; cbindgen_private::WindowAdapterRcOpaque initialize() { cbindgen_private::slint_window_adapter_new( this, [](void *wa) { delete reinterpret_cast(wa); }, [](void *wa) { return reinterpret_cast(wa)->renderer().renderer_handle(); }, [](void *wa, bool visible) { reinterpret_cast(wa)->set_visible(visible); }, [](void *wa) { reinterpret_cast(wa)->request_redraw(); }, [](void *wa) -> cbindgen_private::IntSize { return reinterpret_cast(wa)->size(); }, [](void *wa, cbindgen_private::IntSize size) { reinterpret_cast(wa)->set_size( slint::PhysicalSize({ size.width, size.height })); }, [](void *wa, const cbindgen_private::WindowProperties *p) { reinterpret_cast(wa)->update_window_properties( *reinterpret_cast(p)); }, [](void *wa, cbindgen_private::Point2D *point) -> bool { if (auto pos = reinterpret_cast(wa)->position()) { *point = *pos; return true; } else { return false; } }, [](void *wa, cbindgen_private::Point2D point) { reinterpret_cast(wa)->set_position( slint::PhysicalPosition({ point.x, point.y })); }, &self); was_initialized = true; return self; } friend inline void set_platform(std::unique_ptr platform); public: /// Construct a WindowAdapter explicit WindowAdapter() { } virtual ~WindowAdapter() = default; /// This function is called by Slint when the slint window is shown or hidden. /// /// Re-implement this function to forward the call to show/hide the native window /// /// When the window becomes visible, this is a good time to call /// slint::Window::dispatch_scale_factor_change_event to initialise the scale factor. virtual void set_visible(bool) { } /// This function is called when Slint detects that the window need to be repainted. /// /// Reimplement this function to forward the call to the window manager. /// /// You should not render the window in the implementation of this call. Instead you should /// do that in the next iteration of the event loop, or in a callback from the window manager. virtual void request_redraw() { } /// Request a new size for the window to the specified size on the screen, in physical or /// logical pixels and excluding a window frame (if present). /// /// This is called from slint::Window::set_size(). /// /// The default implementation does nothing /// /// This function should send the size to the Windowing system. If the window size actually /// changes, you should call slint::Window::dispatch_resize_event to propagate the new size /// to the slint view. virtual void set_size(slint::PhysicalSize) { } /// Returns the actual physical size of the window virtual slint::PhysicalSize size() = 0; /// Sets the position of the window on the screen, in physical screen coordinates and including /// a window frame (if present). /// /// The default implementation does nothing /// /// Called from slint::Window::set_position(). virtual void set_position(slint::PhysicalPosition) { } /// Returns the position of the window on the screen, in physical screen coordinates and /// including a window frame (if present). /// /// The default implementation returns std::nullopt. /// /// Called from slint::Window::position(). virtual std::optional position() { return std::nullopt; } /// This struct contains getters that provide access to properties of the Window /// element, and is used with WindowAdapter::update_window_properties(). struct WindowProperties { /// Returns the title of the window. SharedString title() const { SharedString out; cbindgen_private::slint_window_properties_get_title(inner(), &out); return out; } /// Returns the background brush of the window. Brush background() const { Brush out; cbindgen_private::slint_window_properties_get_background(inner(), &out); return out; } /// \deprecated Use is_fullscreen() instead [[deprecated("Renamed is_fullscreen()")]] bool fullscreen() const { return is_fullscreen(); } /// Returns true if the window should be shown fullscreen; false otherwise. bool is_fullscreen() const { return cbindgen_private::slint_window_properties_get_fullscreen(inner()); } /// Returns true if the window should be minimized; false otherwise bool is_minimized() const { return cbindgen_private::slint_window_properties_get_minimized(inner()); } /// Returns true if the window should be maximized; false otherwise bool is_maximized() const { return cbindgen_private::slint_window_properties_get_maximized(inner()); } /// This struct describes the layout constraints of a window. /// /// It is the return value of WindowProperties::layout_constraints(). struct LayoutConstraints { /// This represents the minimum size the window can be. If this is set, the window /// should not be able to be resized smaller than this size. If it is left unset, there /// is no minimum size. std::optional min; /// This represents the maximum size the window can be. If this is set, the window /// should not be able to be resized larger than this size. If it is left unset, there /// is no maximum size. std::optional max; /// This represents the preferred size of the window. This is the size of the window /// should have by default LogicalSize preferred; }; /// Returns the layout constraints of the window LayoutConstraints layout_constraints() const { auto lc = cbindgen_private::slint_window_properties_get_layout_constraints(inner()); return LayoutConstraints { .min = lc.has_min ? std::optional(LogicalSize(lc.min)) : std::nullopt, .max = lc.has_max ? std::optional(LogicalSize(lc.max)) : std::nullopt, .preferred = LogicalSize(lc.preferred) }; } private: /// This struct is opaque and cannot be constructed by C++ WindowProperties() = delete; ~WindowProperties() = delete; WindowProperties(const WindowProperties &) = delete; WindowProperties &operator=(const WindowProperties &) = delete; const cbindgen_private::WindowProperties *inner() const { return reinterpret_cast(this); } }; /// Re-implement this function to update the properties such as window title or layout /// constraints. /// /// This function is called before `set_visible(true)`, and will be called again when the /// properties that were queried on the last call are changed. If you do not query any /// properties, it may not be called again. virtual void update_window_properties(const WindowProperties &) { } /// Re-implement this function to provide a reference to the renderer for use with the window /// adapter. /// /// Your re-implementation should contain a renderer such as SoftwareRenderer or SkiaRenderer /// and you must return a reference to it. virtual AbstractRenderer &renderer() = 0; /// Return the slint::Window associated with this window. /// /// Note that this function can only be called if the window was initialized, which is only /// the case after it has been returned from a call to Platform::create_window_adapter const Window &window() const { if (!was_initialized) std::abort(); // This works because cbindgen_private::WindowAdapterRcOpaque and Window have the same // layout return *reinterpret_cast(&self); } /// Overload Window &window() { if (!was_initialized) std::abort(); // This works because cbindgen_private::WindowAdapterRcOpaque and Window have the same // layout return *reinterpret_cast(&self); } }; /// The platform acts as a factory to create WindowAdapter instances. /// /// Call slint::platform::set_platform() before creating any other Slint handles. Any subsequently /// created Slint windows will use the WindowAdapter provided by the create_window_adapter function. class Platform { public: virtual ~Platform() = default; Platform(const Platform &) = delete; Platform &operator=(const Platform &) = delete; Platform() = default; /// Returns a new WindowAdapter virtual std::unique_ptr create_window_adapter() = 0; #if defined(SLINT_FEATURE_FREESTANDING) || defined(DOXYGEN) /// Returns the amount of milliseconds since start of the application. /// /// This function should only be implemented if the runtime is compiled with /// SLINT_FEATURE_FREESTANDING virtual std::chrono::milliseconds duration_since_start() = 0; #endif /// The type of clipboard used in Platform::clipboard_text and PLatform::set_clipboard_text. enum class Clipboard { /// This is the default clipboard used for text action for Ctrl+V, Ctrl+C. /// Corresponds to the secondary selection on X11. DefaultClipboard = static_cast(cbindgen_private::Clipboard::DefaultClipboard), /// This is the clipboard that is used when text is selected /// Corresponds to the primary selection on X11. /// The Platform implementation should do nothing if copy on select is not supported on that /// platform. SelectionClipboard = static_cast(cbindgen_private::Clipboard::SelectionClipboard), }; /// Sends the given text into the system clipboard. /// /// If the platform doesn't support the specified clipboard, this function should do nothing virtual void set_clipboard_text(const SharedString &, Clipboard) { } /// Returns a copy of text stored in the system clipboard, if any. /// /// If the platform doesn't support the specified clipboard, the function should return nullopt virtual std::optional clipboard_text(Clipboard) { return {}; } /// Spins an event loop and renders the visible windows. virtual void run_event_loop() { } /// Exits the event loop. /// /// This is what is called by slint::quit_event_loop() and can be called from a different thread /// or re-enter from the event loop virtual void quit_event_loop() { } /// A task that is passed to the Platform::run_in_event_loop function and needs to be /// run in the event loop and not in any other thread. class Task { cbindgen_private::PlatformTaskOpaque inner { nullptr, nullptr }; friend inline void set_platform(std::unique_ptr platform); explicit Task(cbindgen_private::PlatformTaskOpaque inner) : inner(inner) { } public: ~Task() { if (inner._0) { cbindgen_private::slint_platform_task_drop( std::exchange(inner, { nullptr, nullptr })); } } Task(const Task &) = delete; Task &operator=(const Task &) = delete; /// Move constructor. A moved from Task can no longer be run. Task(Task &&other) : inner(other.inner) { other.inner = { nullptr, nullptr }; } /// Move operator. Task &operator=(Task &&other) { std::swap(other.inner, inner); return *this; } /// Run the task. /// /// Can only be invoked once and should only be called from the event loop. void run() && { private_api::assert_main_thread(); assert(inner._0 && "calling invoke form a moved-from Task"); if (inner._0) { cbindgen_private::slint_platform_task_run( std::exchange(inner, { nullptr, nullptr })); } }; }; /// Run a task from the event loop. /// /// This function is called by slint::invoke_from_event_loop(). /// It can be called from any thread, but the passed function must only be called /// from the event loop. /// Reimplements this function and moves the event to the event loop before calling /// Task::run() virtual void run_in_event_loop(Task) { } }; /// Registers the platform with Slint. Must be called before Slint windows are created. /// Can only be called once in an application. inline void set_platform(std::unique_ptr platform) { cbindgen_private::slint_platform_register( platform.release(), [](void *p) { delete reinterpret_cast(p); }, [](void *p, cbindgen_private::WindowAdapterRcOpaque *out) { auto w = reinterpret_cast(p)->create_window_adapter(); *out = w->initialize(); (void)w.release(); }, []([[maybe_unused]] void *p) -> uint64_t { #ifndef SLINT_FEATURE_FREESTANDING return 0; #else return reinterpret_cast(p)->duration_since_start().count(); #endif }, [](void *p, const SharedString *text, cbindgen_private::Clipboard clipboard) { reinterpret_cast(p)->set_clipboard_text( *text, static_cast(clipboard)); }, [](void *p, SharedString *out_text, cbindgen_private::Clipboard clipboard) -> bool { auto maybe_clipboard = reinterpret_cast(p)->clipboard_text( static_cast(clipboard)); bool status = maybe_clipboard.has_value(); if (status) *out_text = *maybe_clipboard; return status; }, [](void *p) { return reinterpret_cast(p)->run_event_loop(); }, [](void *p) { return reinterpret_cast(p)->quit_event_loop(); }, [](void *p, cbindgen_private::PlatformTaskOpaque event) { return reinterpret_cast(p)->run_in_event_loop(Platform::Task(event)); }); } #ifdef SLINT_FEATURE_RENDERER_SOFTWARE /// A 16bit pixel that has 5 red bits, 6 green bits and 5 blue bits struct Rgb565Pixel { /// The blue component, encoded in 5 bits. uint16_t b : 5; /// The green component, encoded in 6 bits. uint16_t g : 6; /// The red component, encoded in 5 bits. uint16_t r : 5; /// Default constructor. constexpr Rgb565Pixel() : b(0), g(0), r(0) { } /// \brief Constructor that constructs from an Rgb8Pixel. explicit constexpr Rgb565Pixel(const Rgb8Pixel &pixel) : b(pixel.b >> 3), g(pixel.g >> 2), r(pixel.r >> 3) { } /// \brief Get the red component as an 8-bit value. /// /// The bits are shifted so that the result is between 0 and 255. /// \return The red component as an 8-bit value. constexpr uint8_t red() const { return (r << 3) | (r >> 2); } /// \brief Get the green component as an 8-bit value. /// /// The bits are shifted so that the result is between 0 and 255. /// \return The green component as an 8-bit value. constexpr uint8_t green() const { return (g << 2) | (g >> 4); } /// \brief Get the blue component as an 8-bit value. /// /// The bits are shifted so that the result is between 0 and 255. /// \return The blue component as an 8-bit value. constexpr uint8_t blue() const { return (b << 3) | (b >> 2); } /// \brief Convert to Rgb8Pixel. constexpr operator Rgb8Pixel() const { return { red(), green(), blue() }; } /// Returns true if \a lhs \a rhs are pixels with identical colors. friend bool operator==(const Rgb565Pixel &lhs, const Rgb565Pixel &rhs) = default; }; /// Slint's software renderer. /// /// To be used as a template parameter of the WindowAdapter. /// /// Use the render() function to render in a buffer class SoftwareRenderer : public AbstractRenderer { mutable cbindgen_private::SoftwareRendererOpaque inner; /// \private cbindgen_private::RendererPtr renderer_handle() const override { return cbindgen_private::slint_software_renderer_handle(inner); } public: /// Represents a region on the screen, used for partial rendering. /// /// The region may be composed of multiple sub-regions. struct PhysicalRegion { /// Returns the size of the bounding box of this region. PhysicalSize bounding_box_size() const { if (inner.count == 0) { return PhysicalSize(); } auto origin = bounding_box_origin(); PhysicalSize size({ .width = uint32_t(inner.rectangles[0].max.x - origin.x), .height = uint32_t(inner.rectangles[0].max.y - origin.y) }); for (size_t i = 1; i < inner.count; ++i) { size.width = std::max(size.width, uint32_t(inner.rectangles[i].max.x - origin.x)); size.height = std::max(size.height, uint32_t(inner.rectangles[i].max.y - origin.y)); } return size; } /// Returns the origin of the bounding box of this region. PhysicalPosition bounding_box_origin() const { if (inner.count == 0) { return PhysicalPosition(); } PhysicalPosition origin( { .x = inner.rectangles[0].min.x, .y = inner.rectangles[0].min.y }); for (size_t i = 1; i < inner.count; ++i) { origin.x = std::min(origin.x, inner.rectangles[i].min.x); origin.y = std::min(origin.y, inner.rectangles[i].min.y); } return origin; } /// Returns a view on all the rectangles in this region. /// The rectangles do not overlap. /// The returned type is a C++ view over PhysicalRegion::Rect structs. /// /// It can be used like so: /// ```cpp /// for (auto [origin, size] : region.rectangles()) { /// // Do something with the rect /// } /// ``` auto rectangles() const { SharedVector rectangles; cbindgen_private::slint_software_renderer_region_to_rects(&inner, &rectangles); # if __cpp_lib_ranges >= 202110L // DR20 P2415R2 using std::ranges::owning_view; # else struct owning_view : std::ranges::view_interface { SharedVector rectangles; owning_view(SharedVector &&rectangles) : rectangles(rectangles) { } auto begin() const { return rectangles.begin(); } auto end() const { return rectangles.end(); } }; # endif return owning_view(std::move(rectangles)) | std::views::transform([](const auto &r) { return Rect { .origin = PhysicalPosition({ .x = r.x, .y = r.y }), .size = PhysicalSize({ .width = uint32_t(r.width), .height = uint32_t(r.height) }) }; }); } /// A Rectangle defined with an origin and a size. /// The PhysicalRegion::rectangles() function returns a view over them struct Rect { /// The origin of the rectangle. PhysicalPosition origin; /// The size of the rectangle. PhysicalSize size; }; private: cbindgen_private::PhysicalRegion inner; friend class SoftwareRenderer; PhysicalRegion(cbindgen_private::PhysicalRegion inner) : inner(std::move(inner)) { } }; /// This enum describes which parts of the buffer passed to the SoftwareRenderer may be /// re-used to speed up painting. enum class RepaintBufferType : uint32_t { /// The full window is always redrawn. No attempt at partial rendering will be made. NewBuffer = 0, /// Only redraw the parts that have changed since the previous call to render(). /// /// This variant assumes that the same buffer is passed on every call to render() and /// that it still contains the previously rendered frame. ReusedBuffer = 1, /// Redraw the part that have changed since the last two frames were drawn. /// /// This is used when using double buffering and swapping of the buffers. SwappedBuffers = 2, }; # ifdef SLINT_FEATURE_EXPERIMENTAL /// Representation of a texture to blend in the destination buffer. // (FIXME: this is currently opaque, but should be exposed) using DrawTextureArgs = cbindgen_private::DrawTextureArgs; /// Arguments for draw_rectagle using DrawRectangleArgs = cbindgen_private::DrawRectangleArgs; /// Abstract base class for a target pixel buffer where certain drawing operations can be /// delegated. Use this to implement support for hardware accelerators such as DMA2D, PPA, or /// PXP on Microcontrollers. /// /// **Note**: This class is still experimental - its API is subject to changes and not /// stabilized yet. To use the class, you must enable the `SLINT_FEATURE_EXPERIMENTAL=ON` CMake /// option. template struct TargetPixelBuffer { virtual ~TargetPixelBuffer() { } /// Returns a span of pixels for the specified line number. virtual std::span line_slice(std::size_t line_number) = 0; /// Returns the number of lines in the buffer. This is the height of the buffer in pixels. virtual std::size_t num_lines() = 0; /// Draw a portion of provided texture to the specified pixel coordinates. /// Each pixel of the texture is to be blended with the given colorize color as well as the /// alpha value. // FIXME: Texture is currently opaque, but should be exposed virtual bool draw_texture(const DrawTextureArgs &texture, const PhysicalRegion &clip) = 0; /// Fill the background of the buffer with the given brush. virtual bool fill_background(const Brush &brush, const PhysicalRegion &clip) = 0; /// Draw a rectangle specified by the DrawRectangleArgs. That rectangle must be clipped to /// the given region. virtual bool draw_rectangle(const DrawRectangleArgs &args, const PhysicalRegion &clip) = 0; private: friend class SoftwareRenderer; cbindgen_private::CppTargetPixelBuffer wrap() { return cbindgen_private::CppTargetPixelBuffer { .user_data = this, .line_slice = [](void *self, uintptr_t line_number, PixelType **slice_ptr, uintptr_t *slice_len) { auto *buffer = reinterpret_cast *>(self); auto slice = buffer->line_slice(line_number); *slice_ptr = slice.data(); *slice_len = slice.size(); }, .num_lines = [](void *self) { auto *buffer = reinterpret_cast *>(self); return buffer->num_lines(); }, .fill_background = [](void *self, const Brush *brush, const cbindgen_private::PhysicalRegion *clip) { auto *buffer = reinterpret_cast *>(self); auto clip_region = PhysicalRegion { *clip }; return buffer->fill_background(*brush, clip_region); }, .draw_rectangle = [](void *self, const cbindgen_private::DrawRectangleArgs *args, const cbindgen_private::PhysicalRegion *clip) { auto *buffer = reinterpret_cast *>(self); auto clip_region = PhysicalRegion { *clip }; return buffer->draw_rectangle(*args, clip_region); }, .draw_texture = [](void *self, const cbindgen_private::DrawTextureArgs *texture, const cbindgen_private::PhysicalRegion *clip) { auto *buffer = reinterpret_cast *>(self); auto clip_region = PhysicalRegion { *clip }; return buffer->draw_texture(*texture, clip_region); } }; } }; # endif virtual ~SoftwareRenderer() { cbindgen_private::slint_software_renderer_drop(inner); }; SoftwareRenderer(const SoftwareRenderer &) = delete; SoftwareRenderer &operator=(const SoftwareRenderer &) = delete; /// Constructs a new SoftwareRenderer with the \a buffer_type as strategy for handling the /// differences between rendering buffers. explicit SoftwareRenderer(RepaintBufferType buffer_type) { inner = cbindgen_private::slint_software_renderer_new(uint32_t(buffer_type)); } /// Render the window scene into a pixel buffer /// /// The buffer must be at least as large as the associated slint::Window /// /// The stride is the amount of pixels between two lines in the buffer. /// It is must be at least as large as the width of the window. PhysicalRegion render(std::span buffer, std::size_t pixel_stride) const { auto r = cbindgen_private::slint_software_renderer_render_rgb8(inner, buffer.data(), buffer.size(), pixel_stride); return PhysicalRegion { r }; } /// Render the window scene into an RGB 565 encoded pixel buffer /// /// The buffer must be at least as large as the associated slint::Window /// /// The stride is the amount of pixels between two lines in the buffer. /// It is must be at least as large as the width of the window. PhysicalRegion render(std::span buffer, std::size_t pixel_stride) const { auto r = cbindgen_private::slint_software_renderer_render_rgb565( inner, reinterpret_cast(buffer.data()), buffer.size(), pixel_stride); return PhysicalRegion { r }; } /// Render the window scene, line by line. The provided Callback will be invoked for each line /// that needs to rendered. /// /// The renderer uses a cache internally and will only render the part of the window /// which are dirty. /// /// This function returns the physical region that was rendered considering the rotation. /// /// The callback must be an invocable with the signature (size_t line, size_t begin, size_t end, /// auto render_fn). It is invoked with the line number as first parameter, and the start x and /// end x coordinates of the line as second and third parameter. The implementation must provide /// a line buffer (as std::span) and invoke the provided fourth parameter (render_fn) with it, /// to fill it with pixels. After the line buffer is filled with pixels, your implementation is /// free to flush that line to the screen for display. /// /// The first template parameter (PixelType) must be specified and can be either Rgb565Pixel or /// Rgb8Pixel. template # if !defined(__clang__) || __clang_major__ >= 17 requires requires(Callback callback) { callback(size_t(0), size_t(0), size_t(0), [&callback](std::span) {}); } # endif PhysicalRegion render_by_line(Callback process_line_callback) const { auto process_line_fn = [](void *process_line_callback_ptr, uintptr_t line, uintptr_t line_start, uintptr_t line_end, void (*render_fn)(const void *, PixelType *, std::size_t), const void *render_fn_data) { (*reinterpret_cast(process_line_callback_ptr))( std::size_t(line), std::size_t(line_start), std::size_t(line_end), [render_fn, render_fn_data](std::span line_span) { render_fn(render_fn_data, line_span.data(), line_span.size()); }); }; if constexpr (std::is_same_v) { return PhysicalRegion { cbindgen_private::slint_software_renderer_render_by_line_rgb565( inner, process_line_fn, &process_line_callback) }; } else if constexpr (std::is_same_v) { return PhysicalRegion { cbindgen_private::slint_software_renderer_render_by_line_rgb8( inner, process_line_fn, &process_line_callback) }; } else { static_assert(std::is_same_v || std::is_same_v, "Unsupported PixelType. It must be either Rgba8Pixel or Rgb565Pixel"); } } # ifdef SLINT_FEATURE_EXPERIMENTAL /// Renders into the given TargetPixelBuffer. /// /// **Note**: This class is still experimental - its API is subject to changes and not /// stabilized yet. To use the class, you must enable the `SLINT_FEATURE_EXPERIMENTAL=ON` CMake /// option. PhysicalRegion render(TargetPixelBuffer *buffer) const { auto wrapper = buffer->wrap(); auto r = cbindgen_private::slint_software_renderer_render_accel_rgb8(inner, &wrapper); return PhysicalRegion { r }; } /// Renders into the given TargetPixelBuffer. /// /// **Note**: This class is still experimental - its API is subject to changes and not /// stabilized yet. To use the class, you must enable the `SLINT_FEATURE_EXPERIMENTAL=ON` CMake /// option. PhysicalRegion render(TargetPixelBuffer *buffer) const { auto wrapper = buffer->wrap(); auto r = cbindgen_private::slint_software_renderer_render_accel_rgb565(inner, &wrapper); return PhysicalRegion { r }; } # endif /// This enum describes the rotation that is applied to the buffer when rendering. /// To be used in set_rendering_rotation() enum class RenderingRotation { /// No rotation NoRotation = 0, /// Rotate 90° to the left Rotate90 = 90, /// 180° rotation (upside-down) Rotate180 = 180, /// Rotate 90° to the right Rotate270 = 270, }; /// Set how the window needs to be rotated in the buffer. /// /// This is typically used to implement screen rotation in software void set_rendering_rotation(RenderingRotation rotation) { cbindgen_private::slint_software_renderer_set_rendering_rotation( inner, static_cast(rotation)); } }; #endif #ifdef SLINT_FEATURE_RENDERER_SKIA /// An opaque, low-level window handle that internalizes everything necessary to exchange messages /// with the windowing system. This includes the connection to the display server, if necessary. /// /// Note that this class does not provide any kind of ownership. The caller is responsible for /// ensuring that the pointers supplied to the constructor are valid throughout the lifetime of the /// NativeWindowHandle. class NativeWindowHandle { cbindgen_private::CppRawHandleOpaque inner; friend class SkiaRenderer; NativeWindowHandle(cbindgen_private::CppRawHandleOpaque inner) : inner(inner) { } public: NativeWindowHandle() = delete; NativeWindowHandle(const NativeWindowHandle &) = delete; NativeWindowHandle &operator=(const NativeWindowHandle &) = delete; /// Creates a new NativeWindowHandle by moving the handle data from \a other into this /// NativeWindowHandle. NativeWindowHandle(NativeWindowHandle &&other) { inner = std::exchange(other.inner, nullptr); } /// Creates a new NativeWindowHandle by moving the handle data from \a other into this /// NativeWindowHandle. NativeWindowHandle &operator=(NativeWindowHandle &&other) { if (this == &other) { return *this; } if (inner) { cbindgen_private::slint_raw_window_handle_drop(inner); } inner = std::exchange(other.inner, nullptr); return *this; } # if (!defined(__APPLE__) && !defined(_WIN32) && !defined(_WIN64)) || defined(DOXYGEN) /// Creates a new NativeWindowHandle from the given xcb_window_t \a window, /// xcb_visualid_t \a visual_id, XCB \a connection, and \a screen number. static NativeWindowHandle from_x11_xcb(uint32_t /*xcb_window_t*/ window, uint32_t /*xcb_visualid_t*/ visual_id, xcb_connection_t *connection, int screen) { return { cbindgen_private::slint_new_raw_window_handle_x11_xcb(window, visual_id, connection, screen) }; } /// Creates a new NativeWindowHandle from the given XLib \a window, /// VisualID \a visual_id, Display \a display, and \a screen number. static NativeWindowHandle from_x11_xlib(uint32_t /*Window*/ window, unsigned long /*VisualID*/ visual_id, void /*Display*/ *display, int screen) { return { cbindgen_private::slint_new_raw_window_handle_x11_xlib(window, visual_id, display, screen) }; } /// Creates a new NativeWindowHandle from the given wayland \a surface, /// and \a display. static NativeWindowHandle from_wayland(wl_surface *surface, wl_display *display) { return { cbindgen_private::slint_new_raw_window_handle_wayland(surface, display) }; } # endif # if (defined(__APPLE__) && !defined(_WIN32) && !defined(_WIN64)) || defined(DOXYGEN) /// Creates a new NativeWindowHandle from the given \a nsview, and \a nswindow. static NativeWindowHandle from_appkit(NSView *nsview, NSWindow *nswindow) { return { cbindgen_private::slint_new_raw_window_handle_appkit(nsview, nswindow) }; } # endif # if (!defined(__APPLE__) && (defined(_WIN32) || defined(_WIN64))) || defined(DOXYGEN) /// Creates a new NativeWindowHandle from the given HWND \a hwnd, and HINSTANCE \a hinstance. static NativeWindowHandle from_win32(void *hwnd, void *hinstance) { return { cbindgen_private::slint_new_raw_window_handle_win32(hwnd, hinstance) }; } # endif /// Destroys the NativeWindowHandle. ~NativeWindowHandle() { if (inner) { cbindgen_private::slint_raw_window_handle_drop(inner); } } }; /// Slint's Skia renderer. /// /// Create the renderer when you have created a native window with a non-zero size. /// Call the render() function to render the scene into the window. class SkiaRenderer : public AbstractRenderer { mutable cbindgen_private::SkiaRendererOpaque inner; /// \private cbindgen_private::RendererPtr renderer_handle() const override { return cbindgen_private::slint_skia_renderer_handle(inner); } public: virtual ~SkiaRenderer() { cbindgen_private::slint_skia_renderer_drop(inner); } SkiaRenderer(const SkiaRenderer &) = delete; SkiaRenderer &operator=(const SkiaRenderer &) = delete; /// Constructs a new Skia renderer for the given window - referenced by the provided /// WindowHandle - and the specified initial size. explicit SkiaRenderer(const NativeWindowHandle &window_handle, PhysicalSize initial_size) { inner = cbindgen_private::slint_skia_renderer_new(window_handle.inner, initial_size); } /// Renders the scene into the window provided to the SkiaRenderer's constructor. void render() const { cbindgen_private::slint_skia_renderer_render(inner); } }; #endif /// Call this function at each iteration of the event loop to call the timer handler and advance /// the animations. This should be called before the rendering or processing input events inline void update_timers_and_animations() { cbindgen_private::slint_platform_update_timers_and_animations(); } /// Returns the duration until the next timer if there are pending timers inline std::optional duration_until_next_timer_update() { uint64_t val = cbindgen_private::slint_platform_duration_until_next_timer_update(); if (val == std::numeric_limits::max()) { return std::nullopt; } else if (val >= uint64_t(std::chrono::milliseconds::max().count())) { return std::chrono::milliseconds::max(); } else { return std::chrono::milliseconds(val); } } } } ================================================ FILE: api/cpp/include/slint-stm32.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include #include #include #include #if !defined(SLINT_STM32_BSP_NAME) # error "Please define the SLINT_STM32_BSP_NAME pre-processor macro to the base name of the BSP, without quotes, such as SLINT_STM32_BSP_NAME=stm32h747i_disco" #endif #define SLINT_STRINGIFY(X) SLINT_STRINGIFY2(X) #define SLINT_STRINGIFY2(X) #X #define SLINT_CAT(X, Y) SLINT_CAT2(X, Y) #define SLINT_CAT2(X, Y) X##Y #include SLINT_STRINGIFY(SLINT_STM32_BSP_NAME.h) #include SLINT_STRINGIFY(SLINT_CAT(SLINT_STM32_BSP_NAME, _lcd.h)) #include SLINT_STRINGIFY(SLINT_CAT(SLINT_STM32_BSP_NAME, _ts.h)) #undef SLINT_STRINGIFY #undef SLINT_STRINGIFY2 #undef SLINT_CAT #undef SLINT_CAT2 struct SlintPlatformConfiguration { /// The size of the screen in pixels. slint::PhysicalSize size #if defined(LCD_DEFAULT_WIDTH) && defined(LCD_DEFAULT_HEIGHT) = slint::PhysicalSize({ LCD_DEFAULT_WIDTH, LCD_DEFAULT_HEIGHT }) #endif ; unsigned int lcd_layer_0_address = #if defined(LCD_LAYER_0_ADDRESS) LCD_LAYER_0_ADDRESS #else 0 #endif ; unsigned int lcd_layer_1_address = #if defined(LCD_LAYER_0_ADDRESS) LCD_LAYER_1_ADDRESS #else 0 #endif ; slint::platform::SoftwareRenderer::RenderingRotation rotation = slint::platform::SoftwareRenderer::RenderingRotation::NoRotation; }; namespace slint::private_api { struct StmWindowAdapter : public slint::platform::WindowAdapter { slint::platform::SoftwareRenderer m_renderer { slint::platform::SoftwareRenderer::RepaintBufferType::SwappedBuffers }; bool needs_redraw = true; const slint::PhysicalSize m_size; explicit StmWindowAdapter(slint::PhysicalSize size) : m_size(size) { } slint::platform::AbstractRenderer &renderer() override { return m_renderer; } slint::PhysicalSize size() override { return m_size; } void request_redraw() override { needs_redraw = true; } }; struct StmSlintPlatform : public slint::platform::Platform { using Pixel = slint::platform::Rgb565Pixel; static __IO bool screen_ready; StmWindowAdapter *m_window = nullptr; const slint::PhysicalSize size; slint::platform::SoftwareRenderer::RenderingRotation rotation; std::span buffer1; std::span buffer2; StmSlintPlatform(slint::PhysicalSize size, slint::platform::SoftwareRenderer::RenderingRotation rotation, std::span buffer1, std::span buffer2) : size(size), rotation(rotation), buffer1(buffer1), buffer2(buffer2) { BSP_LCD_LayerConfig_t config; config.X0 = 0; config.X1 = LCD_DEFAULT_WIDTH; config.Y0 = 0; config.Y1 = LCD_DEFAULT_HEIGHT; config.PixelFormat = LCD_PIXEL_FORMAT_RGB565; config.Address = uintptr_t(buffer1.data()); BSP_LCD_ConfigLayer(0, 0, &config); HAL_LTDC_RegisterCallback(&hlcd_ltdc, HAL_LTDC_RELOAD_EVENT_CB_ID, [](auto *) { screen_ready = true; }); } std::unique_ptr create_window_adapter() override { auto w = std::make_unique(size); w->m_renderer.set_rendering_rotation(rotation); m_window = w.get(); return w; } std::chrono::milliseconds duration_since_start() override { return std::chrono::milliseconds(HAL_GetTick()); } void run_event_loop() override { float last_touch_x = 0; float last_touch_y = 0; bool touch_down = false; while (true) { slint::platform::update_timers_and_animations(); if (m_window) { TS_State_t TS_State {}; BSP_TS_GetState(0, &TS_State); if (TS_State.TouchDetected) { auto scale_factor = m_window->window().scale_factor(); last_touch_x = float(TS_State.TouchX) / scale_factor; last_touch_y = float(TS_State.TouchY) / scale_factor; m_window->window().dispatch_pointer_move_event( slint::LogicalPosition({ last_touch_x, last_touch_y })); if (!touch_down) { m_window->window().dispatch_pointer_press_event( slint::LogicalPosition({ last_touch_x, last_touch_y }), slint::PointerEventButton::Left); } touch_down = true; } else if (touch_down) { m_window->window().dispatch_pointer_release_event( slint::LogicalPosition({ last_touch_x, last_touch_y }), slint::PointerEventButton::Left); m_window->window().dispatch_pointer_exit_event(); touch_down = false; } if (std::exchange(m_window->needs_redraw, false)) { while (!screen_ready) { } auto rotated = rotation == slint::platform::SoftwareRenderer::RenderingRotation:: Rotate90 || rotation == slint::platform::SoftwareRenderer::RenderingRotation:: Rotate270; m_window->m_renderer.render( buffer1, rotated ? m_window->m_size.height : m_window->m_size.width); SCB_CleanDCache_by_Addr((uint32_t *)buffer1.data(), buffer1.size()); BSP_LCD_Relaod(0, BSP_LCD_RELOAD_NONE); BSP_LCD_SetLayerAddress(0, 0, uintptr_t(buffer1.data())); screen_ready = false; BSP_LCD_Relaod(0, BSP_LCD_RELOAD_VERTICAL_BLANKING); std::swap(buffer1, buffer2); } } } } }; inline __IO bool StmSlintPlatform::screen_ready = true; } // namespace slint::private_api inline void slint_stm32_init(const SlintPlatformConfiguration &config) { auto a = config.size.width * config.size.height; std::span buffer1( reinterpret_cast( config.lcd_layer_0_address), a); std::span buffer2( reinterpret_cast( config.lcd_layer_1_address), a); slint::platform::set_platform(std::make_unique( config.size, config.rotation, buffer1, buffer2)); } ================================================ FILE: api/cpp/include/slint-testing.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #include "slint.h" #include "slint_testing_internal.h" #include #include #include #include #ifdef SLINT_FEATURE_TESTING # ifdef SLINT_FEATURE_EXPERIMENTAL /// Use the functions and classes in this namespace for in-process UI testing. /// /// This module is still experimental - its API is subject to changes and not stabilized yet. To /// use the module, you must enable the `SLINT_FEATURE_EXPERIMENTAL=ON` and `SLINT_FEATURE_TESTING` /// CMake options. namespace slint::testing { using slint::cbindgen_private::AccessibleRole; using slint::cbindgen_private::LayoutKind; /// Init the testing backend. /// Should be called before any other Slint function that can access the platform. /// Then future windows will not appear on the screen anymore inline void init() { cbindgen_private::slint_testing_init_backend(); } /// Replace the font collection with embedded NotoSans fonts for deterministic test results. inline void configure_test_fonts() { cbindgen_private::slint_testing_configure_test_fonts(); } /// A handle to an element for querying accessible properties, intended for testing purposes. class ElementHandle { cbindgen_private::ElementHandle inner; explicit ElementHandle(const cbindgen_private::ElementHandle *inner) : inner(*inner) { } public: /// Visits visible elements within a component and calls the visitor for each of them. /// /// The visitor must be a callable object that accepts an `ElementHandle` and returns either /// `void`, or a type that can be converted to `bool`. /// - If the visitor returns `void`, the visitation continues until all elements have been /// visited. /// - If the visitor returns a type that can be converted to `bool`, the visitation continues as /// long as the conversion result is false; otherwise, it stops, returning that value. /// If the visitor never returns something that converts to true, then the function returns a /// default constructed value; /// /// ```cpp /// auto element = ElementHandle::visit_elements(component, [&](const ElementHandle& eh) /// -> std::optional { /// return eh.id() == "Foo::bar" ? std::make_optional(eh) : std::nullopt; /// }); /// ``` template Visitor, typename R = std::invoke_result_t> requires((std::is_constructible_v && std::is_default_constructible_v) || std::is_void_v) static auto visit_elements(const ComponentHandle &component, Visitor visitor) -> std::invoke_result_t { // using R = std::invoke_result_t; auto vrc = component.into_dyn(); if constexpr (std::is_void_v) { cbindgen_private::slint_testing_element_visit_elements( &vrc, &visitor, [](void *visitor, const cbindgen_private::ElementHandle *element) { (*reinterpret_cast(visitor))(ElementHandle(element)); return false; }); return; } else { struct VisitorAndResult { Visitor &visitor; R result; } visitor_and_result { visitor, R {} }; cbindgen_private::slint_testing_element_visit_elements( &vrc, &visitor_and_result, [](void *user_data, const cbindgen_private::ElementHandle *element) { auto visitor_and_result = reinterpret_cast(user_data); if (auto r = visitor_and_result->visitor(ElementHandle(element))) { visitor_and_result->result = std::move(r); return true; }; return false; }); return visitor_and_result.result; } } /// Find all elements matching the given accessible label. template static SharedVector find_by_accessible_label(const ComponentHandle &component, std::string_view label) { cbindgen_private::Slice label_view = private_api::string_to_slice(label); auto vrc = component.into_dyn(); SharedVector result; cbindgen_private::slint_testing_element_find_by_accessible_label( &vrc, &label_view, reinterpret_cast *>(&result)); return result; } /// Find all elements matching the given element_id. template static SharedVector find_by_element_id(const ComponentHandle &component, std::string_view element_id) { cbindgen_private::Slice element_id_view = private_api::string_to_slice(element_id); auto vrc = component.into_dyn(); SharedVector result; cbindgen_private::slint_testing_element_find_by_element_id( &vrc, &element_id_view, reinterpret_cast *>(&result)); return result; } /// Find all elements matching the given type name. template static SharedVector find_by_element_type_name(const ComponentHandle &component, std::string_view type_name) { cbindgen_private::Slice element_type_name_view = private_api::string_to_slice(type_name); auto vrc = component.into_dyn(); SharedVector result; cbindgen_private::slint_testing_element_find_by_element_type_name( &vrc, &element_type_name_view, reinterpret_cast *>(&result)); return result; } /// Returns true if the underlying element still exists; false otherwise. bool is_valid() const { return private_api::upgrade_item_weak(inner.item).has_value(); } /// Returns the element's qualified id. Returns None if the element is not valid anymore or the /// element does not have an id. /// A qualified id consists of the name of the surrounding component as well as the provided /// local name, separate by a double colon. /// /// ```slint,no-preview /// component PushButton { /// /* .. */ /// } /// /// export component App { /// mybutton := PushButton { } // known as `App::mybutton` /// PushButton { } // no id /// } /// ``` std::optional id() const { SharedString id; if (cbindgen_private::slint_testing_element_id(&inner, &id)) { return id; } else { return std::nullopt; } } /// Returns the element's type name; std::nullopt if the element is not valid anymore. /// ```slint,no-preview /// component PushButton { /// /* .. */ /// } /// /// export component App { /// mybutton := PushButton { } // type_name is "PushButton" /// } /// ``` std::optional type_name() const { SharedString type_name; if (cbindgen_private::slint_testing_element_type_name(&inner, &type_name)) { return type_name; } else { return std::nullopt; } } /// Returns the element's base types as an iterator; None if the element is not valid anymore. /// /// ```slint,no-preview /// component ButtonBase { /// /* .. */ /// } /// /// component PushButton inherits ButtonBase { /// /* .. */ /// } /// /// export component App { /// mybutton := PushButton { } // bases will be ["ButtonBase"] /// } /// ``` std::optional> bases() const { SharedVector bases; if (cbindgen_private::slint_testing_element_bases(&inner, &bases)) { return bases; } else { return std::nullopt; } } /// Returns the layout kind if this element is a layout container; /// std::nullopt if the element is not a layout or is not valid anymore. std::optional layout_kind() const { LayoutKind kind {}; if (cbindgen_private::slint_testing_element_layout_kind(&inner, &kind)) { return kind; } else { return std::nullopt; } } /// Returns the value of the element's `accessible-role` property, if present. Use this property /// to locate elements by their type/role, i.e. buttons, checkboxes, etc. std::optional accessible_role() const { if (inner.element_index != 0) return std::nullopt; if (auto item = private_api::upgrade_item_weak(inner.item)) { return item->item_tree.vtable()->accessible_role(item->item_tree.borrow(), item->index); } return std::nullopt; } /// Returns the accessible-label of that element, if any. std::optional accessible_label() const { return get_accessible_string_property(cbindgen_private::AccessibleStringProperty::Label); } /// Returns the accessible-enabled of that element, if any. std::optional accessible_enabled() const { return get_accessible_bool_property(cbindgen_private::AccessibleStringProperty::Enabled); } /// Returns the accessible-value of that element, if any. std::optional accessible_value() const { return get_accessible_string_property(cbindgen_private::AccessibleStringProperty::Value); } /// Returns the accessible-placeholder-text of that element, if any. std::optional accessible_placeholder_text() const { return get_accessible_string_property( cbindgen_private::AccessibleStringProperty::PlaceholderText); } /// Returns the accessible-description of that element, if any. std::optional accessible_description() const { return get_accessible_string_property( cbindgen_private::AccessibleStringProperty::Description); } /// Returns the accessible-id of that element, if any. std::optional accessible_id() const { return get_accessible_string_property(cbindgen_private::AccessibleStringProperty::Id); } /// Returns the accessible-value-maximum of that element, if any. std::optional accessible_value_maximum() const { if (auto result = get_accessible_string_property( cbindgen_private::AccessibleStringProperty::ValueMaximum)) { float value = 0.0; if (cbindgen_private::slint_string_to_float(&*result, &value)) { return value; } } return std::nullopt; } /// Returns the accessible-value-minimum of that element, if any. std::optional accessible_value_minimum() const { if (auto result = get_accessible_string_property( cbindgen_private::AccessibleStringProperty::ValueMinimum)) { float value = 0.0; if (cbindgen_private::slint_string_to_float(&*result, &value)) { return value; } } return std::nullopt; } /// Returns the accessible-value-step of that element, if any. std::optional accessible_value_step() const { if (auto result = get_accessible_string_property( cbindgen_private::AccessibleStringProperty::ValueStep)) { float value = 0.0; if (cbindgen_private::slint_string_to_float(&*result, &value)) { return value; } } return std::nullopt; } /// Returns the accessible-checked of that element, if any. std::optional accessible_checked() const { return get_accessible_bool_property(cbindgen_private::AccessibleStringProperty::Checked); } /// Returns the accessible-checkable of that element, if any. std::optional accessible_checkable() const { return get_accessible_bool_property(cbindgen_private::AccessibleStringProperty::Checkable); } /// Returns the accessible-item-selected of that element, if any. std::optional accessible_item_selected() const { return get_accessible_bool_property( cbindgen_private::AccessibleStringProperty::ItemSelected); } /// Returns the accessible-item-selectable of that element, if any. std::optional accessible_item_selectable() const { return get_accessible_bool_property( cbindgen_private::AccessibleStringProperty::ItemSelectable); } /// Returns the accessible-item-index of that element, if any. std::optional accessible_item_index() const { if (auto result = get_accessible_string_property( cbindgen_private::AccessibleStringProperty::ItemIndex)) { uintptr_t value = 0; if (cbindgen_private::slint_string_to_usize(&*result, &value)) { return value; } } return std::nullopt; } /// Returns the accessible-item-count of that element, if any. std::optional accessible_item_count() const { if (auto result = get_accessible_string_property( cbindgen_private::AccessibleStringProperty::ItemCount)) { uintptr_t value = 0; if (cbindgen_private::slint_string_to_usize(&*result, &value)) { return value; } } return std::nullopt; } /// Returns the accessible-expanded of that element, if any. std::optional accessible_expanded() const { return get_accessible_bool_property(cbindgen_private::AccessibleStringProperty::Expanded); } /// Returns the accessible-expandable of that element, if any. std::optional accessible_expandable() const { return get_accessible_bool_property(cbindgen_private::AccessibleStringProperty::Expandable); } /// Returns the accessible-read-only of that element, if any. std::optional accessible_read_only() const { return get_accessible_bool_property(cbindgen_private::AccessibleStringProperty::ReadOnly); } /// Invokes the expand accessibility action of that element /// (`accessible-action-expand`). void invoke_accessible_expand_action() const { if (inner.element_index != 0) return; if (auto item = private_api::upgrade_item_weak(inner.item)) { union ExpandActionHelper { cbindgen_private::AccessibilityAction action; ExpandActionHelper() { action.tag = cbindgen_private::AccessibilityAction::Tag::Expand; } ~ExpandActionHelper() { } } action; item->item_tree.vtable()->accessibility_action(item->item_tree.borrow(), item->index, &action.action); } } /// Sets the accessible-value of that element. /// /// Setting the value will invoke the `accessible-action-set-value` callback. void set_accessible_value(SharedString value) const { if (inner.element_index != 0) return; if (auto item = private_api::upgrade_item_weak(inner.item)) { union SetValueHelper { cbindgen_private::AccessibilityAction action; SetValueHelper(SharedString value) { new (&action.set_value) cbindgen_private::AccessibilityAction::SetValue_Body { cbindgen_private::AccessibilityAction::Tag::SetValue, std::move(value) }; } ~SetValueHelper() { action.set_value.~SetValue_Body(); } } action(std::move(value)); item->item_tree.vtable()->accessibility_action(item->item_tree.borrow(), item->index, &action.action); } } /// Invokes the increase accessibility action of that element /// (`accessible-action-increment`). void invoke_accessible_increment_action() const { if (inner.element_index != 0) return; if (auto item = private_api::upgrade_item_weak(inner.item)) { union IncreaseActionHelper { cbindgen_private::AccessibilityAction action; IncreaseActionHelper() { action.tag = cbindgen_private::AccessibilityAction::Tag::Increment; } ~IncreaseActionHelper() { } } action; item->item_tree.vtable()->accessibility_action(item->item_tree.borrow(), item->index, &action.action); } } /// Invokes the decrease accessibility action of that element /// (`accessible-action-decrement`). void invoke_accessible_decrement_action() const { if (inner.element_index != 0) return; if (auto item = private_api::upgrade_item_weak(inner.item)) { union DecreaseActionHelper { cbindgen_private::AccessibilityAction action; DecreaseActionHelper() { action.tag = cbindgen_private::AccessibilityAction::Tag::Decrement; } ~DecreaseActionHelper() { } } action; item->item_tree.vtable()->accessibility_action(item->item_tree.borrow(), item->index, &action.action); } } /// Invokes the default accessibility action of that element /// (`accessible-action-default`). void invoke_accessible_default_action() const { if (inner.element_index != 0) return; if (auto item = private_api::upgrade_item_weak(inner.item)) { union DefaultActionHelper { cbindgen_private::AccessibilityAction action; DefaultActionHelper() { action.tag = cbindgen_private::AccessibilityAction::Tag::Default; } ~DefaultActionHelper() { } } action; item->item_tree.vtable()->accessibility_action(item->item_tree.borrow(), item->index, &action.action); } } /// Returns the size of this element LogicalSize size() const { if (auto item = private_api::upgrade_item_weak(inner.item)) { auto rect = item->item_tree.vtable()->item_geometry(item->item_tree.borrow(), item->index); return LogicalSize({ rect.width, rect.height }); } return LogicalSize({ 0, 0 }); } /// Returns the absolute position of this element LogicalPosition absolute_position() const { if (auto item = private_api::upgrade_item_weak(inner.item)) { cbindgen_private::LogicalRect rect = item->item_tree.vtable()->item_geometry(item->item_tree.borrow(), item->index); cbindgen_private::LogicalPoint abs = slint::cbindgen_private::slint_item_absolute_position(&item->item_tree, item->index); return LogicalPosition({ abs.x + rect.x, abs.y + rect.y }); } return LogicalPosition({ 0, 0 }); } private: std::optional get_accessible_string_property(cbindgen_private::AccessibleStringProperty what) const { if (inner.element_index != 0) return std::nullopt; if (auto item = private_api::upgrade_item_weak(inner.item)) { SharedString result; if (item->item_tree.vtable()->accessible_string_property(item->item_tree.borrow(), item->index, what, &result)) { return result; } } return std::nullopt; } std::optional get_accessible_bool_property(cbindgen_private::AccessibleStringProperty what) const { if (auto result = get_accessible_string_property(what)) { if (*result == "true") return true; else if (*result == "false") return false; } return std::nullopt; } }; } # endif // SLINT_FEATURE_EXPERIMENTAL #endif // SLINT_FEATURE_TESTING ================================================ FILE: api/cpp/include/slint.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include "slint_internal.h" #include "slint_platform_internal.h" #include "slint_qt_internal.h" #include "slint_window.h" #include "slint_models.h" #include "slint_item_tree.h" #include #include #include #include #ifndef SLINT_FEATURE_FREESTANDING # include # include # include # include #endif /// \rst /// The :code:`slint` namespace is the primary entry point into the Slint C++ API. /// All available types are in this namespace. /// /// See the :doc:`Overview <../overview>` documentation for the C++ integration how /// to load :code:`.slint` designs. /// \endrst namespace slint { namespace private_api { /// Convert a slint `{height: length, width: length, x: length, y: length}` to a Rect inline cbindgen_private::Rect convert_anonymous_rect(std::tuple tuple) { // alphabetical order auto [h, w, x, y] = tuple; return cbindgen_private::Rect { .x = x, .y = y, .width = w, .height = h }; } inline void dealloc(const ItemTreeVTable *vtable, uint8_t *ptr, [[maybe_unused]] vtable::Layout layout) { vtable::dealloc(vtable, ptr, layout); } template inline vtable::Layout drop_in_place(ItemTreeRef item_tree) { return vtable::drop_in_place(item_tree); } #if !defined(DOXYGEN) # if defined(_WIN32) || defined(_WIN64) // On Windows cross-dll data relocations are not supported: // https://docs.microsoft.com/en-us/cpp/c-language/rules-and-limitations-for-dllimport-dllexport?view=msvc-160 // so we have a relocation to a function that returns the address we seek. That // relocation will be resolved to the locally linked stub library, the implementation of // which will be patched. # define SLINT_GET_ITEM_VTABLE(VTableName) slint::private_api::slint_get_##VTableName() # else # define SLINT_GET_ITEM_VTABLE(VTableName) (&slint::private_api::VTableName) # endif #endif // !defined(DOXYGEN) inline std::optional upgrade_item_weak(const cbindgen_private::ItemWeak &item_weak) { if (auto item_tree_strong = item_weak.item_tree.lock()) { return { { *item_tree_strong, item_weak.index } }; } else { return std::nullopt; } } inline void debug(const SharedString &str) { cbindgen_private::slint_debug(&str); } } // namespace private_api namespace cbindgen_private { inline LayoutInfo LayoutInfo::merge(const LayoutInfo &other) const { // Note: This "logic" is duplicated from LayoutInfo::merge in layout.rs. return LayoutInfo { std::min(max, other.max), std::min(max_percent, other.max_percent), std::max(min, other.min), std::max(min_percent, other.min_percent), std::max(preferred, other.preferred), std::min(stretch, other.stretch) }; } inline bool operator==(const EasingCurve &a, const EasingCurve &b) { if (a.tag != b.tag) { return false; } else if (a.tag == EasingCurve::Tag::CubicBezier) { return std::equal(a.cubic_bezier._0, a.cubic_bezier._0 + 4, b.cubic_bezier._0); } return true; } } namespace private_api { inline static void register_item_tree(const vtable::VRc *c, const std::optional &maybe_window) { const cbindgen_private::WindowAdapterRcOpaque *window_ptr = maybe_window.has_value() ? &maybe_window->window_handle().handle() : nullptr; cbindgen_private::slint_register_item_tree(c, window_ptr); } inline SharedVector solve_box_layout(const cbindgen_private::BoxLayoutData &data, cbindgen_private::Slice repeater_indices) { SharedVector result; cbindgen_private::Slice ri = make_slice(reinterpret_cast(repeater_indices.ptr), repeater_indices.len); cbindgen_private::slint_solve_box_layout(&data, ri, &result); return result; } inline SharedVector organize_grid_layout(cbindgen_private::Slice input_data, cbindgen_private::Slice repeater_indices, cbindgen_private::Slice repeater_steps) { SharedVector result; cbindgen_private::Slice ri = make_slice(reinterpret_cast(repeater_indices.ptr), repeater_indices.len); cbindgen_private::Slice rs = make_slice(reinterpret_cast(repeater_steps.ptr), repeater_steps.len); cbindgen_private::slint_organize_grid_layout(input_data, ri, rs, &result); return result; } inline SharedVector organize_dialog_button_layout( cbindgen_private::Slice input_data, cbindgen_private::Slice dialog_button_roles) { SharedVector result; cbindgen_private::slint_organize_dialog_button_layout(input_data, dialog_button_roles, &result); return result; } inline SharedVector solve_grid_layout(const cbindgen_private::GridLayoutData &data, cbindgen_private::Slice constraints, cbindgen_private::Orientation orientation, cbindgen_private::Slice repeater_indices, cbindgen_private::Slice repeater_steps) { SharedVector result; cbindgen_private::Slice ri = make_slice(reinterpret_cast(repeater_indices.ptr), repeater_indices.len); cbindgen_private::Slice rs = make_slice(reinterpret_cast(repeater_steps.ptr), repeater_steps.len); cbindgen_private::slint_solve_grid_layout(&data, constraints, orientation, ri, rs, &result); return result; } inline cbindgen_private::LayoutInfo grid_layout_info(const cbindgen_private::GridLayoutOrganizedData &organized_data, cbindgen_private::Slice constraints, cbindgen_private::Slice repeater_indices, cbindgen_private::Slice repeater_steps, float spacing, const cbindgen_private::Padding &padding, cbindgen_private::Orientation orientation) { cbindgen_private::Slice ri = make_slice(reinterpret_cast(repeater_indices.ptr), repeater_indices.len); cbindgen_private::Slice rs = make_slice(reinterpret_cast(repeater_steps.ptr), repeater_steps.len); return cbindgen_private::slint_grid_layout_info(&organized_data, constraints, ri, rs, spacing, &padding, orientation); } inline cbindgen_private::LayoutInfo box_layout_info(cbindgen_private::Slice cells, float spacing, const cbindgen_private::Padding &padding, cbindgen_private::LayoutAlignment alignment) { return cbindgen_private::slint_box_layout_info(cells, spacing, &padding, alignment); } inline cbindgen_private::LayoutInfo box_layout_info_ortho(cbindgen_private::Slice cells, const cbindgen_private::Padding &padding) { return cbindgen_private::slint_box_layout_info_ortho(cells, &padding); } inline SharedVector solve_flexbox_layout(const cbindgen_private::FlexBoxLayoutData &data, cbindgen_private::Slice repeater_indices) { SharedVector result; cbindgen_private::Slice ri = make_slice(reinterpret_cast(repeater_indices.ptr), repeater_indices.len); cbindgen_private::slint_solve_flexbox_layout(&data, ri, &result); return result; } inline cbindgen_private::LayoutInfo flexbox_layout_info(cbindgen_private::Slice cells_h, cbindgen_private::Slice cells_v, float spacing_h, float spacing_v, const cbindgen_private::Padding &padding_h, const cbindgen_private::Padding &padding_v, cbindgen_private::Orientation orientation, cbindgen_private::FlexDirection direction, float constraint_size, cbindgen_private::FlexWrap flex_wrap) { return cbindgen_private::slint_flexbox_layout_info(cells_h, cells_v, spacing_h, spacing_v, &padding_h, &padding_v, orientation, direction, constraint_size, flex_wrap); } /// Access the layout cache of an item within a repeater (standard cache) template inline T layout_cache_access(const SharedVector &cache, int offset, int repeater_index, int entries_per_item) { size_t idx = size_t(cache[offset]) + repeater_index * entries_per_item; return idx < cache.size() ? cache[idx] : 0; } /// Access the layout cache of an item within a grid repeater (two-level indirection cache) /// Formula: cache[cache[jump_index] + repeater_index * stride + child_offset] template inline T layout_cache_grid_repeater_access(const SharedVector &cache, size_t jump_index, size_t repeater_index, size_t stride, size_t child_offset) { size_t base = jump_index < cache.size() ? size_t(cache[jump_index]) : 0; size_t data_idx = base + repeater_index * stride + child_offset; return data_idx < cache.size() ? cache[data_idx] : 0; } template inline cbindgen_private::LayoutInfo item_layout_info(VT *itemvtable, ItemType *item_ptr, cbindgen_private::Orientation orientation, WindowAdapterRc *window_adapter, const ItemTreeRc &component_rc, uint32_t item_index) { cbindgen_private::ItemRc item_rc { component_rc, item_index }; return itemvtable->layout_info({ itemvtable, item_ptr }, orientation, window_adapter, &item_rc); } } // namespace private_api namespace private_api { template union MaybeUninitialized { T value; ~MaybeUninitialized() { } MaybeUninitialized() { } T take() { T result = std::move(value); value.~T(); return result; } }; inline vtable::VRc create_menu_wrapper(const ItemTreeRc &menu_item_tree, bool (*condition)(const ItemTreeRc *menu_tree) = nullptr) { MaybeUninitialized> maybe; cbindgen_private::slint_menus_create_wrapper(&menu_item_tree, &maybe.value, condition); return maybe.take(); } inline void setup_popup_menu_from_menu_item_tree( const vtable::VRc &shared, Property>> &entries, Callback>(cbindgen_private::MenuEntry)> &sub_menu, Callback &activated) { using cbindgen_private::MenuEntry; entries.set_binding([shared] { SharedVector entries_sv; shared.vtable()->sub_menu(shared.borrow(), nullptr, &entries_sv); std::vector entries_vec(entries_sv.begin(), entries_sv.end()); return std::make_shared>(std::move(entries_vec)); }); sub_menu.set_handler([shared](const auto &entry) { SharedVector entries_sv; shared.vtable()->sub_menu(shared.borrow(), &entry, &entries_sv); std::vector entries_vec(entries_sv.begin(), entries_sv.end()); return std::make_shared>(std::move(entries_vec)); }); activated.set_handler( [shared](const auto &entry) { shared.vtable()->activate(shared.borrow(), &entry); }); } inline SharedString translate(const SharedString &original, const SharedString &context, const SharedString &domain, cbindgen_private::Slice arguments, int n, const SharedString &plural) { SharedString result = original; cbindgen_private::slint_translate(&result, &context, &domain, arguments, n, &plural); return result; } inline StyledText parse_markdown(const SharedString &format_string, cbindgen_private::Slice args) { StyledText result; cbindgen_private::slint_parse_markdown(&format_string, args, &result); return result; } inline StyledText string_to_styled_text(SharedString text) { StyledText result; cbindgen_private::slint_string_to_styled_text(text, &result); return result; } inline SharedString translate_from_bundle(std::span strs, cbindgen_private::Slice arguments) { SharedString result; cbindgen_private::slint_translate_from_bundle( make_slice((reinterpret_cast(strs.data())), strs.size()), arguments, &result); return result; } inline SharedString translate_from_bundle_with_plural(std::span strs, std::span indices, std::span plural_rules, cbindgen_private::Slice arguments, int n) { SharedString result; cbindgen_private::Slice strs_slice = make_slice(reinterpret_cast(strs.data()), strs.size()); cbindgen_private::Slice indices_slice = make_slice(reinterpret_cast(indices.data()), indices.size()); cbindgen_private::Slice plural_rules_slice = make_slice(reinterpret_cast(plural_rules.data()), plural_rules.size()); cbindgen_private::slint_translate_from_bundle_with_plural( strs_slice, indices_slice, plural_rules_slice, arguments, n, &result); return result; } inline SharedString keys_to_string(const cbindgen_private::Keys &keys) { SharedString result; cbindgen_private::slint_keys_to_string(&keys, &result); return result; } template inline float get_resolved_default_font_size(const Component &component) { ItemTreeRc item_tree_rc = (*component.self_weak.lock()).into_dyn(); return slint::cbindgen_private::slint_windowrc_resolved_default_font_size(&item_tree_rc); } } // namespace private_api // Translator API is currently considered experimental due to discussions // about the returned string type (SharedString vs. Cow etc.). Also it // is not available with no_std due to the tr crate. // See dicussion in https://github.com/slint-ui/slint/pull/10979. #if defined(SLINT_FEATURE_EXPERIMENTAL) && !defined(SLINT_FEATURE_FREESTANDING) /// Interface for an external translator. struct Translator { /// Destroys the translator. virtual ~Translator() { } /// Translate a singular string. Arguments are passed as UTF-8 strings. /// Slint will call this method from the thread which runs the event loop. virtual SharedString translate(std::string_view string, std::string_view context) const = 0; /// Translate a plural string. Arguments are passed as UTF-8 strings. /// Slint will call this method from the thread which runs the event loop. virtual SharedString ntranslate(uint64_t n, std::string_view singular, std::string_view plural, std::string_view context) const = 0; }; namespace private_api { /// Helper to dispatch calls from the Rust translator to the C++ translator. struct TranslatorDispatcher { static void drop(const void *obj) { delete cast(obj); } static void translate(const void *obj, private_api::Slice string, private_api::Slice context, slint::SharedString *out) { *out = cast(obj)->translate(private_api::slice_to_string_view(string), private_api::slice_to_string_view(context)); } static void ntranslate(const void *obj, uint64_t n, private_api::Slice singular, private_api::Slice plural, private_api::Slice context, slint::SharedString *out) { *out = cast(obj)->ntranslate(n, private_api::slice_to_string_view(singular), private_api::slice_to_string_view(plural), private_api::slice_to_string_view(context)); } private: static const Translator *cast(const void *obj) { return static_cast(obj); } }; } // namespace private_api /// Register a custom translator. /// /// Allows using a custom translation framework by implementing the /// `slint::Translator` interface. Passing `nullptr` will unregister any /// previously registered translator. /// /// Returns `true` on success, `false` if no platform is available. /// /// Safety & Ownership: /// * The ownership of the translator object is passed to Slint. It will be /// destroyed automatically when the program quits or when /// `set_external_translator()` is called the next time. /// * The methods on the translator object will be called from the thread /// which the Slint event loop is running. /// /// The function is only available when Slint is compiled with /// `SLINT_FEATURE_EXPERIMENTAL` and without `SLINT_FEATURE_FREESTANDING`. /// /// Note that this function has no effect if the `.slint` file was compiled /// with bundled translations. /// /// Example: /// \code /// struct MyTranslator : public slint::Translator { /// slint::SharedString translate(std::string_view string, /// std::string_view context) const override { /// return slint::SharedString("Singular String"); /// } /// /// slint::SharedString ntranslate(uint64_t n, /// std::string_view singular, /// std::string_view plural, /// std::string_view context) const override { /// return slint::SharedString("Plural String"); /// } /// }; /// /// slint::set_external_translator(std::make_unique()); /// \endcode inline bool set_translator(std::unique_ptr obj) { const bool success = cbindgen_private::slint_translate_set_translator( obj.get(), &private_api::TranslatorDispatcher::drop, &private_api::TranslatorDispatcher::translate, &private_api::TranslatorDispatcher::ntranslate); if (success) { obj.release(); // Ownership is moved to Rust. } return success; } #endif /// Forces all the strings that are translated with `@tr(...)` to be re-evaluated. /// Call this function after changing the language at run-time and when translating /// with either gettext or a custom translator. For bundled translations, there is no need /// to call this function. /// /// Example (assuming usage of gettext): /// ```cpp /// my_ui->global().on_french_selected([] { /// setenv("LANGUAGE", langs[l], true); /// slint::update_all_translations(); /// }); /// ``` inline void update_all_translations() { cbindgen_private::slint_translations_mark_dirty(); } /// Select the current translation language when using bundled translations. /// This function requires that the application's `.slint` file was compiled with bundled /// translations. It must be called after creating the first component. /// /// The language string is the locale, which matches the name of the folder that contains the /// `LC_MESSAGES` folder. An empty string or `"en"` will select the default language. /// /// Returns true if the language was selected; false if the language was not found in the list of /// bundled translations. inline bool select_bundled_translation(std::string_view language) { return cbindgen_private::slint_translate_select_bundled_translation( slint::private_api::string_to_slice(language)); } #if !defined(DOXYGEN) cbindgen_private::Flickable::Flickable() { slint_flickable_data_init(&data); } cbindgen_private::Flickable::~Flickable() { slint_flickable_data_free(&data); } cbindgen_private::FocusScope::FocusScope() { slint_maybe_shortcut_list_init(&shortcuts); } cbindgen_private::FocusScope::~FocusScope() { slint_maybe_shortcut_list_free(&shortcuts); } cbindgen_private::NativeStyleMetrics::NativeStyleMetrics(void *) { slint_native_style_metrics_init(this); } cbindgen_private::NativeStyleMetrics::~NativeStyleMetrics() { slint_native_style_metrics_deinit(this); } cbindgen_private::NativePalette::NativePalette(void *) { slint_native_palette_init(this); } cbindgen_private::NativePalette::~NativePalette() { slint_native_palette_deinit(this); } #endif // !defined(DOXYGEN) namespace private_api { // Was used in Slint <= 1.1.0 to have an error message in case of mismatch template struct [[deprecated]] VersionCheckHelper { }; } /// Enum for the event loop mode parameter of the slint::run_event_loop() function. /// It is used to determine when the event loop quits. enum class EventLoopMode { /// The event loop will quit when the last window is closed /// or when slint::quit_event_loop() is called. QuitOnLastWindowClosed, /// The event loop will keep running until slint::quit_event_loop() is called, /// even when all windows are closed. RunUntilQuit }; /// Enters the main event loop. This is necessary in order to receive /// events from the windowing system in order to render to the screen /// and react to user input. /// /// The mode parameter determines the behavior of the event loop when all windows are closed. /// By default, it is set to QuitOnLastWindowClose, which means the event loop will /// quit when the last window is closed. inline void run_event_loop(EventLoopMode mode = EventLoopMode::QuitOnLastWindowClosed) { private_api::assert_main_thread(); cbindgen_private::slint_run_event_loop(mode == EventLoopMode::QuitOnLastWindowClosed); } /// Schedules the main event loop for termination. This function is meant /// to be called from callbacks triggered by the UI. After calling the function, /// it will return immediately and once control is passed back to the event loop, /// the initial call to slint::run_event_loop() will return. inline void quit_event_loop() { cbindgen_private::slint_quit_event_loop(); } /// Adds the specified functor to an internal queue, notifies the event loop to wake up. /// Once woken up, any queued up functors will be invoked. /// This function is thread-safe and can be called from any thread, including the one /// running the event loop. The provided functors will only be invoked from the thread /// that started the event loop. /// /// You can use this to set properties or use any other Slint APIs from other threads, /// by collecting the code in a functor and queuing it up for invocation within the event loop. /// /// The following example assumes that a status message received from a network thread is /// shown in the UI: /// /// ``` /// #include "my_application_ui.h" /// #include /// /// int main(int argc, char **argv) /// { /// auto ui = NetworkStatusUI::create(); /// ui->set_status_label("Pending"); /// /// slint::ComponentWeakHandle weak_ui_handle(ui); /// std::thread network_thread([=]{ /// std::string message = read_message_blocking_from_network(); /// slint::invoke_from_event_loop([&]() { /// if (auto ui = weak_ui_handle.lock()) { /// ui->set_status_label(message); /// } /// }); /// }); /// ... /// ui->run(); /// ... /// } /// ``` /// /// See also blocking_invoke_from_event_loop() for a blocking version of this function template void invoke_from_event_loop(Functor f) { cbindgen_private::slint_post_event( [](void *data) { (*reinterpret_cast(data))(); }, new Functor(std::move(f)), [](void *data) { delete reinterpret_cast(data); }); } #if !defined(SLINT_FEATURE_FREESTANDING) || defined(DOXYGEN) /// Blocking version of invoke_from_event_loop() /// /// Just like invoke_from_event_loop(), this will run the specified functor from the thread running /// the slint event loop. But it will block until the execution of the functor is finished, /// and return that value. /// /// This function must be called from a different thread than the thread that runs the event loop /// otherwise it will result in a deadlock. Calling this function if the event loop is not running /// will also block forever or until the event loop is started in another thread. /// /// The following example is reading the message property from a thread /// /// ``` /// #include "my_application_ui.h" /// #include /// /// int main(int argc, char **argv) /// { /// auto ui = MyApplicationUI::create(); /// ui->set_status_label("Pending"); /// /// std::thread worker_thread([ui]{ /// while (...) { /// auto message = slint::blocking_invoke_from_event_loop([ui]() { /// return ui->get_message(); /// } /// do_something(message); /// ... /// }); /// }); /// ... /// ui->run(); /// ... /// } /// ``` template auto blocking_invoke_from_event_loop(Functor f) -> std::invoke_result_t { std::optional> result; std::mutex mtx; std::condition_variable cv; invoke_from_event_loop([&] { auto r = f(); std::unique_lock lock(mtx); result = std::move(r); cv.notify_one(); }); std::unique_lock lock(mtx); cv.wait(lock, [&] { return result.has_value(); }); return std::move(*result); } # if !defined(DOXYGEN) // Doxygen doesn't see this as an overload of the previous one // clang-format off template requires(std::is_void_v>) void blocking_invoke_from_event_loop(Functor f) // clang-format on { std::mutex mtx; std::condition_variable cv; bool ok = false; invoke_from_event_loop([&] { f(); std::unique_lock lock(mtx); ok = true; cv.notify_one(); }); std::unique_lock lock(mtx); cv.wait(lock, [&] { return ok; }); } # endif #endif /// Sets the application id for use on Wayland or X11 with /// [xdg](https://specifications.freedesktop.org/desktop-entry-spec/latest/) compliant window /// managers. This must be set before the window is shown. inline void set_xdg_app_id(std::string_view xdg_app_id) { private_api::assert_main_thread(); SharedString s = xdg_app_id; cbindgen_private::slint_set_xdg_app_id(&s); } } // namespace slint ================================================ FILE: api/cpp/include/slint_brush.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include #include "slint_color.h" #include "slint_brush_internal.h" #include "slint_string.h" namespace slint { namespace private_api { using cbindgen_private::types::GradientStop; /// \private /// LinearGradientBrush represents a gradient for a brush that is a linear sequence of color stops, /// that are aligned at a specific angle. class LinearGradientBrush { public: /// Constructs an empty linear gradient with no color stops. LinearGradientBrush() = default; /// Constructs a new linear gradient with the specified \a angle. The color stops will be /// constructed from the stops array pointed to be \a firstStop, with the length \a stopCount. LinearGradientBrush(float angle, const GradientStop *firstStop, int stopCount) : inner(make_linear_gradient(angle, firstStop, stopCount)) { } /// Returns the linear gradient's angle in degrees. float angle() const { // The gradient's first stop is a fake stop to store the angle return inner[0].position; } /// Returns the number of gradient stops. int stopCount() const { return int(inner.size()) - 1; } /// Returns a pointer to the first gradient stop; undefined if the gradient has not stops. const GradientStop *stopsBegin() const { return inner.begin() + 1; } /// Returns a pointer past the last gradient stop. The returned pointer cannot be dereferenced, /// it can only be used for comparison. const GradientStop *stopsEnd() const { return inner.end(); } private: cbindgen_private::types::LinearGradientBrush inner; friend class slint::Brush; static SharedVector make_linear_gradient(float angle, const GradientStop *firstStop, int stopCount) { SharedVector gradient; gradient.push_back({ Color::from_argb_encoded(0).inner, angle }); for (int i = 0; i < stopCount; ++i, ++firstStop) gradient.push_back(*firstStop); return gradient; } }; /// \private /// RadialGradientBrush represents a circular gradient centered in the middle class RadialGradientBrush { public: /// Constructs an empty linear gradient with no color stops. RadialGradientBrush() = default; /// Constructs a new circular radial gradient . The color stops will be /// constructed from the stops array pointed to be \a firstStop, with the length \a stopCount. RadialGradientBrush(const GradientStop *firstStop, int stopCount) : inner(make_circle_gradient(firstStop, stopCount)) { } /// Returns the number of gradient stops. int stopCount() const { return int(inner.size()); } /// Returns a pointer to the first gradient stop; undefined if the gradient has not stops. const GradientStop *stopsBegin() const { return inner.begin(); } /// Returns a pointer past the last gradient stop. The returned pointer cannot be dereferenced, /// it can only be used for comparison. const GradientStop *stopsEnd() const { return inner.end(); } private: cbindgen_private::types::RadialGradientBrush inner; friend class slint::Brush; static SharedVector make_circle_gradient(const GradientStop *firstStop, int stopCount) { SharedVector gradient; for (int i = 0; i < stopCount; ++i, ++firstStop) gradient.push_back(*firstStop); return gradient; } }; /// \private /// ConicGradientBrush represents a conic gradient that rotates around a center point class ConicGradientBrush { public: /// Constructs an empty conic gradient with no color stops. ConicGradientBrush() = default; /// Constructs a new conic gradient with the specified starting \a angle. The color stops will /// be constructed from the stops array pointed to be \a firstStop, with the length \a /// stopCount. ConicGradientBrush(float angle, const GradientStop *firstStop, int stopCount) : inner(make_conic_gradient(angle, firstStop, stopCount)) { } /// Returns the conic gradient's starting angle (rotation) in degrees. float angle() const { return inner[0].position; } /// Returns the number of gradient stops. int stopCount() const { return int(inner.size()) - 1; } /// Returns a pointer to the first gradient stop; undefined if the gradient has not stops. const GradientStop *stopsBegin() const { return inner.begin() + 1; } /// Returns a pointer past the last gradient stop. The returned pointer cannot be dereferenced, /// it can only be used for comparison. const GradientStop *stopsEnd() const { return inner.end(); } private: cbindgen_private::types::ConicGradientBrush inner; friend class slint::Brush; static SharedVector make_conic_gradient(float angle, const GradientStop *firstStop, int stopCount) { SharedVector gradient; // The gradient's first stop is a fake stop to store the angle (same pattern as // LinearGradient) gradient.push_back({ Color::from_argb_encoded(0).inner, angle }); for (int i = 0; i < stopCount; ++i, ++firstStop) gradient.push_back(*firstStop); // Normalize stops to [0, 1] range with proper boundary stops cbindgen_private::types::slint_conic_gradient_normalize_stops(&gradient); // Apply rotation if angle is non-zero if (angle != 0.0f) { cbindgen_private::types::slint_conic_gradient_apply_rotation(&gradient, angle); } return gradient; } }; } /// Brush is used to declare how to fill or outline shapes, such as rectangles, paths or text. A /// brush is either a solid color or a linear gradient. class Brush { public: /// Constructs a new brush that is a transparent color. Brush() : Brush(Color {}) { } /// Constructs a new brush that is of color \a color. Brush(const Color &color) : data(Inner::SolidColor(color.inner)) { } /// \private /// Constructs a new brush that is the gradient \a gradient. Brush(const private_api::LinearGradientBrush &gradient) : data(Inner::LinearGradient(gradient.inner)) { } /// \private /// Constructs a new brush that is the gradient \a gradient. Brush(const private_api::RadialGradientBrush &gradient) : data(Inner::RadialGradient(gradient.inner)) { } /// \private /// Constructs a new brush that is the gradient \a gradient. Brush(const private_api::ConicGradientBrush &gradient) : data(Inner::ConicGradient(gradient.inner)) { } /// Returns the color of the brush. If the brush is a gradient, this function returns the color /// of the first stop. inline Color color() const; /// Returns a new version of this brush that has the brightness increased /// by the specified factor. This is done by calling Color::brighter on /// all the colors of this brush. [[nodiscard]] inline Brush brighter(float factor) const; /// Returns a new version of this color that has the brightness decreased /// by the specified factor. This is done by calling Color::darker on /// all the colors of this brush. [[nodiscard]] inline Brush darker(float factor) const; /// Returns a new version of this brush with the opacity decreased by \a factor. /// /// This is done by calling Color::transparentize on all the colors of this brush. [[nodiscard]] inline Brush transparentize(float factor) const; /// Returns a new version of this brush with the related color's opacities /// set to \a alpha. [[nodiscard]] inline Brush with_alpha(float alpha) const; /// Returns true if \a a is equal to \a b. If \a a holds a color, then \a b must also hold a /// color that is identical to \a a's color. If it holds a gradient, then the gradients must be /// identical. Returns false if the brushes differ in what they hold or their respective color /// or gradient are not equal. friend bool operator==(const Brush &a, const Brush &b) { return a.data == b.data; } /// Returns false if \a is not equal to \a b; true otherwise. friend bool operator!=(const Brush &a, const Brush &b) { return a.data != b.data; } private: using Tag = cbindgen_private::types::Brush::Tag; using Inner = cbindgen_private::types::Brush; Inner data; friend struct private_api::Property; }; Color Brush::color() const { Color result; switch (data.tag) { case Tag::SolidColor: result.inner = data.solid_color._0; break; case Tag::LinearGradient: if (data.linear_gradient._0.size() > 1) { result.inner = data.linear_gradient._0[1].color; } break; case Tag::RadialGradient: if (data.radial_gradient._0.size() > 0) { result.inner = data.radial_gradient._0[0].color; } break; case Tag::ConicGradient: if (data.conic_gradient._0.size() > 1) { result.inner = data.conic_gradient._0[1].color; } break; } return result; } inline Brush Brush::brighter(float factor) const { Brush result = *this; switch (data.tag) { case Tag::SolidColor: cbindgen_private::types::slint_color_brighter(&data.solid_color._0, factor, &result.data.solid_color._0); break; case Tag::LinearGradient: for (std::size_t i = 1; i < data.linear_gradient._0.size(); ++i) { cbindgen_private::types::slint_color_brighter(&data.linear_gradient._0[i].color, factor, &result.data.linear_gradient._0[i].color); } break; case Tag::RadialGradient: for (std::size_t i = 0; i < data.radial_gradient._0.size(); ++i) { cbindgen_private::types::slint_color_brighter(&data.radial_gradient._0[i].color, factor, &result.data.radial_gradient._0[i].color); } break; case Tag::ConicGradient: for (std::size_t i = 1; i < data.conic_gradient._0.size(); ++i) { cbindgen_private::types::slint_color_brighter(&data.conic_gradient._0[i].color, factor, &result.data.conic_gradient._0[i].color); } break; } return result; } inline Brush Brush::darker(float factor) const { Brush result = *this; switch (data.tag) { case Tag::SolidColor: cbindgen_private::types::slint_color_darker(&data.solid_color._0, factor, &result.data.solid_color._0); break; case Tag::LinearGradient: for (std::size_t i = 1; i < data.linear_gradient._0.size(); ++i) { cbindgen_private::types::slint_color_darker(&data.linear_gradient._0[i].color, factor, &result.data.linear_gradient._0[i].color); } break; case Tag::RadialGradient: for (std::size_t i = 0; i < data.radial_gradient._0.size(); ++i) { cbindgen_private::types::slint_color_darker(&data.radial_gradient._0[i].color, factor, &result.data.radial_gradient._0[i].color); } break; case Tag::ConicGradient: for (std::size_t i = 1; i < data.conic_gradient._0.size(); ++i) { cbindgen_private::types::slint_color_darker(&data.conic_gradient._0[i].color, factor, &result.data.conic_gradient._0[i].color); } break; } return result; } inline Brush Brush::transparentize(float factor) const { Brush result = *this; switch (data.tag) { case Tag::SolidColor: cbindgen_private::types::slint_color_transparentize(&data.solid_color._0, factor, &result.data.solid_color._0); break; case Tag::LinearGradient: for (std::size_t i = 1; i < data.linear_gradient._0.size(); ++i) { cbindgen_private::types::slint_color_transparentize( &data.linear_gradient._0[i].color, factor, &result.data.linear_gradient._0[i].color); } break; case Tag::RadialGradient: for (std::size_t i = 0; i < data.radial_gradient._0.size(); ++i) { cbindgen_private::types::slint_color_transparentize( &data.radial_gradient._0[i].color, factor, &result.data.radial_gradient._0[i].color); } break; case Tag::ConicGradient: for (std::size_t i = 1; i < data.conic_gradient._0.size(); ++i) { cbindgen_private::types::slint_color_transparentize( &data.conic_gradient._0[i].color, factor, &result.data.conic_gradient._0[i].color); } break; } return result; } inline Brush Brush::with_alpha(float alpha) const { Brush result = *this; switch (data.tag) { case Tag::SolidColor: cbindgen_private::types::slint_color_with_alpha(&data.solid_color._0, alpha, &result.data.solid_color._0); break; case Tag::LinearGradient: for (std::size_t i = 1; i < data.linear_gradient._0.size(); ++i) { cbindgen_private::types::slint_color_with_alpha( &data.linear_gradient._0[i].color, alpha, &result.data.linear_gradient._0[i].color); } break; case Tag::RadialGradient: for (std::size_t i = 0; i < data.radial_gradient._0.size(); ++i) { cbindgen_private::types::slint_color_with_alpha( &data.radial_gradient._0[i].color, alpha, &result.data.radial_gradient._0[i].color); } break; case Tag::ConicGradient: for (std::size_t i = 1; i < data.conic_gradient._0.size(); ++i) { cbindgen_private::types::slint_color_with_alpha( &data.conic_gradient._0[i].color, alpha, &result.data.conic_gradient._0[i].color); } break; } return result; } namespace private_api { template<> inline void Property::set_animated_value( const slint::Brush &new_value, const cbindgen_private::PropertyAnimation &animation_data) const { cbindgen_private::slint_property_set_animated_value_brush(&inner, &value, &new_value, &animation_data); } } // namespace private_api } // namespace slint ================================================ FILE: api/cpp/include/slint_callbacks.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include #include "slint_properties_internal.h" namespace slint::private_api { /// A Callback stores a function pointer with no parameters and no return value. /// It's possible to set that pointer via set_handler() and it can be invoked via call(). This is /// used to implement callbacks in the `.slint` language. template struct Callback; /// A Callback stores a function pointer with \a Arg parameters and a return value of type \a Ret. /// It's possible to set that pointer via set_handler() and it can be invoked via call(). This is /// used to implement callbacks in the `.slint` language. template struct Callback { /// Constructs an empty callback that contains no handler. Callback() { cbindgen_private::slint_callback_init(&inner); } /// Destructs the callback. ~Callback() { cbindgen_private::slint_callback_drop(&inner); } Callback(const Callback &) = delete; Callback(Callback &&) = delete; Callback &operator=(const Callback &) = delete; /// Sets a new handler \a binding for this callback, that will be invoked when call() is called. template void set_handler(F binding) const { cbindgen_private::slint_callback_set_handler( &inner, [](void *user_data, const void *arg, void *ret) { *reinterpret_cast(ret) = std::apply(*reinterpret_cast(user_data), *reinterpret_cast(arg)); }, new F(std::move(binding)), [](void *user_data) { delete reinterpret_cast(user_data); }); } /// Invokes a previously set handler with the parameters \a arg and returns the return value of /// the handler. Ret call(const Arg &...arg) const { Ret r {}; Tuple tuple { arg... }; cbindgen_private::slint_callback_call(&inner, &tuple, &r); return r; } private: using Tuple = std::tuple; cbindgen_private::CallbackOpaque inner; }; /// A Callback stores a function pointer with \a Arg parameters and no return value. /// It's possible to set that pointer via set_handler() and it can be invoked via call(). This is /// used to implement callbacks in the `.slint` language. template struct Callback { /// Constructs an empty callback that contains no handler. Callback() { cbindgen_private::slint_callback_init(&inner); } /// Destructs the callback. ~Callback() { cbindgen_private::slint_callback_drop(&inner); } Callback(const Callback &) = delete; Callback(Callback &&) = delete; Callback &operator=(const Callback &) = delete; /// Sets a new handler \a binding for this callback, that will be invoked when call() is called. template void set_handler(F binding) const { cbindgen_private::slint_callback_set_handler( &inner, [](void *user_data, const void *arg, void *) { std::apply(*reinterpret_cast(user_data), *reinterpret_cast(arg)); }, new F(std::move(binding)), [](void *user_data) { delete reinterpret_cast(user_data); }); } /// Invokes a previously set handler with the parameters \a arg. void call(const Arg &...arg) const { Tuple tuple { arg... }; cbindgen_private::slint_callback_call(&inner, &tuple, reinterpret_cast(0x1)); } private: using Tuple = std::tuple; cbindgen_private::CallbackOpaque inner; }; template struct CallbackSignatureHelper { using Result = R(A); }; template struct CallbackSignatureHelper { using Result = R(); }; template using CallbackHelper = Callback::Result>; } // namespace slint ================================================ FILE: api/cpp/include/slint_color.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include "slint_color_internal.h" namespace slint { namespace private_api { class LinearGradientBrush; class ConicGradientBrush; } class Color; /// RgbaColor stores the red, green, blue and alpha components of a color /// with the precision of the template parameter T. For example if T is float, /// the values are normalized between 0 and 1. If T is uint8_t, they values range /// is 0 to 255. template struct RgbaColor { /// The alpha component. T alpha; /// The red component. T red; /// The green component. T green; /// The blue component. T blue; /// Creates a new RgbaColor instance from a given color. This template function is /// specialized and thus implemented for T == uint8_t and T == float. RgbaColor(const Color &col); }; /// HsvaColor stores the hue, saturation, value, and alpha components of a color in the HSV color /// space. struct HsvaColor { /// The hue component in degrees between 0 and 360. float hue; /// The saturation component, between 0 and 1. float saturation; /// The value component, between 0 and 1. float value; /// The alpha component, between 0 and 1. float alpha; }; /// OklchColor stores the lightness, chroma, hue, and alpha components of a color in the Oklch /// color space (a perceptually uniform color space). struct OklchColor { /// The lightness component, between 0 (black) and 1 (white). float lightness; /// The chroma component (saturation), typically between 0 (grayscale) and ~0.4 (vivid). float chroma; /// The hue component in degrees between 0 and 360. float hue; /// The alpha component, between 0 and 1. float alpha; }; /// Color represents a color in the Slint run-time, represented using 8-bit channels for /// red, green, blue and the alpha (opacity). class Color { public: /// Default constructs a new color that is entirely transparent. Color() { inner.red = inner.green = inner.blue = inner.alpha = 0; } /// Constructs a new color from the given RgbaColor \a col. Color(const RgbaColor &col) { inner.red = col.red; inner.green = col.green; inner.blue = col.blue; inner.alpha = col.alpha; } /// Constructs a new color from the given RgbaColor \a col. Color(const RgbaColor &col) { inner.red = uint8_t(col.red * 255); inner.green = uint8_t(col.green * 255); inner.blue = uint8_t(col.blue * 255); inner.alpha = uint8_t(col.alpha * 255); } /// Construct a color from an integer encoded as `0xAARRGGBB` [[nodiscard]] static Color from_argb_encoded(uint32_t argb_encoded) { Color col; col.inner.red = (argb_encoded >> 16) & 0xff; col.inner.green = (argb_encoded >> 8) & 0xff; col.inner.blue = argb_encoded & 0xff; col.inner.alpha = (argb_encoded >> 24) & 0xff; return col; } /// Returns `(alpha, red, green, blue)` encoded as uint32_t. uint32_t as_argb_encoded() const { return (uint32_t(inner.red) << 16) | (uint32_t(inner.green) << 8) | uint32_t(inner.blue) | (uint32_t(inner.alpha) << 24); } /// Construct a color from the alpha, red, green and blue color channel parameters. [[nodiscard]] static Color from_argb_uint8(uint8_t alpha, uint8_t red, uint8_t green, uint8_t blue) { Color col; col.inner.alpha = alpha; col.inner.red = red; col.inner.green = green; col.inner.blue = blue; return col; } /// Construct a color from the red, green and blue color channel parameters. The alpha /// channel will have the value 255. [[nodiscard]] static Color from_rgb_uint8(uint8_t red, uint8_t green, uint8_t blue) { return from_argb_uint8(255, red, green, blue); } /// Construct a color from the alpha, red, green and blue color channel parameters. [[nodiscard]] static Color from_argb_float(float alpha, float red, float green, float blue) { Color col; col.inner.alpha = uint8_t(alpha * 255); col.inner.red = uint8_t(red * 255); col.inner.green = uint8_t(green * 255); col.inner.blue = uint8_t(blue * 255); return col; } /// Construct a color from the red, green and blue color channel parameters. The alpha /// channel will have the value 255. [[nodiscard]] static Color from_rgb_float(float red, float green, float blue) { return Color::from_argb_float(1.0, red, green, blue); } /// Converts this color to an RgbaColor struct for easy destructuring. [[nodiscard]] inline RgbaColor to_argb_uint() const; /// Converts this color to an RgbaColor struct for easy destructuring. [[nodiscard]] inline RgbaColor to_argb_float() const; /// Construct a color from the HSV color space components. /// The hue is expected to be in the range between 0 and 360, and the other parameters between 0 /// and 1. [[nodiscard]] static Color from_hsva(float h, float s, float v, float a) { Color ret; ret.inner = cbindgen_private::types::slint_color_from_hsva(h, s, v, a); return ret; } /// Convert this color to the HSV color space. /// @returns a new HsvaColor. [[nodiscard]] HsvaColor to_hsva() const { HsvaColor hsv {}; cbindgen_private::types::slint_color_to_hsva(&inner, &hsv.hue, &hsv.saturation, &hsv.value, &hsv.alpha); return hsv; } /// Construct a color from the Oklch color space components. /// Oklch is a perceptually uniform color space. /// The lightness is expected to be in the range between 0 and 1, /// chroma typically between 0 and 0.4, hue between 0 and 360, /// and alpha between 0 and 1. [[nodiscard]] static Color from_oklch(float l, float c, float h, float a) { Color ret; ret.inner = cbindgen_private::types::slint_color_from_oklch(l, c, h, a); return ret; } /// Convert this color to the Oklch color space. /// @returns a new OklchColor. [[nodiscard]] OklchColor to_oklch() const { OklchColor oklch {}; cbindgen_private::types::slint_color_to_oklch(&inner, &oklch.lightness, &oklch.chroma, &oklch.hue, &oklch.alpha); return oklch; } /// Returns the red channel of the color as u8 in the range 0..255. [[nodiscard]] uint8_t red() const { return inner.red; } /// Returns the green channel of the color as u8 in the range 0..255. [[nodiscard]] uint8_t green() const { return inner.green; } /// Returns the blue channel of the color as u8 in the range 0..255. [[nodiscard]] uint8_t blue() const { return inner.blue; } /// Returns the alpha channel of the color as u8 in the range 0..255. [[nodiscard]] uint8_t alpha() const { return inner.alpha; } /// Returns a new version of this color that has the brightness increased /// by the specified factor. This is done by converting the color to the HSV /// color space and multiplying the brightness (value) with (1 + factor). /// The result is converted back to RGB and the alpha channel is unchanged. /// So for example `brighter(0.2)` will increase the brightness by 20%, and /// calling `brighter(-0.5)` will return a color that's 50% darker. [[nodiscard]] inline Color brighter(float factor) const; /// Returns a new version of this color that has the brightness decreased /// by the specified factor. This is done by converting the color to the HSV /// color space and dividing the brightness (value) by (1 + factor). The /// result is converted back to RGB and the alpha channel is unchanged. /// So for example `darker(0.3)` will decrease the brightness by 30%. [[nodiscard]] inline Color darker(float factor) const; /// Returns a new version of this color with the opacity decreased by \a factor. /// /// The transparency is obtained by multiplying the alpha channel by `(1 - factor)`. [[nodiscard]] inline Color transparentize(float factor) const; /// Returns a new color that is a mix of \a this color and \a other. The specified \a factor is /// clamped to be between `0.0` and `1.0` and then applied to \a this color, while `1.0 - /// factor` is applied to \a other. [[nodiscard]] inline Color mix(const Color &other, float factor) const; /// Returns a new version of this color with the opacity set to \a alpha. [[nodiscard]] inline Color with_alpha(float alpha) const; /// Returns true if \a lhs has the same values for the individual color channels as \a rhs; /// false otherwise. friend bool operator==(const Color &lhs, const Color &rhs) = default; /// Writes the \a color to the specified \a stream and returns a reference to the /// stream. friend std::ostream &operator<<(std::ostream &stream, const Color &color) { // Cast to uint32_t to avoid the components being interpreted as char. return stream << "argb(" << uint32_t(color.inner.alpha) << ", " << uint32_t(color.inner.red) << ", " << uint32_t(color.inner.green) << ", " << uint32_t(color.inner.blue) << ")"; } #if !defined(DOXYGEN) // FIXME: we need this to create GradientStop operator const cbindgen_private::types::Color &() const { return inner; } #endif private: cbindgen_private::types::Color inner; friend class private_api::LinearGradientBrush; friend class private_api::ConicGradientBrush; friend class Brush; }; inline Color Color::brighter(float factor) const { Color result; cbindgen_private::types::slint_color_brighter(&inner, factor, &result.inner); return result; } inline Color Color::darker(float factor) const { Color result; cbindgen_private::types::slint_color_darker(&inner, factor, &result.inner); return result; } inline Color Color::transparentize(float factor) const { Color result; cbindgen_private::types::slint_color_transparentize(&inner, factor, &result.inner); return result; } inline Color Color::mix(const Color &other, float factor) const { Color result; cbindgen_private::types::slint_color_mix(&inner, &other.inner, factor, &result.inner); return result; } inline Color Color::with_alpha(float alpha) const { Color result; cbindgen_private::types::slint_color_with_alpha(&inner, alpha, &result.inner); return result; } /// Constructs a new RgbaColor from the color \a color. template<> inline RgbaColor::RgbaColor(const Color &color) { red = color.red(); green = color.green(); blue = color.blue(); alpha = color.alpha(); } /// Constructs a new RgbaColor from the color \a color. template<> inline RgbaColor::RgbaColor(const Color &color) { red = float(color.red()) / 255.f; green = float(color.green()) / 255.f; blue = float(color.blue()) / 255.f; alpha = float(color.alpha()) / 255.f; } RgbaColor Color::to_argb_uint() const { return RgbaColor(*this); } RgbaColor Color::to_argb_float() const { return RgbaColor(*this); } } // namespace slint ================================================ FILE: api/cpp/include/slint_config.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include #if UINTPTR_MAX == 0xFFFFFFFF # define SLINT_TARGET_32 #elif UINTPTR_MAX == 0xFFFFFFFFFFFFFFFFu # define SLINT_TARGET_64 #endif #if !defined(DOXYGEN) # if defined(_MSC_VER) # define SLINT_DLL_IMPORT __declspec(dllimport) # elif defined(__GNUC__) # if defined(_WIN32) || defined(_WIN64) # define SLINT_DLL_IMPORT __declspec(dllimport) # else # define SLINT_DLL_IMPORT __attribute__((visibility("default"))) # endif # else # define SLINT_DLL_IMPORT # endif #endif // !defined(DOXYGEN) ================================================ FILE: api/cpp/include/slint_image.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include #include #include "slint_generated_public.h" #include "slint_size.h" #include "slint_image_internal.h" #include "slint_string.h" #include "slint_sharedvector.h" namespace slint { /// SharedPixelBuffer is a container for storing image data as pixels. It is /// internally reference counted and cheap to copy. /// /// You can construct a new empty shared pixel buffer with its default constructor, /// or you can copy it from an existing contiguous buffer that you might already have, using the /// range constructor. /// /// See the documentation for Image for examples how to use this type to integrate /// Slint with external rendering functions. template struct SharedPixelBuffer { /// Construct an empty SharedPixelBuffer. SharedPixelBuffer() = default; /// Construct a SharedPixelBuffer with the given \a width and \a height. The pixels are default /// initialized. SharedPixelBuffer(uint32_t width, uint32_t height) : m_width(width), m_height(height), m_data(width * height) { } /// Construct a SharedPixelBuffer by copying the data from the \a data array. /// The array must be of size \a width * \a height . SharedPixelBuffer(uint32_t width, uint32_t height, const Pixel *data) : m_width(width), m_height(height), m_data(data, data + (width * height)) { } /// Returns the width of the buffer in pixels. uint32_t width() const { return m_width; } /// Returns the height of the buffer in pixels. uint32_t height() const { return m_height; } /// Returns a const pointer to the first pixel of this buffer. const Pixel *begin() const { return m_data.begin(); } /// Returns a const pointer past this buffer. const Pixel *end() const { return m_data.end(); } /// Returns a pointer to the first pixel of this buffer. Pixel *begin() { return m_data.begin(); } /// Returns a pointer past this buffer. Pixel *end() { return m_data.end(); } /// Returns a const pointer to the first pixel of this buffer. const Pixel *cbegin() const { return m_data.begin(); } /// Returns a const pointer past this buffer. const Pixel *cend() const { return m_data.end(); } /// Compare two SharedPixelBuffers. They are considered equal if all their pixels are equal. bool operator==(const SharedPixelBuffer &other) const = default; private: friend struct Image; friend class Window; uint32_t m_width; uint32_t m_height; SharedVector m_data; }; /// An image type that can be displayed by the Image element /// /// You can construct Image objects from a path to an image file on disk, using /// Image::load_from_path(). /// /// Another typical use-case is to render the image content with C++ code. /// For this it’s most efficient to create a new SharedPixelBuffer with the known dimensions and /// pass the pixel pointer returned by begin() to your rendering function. Afterwards you can create /// an Image using the constructor taking a SharedPixelBuffer. /// /// The following example creates a 320x200 RGB pixel buffer and calls a function to draw a shape /// into it: /// ```cpp /// slint::SharedPixelBuffer pixel_buffer(320, 200); /// low_level_render(pixel_buffer.width(), pixel_buffer.height(), /// static_cast(pixel_buffer.begin())); /// slint::Image image(pixel_buffer); /// ``` /// /// Another use-case is to import existing image data into Slint, by /// creating a new Image through copying of the buffer: /// /// ```cpp /// slint::Image image(slint::SharedPixelBuffer(the_width, the_height, /// static_cast(the_data)); /// ``` /// /// This only works if the static_cast is valid and the underlying data has the same /// memory layout as slint::Rgb8Pixel or slint::Rgba8Pixel. Otherwise, you will have to do a /// pixel conversion as you copy the pixels: /// /// ```cpp /// slint::SharedPixelBuffer pixel_buffer(the_width, the_height); /// slint::Rgb8Pixel *raw_data = pixel_buffer.begin(); /// for (int i = 0; i < the_width * the_height; i++) { /// raw_data[i] = { bgr_data[i * 3 + 2], bgr_data[i * 3 + 1], bgr_data[i * 3] }; /// } /// ``` struct Image { public: /// This enum describes the origin to use when rendering a borrowed OpenGL texture. enum class BorrowedOpenGLTextureOrigin { /// The top-left of the texture is the top-left of the texture drawn on the screen. TopLeft, /// The bottom-left of the texture is the top-left of the texture draw on the screen, /// flipping it vertically. BottomLeft, }; Image() : data(Data::ImageInner_None()) { } #if !defined(SLINT_FEATURE_FREESTANDING) || defined(DOXYGEN) /// Load an image from an image file [[nodiscard]] static Image load_from_path(const SharedString &file_path) { Image img; cbindgen_private::types::slint_image_load_from_path(&file_path, &img.data); return img; } #endif /// Constructs a new Image from an existing OpenGL texture. The texture remains borrowed by /// Slint for the duration of being used for rendering, such as when assigned as source property /// to an `Image` element. It's the application's responsibility to delete the texture when it /// is not used anymore. /// /// The texture must be bindable against the `GL_TEXTURE_2D` target, have `GL_RGBA` as format /// for the pixel data. /// /// When Slint renders the texture, it assumes that the origin of the texture is at the /// top-left. This is different from the default OpenGL coordinate system. If you want to /// flip the origin, use BorrowedOpenGLTextureOrigin::BottomLeft. /// /// Safety: /// /// This function is unsafe because invalid texture ids may lead to undefined behavior in OpenGL /// drivers. A valid texture id is one that was created by the same OpenGL context that is /// current during any of the invocations of the callback set on /// [`Window::set_rendering_notifier()`]. OpenGL contexts between instances of [`slint::Window`] /// are not sharing resources. Consequently /// [`slint::Image`] objects created from borrowed OpenGL textures cannot be shared between /// different windows. [[nodiscard]] static Image create_from_borrowed_gl_2d_rgba_texture( uint32_t texture_id, Size size, BorrowedOpenGLTextureOrigin origin = BorrowedOpenGLTextureOrigin::TopLeft) { cbindgen_private::types::BorrowedOpenGLTextureOrigin origin_private = origin == BorrowedOpenGLTextureOrigin::TopLeft ? cbindgen_private::types::BorrowedOpenGLTextureOrigin::TopLeft : cbindgen_private::types::BorrowedOpenGLTextureOrigin::BottomLeft; return Image(Data::ImageInner_BorrowedOpenGLTexture( cbindgen_private::types::BorrowedOpenGLTexture { texture_id, size, origin_private, }) ); } /// Construct an image from a SharedPixelBuffer of RGB pixels. Image(SharedPixelBuffer buffer) : data(Data::ImageInner_EmbeddedImage( cbindgen_private::types::ImageCacheKey::Invalid(), cbindgen_private::types::SharedImageBuffer::RGB8( cbindgen_private::types::SharedPixelBuffer { .width = buffer.width(), .height = buffer.height(), .data = buffer.m_data }))) { } /// Construct an image from a SharedPixelBuffer of RGBA pixels. Image(SharedPixelBuffer buffer) : data(Data::ImageInner_EmbeddedImage( cbindgen_private::types::ImageCacheKey::Invalid(), cbindgen_private::types::SharedImageBuffer::RGBA8( cbindgen_private::types::SharedPixelBuffer { .width = buffer.width(), .height = buffer.height(), .data = buffer.m_data }))) { } /// Returns the size of the Image in pixels. Size size() const { return cbindgen_private::types::slint_image_size(&data); } /// Returns the path of the image on disk, if it was constructed via Image::load_from_path(). std::optional path() const { if (auto *str = cbindgen_private::types::slint_image_path(&data)) { return *str; } else { return {}; } } /// Sets the nine-slice edges of the image. /// /// [Nine-slice scaling](https://en.wikipedia.org/wiki/9-slice_scaling) is a method for scaling /// images in such a way that the corners are not distorted. /// The arguments define the pixel sizes of the edges that cut the image into 9 slices. void set_nine_slice_edges(unsigned short top, unsigned short right, unsigned short bottom, unsigned short left) { cbindgen_private::types::slint_image_set_nine_slice_edges(&data, top, right, bottom, left); } /// Returns the pixel buffer for the Image if available in RGB format without alpha. /// Returns nullopt if the pixels cannot be obtained, for example when the image was created /// from borrowed OpenGL textures. std::optional> to_rgb8() const { SharedPixelBuffer result; if (cbindgen_private ::types::slint_image_to_rgb8(&data, &result.m_data, &result.m_width, &result.m_height)) { return result; } else { return {}; } } /// Returns the pixel buffer for the Image if available in RGBA format. /// Returns nullopt if the pixels cannot be obtained, for example when the image was created /// from borrowed OpenGL textures. std::optional> to_rgba8() const { SharedPixelBuffer result; if (cbindgen_private ::types::slint_image_to_rgba8(&data, &result.m_data, &result.m_width, &result.m_height)) { return result; } else { return {}; } } /// Returns the pixel buffer for the Image if available in RGBA format, with the alpha channel /// pre-multiplied to the red, green, and blue channels. Returns nullopt if the pixels cannot be /// obtained, for example when the image was created from borrowed OpenGL textures. std::optional> to_rgba8_premultiplied() const { SharedPixelBuffer result; if (cbindgen_private ::types::slint_image_to_rgba8_premultiplied( &data, &result.m_data, &result.m_width, &result.m_height)) { return result; } else { return {}; } } /// Returns true if \a a refers to the same image as \a b; false otherwise. friend bool operator==(const Image &a, const Image &b) { return cbindgen_private::types::slint_image_compare_equal(&a.data, &b.data); } /// Returns false if \a a refers to the same image as \a b; true otherwise. friend bool operator!=(const Image &a, const Image &b) { return !(a == b); } /// \private explicit Image(cbindgen_private::types::Image inner) : data(inner) { } private: using Tag = cbindgen_private::types::ImageInner::Tag; using Data = cbindgen_private::types::Image; Data data; }; namespace private_api { inline Image load_image_from_embedded_data(std::span data, std::string_view extension) { cbindgen_private::types::Image img(cbindgen_private::types::Image::ImageInner_None()); cbindgen_private::types::slint_image_load_from_embedded_data( make_slice(data.data(), data.size()), string_to_slice(extension), &img); return Image(img); } inline Image image_from_embedded_textures(const cbindgen_private::types::StaticTextures *textures) { cbindgen_private::types::Image img(cbindgen_private::types::Image::ImageInner_None()); cbindgen_private::types::slint_image_from_embedded_textures(textures, &img); return Image(img); } } } ================================================ FILE: api/cpp/include/slint_interpreter.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #warning "the header is renamed " #include "slint-interpreter.h" ================================================ FILE: api/cpp/include/slint_item_tree.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include "slint_internal.h" #include "slint_window.h" namespace slint { namespace private_api { // Bring opaque structure in scope using namespace cbindgen_private; using ItemTreeRef = vtable::VRef; using IndexRange = cbindgen_private::IndexRange; using ItemRef = vtable::VRef; using ItemVisitorRefMut = vtable::VRefMut; using ItemTreeNode = cbindgen_private::ItemTreeNode; using ItemArrayEntry = vtable::VOffset; using ItemArray = slint::cbindgen_private::Slice; constexpr inline ItemTreeNode make_item_node(uint32_t child_count, uint32_t child_index, uint32_t parent_index, uint32_t item_array_index, bool is_accessible) { return ItemTreeNode { ItemTreeNode::Item_Body { ItemTreeNode::Tag::Item, is_accessible, child_count, child_index, parent_index, item_array_index } }; } constexpr inline ItemTreeNode make_dyn_node(std::uint32_t offset, std::uint32_t parent_index) { return ItemTreeNode { ItemTreeNode::DynamicTree_Body { ItemTreeNode::Tag::DynamicTree, offset, parent_index } }; } inline ItemRef get_item_ref(ItemTreeRef item_tree, const cbindgen_private::Slice item_tree_array, const private_api::ItemArray item_array, int index) { const auto item_array_index = item_tree_array.ptr[index].item.item_array_index; const auto item = item_array[item_array_index]; return ItemRef { item.vtable, reinterpret_cast(item_tree.instance) + item.offset }; } } // namespace private_api template class ComponentWeakHandle; /// The component handle is like a shared pointer to a component in the generated code. /// In order to get a component, use `T::create()` where T is the name of the component /// in the .slint file. This give you a `ComponentHandle` template class ComponentHandle { vtable::VRc inner; friend class ComponentWeakHandle; public: /// internal constructor ComponentHandle(const vtable::VRc &inner) : inner(inner) { } /// Arrow operator that implements pointer semantics. const T *operator->() const { private_api::assert_main_thread(); return inner.operator->(); } /// Dereference operator that implements pointer semantics. const T &operator*() const { private_api::assert_main_thread(); return inner.operator*(); } /// Arrow operator that implements pointer semantics. T *operator->() { private_api::assert_main_thread(); return inner.operator->(); } /// Dereference operator that implements pointer semantics. T &operator*() { private_api::assert_main_thread(); return inner.operator*(); } /// internal function that returns the internal handle vtable::VRc into_dyn() const { return inner.into_dyn(); } }; /// A weak reference to the component. Can be constructed from a `ComponentHandle` template class ComponentWeakHandle { vtable::VWeak inner; public: /// Constructs a null ComponentWeakHandle. lock() will always return empty. ComponentWeakHandle() = default; /// Copy-constructs a new ComponentWeakHandle from \a other. ComponentWeakHandle(const ComponentHandle &other) : inner(other.inner) { } /// Returns a new strong ComponentHandle if the component the weak handle points to is /// still referenced by any other ComponentHandle. An empty std::optional is returned /// otherwise. std::optional> lock() const { private_api::assert_main_thread(); if (auto l = inner.lock()) { return { ComponentHandle(*l) }; } else { return {}; } } }; } ================================================ FILE: api/cpp/include/slint_live_preview.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include "slint.h" #ifndef SLINT_FEATURE_LIVE_PREVIEW # error SLINT_FEATURE_LIVE_PREVIEW must be activated #else # include "slint-interpreter.h" /// Internal API to support the live-preview generated code namespace slint::private_api::live_preview { template requires(std::convertible_to) slint::interpreter::Value into_slint_value(const T &val) { return val; } template requires requires(T val) { val.into_slint_value(); } slint::interpreter::Value into_slint_value(const T &val) { return val.into_slint_value(); } inline slint::interpreter::Value into_slint_value(const slint::interpreter::Value &val) { return val; } template requires std::is_same_v inline void from_slint_value(const slint::interpreter::Value &, const T *) { } inline bool from_slint_value(const slint::interpreter::Value &val, const bool *) { return val.to_bool().value(); } inline slint::SharedString from_slint_value(const slint::interpreter::Value &val, const slint::SharedString *) { return val.to_string().value(); } inline int from_slint_value(const slint::interpreter::Value &val, const int *) { return val.to_number().value(); } inline float from_slint_value(const slint::interpreter::Value &val, const float *) { return val.to_number().value(); } inline slint::Color from_slint_value(const slint::interpreter::Value &val, const slint::Color *) { return val.to_brush().value().color(); } inline interpreter::Value into_slint_value(const slint::Color &val) { return slint::Brush(val); } inline slint::Brush from_slint_value(const slint::interpreter::Value &val, const slint::Brush *) { return val.to_brush().value(); } inline slint::Image from_slint_value(const slint::interpreter::Value &val, const slint::Image *) { return val.to_image().value(); } /// duration inline long int from_slint_value(const slint::interpreter::Value &val, const long int *) { return val.to_number().value(); } inline interpreter::Value into_slint_value(const long int &val) { return double(val); } template std::shared_ptr> from_slint_value(const slint::interpreter::Value &, const std::shared_ptr> *); template slint::interpreter::Value into_slint_value(const std::shared_ptr> &val); inline slint::interpreter::Value into_slint_value(const slint::StandardListViewItem &val) { slint::interpreter::Struct s; s.set_field("text", val.text); return s; } inline slint::StandardListViewItem from_slint_value(const slint::interpreter::Value &val, const slint::StandardListViewItem *) { auto s = val.to_struct().value(); return slint::StandardListViewItem { .text = s.get_field("text").value().to_string().value() }; } inline slint::interpreter::Value into_slint_value(const slint::LogicalPosition &val) { slint::interpreter::Struct s; s.set_field("x", val.x); s.set_field("y", val.y); return s; } inline slint::LogicalPosition from_slint_value(const slint::interpreter::Value &val, const slint::LogicalPosition *) { auto s = val.to_struct().value(); return slint::LogicalPosition({ float(s.get_field("x").value().to_number().value()), float(s.get_field("y").value().to_number().value()) }); } template T from_slint_value(const slint::interpreter::Value &v) { return from_slint_value(v, static_cast(nullptr)); } class LiveReloadingComponent { const cbindgen_private::LiveReloadingComponentInner *inner; public: /// Libraries is an array of string that have in the form `lib=...` LiveReloadingComponent(std::string_view file_name, std::string_view component_name, const slint::SharedVector &include_paths, const slint::SharedVector &libraries, std::string_view style, std::string_view translation_domain, bool no_default_translation_context) { assert_main_thread(); inner = cbindgen_private::slint_live_preview_new( string_to_slice(file_name), string_to_slice(component_name), &include_paths, &libraries, string_to_slice(style), string_to_slice(translation_domain), no_default_translation_context); } LiveReloadingComponent(const LiveReloadingComponent &other) : inner(other.inner) { assert_main_thread(); cbindgen_private::slint_live_preview_clone(other.inner); } LiveReloadingComponent &operator=(const LiveReloadingComponent &other) { assert_main_thread(); if (this == &other) return *this; cbindgen_private::slint_live_preview_drop(inner); inner = other.inner; cbindgen_private::slint_live_preview_clone(inner); return *this; } ~LiveReloadingComponent() { assert_main_thread(); cbindgen_private::slint_live_preview_drop(inner); } void set_property(std::string_view name, const interpreter::Value &value) const { assert_main_thread(); return cbindgen_private::slint_live_preview_set_property(inner, string_to_slice(name), value.inner); } interpreter::Value get_property(std::string_view name) const { assert_main_thread(); auto val = slint::interpreter::Value( cbindgen_private::slint_live_preview_get_property(inner, string_to_slice(name))); return val; } template interpreter::Value invoke(std::string_view name, Args &...args) const { assert_main_thread(); std::array args_values { into_slint_value(args)... }; cbindgen_private::Slice args_slice { reinterpret_cast(args_values.data()), args_values.size() }; interpreter::Value val(cbindgen_private::slint_live_preview_invoke( inner, string_to_slice(name), args_slice)); return val; } template> F> requires(std::is_convertible_v>, interpreter::Value>) void set_callback(std::string_view name, F &&callback) const { assert_main_thread(); auto actual_cb = [](void *data, cbindgen_private::Slice> arg) { std::span args_view { reinterpret_cast(arg.ptr), arg.len }; interpreter::Value r = (*reinterpret_cast(data))(args_view); auto inner = r.inner; r.inner = cbindgen_private::slint_interpreter_value_new(); return inner; }; return cbindgen_private::slint_live_preview_set_callback( inner, slint::private_api::string_to_slice(name), actual_cb, new F(std::move(callback)), [](void *data) { delete reinterpret_cast(data); }); } slint::Window &window() const { const cbindgen_private::WindowAdapterRcOpaque *win_ptr = nullptr; cbindgen_private::slint_live_preview_window(inner, &win_ptr); return const_cast(*reinterpret_cast(win_ptr)); } // Helper function that abuse the friend on Value static slint::interpreter::Value value_from_enum(std::string_view name, std::string_view value) { return slint::interpreter::Value(cbindgen_private::slint_interpreter_value_new_enum( string_to_slice(name), string_to_slice(value))); } static slint::SharedString get_enum_value(const slint::interpreter::Value &value) { slint::SharedString result; slint::cbindgen_private::slint_interpreter_value_enum_to_string(value.inner, &result); return result; } }; class LiveReloadModelWrapperBase : public private_api::ModelChangeListener { cbindgen_private::ModelNotifyOpaque notify; // This means that the rust code has ownership of "this" until the drop function is called std::shared_ptr self = nullptr; void row_added(size_t index, size_t count) override { cbindgen_private::slint_interpreter_model_notify_row_added(¬ify, index, count); } void row_changed(size_t index) override { cbindgen_private::slint_interpreter_model_notify_row_changed(¬ify, index); } void row_removed(size_t index, size_t count) override { cbindgen_private::slint_interpreter_model_notify_row_removed(¬ify, index, count); } void reset() override { cbindgen_private::slint_interpreter_model_notify_reset(¬ify); } static const ModelAdaptorVTable *vtable() { auto row_count = [](VRef self) -> uintptr_t { return reinterpret_cast(self.instance)->row_count(); }; auto row_data = [](VRef self, uintptr_t row) -> slint::cbindgen_private::Value * { std::optional v = reinterpret_cast(self.instance) ->row_data(int(row)); if (v.has_value()) { slint::cbindgen_private::Value *rval = v->inner; v->inner = cbindgen_private::slint_interpreter_value_new(); return rval; } else { return nullptr; } }; auto set_row_data = [](VRef self, uintptr_t row, slint::cbindgen_private::Value *value) { interpreter::Value v(std::move(value)); reinterpret_cast(self.instance)->set_row_data(row, v); }; auto get_notify = [](VRef self) -> const cbindgen_private::ModelNotifyOpaque * { return &reinterpret_cast(self.instance)->notify; }; auto drop = [](vtable::VRefMut self) { reinterpret_cast(self.instance)->self = nullptr; }; static const ModelAdaptorVTable vt { row_count, row_data, set_row_data, get_notify, drop }; return &vt; } protected: LiveReloadModelWrapperBase() { cbindgen_private::slint_interpreter_model_notify_new(¬ify); } virtual ~LiveReloadModelWrapperBase() { cbindgen_private::slint_interpreter_model_notify_destructor(¬ify); } virtual int row_count() const = 0; virtual std::optional row_data(int i) const = 0; virtual void set_row_data(int i, const slint::interpreter::Value &value) = 0; static interpreter::Value wrap(std::shared_ptr wrapper) { wrapper->self = wrapper; return interpreter::Value(cbindgen_private::slint_interpreter_value_new_model( reinterpret_cast(wrapper.get()), vtable())); } public: // get the model wrapper from a value (or nullptr if the value don't contain a model) static const LiveReloadModelWrapperBase *get(const slint::interpreter::Value &value) { if (auto model = cbindgen_private::slint_interpreter_value_to_model(value.inner, vtable())) { return reinterpret_cast(model); } else { return nullptr; } } }; template class LiveReloadModelWrapper : public LiveReloadModelWrapperBase { public: LiveReloadModelWrapper(std::shared_ptr> model) : model(std::move(model)) { } std::shared_ptr> model = nullptr; int row_count() const override { return model->row_count(); } std::optional row_data(int i) const override { if (auto v = model->row_data(i)) return into_slint_value(*v); else return {}; } void set_row_data(int i, const slint::interpreter::Value &value) override { model->set_row_data(i, from_slint_value(value)); } static slint::interpreter::Value wrap(std::shared_ptr> model) { auto self = std::make_shared>(model); auto peer = std::weak_ptr(self); model->attach_peer(peer); return LiveReloadModelWrapperBase::wrap(self); } }; template concept HasFromSlintValue = requires(const slint::interpreter::Value &val) { { from_slint_value(val, std::declval()) }; }; template slint::interpreter::Value into_slint_value(const std::shared_ptr> &val) { if (!val) { return {}; } if constexpr (HasFromSlintValue) { return LiveReloadModelWrapper::wrap(val); } return {}; } template std::shared_ptr> from_slint_value(const slint::interpreter::Value &value, const std::shared_ptr> *) { if (const LiveReloadModelWrapperBase *base = LiveReloadModelWrapperBase::get(value)) { if (auto wrapper = dynamic_cast *>(base)) { return wrapper->model; } } if constexpr (HasFromSlintValue) { if (auto array = value.to_array(); array && array->size() > 0) { std::vector data; data.reserve(array->size()); for (auto &v : *array) { data.push_back(from_slint_value(v)); } return std::make_shared>(std::move(data)); } } return {}; } } // namespace slint::private_api::live_preview #endif // SLINT_FEATURE_LIVE_PREVIEW ================================================ FILE: api/cpp/include/slint_models.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include "slint_item_tree.h" #include #include #include #include #include namespace slint { namespace private_api { struct ModelChangeListener { virtual ~ModelChangeListener() = default; virtual void row_added(size_t index, size_t count) = 0; virtual void row_removed(size_t index, size_t count) = 0; virtual void row_changed(size_t index) = 0; virtual void reset() = 0; }; using ModelPeer = std::weak_ptr; template auto access_array_index(const std::shared_ptr &model, std::ptrdiff_t index) { if (!model || index < 0) { return decltype(*model->row_data_tracked(index)) {}; } else if (const auto v = model->row_data_tracked(index)) { return *v; } else { return decltype(*v) {}; } } template long int model_length(const std::shared_ptr &model) { if (!model) { return 0; } else { model->track_row_count_changes(); return model->row_count(); } } } // namespace private_api /// \rst /// A Model is providing Data for Slint |Models|_ or |ListView|_ elements of the /// :code:`.slint` language /// \endrst /// /// This is typically used in a `std::shared_ptr`. /// Model is an abstract class and you can derive from it to provide your own data model, /// or use one of the provided models such as `slint::VectorModel` /// /// An implementation of the Model can provide data to slint by re-implementing the `row_count` and /// `row_data` functions. It is the responsibility of the Model implementation to call the /// `Model::notify_row_changed()`, `Model::notify_row_added()`, `Model::notify_row_removed()`, or /// `Model::notify_reset()` functions when the underlying data changes. /// /// Note that the Model is not thread-safe. All Model operations need to be done in the main thread. /// If you need to update the model data from another thread, use the /// `slint::invoke_from_event_loop()` function to send the data to the main thread and update the /// model. template class Model { public: virtual ~Model() = default; Model() = default; Model(const Model &) = delete; Model &operator=(const Model &) = delete; /// The amount of row in the model virtual size_t row_count() const = 0; /// Returns the data for a particular row. This function should be called with `row < /// row_count()`. virtual std::optional row_data(size_t i) const = 0; /// Sets the data for a particular row. /// /// This function should only be called with `row < row_count()`. /// /// If the model cannot support data changes, then it is ok to do nothing. /// The default implementation will print a warning to stderr. /// /// If the model can update the data, it should also call `row_changed` virtual void set_row_data(size_t, const ModelData &) { #ifndef SLINT_FEATURE_FREESTANDING std::cerr << "Model::set_row_data was called on a read-only model" << std::endl; #endif }; /// \private /// Internal function called by the view to register itself void attach_peer(private_api::ModelPeer p) { peers.push_back(std::move(p)); } /// \private /// Internal function called from within bindings to register with the currently /// evaluating dependency and get notified when this model's row count changes. void track_row_count_changes() const { model_row_count_dirty_property.get(); } /// \private /// Internal function called from within bindings to register with the currently /// evaluating dependency and get notified when this model's row data changes. void track_row_data_changes(size_t row) const { auto it = std::lower_bound(tracked_rows.begin(), tracked_rows.end(), row); if (it == tracked_rows.end() || row < *it) { tracked_rows.insert(it, row); } model_row_data_dirty_property.get(); } /// \private /// Convenience function that calls `track_row_data_changes` before returning `row_data` std::optional row_data_tracked(size_t row) const { track_row_data_changes(row); return row_data(row); } protected: /// Notify the views that a specific row was changed /// /// Your model implementation should call this function after the data of a row changes. void notify_row_changed(size_t row) { private_api::assert_main_thread(); if (std::binary_search(tracked_rows.begin(), tracked_rows.end(), row)) { model_row_data_dirty_property.mark_dirty(); } for_each_peers([=](auto peer) { peer->row_changed(row); }); } /// Notify the views that rows were added /// /// Your model implementation should call this function after the row were added. void notify_row_added(size_t index, size_t count) { private_api::assert_main_thread(); model_row_count_dirty_property.mark_dirty(); tracked_rows.clear(); model_row_data_dirty_property.mark_dirty(); for_each_peers([=](auto peer) { peer->row_added(index, count); }); } /// Notify the views that rows were removed /// /// Your model implementation should call this function after the row were removed. void notify_row_removed(size_t index, size_t count) { private_api::assert_main_thread(); model_row_count_dirty_property.mark_dirty(); tracked_rows.clear(); model_row_data_dirty_property.mark_dirty(); for_each_peers([=](auto peer) { peer->row_removed(index, count); }); } /// Notify the views that the model has been changed and that everything needs to be reloaded /// /// Your model implementation should call this function after the model has been changed. void notify_reset() { private_api::assert_main_thread(); model_row_count_dirty_property.mark_dirty(); tracked_rows.clear(); model_row_data_dirty_property.mark_dirty(); for_each_peers([=](auto peer) { peer->reset(); }); } /// \deprecated [[deprecated("Renamed to notify_row_changed")]] void row_changed(size_t row) { notify_row_changed(row); } /// \deprecated [[deprecated("Renamed to notify_row_added")]] void row_added(size_t index, size_t count) { notify_row_added(index, count); } /// \deprecated [[deprecated("Renamed to notify_row_removed")]] void row_removed(size_t index, size_t count) { notify_row_removed(index, count); } /// \deprecated [[deprecated("Renamed to notify_reset")]] void reset() { notify_reset(); } private: template void for_each_peers(const F &f) { peers.erase(std::remove_if(peers.begin(), peers.end(), [&](const auto &p) { if (auto pp = p.lock()) { f(pp); return false; } return true; }), peers.end()); } std::vector peers; private_api::Property model_row_count_dirty_property; private_api::Property model_row_data_dirty_property; mutable std::vector tracked_rows; }; namespace private_api { /// A Model backed by a std::array of constant size /// \private template class ArrayModel : public Model { std::array data; public: /// Constructs a new ArrayModel by forwarding \a to the std::array constructor. template ArrayModel(A &&...a) : data { std::forward(a)... } { } size_t row_count() const override { return Count; } std::optional row_data(size_t i) const override { if (i >= row_count()) return {}; return data[i]; } void set_row_data(size_t i, const ModelData &value) override { if (i < row_count()) { data[i] = value; this->notify_row_changed(i); } } }; // Specialize for the empty array. We can't have a Model, but `int` will work for our purpose template<> class ArrayModel<0, void> : public Model { public: size_t row_count() const override { return 0; } std::optional row_data(size_t) const override { return {}; } }; /// Model to be used when we just want to repeat without data. struct UIntModel : Model { /// Constructs a new IntModel with \a d rows. UIntModel(uint32_t d) : data(d) { } /// \private uint32_t data; /// \copydoc Model::row_count size_t row_count() const override { return data; } std::optional row_data(size_t value) const override { if (value >= row_count()) return {}; return static_cast(value); } }; } // namespace private_api /// A Model backed by a SharedVector template class VectorModel : public Model { std::vector data; public: /// Constructs a new empty VectorModel. VectorModel() = default; /// Constructs a new VectorModel from \a array. VectorModel(std::vector array) : data(std::move(array)) { } size_t row_count() const override { return data.size(); } std::optional row_data(size_t i) const override { if (i >= row_count()) return {}; return std::optional { data[i] }; } void set_row_data(size_t i, const ModelData &value) override { if (i < row_count()) { data[i] = value; this->notify_row_changed(i); } } /// Append a new row with the given value void push_back(const ModelData &value) { data.push_back(value); this->notify_row_added(data.size() - 1, 1); } /// Remove the row at the given index from the model void erase(size_t index) { data.erase(data.begin() + index); this->notify_row_removed(index, 1); } /// Inserts the given value as a new row at the specified index void insert(size_t index, const ModelData &value) { data.insert(data.begin() + index, value); this->notify_row_added(index, 1); } /// Erases all rows from the VectorModel. void clear() { if (!data.empty()) { data.clear(); this->notify_reset(); } } /// Replaces the underlying VectorModel's vector with \a array. void set_vector(std::vector array) { data = std::move(array); this->notify_reset(); } }; template class FilterModel; namespace private_api { template struct FilterModelInner : private_api::ModelChangeListener { FilterModelInner(std::shared_ptr> source_model, std::function filter_fn, slint::FilterModel &target_model) : source_model(source_model), filter_fn(filter_fn), target_model(target_model) { } void row_added(size_t index, size_t count) override { if (filtered_rows_dirty) { reset(); return; } if (count == 0) { return; } std::vector added_accepted_rows; for (auto i = index; i < index + count; ++i) { if (auto data = source_model->row_data(i)) { if (filter_fn(*data)) { added_accepted_rows.push_back(i); } } } if (added_accepted_rows.empty()) { return; } auto insertion_point = std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index); insertion_point = accepted_rows.insert(insertion_point, added_accepted_rows.begin(), added_accepted_rows.end()); for (auto it = insertion_point + added_accepted_rows.size(); it != accepted_rows.end(); ++it) (*it) += count; target_model.notify_row_added(insertion_point - accepted_rows.begin(), added_accepted_rows.size()); } void row_changed(size_t index) override { if (filtered_rows_dirty) { reset(); return; } auto existing_row = std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index); auto existing_row_index = std::distance(accepted_rows.begin(), existing_row); bool is_contained = existing_row != accepted_rows.end() && *existing_row == index; auto accepted_updated_row = filter_fn(*source_model->row_data(index)); if (is_contained && accepted_updated_row) { target_model.notify_row_changed(existing_row_index); } else if (!is_contained && accepted_updated_row) { accepted_rows.insert(existing_row, index); target_model.notify_row_added(existing_row_index, 1); } else if (is_contained && !accepted_updated_row) { accepted_rows.erase(existing_row); target_model.notify_row_removed(existing_row_index, 1); } } void row_removed(size_t index, size_t count) override { if (filtered_rows_dirty) { reset(); return; } auto mapped_row_start = std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index); auto mapped_row_end = std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index + count); auto mapped_removed_len = std::distance(mapped_row_start, mapped_row_end); auto mapped_removed_index = (mapped_row_start != accepted_rows.end() && *mapped_row_start == index) ? std::optional(mapped_row_start - accepted_rows.begin()) : std::nullopt; auto it = accepted_rows.erase(mapped_row_start, mapped_row_end); for (; it != accepted_rows.end(); ++it) { *it -= count; } if (mapped_removed_index) { target_model.notify_row_removed(*mapped_removed_index, mapped_removed_len); } } void reset() override { filtered_rows_dirty = true; update_mapping(); target_model.notify_reset(); } void update_mapping() { if (!filtered_rows_dirty) { return; } accepted_rows.clear(); for (size_t i = 0, count = source_model->row_count(); i < count; ++i) { if (auto data = source_model->row_data(i)) { if (filter_fn(*data)) { accepted_rows.push_back(i); } } } filtered_rows_dirty = false; } bool filtered_rows_dirty = true; std::shared_ptr> source_model; std::function filter_fn; std::vector accepted_rows; slint::FilterModel &target_model; }; } /// The FilterModel acts as an adapter model for a given source model by applying a filter /// function. The filter function is called for each row on the source model and if the /// filter accepts the row (i.e. returns true), the row is also visible in the FilterModel. template class FilterModel : public Model { friend struct private_api::FilterModelInner; public: /// Constructs a new FilterModel that provides a limited view on the \a source_model by applying /// \a filter_fn on each row. If the provided function returns true, the row is exposed by the /// FilterModel. FilterModel(std::shared_ptr> source_model, std::function filter_fn) : inner(std::make_shared>( std::move(source_model), std::move(filter_fn), *this)) { inner->source_model->attach_peer(inner); } size_t row_count() const override { inner->update_mapping(); return inner->accepted_rows.size(); } std::optional row_data(size_t i) const override { inner->update_mapping(); if (i >= inner->accepted_rows.size()) return {}; return inner->source_model->row_data(inner->accepted_rows[i]); } void set_row_data(size_t i, const ModelData &value) override { inner->update_mapping(); inner->source_model->set_row_data(inner->accepted_rows[i], value); } /// Re-applies the model's filter function on each row of the source model. Use this if state /// external to the filter function has changed. void reset() { inner->reset(); } /// Given the \a filtered_row index, this function returns the corresponding row index in the /// source model. int unfiltered_row(int filtered_row) const { inner->update_mapping(); return inner->accepted_rows[filtered_row]; } /// Returns the source model of this filter model. std::shared_ptr> source_model() const { return inner->source_model; } private: std::shared_ptr> inner; }; template class MapModel; namespace private_api { template struct MapModelInner : private_api::ModelChangeListener { MapModelInner(slint::MapModel &target_model) : target_model(target_model) { } void row_added(size_t index, size_t count) override { target_model.notify_row_added(index, count); } void row_changed(size_t index) override { target_model.notify_row_changed(index); } void row_removed(size_t index, size_t count) override { target_model.notify_row_removed(index, count); } void reset() override { target_model.notify_reset(); } slint::MapModel &target_model; }; } /// The MapModel acts as an adapter model for a given source model by applying a mapping /// function. The mapping function is called for each row on the source model and allows /// transforming the values on the fly. The MapModel has two template parameters: The /// SourceModelData specifies the data type of the underlying source model, and the /// MappedModelData the data type of this MapModel. This permits not only changing the /// values of the underlying source model, but also changing the data type itself. For /// example a MapModel can be used to adapt a model that provides numbers to be a model /// that exposes all numbers converted to strings, by calling `std::to_string` on each /// value given in the mapping lambda expression. /// /// \code /// auto source_model = std::make_shared>(...); /// auto mapped_model = std::make_shared>( /// source_model, [](const Person &person) { // return fmt::format("{} {}", person.first, person.last); // }); /// \endcode template class MapModel : public Model { friend struct private_api::MapModelInner; public: /// Constructs a new MapModel that provides an altered view on the \a source_model by applying /// \a map_fn on the data in each row. MapModel(std::shared_ptr> source_model, std::function map_fn) : inner(std::make_shared>( *this)), model(source_model), map_fn(map_fn) { model->attach_peer(inner); } size_t row_count() const override { return model->row_count(); } std::optional row_data(size_t i) const override { if (auto source_data = model->row_data(i)) return map_fn(*source_data); else return {}; } /// Returns the source model of this filter model. std::shared_ptr> source_model() const { return model; } /// Re-applies the model's mapping function on each row of the source model. Use this if state /// external to the mapping function has changed. void reset() { inner->reset(); } private: std::shared_ptr> inner; std::shared_ptr> model; std::function map_fn; }; template class SortModel; namespace private_api { template struct SortModelInner : private_api::ModelChangeListener { SortModelInner(std::shared_ptr> source_model, std::function comp, slint::SortModel &target_model) : source_model(source_model), comp(comp), target_model(target_model) { } void row_added(size_t first_inserted_row, size_t count) override { if (sorted_rows_dirty) { reset(); return; } // Adjust the existing sorted row indices to match the updated source model for (auto &row : sorted_rows) { if (row >= first_inserted_row) row += count; } for (size_t row = first_inserted_row; row < first_inserted_row + count; ++row) { auto inserted_value = source_model->row_data(row); if (!inserted_value) continue; auto insertion_point = std::lower_bound(sorted_rows.begin(), sorted_rows.end(), *inserted_value, [this](size_t sorted_row, const ModelData &inserted_value) { auto sorted_elem = source_model->row_data(sorted_row); return sorted_elem && comp(*sorted_elem, inserted_value); }); insertion_point = sorted_rows.insert(insertion_point, row); target_model.notify_row_added(std::distance(sorted_rows.begin(), insertion_point), 1); } } void row_changed(size_t changed_row) override { if (sorted_rows_dirty) { reset(); return; } auto removed_row_it = sorted_rows.erase(std::find(sorted_rows.begin(), sorted_rows.end(), changed_row)); auto removed_row = std::distance(sorted_rows.begin(), removed_row_it); auto changed_value = source_model->row_data(changed_row); if (!changed_value) return; auto insertion_point = std::lower_bound(sorted_rows.begin(), sorted_rows.end(), *changed_value, [this](size_t sorted_row, const ModelData &changed_value) { auto sorted_elem = source_model->row_data(sorted_row); return sorted_elem && comp(*sorted_elem, changed_value); }); insertion_point = sorted_rows.insert(insertion_point, changed_row); auto inserted_row = std::distance(sorted_rows.begin(), insertion_point); if (inserted_row == removed_row) { target_model.notify_row_changed(removed_row); } else { target_model.notify_row_removed(removed_row, 1); target_model.notify_row_added(inserted_row, 1); } } void row_removed(size_t first_removed_row, size_t count) override { if (sorted_rows_dirty) { reset(); return; } std::vector removed_rows; removed_rows.reserve(count); for (auto it = sorted_rows.begin(); it != sorted_rows.end();) { if (*it >= first_removed_row) { if (*it < first_removed_row + count) { removed_rows.push_back(std::distance(sorted_rows.begin(), it)); it = sorted_rows.erase(it); continue; } else { *it -= count; } } ++it; } for (auto removed_row : removed_rows) { target_model.notify_row_removed(removed_row, 1); } } void reset() override { sorted_rows_dirty = true; target_model.notify_reset(); } void ensure_sorted() { if (!sorted_rows_dirty) { return; } sorted_rows.resize(source_model->row_count()); for (size_t i = 0; i < sorted_rows.size(); ++i) sorted_rows[i] = i; std::sort(sorted_rows.begin(), sorted_rows.end(), [this](auto lhs_index, auto rhs_index) { auto lhs_elem = source_model->row_data(lhs_index); auto rhs_elem = source_model->row_data(rhs_index); return rhs_elem && lhs_elem && comp(*lhs_elem, *rhs_elem); }); sorted_rows_dirty = false; } std::shared_ptr> source_model; std::function comp; slint::SortModel &target_model; std::vector sorted_rows; bool sorted_rows_dirty = true; }; } /// The SortModel acts as an adapter model for a given source model by sorting all rows /// with by order provided by the given sorting function. The sorting function is called for /// pairs of elements of the source model. /// /// \code /// auto source_model = std::make_shared>( // std::vector { "lorem", "ipsum", "dolor" }); /// auto sorted_model = std::make_shared>( /// source_model, [](auto lhs, auto rhs) { return lhs < rhs; })); /// \endcode template class SortModel : public Model { friend struct private_api::SortModelInner; public: /// Constructs a new SortModel that provides a sorted view on the \a source_model by applying /// the order given by the specified \a comp. SortModel(std::shared_ptr> source_model, std::function comp) : inner(std::make_shared>(std::move(source_model), std::move(comp), *this)) { inner->source_model->attach_peer(inner); } size_t row_count() const override { return inner->source_model->row_count(); } std::optional row_data(size_t i) const override { inner->ensure_sorted(); return inner->source_model->row_data(inner->sorted_rows[i]); } void set_row_data(size_t i, const ModelData &value) override { inner->source_model->set_row_data(inner->sorted_rows[i], value); } /// Re-applies the model's sort function on each row of the source model. Use this if state /// external to the sort function has changed. void reset() { inner->reset(); } /// Given the \a sorted_row_index, this function returns the corresponding row index in the /// source model. int unsorted_row(int sorted_row_index) const { inner->ensure_sorted(); return inner->sorted_rows[sorted_row_index]; } /// Returns the source model of this filter model. std::shared_ptr> source_model() const { return inner->source_model; } private: std::shared_ptr> inner; }; template class ReverseModel; namespace private_api { template struct ReverseModelInner : private_api::ModelChangeListener { ReverseModelInner(std::shared_ptr> source_model, slint::ReverseModel &target_model) : source_model(source_model), target_model(target_model) { } void row_added(size_t first_inserted_row, size_t count) override { auto row_count = source_model->row_count(); auto old_row_count = row_count - count; auto row = old_row_count - first_inserted_row; target_model.notify_row_added(row, count); } void row_changed(size_t changed_row) override { target_model.notify_row_changed(source_model->row_count() - 1 - changed_row); } void row_removed(size_t first_removed_row, size_t count) override { target_model.notify_row_removed(source_model->row_count() - first_removed_row, count); } void reset() override { target_model.notify_reset(); } std::shared_ptr> source_model; slint::ReverseModel &target_model; }; } /// The ReverseModel acts as an adapter model for a given source model by reserving all rows. /// This means that the first row in the source model is the last row of this model, the second /// row is the second last, and so on. /// /// \code /// auto source_model = std::make_shared>( // std::vector { 1, 2, 3, 4, 5 }); /// auto reversed_model = std::make_shared>(source_model); /// \endcode template class ReverseModel : public Model { friend struct private_api::ReverseModelInner; public: /// Constructs a new ReverseModel that provides a reversed view on the \a source_model. ReverseModel(std::shared_ptr> source_model) : inner(std::make_shared>(std::move(source_model), *this)) { inner->source_model->attach_peer(inner); } size_t row_count() const override { return inner->source_model->row_count(); } std::optional row_data(size_t i) const override { auto count = inner->source_model->row_count(); return inner->source_model->row_data(count - i - 1); } void set_row_data(size_t i, const ModelData &value) override { auto count = inner->source_model->row_count(); inner->source_model->set_row_data(count - i - 1, value); } /// Returns the source model of this reserve model. std::shared_ptr> source_model() const { return inner->source_model; } private: std::shared_ptr> inner; }; namespace private_api { template class Repeater { struct RepeaterInner : ModelChangeListener { enum class State { Clean, Dirty }; struct RepeatedInstanceWithState { State state = State::Dirty; std::optional> ptr; }; std::vector data; private_api::Property is_dirty { true }; std::shared_ptr> model; void row_added(size_t index, size_t count) override { if (index > data.size()) { // Can happen before ensure_updated was called return; } is_dirty.set(true); data.resize(data.size() + count); std::rotate(data.begin() + index, data.end() - count, data.end()); for (std::size_t i = index; i < data.size(); ++i) { // all the indexes are dirty data[i].state = State::Dirty; } } void row_changed(size_t index) override { if (index >= data.size()) { return; } auto &c = data[index]; if (model && c.ptr) { std::optional data = model->row_data(index); (*c.ptr)->update_data(index, data ? *data : ModelData {}); c.state = State::Clean; } else { c.state = State::Dirty; } } void row_removed(size_t index, size_t count) override { if (index + count > data.size()) { // Can happen before ensure_updated was called return; } is_dirty.set(true); data.erase(data.begin() + index, data.begin() + index + count); for (std::size_t i = index; i < data.size(); ++i) { // all the indexes are dirty data[i].state = State::Dirty; } } void reset() override { is_dirty.set(true); data.clear(); } }; private_api::Property>> model; mutable std::shared_ptr inner; vtable::VRef item_at(int i) const { const auto &x = inner->data.at(i); return { &C::static_vtable, const_cast(&(**x.ptr)) }; } public: template void set_model_binding(F &&binding) const { model.set_binding(std::forward(binding)); } template void ensure_updated(const Parent *parent) const { if (model.is_dirty()) { auto old_model = model.get_internal(); auto m = model.get(); if (!inner || old_model != m) { inner = std::make_shared(); if (m) { inner->model = m; m->attach_peer(inner); } } } if (inner && inner->is_dirty.get()) { inner->is_dirty.set(false); if (auto m = model.get()) { auto count = m->row_count(); inner->data.resize(count); for (size_t i = 0; i < count; ++i) { auto &c = inner->data[i]; bool created = false; if (!c.ptr) { c.ptr = C::create(parent); created = true; } if (c.state == RepeaterInner::State::Dirty) { std::optional data = m->row_data(i); (*c.ptr)->update_data(i, data ? *data : ModelData {}); } if (created) { (*c.ptr)->init(); } } } else { inner->data.clear(); } } else { // just do a get() on the model to register dependencies so that, for example, the // layout property tracker becomes dirty. model.get(); } } template void ensure_updated_listview(const Parent *parent, const private_api::Property *viewport_width, const private_api::Property *viewport_height, const private_api::Property *viewport_y, float listview_width, [[maybe_unused]] float listview_height) const { // TODO: the rust code in model.rs try to only allocate as many items as visible items ensure_updated(parent); float h = compute_layout_listview(viewport_width, listview_width, viewport_y->get()); viewport_height->set(h); } uint64_t visit(TraversalOrder order, private_api::ItemVisitorRefMut visitor) const { for (std::size_t i = 0; i < inner->data.size(); ++i) { auto index = order == TraversalOrder::BackToFront ? i : inner->data.size() - 1 - i; auto ref = item_at(index); if (ref.vtable->visit_children_item(ref, -1, order, visitor) != std::numeric_limits::max()) { return index; } } return std::numeric_limits::max(); } vtable::VWeak instance_at(std::size_t i) const { if (i >= inner->data.size()) { return {}; } const auto &x = inner->data.at(i); return vtable::VWeak { x.ptr->into_dyn() }; } private_api::IndexRange index_range() const { return private_api::IndexRange { 0, inner->data.size() }; } std::size_t len() const { return inner ? inner->data.size() : 0; } float compute_layout_listview(const private_api::Property *viewport_width, float listview_width, float viewport_y) const { float offset = viewport_y; auto vp_width = listview_width; if (!inner) return offset; for (auto &x : inner->data) { vp_width = std::max(vp_width, (*x.ptr)->listview_layout(&offset)); } viewport_width->set(vp_width); return offset - viewport_y; } void model_set_row_data(size_t row, const ModelData &data) const { if (model.is_dirty()) { std::abort(); } if (auto m = model.get()) { if (row < m->row_count()) { m->set_row_data(row, data); } } } void for_each(auto &&f) const { if (inner) { for (auto &&x : inner->data) { f(*x.ptr); } } } }; template class Conditional { private_api::Property model; mutable std::optional> instance; public: template void set_model_binding(F &&binding) const { model.set_binding(std::forward(binding)); } template void ensure_updated(const Parent *parent) const { if (!model.get()) { instance = std::nullopt; } else if (!instance) { instance = C::create(parent); (*instance)->init(); } } uint64_t visit(TraversalOrder order, private_api::ItemVisitorRefMut visitor) const { if (instance) { vtable::VRef ref { &C::static_vtable, const_cast(&(**instance)) }; if (ref.vtable->visit_children_item(ref, -1, order, visitor) != std::numeric_limits::max()) { return 0; } } return std::numeric_limits::max(); } vtable::VWeak instance_at(std::size_t i) const { if (i != 0 || !instance) { return {}; } return vtable::VWeak { instance->into_dyn() }; } private_api::IndexRange index_range() const { return private_api::IndexRange { 0, len() }; } std::size_t len() const { return instance ? 1 : 0; } void for_each(auto &&f) const { if (instance) { f(*instance); } } }; } // namespace private_api } // namespace slint ================================================ FILE: api/cpp/include/slint_pathdata.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include #include #include "slint_pathdata_internal.h" namespace slint::private_api { using cbindgen_private::PathEvent; using cbindgen_private::types::PathArcTo; using cbindgen_private::types::PathCubicTo; using cbindgen_private::types::PathElement; using cbindgen_private::types::PathLineTo; using cbindgen_private::types::PathMoveTo; using cbindgen_private::types::PathQuadraticTo; using cbindgen_private::types::Point; struct PathData { public: using Tag = cbindgen_private::types::PathData::Tag; PathData() : data(Data::None()) { } PathData(const PathElement *firstElement, size_t count) : data(Data::Elements(elements_from_array(firstElement, count))) { } PathData(const PathEvent *firstEvent, size_t event_count, const Point *firstCoordinate, size_t coordinate_count) : data(events_from_array(firstEvent, event_count, firstCoordinate, coordinate_count)) { } PathData(const SharedString &commands) : data(cbindgen_private::types::PathData::Commands(commands)) { } friend bool operator==(const PathData &a, const PathData &b) = default; private: static SharedVector elements_from_array(const PathElement *firstElement, size_t count) { SharedVector tmp; slint_new_path_elements(&tmp, firstElement, count); return tmp; } static cbindgen_private::types::PathData events_from_array(const PathEvent *firstEvent, size_t event_count, const Point *firstCoordinate, size_t coordinate_count) { SharedVector events; SharedVector coordinates; slint_new_path_events(&events, &coordinates, firstEvent, event_count, firstCoordinate, coordinate_count); return Data::Events(events, coordinates); } using Data = cbindgen_private::types::PathData; Data data; }; } ================================================ FILE: api/cpp/include/slint_point.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include namespace slint { /// The Point structure is used to represent a two-dimensional point /// with x and y coordinates. template struct Point { /// The x coordinate of the point T x; /// The y coordinate of the point T y; /// Compares with \a other and returns true if they are equal; false otherwise. bool operator==(const Point &other) const = default; }; namespace cbindgen_private { // The Point types are expanded to the Point2D<...> type from the euclid crate which // is binary compatible with Point template using Point2D = Point; } /// A position in logical pixel coordinates struct LogicalPosition : public Point { /// Explicitly convert a Point to a LogicalPosition explicit LogicalPosition(const Point p) : Point(p) { }; /// Default construct a LogicalPosition in the origin LogicalPosition() : Point { 0., 0. } { }; }; /// A position in physical pixel coordinates struct PhysicalPosition : public Point { /// Explicitly convert a Point to a LogicalPosition explicit PhysicalPosition(const Point p) : Point(p) { }; /// Default construct a PhysicalPosition in the origin PhysicalPosition() : Point { 0, 0 } { }; }; } ================================================ FILE: api/cpp/include/slint_properties.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include #include namespace slint::cbindgen_private { struct PropertyAnimation; struct ChangeTracker { void *inner; }; } #include "slint_properties_internal.h" #include "slint_builtin_structs_internal.h" namespace slint::private_api { using cbindgen_private::StateInfo; inline void slint_property_set_animated_binding_helper( const cbindgen_private::PropertyHandleOpaque *handle, void (*binding)(void *, int *), void *user_data, void (*drop_user_data)(void *), cbindgen_private::PropertyAnimation (*transition_data)(void *, uint64_t **)) { cbindgen_private::slint_property_set_animated_binding_int(handle, binding, user_data, drop_user_data, transition_data); } inline void slint_property_set_animated_binding_helper( const cbindgen_private::PropertyHandleOpaque *handle, void (*binding)(void *, float *), void *user_data, void (*drop_user_data)(void *), cbindgen_private::PropertyAnimation (*transition_data)(void *, uint64_t **)) { cbindgen_private::slint_property_set_animated_binding_float(handle, binding, user_data, drop_user_data, transition_data); } inline void slint_property_set_animated_binding_helper( const cbindgen_private::PropertyHandleOpaque *handle, void (*binding)(void *, Color *), void *user_data, void (*drop_user_data)(void *), cbindgen_private::PropertyAnimation (*transition_data)(void *, uint64_t **)) { cbindgen_private::slint_property_set_animated_binding_color(handle, binding, user_data, drop_user_data, transition_data); } inline void slint_property_set_animated_binding_helper( const cbindgen_private::PropertyHandleOpaque *handle, void (*binding)(void *, Brush *), void *user_data, void (*drop_user_data)(void *), cbindgen_private::PropertyAnimation (*transition_data)(void *, uint64_t **)) { cbindgen_private::slint_property_set_animated_binding_brush(handle, binding, user_data, drop_user_data, transition_data); } template struct Property { Property() { cbindgen_private::slint_property_init(&inner); } ~Property() { cbindgen_private::slint_property_drop(&inner); } Property(const Property &) = delete; Property(Property &&) = delete; Property &operator=(const Property &) = delete; explicit Property(const T &value) : value(value) { cbindgen_private::slint_property_init(&inner); } /* Should it be implicit? void operator=(const T &value) { set(value); }*/ void set(const T &value) const { if ((inner._0 & 0b10) == 0b10 || this->value != value) { this->value = value; cbindgen_private::slint_property_set_changed(&inner, &this->value); } } const T &get() const { cbindgen_private::slint_property_update(&inner, &value); return value; } template void set_binding(F binding) const { cbindgen_private::slint_property_set_binding( &inner, [](void *user_data, void *value) { *reinterpret_cast(value) = (*reinterpret_cast(user_data))(); }, new F(binding), [](void *user_data) { delete reinterpret_cast(user_data); }, nullptr, nullptr); } inline void set_animated_value(const T &value, const cbindgen_private::PropertyAnimation &animation_data) const; template inline void set_animated_binding(F binding, Trans animation) const { struct UserData { F binding; Trans animation; }; private_api::slint_property_set_animated_binding_helper( &inner, [](void *user_data, T *value) { *reinterpret_cast(value) = reinterpret_cast(user_data)->binding(); }, new UserData { binding, animation }, [](void *user_data) { delete reinterpret_cast(user_data); }, [](void *user_data, uint64_t **instant) { return reinterpret_cast(user_data)->animation(instant); }); } bool is_dirty() const { return cbindgen_private::slint_property_is_dirty(&inner); } void mark_dirty() const { cbindgen_private::slint_property_mark_dirty(&inner); } static void link_two_way(const Property *p1, const Property *p2) { auto value = p2->get(); cbindgen_private::PropertyHandleOpaque handle {}; if ((p2->inner._0 & 0b10) == 0b10) { std::swap(handle, const_cast *>(p2)->inner); } auto common_property = std::make_shared>(handle, std::move(value)); cbindgen_private::slint_property_set_binding( &p1->inner, TwoWayBinding::call_fn, new TwoWayBinding { common_property }, TwoWayBinding::del_fn, TwoWayBinding::intercept_fn, TwoWayBinding::intercept_binding_fn); cbindgen_private::slint_property_set_binding( &p2->inner, TwoWayBinding::call_fn, new TwoWayBinding { common_property }, TwoWayBinding::del_fn, TwoWayBinding::intercept_fn, TwoWayBinding::intercept_binding_fn); } template static void link_two_way_with_map(const Property *prop1, const Property *prop2, M1 map1, M2 map2) { // TODO: neither this nor link_two_way manages to re-use a common_property like the Rust // equivalent does. auto value = prop1->get(); cbindgen_private::PropertyHandleOpaque handle {}; if ((prop1->inner._0 & 0b10) == 0b10) { std::swap(handle, const_cast *>(prop1)->inner); } auto common_property = std::make_shared>(handle, std::move(value)); struct TwoWayBindingWithMap { std::shared_ptr> common_property; M1 map_to; M2 map_from; }; auto del_fn = [](void *user_data) { delete reinterpret_cast(user_data); }; auto call_fn = [](void *user_data, void *value) { auto self = reinterpret_cast(user_data); *reinterpret_cast(value) = self->map_to(self->common_property->get()); }; auto intercept_fn = [](void *user_data, const void *value) { auto self = reinterpret_cast(user_data); T old = self->common_property->get(); self->map_from(old, *reinterpret_cast(value)); self->common_property->set(old); return true; }; auto intercept_binding_fn = [](void *user_data, void *t2_binding) { struct BindingMapper { void *t2_binding; M1 map_to; M2 map_from; ~BindingMapper() { cbindgen_private::slint_property_delete_binding(t2_binding); } const BindingMapper &operator=(const BindingMapper &) = delete; }; auto self = reinterpret_cast(user_data); cbindgen_private::slint_property_set_binding( &self->common_property->inner, [](void *user_data, void *value) { auto self = reinterpret_cast(user_data); T &v = *reinterpret_cast(value); T2 sub_value = self->map_to(v); cbindgen_private::slint_property_evaluate_binding(self->t2_binding, &sub_value); self->map_from(v, sub_value); }, new BindingMapper { t2_binding, self->map_to, self->map_from }, [](void *user_data) { delete reinterpret_cast(user_data); }, nullptr, nullptr); return true; }; cbindgen_private::slint_property_set_binding( &prop1->inner, TwoWayBinding::call_fn, new TwoWayBinding { common_property }, TwoWayBinding::del_fn, TwoWayBinding::intercept_fn, TwoWayBinding::intercept_binding_fn); cbindgen_private::slint_property_set_binding( &prop2->inner, call_fn, new TwoWayBindingWithMap { common_property, map1, map2 }, del_fn, intercept_fn, intercept_binding_fn); } /// Internal (private) constructor used by link_two_way explicit Property(cbindgen_private::PropertyHandleOpaque inner, T value) : inner(inner), value(std::move(value)) { } const T &get_internal() const { return value; } void set_constant() const { cbindgen_private::slint_property_set_constant(&inner); } private: cbindgen_private::PropertyHandleOpaque inner; mutable T value {}; template friend void set_state_binding(const Property &property, F binding); template friend struct Property; struct TwoWayBinding { std::shared_ptr> common_property; static void del_fn(void *user_data) { delete reinterpret_cast(user_data); }; static void call_fn(void *user_data, void *value) { *reinterpret_cast(value) = reinterpret_cast(user_data)->common_property->get(); }; static bool intercept_fn(void *user_data, const void *value) { reinterpret_cast(user_data)->common_property->set( *reinterpret_cast(value)); return true; }; static bool intercept_binding_fn(void *user_data, void *value) { cbindgen_private::slint_property_set_binding_internal( &reinterpret_cast(user_data)->common_property->inner, value); return true; }; }; }; template<> inline void Property::set_animated_value( const int32_t &new_value, const cbindgen_private::PropertyAnimation &animation_data) const { cbindgen_private::slint_property_set_animated_value_int(&inner, value, new_value, &animation_data); } template<> inline void Property::set_animated_value(const float &new_value, const cbindgen_private::PropertyAnimation &animation_data) const { cbindgen_private::slint_property_set_animated_value_float(&inner, value, new_value, &animation_data); } template<> inline void Property::set_animated_value(const Color &new_value, const cbindgen_private::PropertyAnimation &animation_data) const { cbindgen_private::slint_property_set_animated_value_color(&inner, value, new_value, &animation_data); } template void set_state_binding(const Property &property, F binding) { cbindgen_private::slint_property_set_state_binding( &property.inner, [](void *user_data) -> int32_t { return (*reinterpret_cast(user_data))(); }, new F(binding), [](void *user_data) { delete reinterpret_cast(user_data); }); } /// PropertyTracker allows keeping track of when properties change and lazily evaluate code /// if necessary. /// Once constructed, you can call evaluate() with a functor that will be invoked. Any /// Property types that have their value read from within the invoked functor or any code that's /// reached from there are added to internal book-keeping. When after returning from evaluate(), /// any of these accessed properties change their value, the property tracker's is_dirt() function /// will return true. /// /// PropertyTracker instances nest, so if during the evaluation of one tracker, another tracker's /// evaluate() function gets called and properties from within that evaluation change their value /// later, both tracker instances will report true for is_dirty(). If you would like to disable the /// nesting, use the evaluate_as_dependency_root() function instead. struct PropertyTracker { /// Constructs a new property tracker instance. PropertyTracker() { cbindgen_private::slint_property_tracker_init(&inner); } /// Destroys the property tracker. ~PropertyTracker() { cbindgen_private::slint_property_tracker_drop(&inner); } /// The copy constructor is intentionally deleted, property trackers cannot be copied. PropertyTracker(const PropertyTracker &) = delete; /// The assignment operator is intentionally deleted, property trackers cannot be copied. PropertyTracker &operator=(const PropertyTracker &) = delete; /// Returns true if any properties accessed during the last evaluate() call have changed their /// value since then. bool is_dirty() const { return cbindgen_private::slint_property_tracker_is_dirty(&inner); } /// Invokes the provided functor \a f and tracks accessed to any properties during that /// invocation. template auto evaluate(const F &f) const -> std::enable_if_t> { cbindgen_private::slint_property_tracker_evaluate( &inner, [](void *f) { (*reinterpret_cast(f))(); }, const_cast(&f)); } /// Invokes the provided functor \a f and tracks accessed to any properties during that /// invocation. Use this overload if your functor returns a value, as evaluate() will pass it on /// and return it. template auto evaluate(const F &f) const -> std::enable_if_t, decltype(f())> { decltype(f()) result; this->evaluate([&] { result = f(); }); return result; } /// Invokes the provided functor \a f and tracks accessed to any properties during that /// invocation. /// /// This starts a new dependency chain and if called during the evaluation of another /// property tracker, the outer tracker will not be notified if any accessed properties change. template auto evaluate_as_dependency_root(const F &f) const -> std::enable_if_t> { cbindgen_private::slint_property_tracker_evaluate_as_dependency_root( &inner, [](void *f) { (*reinterpret_cast(f))(); }, const_cast(&f)); } /// Invokes the provided functor \a f and tracks accessed to any properties during that /// invocation. Use this overload if your functor returns a value, as evaluate() will pass it on /// and return it. /// /// This starts a new dependency chain and if called during the evaluation of another /// property tracker, the outer tracker will not be notified if any accessed properties change. template auto evaluate_as_dependency_root(const F &f) const -> std::enable_if_t, decltype(f())> { decltype(f()) result; this->evaluate_as_dependency_root([&] { result = f(); }); return result; } private: cbindgen_private::PropertyTrackerOpaque inner; }; struct ChangeTracker { ChangeTracker() { cbindgen_private::slint_change_tracker_construct(&inner); } ~ChangeTracker() { cbindgen_private::slint_change_tracker_drop(&inner); } template void init(Data data, FnEval fn_eval, FnNotify fn_notify) { using Value = std::invoke_result_t; struct Inner { Data data; FnEval fn_eval; FnNotify fn_notify; Value value; }; auto data_ptr = new Inner { std::move(data), std::move(fn_eval), std::move(fn_notify), Value() }; cbindgen_private::slint_change_tracker_init( &inner, data_ptr, [](void *d) { delete reinterpret_cast(d); }, [](void *d) { auto inner = reinterpret_cast(d); auto v = inner->fn_eval(inner->data); bool r = v != inner->value; inner->value = v; return r; }, [](void *d) { auto inner = reinterpret_cast(d); inner->fn_notify(inner->data, inner->value); }); } private: cbindgen_private::ChangeTracker inner; }; } // namespace slint::private_api ================================================ FILE: api/cpp/include/slint_sharedvector.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include "slint_sharedvector_internal.h" #include #include #include #include namespace slint { /// SharedVector is a vector template class similar to std::vector that's primarily used for passing /// data in and out of the Slint run-time library. It uses implicit-sharing to make creating /// copies cheap. Only when a function changes the vector's data, a copy is is made. template struct SharedVector { /// Creates a new, empty vector. SharedVector() : inner(const_cast(reinterpret_cast( cbindgen_private::slint_shared_vector_empty()))) { } /// Creates a new vector that holds all the elements of the given std::initializer_list \a args. SharedVector(std::initializer_list args) : SharedVector(SharedVector::with_capacity(args.size())) { auto new_data = reinterpret_cast(inner + 1); auto input_it = args.begin(); for (std::size_t i = 0; i < args.size(); ++i, ++input_it) { new (new_data + i) T(*input_it); inner->size++; } } /// Creates a vector of a given size, with default-constructed data. explicit SharedVector(size_t size) : SharedVector(SharedVector::with_capacity(size)) { auto new_data = reinterpret_cast(inner + 1); for (std::size_t i = 0; i < size; ++i) { new (new_data + i) T(); inner->size++; } } /// Creates a vector of a given size, initialized with copies of the \a value. explicit SharedVector(size_t size, const T &value) : SharedVector(SharedVector::with_capacity(size)) { auto new_data = reinterpret_cast(inner + 1); for (std::size_t i = 0; i < size; ++i) { new (new_data + i) T(value); inner->size++; } } /// Constructs the container with the contents of the range `[first, last)`. template SharedVector(InputIt first, InputIt last) : SharedVector(SharedVector::with_capacity(std::distance(first, last))) { std::uninitialized_copy(first, last, begin()); inner->size = inner->capacity; } /// Creates a new vector that is a copy of \a other. SharedVector(const SharedVector &other) : inner(other.inner) { if (inner->refcount > 0) { ++inner->refcount; } } /// Destroys this vector. The underlying data is destroyed if no other /// vector references it. ~SharedVector() { drop(); } /// Assigns the data of \a other to this vector and returns a reference to this vector. SharedVector &operator=(const SharedVector &other) { if (other.inner == inner) { return *this; } drop(); inner = other.inner; if (inner->refcount > 0) { ++inner->refcount; } return *this; } /// Move-assign's \a other to this vector and returns a reference to this vector. SharedVector &operator=(SharedVector &&other) { std::swap(inner, other.inner); return *this; } /// Returns a const pointer to the first element of this vector. const T *cbegin() const { return reinterpret_cast(inner + 1); } /// Returns a const pointer that points past the last element of this vector. The /// pointer cannot be dereferenced, it can only be used for comparison. const T *cend() const { return cbegin() + inner->size; } /// Returns a const pointer to the first element of this vector. const T *begin() const { return cbegin(); } /// Returns a const pointer that points past the last element of this vector. The /// pointer cannot be dereferenced, it can only be used for comparison. const T *end() const { return cend(); } /// Returns a pointer to the first element of this vector. T *begin() { detach(inner->size); return reinterpret_cast(inner + 1); } /// Returns a pointer that points past the last element of this vector. The /// pointer cannot be dereferenced, it can only be used for comparison. T *end() { detach(inner->size); return begin() + inner->size; } /// Returns the number of elements in this vector. std::size_t size() const { return inner->size; } /// Returns true if there are no elements on this vector; false otherwise. bool empty() const { return inner->size == 0; } /// This indexing operator returns a reference to the \a `index`th element of this vector. T &operator[](std::size_t index) { return begin()[index]; } /// This indexing operator returns a const reference to the \a `index`th element of this vector. const T &operator[](std::size_t index) const { return begin()[index]; } /// Returns a reference to the \a `index`th element of this vector. const T &at(std::size_t index) const { return begin()[index]; } /// Appends the \a value as a new element to the end of this vector. void push_back(const T &value) { detach(inner->size + 1); new (end()) T(value); inner->size++; } /// Moves the \a value as a new element to the end of this vector. void push_back(T &&value) { detach(inner->size + 1); new (end()) T(std::move(value)); inner->size++; } /// Clears the vector and removes all elements. The capacity remains unaffected. void clear() { if (inner->refcount != 1) { *this = SharedVector(); } else { auto b = cbegin(), e = cend(); inner->size = 0; for (auto it = b; it < e; ++it) { it->~T(); } } } /// Returns true if the vector \a a has the same number of elements as \a b /// and all the elements also compare equal; false otherwise. friend bool operator==(const SharedVector &a, const SharedVector &b) { if (a.size() != b.size()) return false; return std::equal(a.cbegin(), a.cend(), b.cbegin()); } /// \private std::size_t capacity() const { return inner->capacity; } private: void detach(std::size_t expected_capacity) { if (inner->refcount == 1 && expected_capacity <= inner->capacity) { return; } auto new_array = SharedVector::with_capacity(expected_capacity); auto old_data = reinterpret_cast(inner + 1); auto new_data = reinterpret_cast(new_array.inner + 1); for (std::size_t i = 0; i < inner->size; ++i) { new (new_data + i) T(old_data[i]); new_array.inner->size++; } *this = std::move(new_array); } void drop() { if (inner->refcount > 0 && (--inner->refcount) == 0) { auto b = cbegin(), e = cend(); for (auto it = b; it < e; ++it) { it->~T(); } cbindgen_private::slint_shared_vector_free(reinterpret_cast(inner), sizeof(SharedVectorHeader) + inner->capacity * sizeof(T), alignof(SharedVectorHeader)); } } static SharedVector with_capacity(std::size_t capacity) { auto mem = cbindgen_private::slint_shared_vector_allocate( sizeof(SharedVectorHeader) + capacity * sizeof(T), alignof(SharedVectorHeader)); return SharedVector(new (mem) SharedVectorHeader { { 1 }, 0, capacity }); } #if !defined(DOXYGEN) // Unfortunately, this cannot be generated by cbindgen because std::atomic is not understood struct SharedVectorHeader { std::atomic refcount; std::size_t size; std::size_t capacity; }; static_assert(alignof(T) <= alignof(SharedVectorHeader), "Not yet supported because we would need to add padding"); SharedVectorHeader *inner; explicit SharedVector(SharedVectorHeader *inner) : inner(inner) { } #endif }; #if !defined(DOXYGEN) // Hide these from Doxygen as Slice is private API template bool operator==(cbindgen_private::Slice a, cbindgen_private::Slice b) { if (a.len != b.len) return false; return std::equal(a.ptr, a.ptr + a.len, b.ptr); } template bool operator!=(cbindgen_private::Slice a, cbindgen_private::Slice b) { return !(a != b); } #endif // !defined(DOXYGEN) } ================================================ FILE: api/cpp/include/slint_size.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include namespace slint { /// The Size structure is used to represent a two-dimensional size /// with width and height. template struct Size { /// The width of the size T width; /// The height of the size T height; /// Compares with \a other and returns true if they are equal; false otherwise. bool operator==(const Size &other) const = default; }; namespace cbindgen_private { // The Size types are expanded to the Size2D<...> type from the euclid crate which // is binary compatible with Size template using Size2D = Size; } /// A size given in logical pixels struct LogicalSize : public Size { /// Explicitly convert a Size to a LogicalSize explicit constexpr LogicalSize(const Size s = { 0, 0 }) : Size(s) { } }; /// A size given in physical pixels. struct PhysicalSize : public Size { /// Explicitly convert a Size to a LogicalSize explicit constexpr PhysicalSize(const Size s = { 0, 0 }) : Size(s) { } }; } ================================================ FILE: api/cpp/include/slint_string.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include #include #include "slint_string_internal.h" namespace slint { /// A string type used by the Slint run-time. /// /// SharedString uses implicit data sharing to make it efficient to pass around copies. When /// copying, a reference to the data is cloned, not the data itself. /// /// The class provides constructors from std::string_view as well as the automatic conversion to /// a std::string_view. /// /// For convenience, it's also possible to convert a number to a string using /// SharedString::from_number(double). /// /// Under the hood the string data is UTF-8 encoded and it is always terminated with a null /// character. struct SharedString { /// Creates an empty default constructed string. SharedString() { cbindgen_private::slint_shared_string_from_bytes(this, "", 0); } /// Creates a new SharedString from the string view \a s. The underlying string data /// is copied. SharedString(std::string_view s) { cbindgen_private::slint_shared_string_from_bytes(this, s.data(), s.size()); } /// Creates a new SharedString from the null-terminated string pointer \a s. The underlying /// string data is copied. It is assumed that the string is UTF-8 encoded. SharedString(const char *s) : SharedString(std::string_view(s)) { } /// Creates a new SharedString from the null-terminated string pointer \a s. The underlying /// string data is copied. SharedString(const char8_t *s) : SharedString(reinterpret_cast(s)) { } /// Creates a new SharedString from the string view \a s. The underlying string data is copied. SharedString(std::u8string_view s) { cbindgen_private::slint_shared_string_from_bytes( this, reinterpret_cast(s.data()), s.size()); } /// Creates a new SharedString from \a other. SharedString(const SharedString &other) { cbindgen_private::slint_shared_string_clone(this, &other); } /// Destroys this SharedString and frees the memory if this is the last instance /// referencing it. ~SharedString() { cbindgen_private::slint_shared_string_drop(this); } /// Assigns \a other to this string and returns a reference to this string. SharedString &operator=(const SharedString &other) { cbindgen_private::slint_shared_string_drop(this); cbindgen_private::slint_shared_string_clone(this, &other); return *this; } /// Assigns the string view \a s to this string and returns a reference to this string. /// The underlying string data is copied. It is assumed that the string is UTF-8 encoded. SharedString &operator=(std::string_view s) { cbindgen_private::slint_shared_string_drop(this); cbindgen_private::slint_shared_string_from_bytes(this, s.data(), s.size()); return *this; } /// Assigns null-terminated string pointer \a s to this string and returns a reference /// to this string. The underlying string data is copied. It is assumed that the string /// is UTF-8 encoded. SharedString &operator=(const char *s) { return *this = std::string_view(s); } /// Move-assigns \a other to this SharedString instance. SharedString &operator=(SharedString &&other) { std::swap(inner, other.inner); return *this; } /// Provides a view to the string data. The returned view is only valid as long as at /// least this SharedString exists. operator std::string_view() const { return cbindgen_private::slint_shared_string_bytes(this); } /// Provides a raw pointer to the string data. The returned pointer is only valid as long as at /// least this SharedString exists. auto data() const -> const char * { return cbindgen_private::slint_shared_string_bytes(this); } /// Size of the string, in bytes. This excludes the terminating null character. std::size_t size() const { return std::string_view(*this).size(); } /// Returns a pointer to the first character. It is only safe to dereference the pointer if the /// string contains at least one character. const char *begin() const { return data(); } /// Returns a point past the last character of the string. It is not safe to dereference the /// pointer, but it is suitable for comparison. const char *end() const { std::string_view view(*this); return view.data() + view.size(); } /// \return true if the string contains no characters; false otherwise. bool empty() const { return std::string_view(*this).empty(); } /// \return true if the string starts with the specified prefix string; false otherwise bool starts_with(std::string_view prefix) const { return std::string_view(*this).substr(0, prefix.size()) == prefix; } /// \return true if the string ends with the specified prefix string; false otherwise bool ends_with(std::string_view prefix) const { std::string_view self_view(*this); return self_view.size() >= prefix.size() && self_view.compare(self_view.size() - prefix.size(), std::string_view::npos, prefix) == 0; } /// Reset to an empty string void clear() { *this = std::string_view("", 0); } /// Creates a new SharedString from the given number \a n. The string representation of the /// number uses a minimal formatting scheme: If \a n has no fractional part, the number will be /// formatted as an integer. /// /// For example: /// \code /// auto str = slint::SharedString::from_number(42); // creates "42" /// auto str2 = slint::SharedString::from_number(100.5) // creates "100.5" /// \endcode static SharedString from_number(double n) { return SharedString(n); } /// Returns the lowercase equivalent of this string, as a new SharedString. /// /// For example: /// \code /// auto str = slint::SharedString("Hello"); /// auto str2 = str.to_lowercase(); // creates "hello" /// \endcode SharedString to_lowercase() const { auto out = SharedString(); cbindgen_private::slint_shared_string_to_lowercase(&out, this); return out; } /// Returns the uppercase equivalent of this string, as a new SharedString. /// /// For example: /// \code /// auto str = slint::SharedString("Hello"); /// auto str2 = str.to_uppercase(); // creates "HELLO" /// \endcode SharedString to_uppercase() const { auto out = SharedString(); cbindgen_private::slint_shared_string_to_uppercase(&out, this); return out; } /// Returns true if \a a is equal to \a b; otherwise returns false. friend bool operator==(const SharedString &a, const SharedString &b) { return std::string_view(a) == std::string_view(b); } /// Returns true if \a a is not equal to \a b; otherwise returns false. friend bool operator!=(const SharedString &a, const SharedString &b) { return std::string_view(a) != std::string_view(b); } /// Returns true if \a a is lexicographically less than \a b; false otherwise. friend bool operator<(const SharedString &a, const SharedString &b) { return std::string_view(a) < std::string_view(b); } /// Returns true if \a a is lexicographically less or equal than \a b; false otherwise. friend bool operator<=(const SharedString &a, const SharedString &b) { return std::string_view(a) <= std::string_view(b); } /// Returns true if \a a is lexicographically greater than \a b; false otherwise. friend bool operator>(const SharedString &a, const SharedString &b) { return std::string_view(a) > std::string_view(b); } /// Returns true if \a a is lexicographically greater or equal than \a b; false otherwise. friend bool operator>=(const SharedString &a, const SharedString &b) { return std::string_view(a) >= std::string_view(b); } /// Writes the \a shared_string to the specified \a stream and returns a reference to the /// stream. friend std::ostream &operator<<(std::ostream &stream, const SharedString &shared_string) { return stream << std::string_view(shared_string); } /// Concatenates \a a and \a and returns the result as a new SharedString. friend SharedString operator+(const SharedString &a, std::string_view b) { SharedString a2 = a; return a2 += b; } /// Move-concatenates \a b to \a and returns a reference to \a a. friend SharedString operator+(SharedString &&a, std::string_view b) { a += b; return a; } /// Appends \a other to this string and returns a reference to this. SharedString &operator+=(std::string_view other) { cbindgen_private::slint_shared_string_append(this, other.data(), other.size()); return *this; } private: /// Use SharedString::from_number explicit SharedString(double n) { cbindgen_private::slint_shared_string_from_number(this, n); } void *inner; // opaque }; namespace private_api { /// Styled text that has been parsed and seperated into paragraphs struct StyledText { public: /// Creates an default styled text. StyledText() { cbindgen_private::slint_styled_text_new(this); } /// Destroys this StyledText. ~StyledText() { cbindgen_private::slint_styled_text_drop(this); } /// Creates a new StyledText from \a other. StyledText(const StyledText &other) { cbindgen_private::slint_styled_text_clone(this, &other); } /// Assigns \a other to this styled text and returns a reference to this styled text. StyledText &operator=(const StyledText &other) { cbindgen_private::slint_styled_text_drop(this); cbindgen_private::slint_styled_text_clone(this, &other); return *this; } /// Move-assigns \a other to this StyledText instance. StyledText &operator=(StyledText &&other) { std::swap(inner, other.inner); return *this; } /// Returns true if \a a is equal to \a b; otherwise returns false. friend bool operator==(const StyledText &a, const StyledText &b) { return cbindgen_private::slint_styled_text_eq(&a, &b); } private: // Ensure that the alignment (8 bytes) is the same as the Rust struct. void *inner alignas(8); }; template inline cbindgen_private::Slice make_slice(const T *ptr, size_t len) { return cbindgen_private::Slice { // Rust uses a NonNull, so even empty slices shouldn't use nullptr .ptr = ptr ? const_cast(ptr) : reinterpret_cast(sizeof(T)), .len = len, }; } template inline cbindgen_private::Slice> make_slice(std::span span) { return make_slice(span.data(), span.size()); } inline cbindgen_private::Slice string_to_slice(std::string_view str) { return make_slice(reinterpret_cast(str.data()), str.size()); } inline std::string_view slice_to_string_view(cbindgen_private::Slice str) { return std::string_view(reinterpret_cast(str.ptr), str.len); } } } ================================================ FILE: api/cpp/include/slint_tests_helpers.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include "slint-testing.h" #include // this file contains function useful for internal testing namespace slint::private_api::testing { inline void mock_elapsed_time(int64_t time_in_ms) { cbindgen_private::slint_mock_elapsed_time(time_in_ms); } inline uint64_t get_mocked_time() { return cbindgen_private::slint_get_mocked_time(); } template inline void send_mouse_click(const Component *component, float x, float y) { cbindgen_private::slint_send_mouse_click(x, y, &component->window().window_handle()); } template inline void send_keyboard_key_text(const Component *component, const slint::SharedString &text, bool pressed) { cbindgen_private::slint_send_keyboard_key_text(&text, pressed, &component->window().window_handle()); } template inline void send_keyboard_char(const Component *component, const slint::SharedString &str, bool pressed) { cbindgen_private::slint_send_keyboard_char(&str, pressed, &component->window().window_handle()); } template inline void send_key_combo(const Component *component, std::vector keys) { for (const auto &key : keys) { send_keyboard_key_text(component, key, true); } std::reverse(std::begin(keys), std::end(keys)); for (const auto &key : keys) { send_keyboard_key_text(component, key, false); } } template inline void send_keyboard_string_sequence(const Component *component, const slint::SharedString &str) { cbindgen_private::send_keyboard_string_sequence(&str, &component->window().window_handle()); } #define assert_eq(A, B) \ slint::private_api::testing::assert_eq_impl(A, B, #A, #B, __FILE__, __LINE__) template B> void assert_eq_impl(const A &a, const B &b, const char *a_str, const char *b_str, const char *file, int line) { bool nok = true; if constexpr (std::is_integral_v && std::is_integral_v) { // Do a cast to the common type to avoid warning about signed vs. unsigned compare using T = std::common_type_t; nok = T(a) != T(b); } else if constexpr (std::is_floating_point_v && std::is_floating_point_v) { const double dEpsilon = 0.000001; // or some other small number nok = fabs(a - b) > dEpsilon * fabs(a); } else { nok = a != b; } if (nok) { std::cerr << file << ":" << line << ": assert_eq FAILED!\n" << a_str << ": " << a << "\n" << b_str << ": " << b << std::endl; std::abort(); } } } // namespace slint ================================================ FILE: api/cpp/include/slint_timer.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 // cSpell: ignore singleshot #pragma once #include #include #ifndef SLINT_FEATURE_FREESTANDING # include # include #endif namespace slint { namespace private_api { /// Internal function that checks that the API that must be called from the main /// thread is indeed called from the main thread, or abort the program otherwise /// /// Most API should be called from the main thread. When using thread one must /// use slint::invoke_from_event_loop inline void assert_main_thread() { #ifndef SLINT_FEATURE_FREESTANDING # ifndef NDEBUG static auto main_thread_id = std::this_thread::get_id(); if (main_thread_id != std::this_thread::get_id()) { std::cerr << "A function that should be only called from the main thread was called from a " "thread." << std::endl; std::cerr << "Most API should be called from the main thread. When using thread one must " "use slint::invoke_from_event_loop." << std::endl; std::abort(); } # endif #endif } } // namespace private_api using cbindgen_private::TimerMode; /// A Timer that can call a callback at repeated interval /// /// Use the static single_shot function to make a single shot timer struct Timer { /// Construct a null timer. Use the start() method to activate the timer with a mode, interval /// and callback. Timer() = default; /// Construct a timer which will repeat the callback every `interval` milliseconds until /// the destructor of the timer is called. /// /// This is a convenience function and equivalent to calling /// `start(slint::TimerMode::Repeated, interval, callback);` on a default constructed Timer. template Timer(std::chrono::milliseconds interval, F callback) : id(cbindgen_private::slint_timer_start( 0, TimerMode::Repeated, interval.count(), [](void *data) { (*reinterpret_cast(data))(); }, new F(std::move(callback)), [](void *data) { delete reinterpret_cast(data); })) { private_api::assert_main_thread(); } Timer(const Timer &) = delete; Timer &operator=(const Timer &) = delete; ~Timer() { private_api::assert_main_thread(); cbindgen_private::slint_timer_destroy(id); } /// Starts the timer with the given \a mode and \a interval, in order for the \a callback to /// called when the timer fires. If the timer has been started previously and not fired yet, /// then it will be restarted. template void start(TimerMode mode, std::chrono::milliseconds interval, F callback) { private_api::assert_main_thread(); id = cbindgen_private::slint_timer_start( id, mode, interval.count(), [](void *data) { (*reinterpret_cast(data))(); }, new F(std::move(callback)), [](void *data) { delete reinterpret_cast(data); }); } /// Stops the previously started timer. Does nothing if the timer has never been started. A /// stopped timer cannot be restarted with restart(). Use start() instead. void stop() { private_api::assert_main_thread(); cbindgen_private::slint_timer_stop(id); } /// Restarts the timer. If the timer was previously started by calling [`Self::start()`] /// with a duration and callback, then the time when the callback will be next invoked /// is re-calculated to be in the specified duration relative to when this function is called. /// /// Does nothing if the timer was never started. void restart() { private_api::assert_main_thread(); cbindgen_private::slint_timer_restart(id); } /// Returns true if the timer is running; false otherwise. bool running() const { private_api::assert_main_thread(); return cbindgen_private::slint_timer_running(id); } /// Returns the interval of the timer. /// Returns 0 if the timer was never started. std::chrono::milliseconds interval() const { private_api::assert_main_thread(); return std::chrono::milliseconds(cbindgen_private::slint_timer_interval(id)); } /// Call the callback after the given duration. template static void single_shot(std::chrono::milliseconds duration, F callback) { private_api::assert_main_thread(); cbindgen_private::slint_timer_singleshot( duration.count(), [](void *data) { (*reinterpret_cast(data))(); }, new F(std::move(callback)), [](void *data) { delete reinterpret_cast(data); }); } private: uint64_t id = 0; }; } // namespace slint ================================================ FILE: api/cpp/include/slint_window.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include "slint_internal.h" #if (!defined(__APPLE__) && !defined(_WIN32) && !defined(_WIN64) \ && !defined(SLINT_FEATURE_FREESTANDING)) \ || defined(DOXYGEN) struct wl_surface; struct wl_display; #endif #if (defined(__APPLE__) && !defined(_WIN32) && !defined(_WIN64) \ && !defined(SLINT_FEATURE_FREESTANDING)) \ || defined(DOXYGEN) # ifdef __OBJC__ @class NSView; # else typedef struct objc_object NSView; # endif #endif #if (!defined(__APPLE__) && (defined(_WIN32) || defined(_WIN64)) \ && !defined(SLINT_FEATURE_FREESTANDING)) \ || defined(DOXYGEN) class HINSTANCE__; typedef HINSTANCE__ *HINSTANCE; class HWND__; typedef HWND__ *HWND; #endif namespace slint { #if !defined(DOXYGEN) namespace platform { class SkiaRenderer; class SoftwareRenderer; } #endif namespace private_api { using ItemTreeRc = vtable::VRc; using slint::LogicalPosition; /// Looking forward for C++23 std::optional::transform template auto optional_transform(const std::optional &o, F &&f) -> decltype(std::optional(f(*o))) { if (o) { return std::optional(f(*o)); } return std::nullopt; } template void optional_then(const std::optional &o, F &&f) { if (o) { f(*o); } } /// Waiting for C++23 std::optional::and_then template auto optional_and_then(const std::optional &o, F &&f) -> decltype(f(*o)) { if (o) { return f(*o); } return std::nullopt; } template T optional_or_default(const std::optional &o) { if (o) { return *o; } return {}; } class WindowAdapterRc { public: explicit WindowAdapterRc(cbindgen_private::WindowAdapterRcOpaque adopted_inner) { assert_main_thread(); cbindgen_private::slint_windowrc_clone(&adopted_inner, &inner); } WindowAdapterRc() { cbindgen_private::slint_windowrc_init(&inner); } ~WindowAdapterRc() { cbindgen_private::slint_windowrc_drop(&inner); } WindowAdapterRc(const WindowAdapterRc &other) : WindowAdapterRc(other.inner) { } WindowAdapterRc(WindowAdapterRc &&) = delete; WindowAdapterRc &operator=(WindowAdapterRc &&) = delete; WindowAdapterRc &operator=(const WindowAdapterRc &other) { assert_main_thread(); if (this != &other) { cbindgen_private::slint_windowrc_drop(&inner); cbindgen_private::slint_windowrc_clone(&other.inner, &inner); } return *this; } void show() const { slint_windowrc_show(&inner); } void hide() const { slint_windowrc_hide(&inner); } bool is_visible() const { return slint_windowrc_is_visible(&inner); } float scale_factor() const { return slint_windowrc_get_scale_factor(&inner); } void set_const_scale_factor(float value) const { slint_windowrc_set_const_scale_factor(&inner, value); } cbindgen_private::ColorScheme color_scheme() const { return slint_windowrc_color_scheme(&inner); } bool supports_native_menu_bar() const { return slint_windowrc_supports_native_menu_bar(&inner); } bool text_input_focused() const { return slint_windowrc_get_text_input_focused(&inner); } void set_text_input_focused(bool value) const { slint_windowrc_set_text_input_focused(&inner, value); } template void unregister_item_tree(Component *c, ItemArray items) const { cbindgen_private::slint_unregister_item_tree( vtable::VRef { &Component::static_vtable, c }, items, &inner); } void set_focus_item(const ItemTreeRc &component_rc, uint32_t item_index, bool set_focus, cbindgen_private::FocusReason reason) { cbindgen_private::ItemRc item_rc { component_rc, item_index }; cbindgen_private::slint_windowrc_set_focus_item(&inner, &item_rc, set_focus, reason); } void set_component(const cbindgen_private::ItemTreeWeak &weak) const { auto item_tree_rc = (*weak.lock()).into_dyn(); slint_windowrc_set_component(&inner, &item_tree_rc); } template uint32_t show_popup(const Parent *parent_component, PosGetter pos, cbindgen_private::PopupClosePolicy close_policy, cbindgen_private::ItemRc parent_item) const { auto popup = Component::create(parent_component); auto p = pos(popup); auto popup_dyn = popup.into_dyn(); auto id = cbindgen_private::slint_windowrc_show_popup(&inner, &popup_dyn, p, close_policy, &parent_item, false); popup->user_init(); return id; } void close_popup(uint32_t popup_id) const { if (popup_id > 0) { cbindgen_private::slint_windowrc_close_popup(&inner, popup_id); } } template uint32_t show_popup_menu( SharedGlobals *globals, LogicalPosition pos, cbindgen_private::ItemRc context_menu_rc, InitFn init, std::optional> menu = std::nullopt) const { if (menu) { if (cbindgen_private::slint_windowrc_show_native_popup_menu(&inner, &menu.value(), pos, &context_menu_rc)) { return 0; } } auto popup = Component::create(globals); init(&*popup); auto popup_dyn = popup.into_dyn(); auto id = cbindgen_private::slint_windowrc_show_popup( &inner, &popup_dyn, pos, cbindgen_private::PopupClosePolicy::CloseOnClickOutside, &context_menu_rc, true); popup->user_init(); return id; } template F> std::optional set_rendering_notifier(F callback) const { auto actual_cb = [](RenderingState state, GraphicsAPI graphics_api, void *data) { (*reinterpret_cast(data))(state, graphics_api); }; SetRenderingNotifierError err; if (cbindgen_private::slint_windowrc_set_rendering_notifier( &inner, actual_cb, [](void *user_data) { delete reinterpret_cast(user_data); }, new F(std::move(callback)), &err)) { return {}; } else { return err; } } // clang-format off template requires(std::is_convertible_v, CloseRequestResponse>) void on_close_requested(F callback) const // clang-format on { auto actual_cb = [](void *data) { return (*reinterpret_cast(data))(); }; cbindgen_private::slint_windowrc_on_close_requested( &inner, actual_cb, [](void *user_data) { delete reinterpret_cast(user_data); }, new F(std::move(callback))); } void request_redraw() const { cbindgen_private::slint_windowrc_request_redraw(&inner); } slint::PhysicalPosition position() const { slint::PhysicalPosition pos; cbindgen_private::slint_windowrc_position(&inner, &pos); return pos; } void set_logical_position(const slint::LogicalPosition &pos) { cbindgen_private::slint_windowrc_set_logical_position(&inner, &pos); } void set_physical_position(const slint::PhysicalPosition &pos) { cbindgen_private::slint_windowrc_set_physical_position(&inner, &pos); } slint::PhysicalSize size() const { return slint::PhysicalSize(cbindgen_private::slint_windowrc_size(&inner)); } void set_logical_size(const slint::LogicalSize &size) { cbindgen_private::slint_windowrc_set_logical_size(&inner, &size); } void set_physical_size(const slint::PhysicalSize &size) { cbindgen_private::slint_windowrc_set_physical_size(&inner, &size); } /// Send a pointer event to this window void dispatch_pointer_event(const cbindgen_private::MouseEvent &event) { private_api::assert_main_thread(); cbindgen_private::slint_windowrc_dispatch_pointer_event(&inner, &event); } /// Registers a font by the specified path. The path must refer to an existing /// TrueType font. /// \returns an empty optional on success, otherwise an error string inline std::optional register_font_from_path(const SharedString &path) { SharedString maybe_err; cbindgen_private::slint_register_font_from_path(&inner, &path, &maybe_err); if (!maybe_err.empty()) { return maybe_err; } else { return {}; } } /// Registers a font by the data. The data must be valid TrueType font data. /// \returns an empty optional on success, otherwise an error string inline std::optional register_font_from_data(const uint8_t *data, std::size_t len) { SharedString maybe_err; cbindgen_private::slint_register_font_from_data(&inner, make_slice(data, len), &maybe_err); if (!maybe_err.empty()) { return maybe_err; } else { return {}; } } /// Registers a bitmap font for use with the software renderer. inline void register_bitmap_font(const cbindgen_private::BitmapFont &font) { cbindgen_private::slint_register_bitmap_font(&inner, &font); } /// \private const cbindgen_private::WindowAdapterRcOpaque &handle() const { return inner; } private: friend class slint::platform::SkiaRenderer; friend class slint::platform::SoftwareRenderer; cbindgen_private::WindowAdapterRcOpaque inner; }; } /// This class represents a window towards the windowing system, that's used to render the /// scene of a component. It provides API to control windowing system specific aspects such /// as the position on the screen. class Window { public: /// \private /// Internal function used by the generated code to construct a new instance of this /// public API wrapper. explicit Window(const private_api::WindowAdapterRc &windowrc) : inner(windowrc) { } Window(const Window &other) = delete; Window &operator=(const Window &other) = delete; Window(Window &&other) = delete; Window &operator=(Window &&other) = delete; /// Destroys this window. Window instances are explicitly shared and reference counted. /// If this window instance is the last one referencing the window towards the windowing /// system, then it will also become hidden and destroyed. ~Window() = default; /// Shows the window on the screen. An additional strong reference on the /// associated component is maintained while the window is visible. /// /// Call hide() to make the window invisible again, and drop the additional /// strong reference. void show() { private_api::assert_main_thread(); inner.show(); } /// Hides the window, so that it is not visible anymore. The additional strong /// reference on the associated component, that was created when show() was called, is /// dropped. void hide() { private_api::assert_main_thread(); inner.hide(); } /// Returns the visibility state of the window. This function can return false even if you /// previously called show() on it, for example if the user minimized the window. bool is_visible() const { private_api::assert_main_thread(); return inner.is_visible(); } /// This function allows registering a callback that's invoked during the different phases of /// rendering. This allows custom rendering on top or below of the scene. /// /// The provided callback must be callable with a slint::RenderingState and the /// slint::GraphicsAPI argument. /// /// On success, the function returns a std::optional without value. On error, the function /// returns the error code as value in the std::optional. template F> std::optional set_rendering_notifier(F &&callback) const { private_api::assert_main_thread(); return inner.set_rendering_notifier(std::forward(callback)); } /// This function allows registering a callback that's invoked when the user tries to close /// a window. /// The callback has to return a CloseRequestResponse. // clang-format off template requires(std::is_convertible_v, CloseRequestResponse>) void on_close_requested(F &&callback) const // clang-format on { private_api::assert_main_thread(); return inner.on_close_requested(std::forward(callback)); } /// This function issues a request to the windowing system to redraw the contents of the window. void request_redraw() const { private_api::assert_main_thread(); inner.request_redraw(); } /// Returns the position of the window on the screen, in physical screen coordinates and /// including a window frame (if present). slint::PhysicalPosition position() const { private_api::assert_main_thread(); return inner.position(); } /// Sets the position of the window on the screen, in physical screen coordinates and including /// a window frame (if present). /// Note that on some windowing systems, such as Wayland, this functionality is not available. void set_position(const slint::LogicalPosition &pos) { private_api::assert_main_thread(); inner.set_logical_position(pos); } /// Sets the position of the window on the screen, in physical screen coordinates and including /// a window frame (if present). /// Note that on some windowing systems, such as Wayland, this functionality is not available. void set_position(const slint::PhysicalPosition &pos) { private_api::assert_main_thread(); inner.set_physical_position(pos); } /// Returns the size of the window on the screen, in physical screen coordinates and excluding /// a window frame (if present). slint::PhysicalSize size() const { private_api::assert_main_thread(); return inner.size(); } /// Resizes the window to the specified size on the screen, in logical pixels and excluding /// a window frame (if present). void set_size(const slint::LogicalSize &size) { private_api::assert_main_thread(); inner.set_logical_size(size); } /// Resizes the window to the specified size on the screen, in physical pixels and excluding /// a window frame (if present). void set_size(const slint::PhysicalSize &size) { private_api::assert_main_thread(); inner.set_physical_size(size); } /// This function returns the scale factor that allows converting between logical and /// physical pixels. float scale_factor() const { private_api::assert_main_thread(); return inner.scale_factor(); } /// Returns if the window is currently fullscreen bool is_fullscreen() const { private_api::assert_main_thread(); return cbindgen_private::slint_windowrc_is_fullscreen(&inner.handle()); } /// Set or unset the window to display fullscreen. void set_fullscreen(bool fullscreen) { private_api::assert_main_thread(); cbindgen_private::slint_windowrc_set_fullscreen(&inner.handle(), fullscreen); } /// Returns if the window is currently maximized bool is_maximized() const { private_api::assert_main_thread(); return cbindgen_private::slint_windowrc_is_maximized(&inner.handle()); } /// Maximize or unmaximize the window. void set_maximized(bool maximized) { private_api::assert_main_thread(); cbindgen_private::slint_windowrc_set_maximized(&inner.handle(), maximized); } /// Returns if the window is currently minimized bool is_minimized() const { private_api::assert_main_thread(); return cbindgen_private::slint_windowrc_is_minimized(&inner.handle()); } /// Minimize or unminimze the window. void set_minimized(bool minimized) { private_api::assert_main_thread(); cbindgen_private::slint_windowrc_set_minimized(&inner.handle(), minimized); } /// Dispatch a key press event to the scene. /// /// Use this when you're implementing your own backend and want to forward user input events. /// /// The \a text is the unicode representation of the key. void dispatch_key_press_event(const SharedString &text) { private_api::assert_main_thread(); cbindgen_private::slint_windowrc_dispatch_key_event( &inner.handle(), cbindgen_private::KeyEventType::KeyPressed, &text, false); } /// Dispatch an auto-repeated key press event to the scene. /// /// Use this when you're implementing your own backend and want to forward user input events. /// /// The \a text is the unicode representation of the key. void dispatch_key_press_repeat_event(const SharedString &text) { private_api::assert_main_thread(); cbindgen_private::slint_windowrc_dispatch_key_event( &inner.handle(), cbindgen_private::KeyEventType::KeyPressed, &text, true); } /// Dispatch a key release event to the scene. /// /// Use this when you're implementing your own backend and want to forward user input events. /// /// The \a text is the unicode representation of the key. void dispatch_key_release_event(const SharedString &text) { private_api::assert_main_thread(); cbindgen_private::slint_windowrc_dispatch_key_event( &inner.handle(), cbindgen_private::KeyEventType::KeyReleased, &text, false); } /// Dispatches a pointer or mouse press event to the scene. /// /// Use this function when you're implementing your own backend and want to forward user /// pointer/mouse events. /// /// \a pos represents the logical position of the pointer relative to the window. /// \a button is the button that was pressed. void dispatch_pointer_press_event(LogicalPosition pos, PointerEventButton button) { private_api::assert_main_thread(); inner.dispatch_pointer_event( slint::cbindgen_private::MouseEvent::Pressed({ pos.x, pos.y }, button, 0, false)); } /// Dispatches a pointer or mouse release event to the scene. /// /// Use this function when you're implementing your own backend and want to forward user /// pointer/mouse events. /// /// \a pos represents the logical position of the pointer relative to the window. /// \a button is the button that was released. void dispatch_pointer_release_event(LogicalPosition pos, PointerEventButton button) { private_api::assert_main_thread(); inner.dispatch_pointer_event( slint::cbindgen_private::MouseEvent::Released({ pos.x, pos.y }, button, 0, false)); } /// Dispatches a pointer exit event to the scene. /// /// Use this function when you're implementing your own backend and want to forward user /// pointer/mouse events. /// /// This event is triggered when the pointer exits the window. void dispatch_pointer_exit_event() { private_api::assert_main_thread(); inner.dispatch_pointer_event(slint::cbindgen_private::MouseEvent::Exit()); } /// Dispatches a pointer move event to the scene. /// /// Use this function when you're implementing your own backend and want to forward user /// pointer/mouse events. /// /// \a pos represents the logical position of the pointer relative to the window. void dispatch_pointer_move_event(LogicalPosition pos) { private_api::assert_main_thread(); inner.dispatch_pointer_event( slint::cbindgen_private::MouseEvent::Moved({ pos.x, pos.y }, false)); } /// Dispatches a scroll (or wheel) event to the scene. /// /// Use this function when you're implementing your own backend and want to forward user wheel /// events. /// /// \a parameter represents the logical position of the pointer relative to the window. /// \a delta_x and \a delta_y represent the scroll delta values in the X and Y /// directions in logical pixels. void dispatch_pointer_scroll_event(LogicalPosition pos, float delta_x, float delta_y) { private_api::assert_main_thread(); inner.dispatch_pointer_event( slint::cbindgen_private::MouseEvent::Wheel({ pos.x, pos.y }, delta_x, delta_y)); } /// Set the logical size of this window after a resize event /// /// The backend must send this event to ensure that the `width` and `height` property of the /// root Window element are properly set. void dispatch_resize_event(slint::LogicalSize s) { private_api::assert_main_thread(); using slint::cbindgen_private::WindowEvent; WindowEvent event { .resized = WindowEvent::Resized_Body { .tag = WindowEvent::Tag::Resized, .size = { s.width, s.height } } }; cbindgen_private::slint_windowrc_dispatch_event(&inner.handle(), &event); } /// The window's scale factor has changed. This can happen for example when the display's /// resolution changes, the user selects a new scale factor in the system settings, or the /// window is moved to a different screen. Platform implementations should dispatch this event /// also right after the initial window creation, to set the initial scale factor the windowing /// system provided for the window. void dispatch_scale_factor_change_event(float factor) { private_api::assert_main_thread(); using slint::cbindgen_private::WindowEvent; WindowEvent event { .scale_factor_changed = WindowEvent::ScaleFactorChanged_Body { .tag = WindowEvent::Tag::ScaleFactorChanged, .scale_factor = factor } }; cbindgen_private::slint_windowrc_dispatch_event(&inner.handle(), &event); } /// The Window was activated or de-activated. /// /// The backend should dispatch this event with true when the window gains focus /// and false when the window loses focus. void dispatch_window_active_changed_event(bool active) { private_api::assert_main_thread(); using slint::cbindgen_private::WindowEvent; WindowEvent event { .window_active_changed = WindowEvent::WindowActiveChanged_Body { .tag = WindowEvent::Tag::WindowActiveChanged, ._0 = active } }; cbindgen_private::slint_windowrc_dispatch_event(&inner.handle(), &event); } /// The user requested to close the window. /// /// The backend should send this event when the user tries to close the window,for example by /// pressing the close button. /// /// This will have the effect of invoking the callback set in Window::on_close_requested() and /// then hiding the window depending on the return value of the callback. void dispatch_close_requested_event() { private_api::assert_main_thread(); using slint::cbindgen_private::WindowEvent; WindowEvent event { .tag = WindowEvent::Tag::CloseRequested }; cbindgen_private::slint_windowrc_dispatch_event(&inner.handle(), &event); } /// Returns true if there is an animation currently active on any property in the Window. bool has_active_animations() const { private_api::assert_main_thread(); return cbindgen_private::slint_windowrc_has_active_animations(&inner.handle()); } /// Takes a snapshot of the window contents and returns it as RGBA8 encoded pixel buffer. /// /// Note that this function may be slow to call as it may need to re-render the scene. std::optional> take_snapshot() const { SharedPixelBuffer result; if (cbindgen_private::slint_windowrc_take_snapshot(&inner.handle(), &result.m_data, &result.m_width, &result.m_height)) { return result; } else { return {}; } } #if (!defined(__APPLE__) && !defined(_WIN32) && !defined(_WIN64) \ && !defined(SLINT_FEATURE_FREESTANDING)) \ || defined(DOXYGEN) /// Returns the wl_surface for this window. /// /// If the underlying window handle hasn't been created yet or isn't applicable for the /// platform, this will return nullptr. wl_surface *wayland_surface() const { return static_cast( cbindgen_private::slint_windowrc_wlsurface_wayland(&inner.handle())); } /// Returns the wl_display for this window. /// /// If the underlying window handle hasn't been created yet or isn't applicable for the /// platform, this will return nullptr. wl_display *wayland_display() const { return static_cast( cbindgen_private::slint_windowrc_wldisplay_wayland(&inner.handle())); } #endif #if (defined(__APPLE__) && !defined(_WIN32) && !defined(_WIN64) \ && !defined(SLINT_FEATURE_FREESTANDING)) \ || defined(DOXYGEN) /// Returns the NSView for this window. /// /// If the underlying window handle hasn't been created yet or isn't applicable for the /// platform, this will return nullptr. NSView *appkit_view() const { return static_cast( cbindgen_private::slint_windowrc_nsview_appkit(&inner.handle())); } #endif #if (!defined(__APPLE__) && (defined(_WIN32) || defined(_WIN64)) \ && !defined(SLINT_FEATURE_FREESTANDING)) \ || defined(DOXYGEN) /// Returns the HINSTANCE for this window. /// /// If the underlying window handle hasn't been created yet or isn't applicable for the /// platform, this will return nullptr. HWND win32_hwnd() const { return static_cast(cbindgen_private::slint_windowrc_hwnd_win32(&inner.handle())); } /// Returns the HINSTANCE for this window. /// /// If the underlying window handle hasn't been created yet or isn't applicable for the /// platform, this will return nullptr. HINSTANCE win32_hinstance() const { return static_cast( cbindgen_private::slint_windowrc_hinstance_win32(&inner.handle())); } #endif /// \private private_api::WindowAdapterRc &window_handle() { return inner; } /// \private const private_api::WindowAdapterRc &window_handle() const { return inner; } private: private_api::WindowAdapterRc inner; }; } ================================================ FILE: api/cpp/include/vtable.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once #include #include #include #include #include #ifdef __APPLE__ # include #endif #if defined(__GNUC__) || defined(__clang__) // In C++17, it is conditionally supported, but still valid for all compiler we care # pragma GCC diagnostic ignored "-Winvalid-offsetof" #endif namespace vtable { template struct VRefMut { const T *vtable; void *instance; }; struct Layout { std::size_t size; std::size_t align; }; // For the C++'s purpose, they are all the same template using VRef = VRefMut; template using Pin = T; template struct VBox { const T *vtable = nullptr; void *instance = nullptr; explicit VBox(const T *vtable, void *instance) : vtable(vtable), instance(instance) { } VBox(const VBox &) = delete; VBox() = default; VBox &operator=(const VBox &) = delete; ~VBox() { if (vtable && instance) { vtable->drop({ vtable, instance }); } } }; struct AllowPin; template struct VOffset { const T *vtable; std::uintptr_t offset; }; template struct VRcInner { template friend class VRc; template friend class VWeak; private: VRcInner() : layout {} { } const VTable *vtable = &X::static_vtable; std::atomic strong_ref = 1; std::atomic weak_ref = 1; std::uint16_t data_offset = offsetof(VRcInner, data); union { X data; Layout layout; }; void *data_ptr() { return reinterpret_cast(this) + data_offset; } ~VRcInner() = delete; }; struct Dyn { }; template class VRc { VRcInner *inner; VRc(VRcInner *inner) : inner(inner) { } template friend class VWeak; public: ~VRc() { if (!--inner->strong_ref) { Layout layout = inner->vtable->drop_in_place({ inner->vtable, &inner->data }); layout.size = std::max(layout.size, sizeof(Layout)); // because of the union layout.size += inner->data_offset; layout.align = std::max(layout.align, alignof(VRcInner)); inner->layout = layout; if (!--inner->weak_ref) { inner->vtable->dealloc(inner->vtable, reinterpret_cast(inner), layout); } } } VRc(const VRc &other) : inner(other.inner) { inner->strong_ref++; } VRc &operator=(const VRc &other) { if (inner == other.inner) return *this; this->~VRc(); new (this) VRc(other); return *this; } /// Construct a new VRc holding an X. /// /// The type X must have a static member `static_vtable` of type VTable template static VRc make(Args... args) { #if !defined(__APPLE__) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14 auto mem = ::operator new(sizeof(VRcInner), static_cast(alignof(VRcInner))); #else auto mem = ::operator new(sizeof(VRcInner)); #endif auto inner = new (mem) VRcInner; new (&inner->data) X(args...); return VRc(inner); } const X *operator->() const { return &inner->data; } const X &operator*() const { return inner->data; } X *operator->() { return &inner->data; } X &operator*() { return inner->data; } const VRc &into_dyn() const { return *reinterpret_cast *>(this); } VRef borrow() const { return { inner->vtable, inner->data_ptr() }; } friend bool operator==(const VRc &a, const VRc &b) { return a.inner == b.inner; } friend bool operator!=(const VRc &a, const VRc &b) { return a.inner != b.inner; } const VTable *vtable() const { return inner->vtable; } }; template class VWeak { VRcInner *inner = nullptr; public: VWeak() = default; ~VWeak() { if (inner && !--inner->weak_ref) { inner->vtable->dealloc(inner->vtable, reinterpret_cast(inner), inner->layout); } } VWeak(const VWeak &other) : inner(other.inner) { inner && inner->weak_ref++; } VWeak(const VRc &other) : inner(other.inner) { inner && inner->weak_ref++; } VWeak &operator=(const VWeak &other) { if (inner == other.inner) return *this; this->~VWeak(); new (this) VWeak(other); return *this; } std::optional> lock() const { if (!inner || inner->strong_ref == 0) return {}; inner->strong_ref++; return { VRc(inner) }; } const VWeak &into_dyn() const { return *reinterpret_cast *>(this); } friend bool operator==(const VWeak &a, const VWeak &b) { return a.inner == b.inner; } friend bool operator!=(const VWeak &a, const VWeak &b) { return a.inner != b.inner; } const VTable *vtable() const { return inner ? inner->vtable : nullptr; } }; template class VRcMapped { VRc parent_strong; MappedType *object; template friend class VWeakMapped; public: /// Constructs a pointer to MappedType that shares ownership with parent_strong. template explicit VRcMapped(VRc parent_strong, MappedType *object) : parent_strong(parent_strong.into_dyn()), object(object) { } const MappedType *operator->() const { return object; } const MappedType &operator*() const { return *object; } MappedType *operator->() { return object; } MappedType &operator*() { return *object; } }; template class VWeakMapped { VWeak parent_weak; MappedType *object = nullptr; public: VWeakMapped(const VRcMapped &strong) : parent_weak(strong.parent_strong), object(strong.object) { } VWeakMapped() = default; std::optional> lock() const { if (auto parent = parent_weak.lock()) { return VRcMapped(std::move(*parent), object); } else { return {}; } } }; template inline void dealloc(const VTable *, uint8_t *ptr, [[maybe_unused]] Layout layout) { #ifdef __cpp_sized_deallocation ::operator delete(reinterpret_cast(ptr), layout.size, static_cast(layout.align)); #elif !defined(__APPLE__) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14 ::operator delete(reinterpret_cast(ptr), static_cast(layout.align)); #else ::operator delete(reinterpret_cast(ptr)); #endif } template inline Layout drop_in_place(VRefMut item_tree) { reinterpret_cast(item_tree.instance)->~T(); return vtable::Layout { sizeof(T), alignof(T) }; } } // namespace vtable ================================================ FILE: api/cpp/lib.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 /*! This crate just exposes the function used by the C++ integration */ #![no_std] extern crate alloc; #[cfg(feature = "std")] extern crate std; use alloc::rc::Rc; use alloc::string::ToString; use core::ffi::c_void; use i_slint_core::SharedString; use i_slint_core::items::OperatingSystemType; use i_slint_core::slice::Slice; use i_slint_core::styled_text::StyledText; use i_slint_core::window::{WindowAdapter, ffi::WindowAdapterRcOpaque}; pub mod platform; #[cfg(feature = "i-slint-backend-selector")] use i_slint_backend_selector::with_platform; #[cfg(not(feature = "i-slint-backend-selector"))] pub fn with_platform( f: impl FnOnce( &dyn i_slint_core::platform::Platform, ) -> Result, ) -> Result { i_slint_core::with_platform(|| Err(i_slint_core::platform::PlatformError::NoPlatform), f) } // We need to make sure something from the crate is exported, // otherwise its symbols are not going to be in the final binary #[cfg(feature = "testing")] pub use i_slint_backend_testing; #[cfg(feature = "slint-interpreter")] pub use slint_interpreter; #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_windowrc_init(out: *mut WindowAdapterRcOpaque) { assert_eq!( core::mem::size_of::>(), core::mem::size_of::() ); let win = with_platform(|b| b.create_window_adapter()).unwrap(); unsafe { core::ptr::write(out as *mut Rc, win); } } #[unsafe(no_mangle)] pub extern "C" fn slint_ensure_backend() { with_platform(|_b| { // Nothing to do, just make sure a backend was created Ok(()) }) .unwrap() } #[unsafe(no_mangle)] /// Enters the main event loop. pub extern "C" fn slint_run_event_loop(quit_on_last_window_closed: bool) { with_platform(|b| { if !quit_on_last_window_closed { #[allow(deprecated)] b.set_event_loop_quit_on_last_window_closed(false); } b.run_event_loop() }) .unwrap(); } /// Will execute the given functor in the main thread #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_post_event( event: extern "C" fn(user_data: *mut c_void), user_data: *mut c_void, drop_user_data: Option, ) { struct UserData { user_data: *mut c_void, drop_user_data: Option, } impl Drop for UserData { fn drop(&mut self) { if let Some(x) = self.drop_user_data { x(self.user_data) } } } unsafe impl Send for UserData {} let ud = UserData { user_data, drop_user_data }; i_slint_core::api::invoke_from_event_loop(move || { let ud = &ud; event(ud.user_data); }) .unwrap(); } #[unsafe(no_mangle)] pub extern "C" fn slint_quit_event_loop() { i_slint_core::api::quit_event_loop().unwrap(); } #[cfg(feature = "std")] #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_register_font_from_path( win: *const WindowAdapterRcOpaque, path: &SharedString, error_str: &mut SharedString, ) { let window_adapter = unsafe { &*(win as *const Rc) }; *error_str = match window_adapter .renderer() .register_font_from_path(std::path::Path::new(path.as_str())) { Ok(()) => Default::default(), Err(err) => i_slint_core::string::ToSharedString::to_shared_string(&err), }; } #[cfg(feature = "std")] #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_register_font_from_data( win: *const WindowAdapterRcOpaque, data: i_slint_core::slice::Slice<'static, u8>, error_str: &mut SharedString, ) { let window_adapter = unsafe { &*(win as *const Rc) }; *error_str = match window_adapter.renderer().register_font_from_memory(data.as_slice()) { Ok(()) => Default::default(), Err(err) => i_slint_core::string::ToSharedString::to_shared_string(&err), }; } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_register_bitmap_font( win: *const WindowAdapterRcOpaque, font_data: &'static i_slint_core::graphics::BitmapFont, ) { let window_adapter = unsafe { &*(win as *const Rc) }; window_adapter.renderer().register_bitmap_font(font_data); } #[unsafe(no_mangle)] pub extern "C" fn slint_string_to_float(string: &SharedString, value: &mut f32) -> bool { match string.as_str().parse::() { Ok(v) => { *value = v; true } Err(_) => false, } } #[unsafe(no_mangle)] pub extern "C" fn slint_string_character_count(string: &SharedString) -> usize { unicode_segmentation::UnicodeSegmentation::graphemes(string.as_str(), true).count() } #[unsafe(no_mangle)] pub extern "C" fn slint_string_to_usize(string: &SharedString, value: &mut usize) -> bool { match string.as_str().parse::() { Ok(v) => { *value = v; true } Err(_) => false, } } #[unsafe(no_mangle)] pub extern "C" fn slint_debug(string: &SharedString) { i_slint_core::debug_log!("{string}"); } #[cfg(not(feature = "std"))] mod allocator { use core::alloc::Layout; use core::ffi::c_void; struct CAlloc; unsafe impl core::alloc::GlobalAlloc for CAlloc { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { unsafe extern "C" { pub fn malloc(size: usize) -> *mut c_void; } unsafe { let align = layout.align(); if align <= core::mem::size_of::() { malloc(layout.size()) as *mut u8 } else { // Ideally we'd use aligned_alloc, but that function caused heap corruption with esp-idf let ptr = malloc(layout.size() + align) as *mut u8; let shift = align - (ptr as usize % align); let ptr = ptr.add(shift); core::ptr::write(ptr.sub(1), shift as u8); ptr } } } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { let align = layout.align(); unsafe extern "C" { pub fn free(p: *mut c_void); } unsafe { if align <= core::mem::size_of::() { free(ptr as *mut c_void); } else { let shift = core::ptr::read(ptr.sub(1)) as usize; free(ptr.sub(shift) as *mut c_void); } } } } #[global_allocator] static ALLOCATOR: CAlloc = CAlloc; } #[cfg(all(not(feature = "std"), not(feature = "esp-backtrace")))] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} } #[cfg(feature = "esp-backtrace")] use esp_backtrace as _; #[unsafe(no_mangle)] pub extern "C" fn slint_set_xdg_app_id(_app_id: &SharedString) { #[cfg(feature = "i-slint-backend-selector")] i_slint_backend_selector::with_global_context(|ctx| ctx.set_xdg_app_id(_app_id.clone())) .unwrap(); } #[unsafe(no_mangle)] pub extern "C" fn slint_detect_operating_system() -> OperatingSystemType { i_slint_core::detect_operating_system() } #[unsafe(no_mangle)] pub extern "C" fn slint_parse_markdown( format_string: &SharedString, args: Slice, out: &mut StyledText, ) { *out = i_slint_core::styled_text::parse_markdown(format_string, &args); } #[unsafe(no_mangle)] pub extern "C" fn slint_string_to_styled_text(text: SharedString, out: &mut StyledText) { *out = i_slint_core::styled_text::string_to_styled_text(text.to_string()); } // Translator API is currently considered experimental due to discussions // about the returned string type (SharedString vs. Cow etc.). Also it // is not available with no_std due to the tr crate. // See dicussion in https://github.com/slint-ui/slint/pull/10979. #[cfg(all(feature = "experimental", feature = "std"))] mod translator { use crate::SharedString; use crate::Slice; use alloc::boxed::Box; use core::ffi::c_void; use i_slint_core::translations::Translator; use std::borrow::Cow; type DropCallback = extern "C" fn(obj: *const c_void); type TranslateCallback = extern "C" fn( obj: *const c_void, string: Slice, context: Slice, out: &mut SharedString, ); type NTranslateCallback = extern "C" fn( obj: *const c_void, n: u64, singular: Slice, plural: Slice, context: Slice, out: &mut SharedString, ); struct CppTranslator { pub obj: *const c_void, pub drop: DropCallback, pub translate: TranslateCallback, pub ntranslate: NTranslateCallback, } unsafe impl Send for CppTranslator {} unsafe impl Sync for CppTranslator {} impl Drop for CppTranslator { fn drop(&mut self) { (self.drop)(self.obj); } } impl Translator for CppTranslator { fn translate<'a>(&'a self, string: &'a str, context: Option<&'a str>) -> Cow<'a, str> { let mut out = SharedString::new(); (self.translate)( self.obj, string.as_bytes().into(), context.unwrap_or_default().as_bytes().into(), &mut out, ); Cow::Owned(out.into()) } fn ntranslate<'a>( &'a self, n: u64, singular: &'a str, plural: &'a str, context: Option<&'a str>, ) -> Cow<'a, str> { let mut out = SharedString::new(); (self.ntranslate)( self.obj, n, singular.as_bytes().into(), plural.as_bytes().into(), context.unwrap_or_default().as_bytes().into(), &mut out, ); Cow::Owned(out.into()) } } #[unsafe(no_mangle)] pub extern "C" fn slint_translate_set_translator( obj: *const c_void, drop: DropCallback, translate: TranslateCallback, ntranslate: NTranslateCallback, ) -> bool { #[cfg(feature = "i-slint-backend-selector")] i_slint_backend_selector::with_global_context(|ctx| { if !obj.is_null() { ctx.set_external_translator(Some(Box::new(CppTranslator { obj, drop, translate, ntranslate, }))) } else { ctx.set_external_translator(None) } }) .is_ok() } } ================================================ FILE: api/cpp/platform.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use alloc::rc::Rc; use alloc::{boxed::Box, string::String}; use core::ffi::c_void; use i_slint_core::api::{ LogicalSize, PhysicalPosition, PhysicalSize, Window, WindowPosition, WindowSize, }; use i_slint_core::graphics::IntSize; use i_slint_core::graphics::euclid; use i_slint_core::platform::{Clipboard, Platform, PlatformError}; use i_slint_core::renderer::Renderer; use i_slint_core::window::ffi::WindowAdapterRcOpaque; use i_slint_core::window::{WindowAdapter, WindowProperties}; use i_slint_core::{Brush, SharedString}; type WindowAdapterUserData = *mut c_void; // FIXME wrapper over &dyn Renderer #[repr(C)] pub struct RendererPtr { _a: *const c_void, _b: *const c_void, } pub struct CppWindowAdapter { window: Window, user_data: WindowAdapterUserData, drop: unsafe extern "C" fn(WindowAdapterUserData), /// Safety: the returned pointer must live for the lifetime of self get_renderer_ref: unsafe extern "C" fn(WindowAdapterUserData) -> RendererPtr, set_visible: unsafe extern "C" fn(WindowAdapterUserData, bool), request_redraw: unsafe extern "C" fn(WindowAdapterUserData), size: unsafe extern "C" fn(WindowAdapterUserData) -> IntSize, set_size: unsafe extern "C" fn(WindowAdapterUserData, IntSize), update_window_properties: unsafe extern "C" fn(WindowAdapterUserData, &WindowProperties), position: unsafe extern "C" fn(WindowAdapterUserData, &mut euclid::default::Point2D) -> bool, set_position: unsafe extern "C" fn(WindowAdapterUserData, euclid::default::Point2D), } impl Drop for CppWindowAdapter { fn drop(&mut self) { unsafe { (self.drop)(self.user_data) }; } } impl WindowAdapter for CppWindowAdapter { fn window(&self) -> &Window { &self.window } fn set_visible(&self, visible: bool) -> Result<(), PlatformError> { unsafe { (self.set_visible)(self.user_data, visible) }; Ok(()) } fn position(&self) -> Option { let mut pos = euclid::default::Point2D::::default(); if unsafe { (self.position)(self.user_data, &mut pos) } { Some(i_slint_core::graphics::ffi::physical_position_to_api(pos)) } else { None } } fn set_position(&self, position: WindowPosition) { let physical_position = i_slint_core::graphics::ffi::physical_position_from_api( position.to_physical(self.window.scale_factor()), ); unsafe { (self.set_position)(self.user_data, physical_position) } } fn set_size(&self, size: WindowSize) { let physical_size = i_slint_core::graphics::ffi::physical_size_from_api( size.to_physical(self.window.scale_factor()), ); unsafe { (self.set_size)(self.user_data, physical_size) } } fn size(&self) -> PhysicalSize { let s = unsafe { (self.size)(self.user_data) }; PhysicalSize::new(s.width, s.height) } fn renderer(&self) -> &dyn Renderer { unsafe { core::mem::transmute((self.get_renderer_ref)(self.user_data)) } } fn request_redraw(&self) { unsafe { (self.request_redraw)(self.user_data) } } fn update_window_properties(&self, properties: WindowProperties<'_>) { unsafe { (self.update_window_properties)(self.user_data, &properties) } } } #[unsafe(no_mangle)] pub extern "C" fn slint_window_properties_get_title(wp: &WindowProperties, out: &mut SharedString) { *out = wp.title(); } #[unsafe(no_mangle)] pub extern "C" fn slint_window_properties_get_background(wp: &WindowProperties, out: &mut Brush) { *out = wp.background(); } #[unsafe(no_mangle)] pub extern "C" fn slint_window_properties_get_fullscreen(wp: &WindowProperties) -> bool { wp.is_fullscreen() } #[unsafe(no_mangle)] pub extern "C" fn slint_window_properties_get_minimized(wp: &WindowProperties) -> bool { wp.is_minimized() } #[unsafe(no_mangle)] pub extern "C" fn slint_window_properties_get_maximized(wp: &WindowProperties) -> bool { wp.is_maximized() } #[repr(C)] #[derive(Clone, Copy)] /// a Repr(C) variant of slint::platform::LayoutConstraints pub struct LayoutConstraintsReprC { pub min: i_slint_core::graphics::Size, pub max: i_slint_core::graphics::Size, pub preferred: i_slint_core::graphics::Size, pub has_min: bool, pub has_max: bool, } #[unsafe(no_mangle)] pub extern "C" fn slint_window_properties_get_layout_constraints( wp: &WindowProperties, ) -> LayoutConstraintsReprC { let c = wp.layout_constraints(); LayoutConstraintsReprC { min: i_slint_core::lengths::logical_size_from_api(c.min.unwrap_or_default()).to_untyped(), max: i_slint_core::lengths::logical_size_from_api( c.max.unwrap_or(LogicalSize { width: f32::MAX, height: f32::MAX }), ) .to_untyped(), preferred: i_slint_core::lengths::logical_size_from_api(c.preferred).to_untyped(), has_min: c.min.is_some(), has_max: c.max.is_some(), } } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_window_adapter_new( user_data: WindowAdapterUserData, drop: unsafe extern "C" fn(WindowAdapterUserData), get_renderer_ref: unsafe extern "C" fn(WindowAdapterUserData) -> RendererPtr, set_visible: unsafe extern "C" fn(WindowAdapterUserData, bool), request_redraw: unsafe extern "C" fn(WindowAdapterUserData), size: unsafe extern "C" fn(WindowAdapterUserData) -> IntSize, set_size: unsafe extern "C" fn(WindowAdapterUserData, IntSize), update_window_properties: unsafe extern "C" fn(WindowAdapterUserData, &WindowProperties), position: unsafe extern "C" fn( WindowAdapterUserData, &mut euclid::default::Point2D, ) -> bool, set_position: unsafe extern "C" fn(WindowAdapterUserData, euclid::default::Point2D), target: *mut WindowAdapterRcOpaque, ) { let window = Rc::::new_cyclic(|w| CppWindowAdapter { window: Window::new(w.clone()), user_data, drop, get_renderer_ref, set_visible, request_redraw, size, set_size, update_window_properties, position, set_position, }); unsafe { core::ptr::write(target as *mut Rc, window); } } type PlatformUserData = *mut c_void; struct CppPlatform { user_data: PlatformUserData, drop: unsafe extern "C" fn(PlatformUserData), window_factory: unsafe extern "C" fn(PlatformUserData, *mut WindowAdapterRcOpaque), #[cfg(not(feature = "std"))] duration_since_start: unsafe extern "C" fn(PlatformUserData) -> u64, // silent the warning despite `Clipboard` is a `#[non_exhaustive]` enum from another crate. #[allow(improper_ctypes_definitions)] set_clipboard_text: unsafe extern "C" fn(PlatformUserData, &SharedString, Clipboard), #[allow(improper_ctypes_definitions)] clipboard_text: unsafe extern "C" fn(PlatformUserData, &mut SharedString, Clipboard) -> bool, run_event_loop: unsafe extern "C" fn(PlatformUserData), quit_event_loop: unsafe extern "C" fn(PlatformUserData), invoke_from_event_loop: unsafe extern "C" fn(PlatformUserData, PlatformTaskOpaque), } impl Drop for CppPlatform { fn drop(&mut self) { unsafe { (self.drop)(self.user_data) }; } } impl Platform for CppPlatform { fn create_window_adapter(&self) -> Result, PlatformError> { let mut uninit = core::mem::MaybeUninit::>::uninit(); unsafe { (self.window_factory)( self.user_data, uninit.as_mut_ptr() as *mut WindowAdapterRcOpaque, ); Ok(uninit.assume_init()) } } #[cfg(not(feature = "std"))] fn duration_since_start(&self) -> core::time::Duration { core::time::Duration::from_millis(unsafe { (self.duration_since_start)(self.user_data) }) } fn run_event_loop(&self) -> Result<(), PlatformError> { unsafe { (self.run_event_loop)(self.user_data) }; Ok(()) } fn new_event_loop_proxy(&self) -> Option> { Some(Box::new(CppEventLoopProxy { user_data: self.user_data, quit_event_loop: self.quit_event_loop, invoke_from_event_loop: self.invoke_from_event_loop, })) } fn set_clipboard_text(&self, text: &str, clipboard: Clipboard) { let shared_text = SharedString::from(text); unsafe { (self.set_clipboard_text)(self.user_data, &shared_text, clipboard) } } fn clipboard_text(&self, clipboard: Clipboard) -> Option { let mut out_text = SharedString::new(); let status = unsafe { (self.clipboard_text)(self.user_data, &mut out_text, clipboard) }; status.then(|| out_text.into()) } #[cfg(feature = "esp-println")] fn debug_log(&self, arguments: core::fmt::Arguments) { esp_println::println!("{arguments}"); } } struct CppEventLoopProxy { user_data: PlatformUserData, quit_event_loop: unsafe extern "C" fn(PlatformUserData), invoke_from_event_loop: unsafe extern "C" fn(PlatformUserData, PlatformTaskOpaque), } impl i_slint_core::platform::EventLoopProxy for CppEventLoopProxy { fn quit_event_loop(&self) -> Result<(), i_slint_core::api::EventLoopError> { unsafe { (self.quit_event_loop)(self.user_data) }; Ok(()) } fn invoke_from_event_loop( &self, event: Box, ) -> Result<(), i_slint_core::api::EventLoopError> { unsafe { (self.invoke_from_event_loop)( self.user_data, core::mem::transmute::<*mut dyn FnOnce(), PlatformTaskOpaque>(Box::into_raw(event)), ) }; Ok(()) } } unsafe impl Send for CppEventLoopProxy {} unsafe impl Sync for CppEventLoopProxy {} // silent the warning despite `Clipboard` is a `#[non_exhaustive]` enum from another crate. #[allow(improper_ctypes_definitions)] #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_platform_register( user_data: PlatformUserData, drop: unsafe extern "C" fn(PlatformUserData), window_factory: unsafe extern "C" fn(PlatformUserData, *mut WindowAdapterRcOpaque), #[allow(unused)] duration_since_start: unsafe extern "C" fn(PlatformUserData) -> u64, set_clipboard_text: unsafe extern "C" fn(PlatformUserData, &SharedString, Clipboard), clipboard_text: unsafe extern "C" fn(PlatformUserData, &mut SharedString, Clipboard) -> bool, run_event_loop: unsafe extern "C" fn(PlatformUserData), quit_event_loop: unsafe extern "C" fn(PlatformUserData), invoke_from_event_loop: unsafe extern "C" fn(PlatformUserData, PlatformTaskOpaque), ) { let p = CppPlatform { user_data, drop, window_factory, #[cfg(not(feature = "std"))] duration_since_start, set_clipboard_text, clipboard_text, run_event_loop, quit_event_loop, invoke_from_event_loop, }; i_slint_core::platform::set_platform(Box::new(p)).unwrap(); } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_windowrc_has_active_animations( handle: *const WindowAdapterRcOpaque, ) -> bool { let window_adapter = unsafe { &*(handle as *const Rc) }; window_adapter.window().has_active_animations() } #[unsafe(no_mangle)] pub extern "C" fn slint_platform_update_timers_and_animations() { i_slint_core::platform::update_timers_and_animations() } /// Returns the duration in millisecond until the next timer or `u64::MAX` if there is no pending timers #[unsafe(no_mangle)] pub extern "C" fn slint_platform_duration_until_next_timer_update() -> u64 { i_slint_core::platform::duration_until_next_timer_update() .map_or(u64::MAX, |d| d.as_millis() as u64) } #[repr(C)] pub struct PlatformTaskOpaque(*const c_void, *const c_void); #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_platform_task_drop(event: PlatformTaskOpaque) { unsafe { drop(Box::from_raw(core::mem::transmute::(event))); } } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_platform_task_run(event: PlatformTaskOpaque) { unsafe { let f = Box::from_raw(core::mem::transmute::(event)); f(); } } #[cfg(feature = "renderer-software")] mod software_renderer { use super::*; type SoftwareRendererOpaque = *const c_void; use i_slint_core::SharedVector; use i_slint_core::graphics::{IntRect, Rgb8Pixel}; use i_slint_renderer_software::{ PhysicalRegion, RepaintBufferType, Rgb565Pixel, SoftwareRenderer, }; #[cfg(feature = "experimental")] use i_slint_renderer_software::{TargetPixelBuffer, TexturePixelFormat}; #[cfg(feature = "experimental")] type CppTargetPixelBufferUserData = *mut c_void; #[cfg(feature = "experimental")] #[repr(C)] pub struct DrawTextureArgs { pub image_data: *const u8, pub pixel_format: TexturePixelFormat, pub byte_stride: usize, pub width: u32, pub height: u32, pub colorize: i_slint_core::Color, pub alpha: u8, pub dst_x: isize, pub dst_y: isize, pub dst_width: usize, pub dst_height: usize, /// 0, 90, 180, or 270 pub rotation: i32, pub has_tiling: bool, pub tiling_offset_x: i32, pub tiling_offset_y: i32, pub tiling_scale_x: f32, pub tiling_scale_y: f32, pub tiling_gap_x: u32, pub tiling_gap_y: u32, } #[cfg(feature = "experimental")] impl From<&i_slint_renderer_software::DrawTextureArgs> for DrawTextureArgs { fn from(from: &i_slint_renderer_software::DrawTextureArgs) -> Self { let source = from.source(); Self { image_data: source.data.as_ptr(), pixel_format: source.pixel_format, byte_stride: source.byte_stride, width: source.width, height: source.height, colorize: from.colorize.unwrap_or_default(), alpha: from.alpha, dst_x: from.dst_x, dst_y: from.dst_y, dst_width: from.dst_width, dst_height: from.dst_height, rotation: from.rotation.angle() as _, has_tiling: from.tiling.is_some(), tiling_offset_x: from.tiling.as_ref().map(|t| t.offset_x).unwrap_or_default(), tiling_offset_y: from.tiling.as_ref().map(|t| t.offset_y).unwrap_or_default(), tiling_scale_x: from.tiling.as_ref().map(|t| t.scale_x).unwrap_or_default(), tiling_scale_y: from.tiling.as_ref().map(|t| t.scale_y).unwrap_or_default(), tiling_gap_x: from.tiling.as_ref().map(|t| t.gap_x).unwrap_or_default(), tiling_gap_y: from.tiling.as_ref().map(|t| t.gap_y).unwrap_or_default(), } } } #[cfg(feature = "experimental")] #[repr(C)] pub struct DrawRectangleArgs { pub x: f32, pub y: f32, pub width: f32, pub height: f32, pub top_left_radius: f32, pub top_right_radius: f32, pub bottom_right_radius: f32, pub bottom_left_radius: f32, pub border_width: f32, pub background: Brush, pub border: Brush, pub alpha: u8, /// 0, 90, 180, or 270 pub rotation: i32, } #[cfg(feature = "experimental")] impl From<&i_slint_renderer_software::DrawRectangleArgs> for DrawRectangleArgs { fn from(from: &i_slint_renderer_software::DrawRectangleArgs) -> Self { Self { x: from.x, y: from.y, width: from.width, height: from.height, top_left_radius: from.top_left_radius, top_right_radius: from.top_right_radius, bottom_right_radius: from.bottom_right_radius, bottom_left_radius: from.bottom_left_radius, border_width: from.border_width, background: from.background.clone(), border: from.border.clone(), alpha: from.alpha, rotation: from.rotation.angle() as _, } } } #[repr(C)] #[cfg(feature = "experimental")] pub struct CppTargetPixelBuffer { user_data: CppTargetPixelBufferUserData, line_slice: unsafe extern "C" fn( CppTargetPixelBufferUserData, usize, slice_ptr: &mut *mut T, slice_len: *mut usize, ), num_lines: unsafe extern "C" fn(CppTargetPixelBufferUserData) -> usize, fill_background: unsafe extern "C" fn(CppTargetPixelBufferUserData, &Brush, &PhysicalRegion) -> bool, draw_rectangle: unsafe extern "C" fn( CppTargetPixelBufferUserData, &DrawRectangleArgs, &PhysicalRegion, ) -> bool, draw_texture: unsafe extern "C" fn( CppTargetPixelBufferUserData, &DrawTextureArgs, &PhysicalRegion, ) -> bool, } #[cfg(feature = "experimental")] impl TargetPixelBuffer for CppTargetPixelBuffer { type TargetPixel = TargetPixel; fn line_slice(&mut self, line_number: usize) -> &mut [Self::TargetPixel] { unsafe { let mut data = core::ptr::null_mut(); let mut len = 0; (self.line_slice)(self.user_data, line_number, &mut data, &mut len); core::slice::from_raw_parts_mut(data, len) } } fn num_lines(&self) -> usize { unsafe { (self.num_lines)(self.user_data) } } /// Fill the background of the buffer with the given brush. fn fill_background( &mut self, brush: &i_slint_core::Brush, region: &PhysicalRegion, ) -> bool { unsafe { (self.fill_background)(self.user_data, brush, region) } } /// Draw a rectangle specified by the DrawRectangleArgs. That rectangle must be clipped to the given region fn draw_rectangle( &mut self, args: &i_slint_renderer_software::DrawRectangleArgs, clip: &PhysicalRegion, ) -> bool { let args = args.into(); unsafe { (self.draw_rectangle)(self.user_data, &args, clip) } } fn draw_texture( &mut self, texture: &i_slint_renderer_software::DrawTextureArgs, clip: &PhysicalRegion, ) -> bool { let texture = texture.into(); unsafe { (self.draw_texture)(self.user_data, &texture, clip) } } } #[unsafe(no_mangle)] pub extern "C" fn slint_software_renderer_new(buffer_age: u32) -> SoftwareRendererOpaque { let repaint_buffer_type = match buffer_age { 0 => RepaintBufferType::NewBuffer, 1 => RepaintBufferType::ReusedBuffer, 2 => RepaintBufferType::SwappedBuffers, _ => unreachable!(), }; Box::into_raw(Box::new(SoftwareRenderer::new_with_repaint_buffer_type(repaint_buffer_type))) as SoftwareRendererOpaque } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_software_renderer_drop(r: SoftwareRendererOpaque) { unsafe { drop(Box::from_raw(r as *mut SoftwareRenderer)); } } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_software_renderer_render_rgb8( r: SoftwareRendererOpaque, buffer: *mut Rgb8Pixel, buffer_len: usize, pixel_stride: usize, ) -> PhysicalRegion { unsafe { let buffer = core::slice::from_raw_parts_mut(buffer, buffer_len); let renderer = &*(r as *const SoftwareRenderer); renderer.render(buffer, pixel_stride) } } #[cfg(feature = "experimental")] #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_software_renderer_render_accel_rgb8( r: SoftwareRendererOpaque, buffer: &mut CppTargetPixelBuffer, ) -> PhysicalRegion { let renderer = unsafe { &*(r as *const SoftwareRenderer) }; renderer.render_into_buffer(buffer) } #[cfg(feature = "experimental")] #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_software_renderer_render_accel_rgb565( r: SoftwareRendererOpaque, buffer: &mut CppTargetPixelBuffer, ) -> PhysicalRegion { let renderer = unsafe { &*(r as *const SoftwareRenderer) }; renderer.render_into_buffer(buffer) } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_software_renderer_render_rgb565( r: SoftwareRendererOpaque, buffer: *mut u16, buffer_len: usize, pixel_stride: usize, ) -> PhysicalRegion { unsafe { let buffer = core::slice::from_raw_parts_mut(buffer as *mut Rgb565Pixel, buffer_len); let renderer = &*(r as *const SoftwareRenderer); renderer.render(buffer, pixel_stride) } } struct LineByLineProcessor { process_line_fn: extern "C" fn( *mut core::ffi::c_void, usize, usize, usize, extern "C" fn(*const core::ffi::c_void, *mut TargetPixel, usize), *const core::ffi::c_void, ), user_data: *mut core::ffi::c_void, } impl i_slint_renderer_software::LineBufferProvider for LineByLineProcessor { type TargetPixel = TargetPixel; fn process_line( &mut self, line: usize, range: core::ops::Range, render_fn: impl FnOnce(&mut [TargetPixel]), ) { self.cpp_process_line(line, range, render_fn); } } impl LineByLineProcessor { fn cpp_process_line( &mut self, line: usize, range: core::ops::Range, render_fn: RenderFn, ) { let mut render_fn = Some(render_fn); let render_fn_ptr = &mut render_fn as *mut Option as *const core::ffi::c_void; extern "C" fn cpp_render_line_callback< TargetPixel, RenderFn: FnOnce(&mut [TargetPixel]), >( render_fn_ptr: *const core::ffi::c_void, line_start: *mut TargetPixel, len: usize, ) { let line_slice = unsafe { core::slice::from_raw_parts_mut(line_start, len) }; let render_fn = unsafe { (*(render_fn_ptr as *mut Option)).take().unwrap() }; render_fn(line_slice); } (self.process_line_fn)( self.user_data, line, range.start, range.end, cpp_render_line_callback::, render_fn_ptr, ); } } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_software_renderer_render_by_line_rgb565( r: SoftwareRendererOpaque, process_line_fn: extern "C" fn( *mut core::ffi::c_void, usize, usize, usize, extern "C" fn(*const core::ffi::c_void, *mut Rgb565Pixel, usize), *const core::ffi::c_void, ), user_data: *mut core::ffi::c_void, ) -> PhysicalRegion { let renderer = unsafe { &*(r as *const SoftwareRenderer) }; let processor = LineByLineProcessor { process_line_fn, user_data }; renderer.render_by_line(processor) } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_software_renderer_render_by_line_rgb8( r: SoftwareRendererOpaque, process_line_fn: extern "C" fn( *mut core::ffi::c_void, usize, usize, usize, extern "C" fn(*const core::ffi::c_void, *mut Rgb8Pixel, usize), *const core::ffi::c_void, ), user_data: *mut core::ffi::c_void, ) -> PhysicalRegion { let renderer = unsafe { &*(r as *const SoftwareRenderer) }; let processor = LineByLineProcessor { process_line_fn, user_data }; renderer.render_by_line(processor) } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_software_renderer_set_rendering_rotation( r: SoftwareRendererOpaque, rotation: i32, ) { use i_slint_renderer_software::RenderingRotation; let renderer = unsafe { &*(r as *const SoftwareRenderer) }; renderer.set_rendering_rotation(match rotation { 90 => RenderingRotation::Rotate90, 180 => RenderingRotation::Rotate180, 270 => RenderingRotation::Rotate270, _ => RenderingRotation::NoRotation, }); } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_software_renderer_handle( r: SoftwareRendererOpaque, ) -> RendererPtr { unsafe { let r = (r as *const SoftwareRenderer) as *const dyn Renderer; core::mem::transmute(r) } } #[unsafe(no_mangle)] pub extern "C" fn slint_software_renderer_region_to_rects( region: &PhysicalRegion, out: &mut SharedVector, ) { *out = region .iter() .map(|r| euclid::rect(r.0.x, r.0.y, r.1.width as i32, r.1.height as i32)) .collect(); } } #[cfg(all(feature = "i-slint-renderer-skia", feature = "raw-window-handle"))] pub mod skia { use super::*; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use std::sync::Arc; struct RawHandlePair((RawWindowHandle, RawDisplayHandle)); impl raw_window_handle::HasDisplayHandle for RawHandlePair { fn display_handle( &self, ) -> Result, raw_window_handle::HandleError> { // Safety: It is assumed that the C++ side keeps the window/display handles alive. Ok(unsafe { raw_window_handle::DisplayHandle::borrow_raw(self.0.1) }) } } impl raw_window_handle::HasWindowHandle for RawHandlePair { fn window_handle( &self, ) -> Result, raw_window_handle::HandleError> { // Safety: It is assumed that the C++ side keeps the window/display handles alive. Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(self.0.0) }) } } /// Safety: This is only needed for the Skia renderer when using WGPU, which isn't supported for C++. unsafe impl std::marker::Send for RawHandlePair {} unsafe impl std::marker::Sync for RawHandlePair {} struct CppRawHandle(Arc); impl From<(RawWindowHandle, RawDisplayHandle)> for CppRawHandle { fn from(pair: (RawWindowHandle, RawDisplayHandle)) -> Self { Self(Arc::new(RawHandlePair(pair))) } } type CppRawHandleOpaque = *const c_void; #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_new_raw_window_handle_win32( hwnd: *mut c_void, _hinstance: *mut c_void, ) -> CppRawHandleOpaque { let handle = CppRawHandle::from(( RawWindowHandle::Win32(raw_window_handle::Win32WindowHandle::new( (hwnd as isize).try_into().expect("C++: NativeWindowHandle created with null hwnd"), )), RawDisplayHandle::Windows(raw_window_handle::WindowsDisplayHandle::new()), )); Box::into_raw(Box::new(handle)) as CppRawHandleOpaque } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_new_raw_window_handle_x11_xcb( window: u32, visual_id: u32, connection: *mut c_void, screen: core::ffi::c_int, ) -> CppRawHandleOpaque { use raw_window_handle::{XcbDisplayHandle, XcbWindowHandle}; let handle = CppRawHandle::from(( RawWindowHandle::Xcb({ let mut hnd = XcbWindowHandle::new( window .try_into() .expect("C++: NativeWindowHandle created with null xcb window handle"), ); hnd.visual_id = visual_id.try_into().ok(); hnd }), RawDisplayHandle::Xcb(XcbDisplayHandle::new( core::ptr::NonNull::new(connection), screen, )), )); Box::into_raw(Box::new(handle)) as CppRawHandleOpaque } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_new_raw_window_handle_x11_xlib( window: core::ffi::c_ulong, visual_id: core::ffi::c_ulong, display: *mut c_void, screen: core::ffi::c_int, ) -> CppRawHandleOpaque { use raw_window_handle::{XlibDisplayHandle, XlibWindowHandle}; let handle = CppRawHandle::from(( RawWindowHandle::Xlib({ let mut hnd = XlibWindowHandle::new(window); hnd.visual_id = visual_id; hnd }), RawDisplayHandle::Xlib(XlibDisplayHandle::new( core::ptr::NonNull::new(display), screen, )), )); Box::into_raw(Box::new(handle)) as CppRawHandleOpaque } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_new_raw_window_handle_wayland( surface: *mut c_void, display: *mut c_void, ) -> CppRawHandleOpaque { use raw_window_handle::{WaylandDisplayHandle, WaylandWindowHandle}; let handle = CppRawHandle::from(( RawWindowHandle::Wayland(WaylandWindowHandle::new( core::ptr::NonNull::new(surface).unwrap(), )), RawDisplayHandle::Wayland(WaylandDisplayHandle::new( core::ptr::NonNull::new(display).unwrap(), )), )); Box::into_raw(Box::new(handle)) as CppRawHandleOpaque } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_new_raw_window_handle_appkit( ns_view: *mut c_void, _ns_window: *mut c_void, ) -> CppRawHandleOpaque { use raw_window_handle::{AppKitDisplayHandle, AppKitWindowHandle}; let handle = CppRawHandle::from(( RawWindowHandle::AppKit(AppKitWindowHandle::new( core::ptr::NonNull::new(ns_view).unwrap(), )), RawDisplayHandle::AppKit(AppKitDisplayHandle::new()), )); Box::into_raw(Box::new(handle)) as CppRawHandleOpaque } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_raw_window_handle_drop(handle: CppRawHandleOpaque) { unsafe { drop(Box::from_raw(handle as *mut CppRawHandle)) } } type SkiaRendererOpaque = *const c_void; type SkiaRenderer = i_slint_renderer_skia::SkiaRenderer; #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_skia_renderer_new( handle_opaque: CppRawHandleOpaque, size: IntSize, ) -> SkiaRendererOpaque { let handle = unsafe { &*(handle_opaque as *const CppRawHandle) }; let boxed_renderer: Box = Box::new( SkiaRenderer::new( &i_slint_renderer_skia::SkiaSharedContext::default(), handle.0.clone(), handle.0.clone(), PhysicalSize { width: size.width, height: size.height }, ) .unwrap(), ); Box::into_raw(boxed_renderer) as SkiaRendererOpaque } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_skia_renderer_drop(r: SkiaRendererOpaque) { unsafe { drop(Box::from_raw(r as *mut SkiaRenderer)) } } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_skia_renderer_render(r: SkiaRendererOpaque) { let r = unsafe { &*(r as *const SkiaRenderer) }; r.render().unwrap(); } #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_skia_renderer_handle(r: SkiaRendererOpaque) -> RendererPtr { unsafe { let r = (r as *const SkiaRenderer) as *const dyn Renderer; core::mem::transmute(r) } } } ================================================ FILE: api/cpp/tests/CMakeLists.txt ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 include(FetchContent) FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git GIT_TAG v3.8.0 ) FetchContent_MakeAvailable(Catch2) find_package(Threads REQUIRED) macro(slint_test NAME) add_executable(test_${NAME} ${NAME}.cpp) target_link_libraries(test_${NAME} PRIVATE Slint Catch2::Catch2WithMain) target_compile_definitions(test_${NAME} PRIVATE SOURCE_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/\" ) # Use debug version of run-time library to enable MSVC iterator debugging set_property(TARGET test_${NAME} PROPERTY MSVC_RUNTIME_LIBRARY MultiThreadedDebugDLL) add_test(NAME test_${NAME} COMMAND test_${NAME}) if(MSVC) target_compile_options(test_${NAME} PRIVATE /W3) else() target_compile_options(test_${NAME} PRIVATE -Wall -Wextra -Werror) endif() if(CMAKE_CXX_COMPILER_ID STREQUAL GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 14) # that warning has false positives in GCC 11 https://github.com/slint-ui/slint/issues/7358 target_compile_options(test_${NAME} PRIVATE -Wno-maybe-uninitialized) endif() endmacro(slint_test) slint_test(datastructures) if(SLINT_FEATURE_INTERPRETER) slint_test(interpreter) slint_test(window) endif() if(SLINT_FEATURE_INTERPRETER AND SLINT_FEATURE_EXPERIMENTAL) slint_test(translator) endif() slint_test(properties) if(NOT SLINT_FEATURE_FREESTANDING) slint_test(eventloop) target_link_libraries(test_eventloop PRIVATE Threads::Threads) endif() slint_test(models) if(SLINT_FEATURE_EXPERIMENTAL AND SLINT_FEATURE_TESTING) slint_test(testing) endif() if(SLINT_FEATURE_COMPILER OR SLINT_COMPILER) add_subdirectory(multiple-includes) add_subdirectory(libraries) endif() slint_test(platform_eventloop) target_link_libraries(test_platform_eventloop PRIVATE Threads::Threads) ================================================ FILE: api/cpp/tests/datastructures.cpp ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #include #include #define CATCH_CONFIG_MAIN #include "catch2/catch_all.hpp" #include SCENARIO("SharedString API") { slint::SharedString str; REQUIRE(str.empty()); REQUIRE(str.size() == 0); REQUIRE(str == ""); REQUIRE(std::string_view(str.data()) == ""); // this test null termination of data() SECTION("Construct from string_view") { std::string foo("Foo"); std::string_view foo_view(foo); str = foo_view; REQUIRE(str == "Foo"); REQUIRE(std::string_view(str.data()) == "Foo"); } SECTION("Construct from char*") { str = "Bar"; REQUIRE(str == "Bar"); } SECTION("concatenate") { str = "Hello"; str += " "; str += slint::SharedString("🦊") + slint::SharedString("!"); REQUIRE(str == "Hello 🦊!"); REQUIRE(std::string_view(str.data()) == "Hello 🦊!"); } SECTION("begin/end") { str = "Hello"; REQUIRE(str.begin() + std::string_view(str).size() == str.end()); } SECTION("size") { str = "Hello"; REQUIRE(str.size() == 5); } SECTION("clear") { str = "Hello"; str.clear(); REQUIRE(str.size() == 0); REQUIRE(std::string_view(str.data()) == ""); } SECTION("to_lowercase") { str = "Hello"; REQUIRE(std::string_view(str.to_lowercase().data()) == "hello"); } SECTION("to_uppercase") { str = "Hello"; REQUIRE(std::string_view(str.to_uppercase().data()) == "HELLO"); } } TEST_CASE("Basic SharedVector API", "[vector]") { slint::SharedVector vec; REQUIRE(vec.empty()); SECTION("Initializer list") { slint::SharedVector vec({ 1, 4, 10 }); REQUIRE(vec.size() == 3); REQUIRE(vec[0] == 1); REQUIRE(vec[1] == 4); REQUIRE(vec[2] == 10); } } TEST_CASE("Property Tracker") { using namespace slint::private_api; PropertyTracker tracker1; PropertyTracker tracker2; Property prop(42); auto r = tracker1.evaluate([&]() { return tracker2.evaluate([&]() { return prop.get(); }); }); REQUIRE(r == 42); prop.set(1); REQUIRE(tracker2.is_dirty()); REQUIRE(tracker1.is_dirty()); r = tracker1.evaluate( [&]() { return tracker2.evaluate_as_dependency_root([&]() { return prop.get(); }); }); REQUIRE(r == 1); prop.set(100); REQUIRE(tracker2.is_dirty()); REQUIRE(!tracker1.is_dirty()); } TEST_CASE("Model row changes") { using namespace slint::private_api; auto model = std::make_shared>(); PropertyTracker tracker; REQUIRE(tracker.evaluate([&]() { model->track_row_count_changes(); return model->row_count(); }) == 0); REQUIRE(!tracker.is_dirty()); model->push_back(1); model->push_back(2); REQUIRE(tracker.is_dirty()); REQUIRE(tracker.evaluate([&]() { model->track_row_count_changes(); return model->row_count(); }) == 2); REQUIRE(!tracker.is_dirty()); model->erase(0); REQUIRE(tracker.is_dirty()); REQUIRE(tracker.evaluate([&]() { model->track_row_count_changes(); return model->row_count(); }) == 1); } TEST_CASE("Track model row data changes") { using namespace slint::private_api; auto model = std::make_shared>(std::vector { 0, 1, 2, 3, 4 }); PropertyTracker tracker; REQUIRE(tracker.evaluate([&]() { model->track_row_data_changes(1); return model->row_data(1); }) == 1); REQUIRE(!tracker.is_dirty()); model->set_row_data(2, 42); REQUIRE(!tracker.is_dirty()); model->set_row_data(1, 100); REQUIRE(tracker.is_dirty()); REQUIRE(tracker.evaluate([&]() { model->track_row_data_changes(1); return model->row_data(1); }) == 100); REQUIRE(!tracker.is_dirty()); // Any changes to rows (even if after tracked rows) for now also marks watched rows as dirty, to // keep the logic simple. model->push_back(200); REQUIRE(tracker.is_dirty()); REQUIRE(tracker.evaluate([&]() { model->track_row_data_changes(1); return model->row_data(1); }) == 100); REQUIRE(!tracker.is_dirty()); model->insert(0, 255); REQUIRE(tracker.is_dirty()); } TEST_CASE("Image") { using namespace slint; Image img; { auto size = img.size(); REQUIRE(size.width == 0.); REQUIRE(size.height == 0.); } { REQUIRE(!img.path().has_value()); } #ifndef SLINT_FEATURE_FREESTANDING img = Image::load_from_path(SOURCE_DIR "/../../../logo/slint-logo-square-light-128x128.png"); { auto size = img.size(); REQUIRE(size.width == 128.); REQUIRE(size.height == 128.); } { auto actual_path = img.path(); REQUIRE(actual_path.has_value()); REQUIRE(*actual_path == SOURCE_DIR "/../../../logo/slint-logo-square-light-128x128.png"); } #endif img = Image(SharedPixelBuffer {}); { auto size = img.size(); REQUIRE(size.width == 0); REQUIRE(size.height == 0); REQUIRE(!img.path().has_value()); } auto red = Rgb8Pixel { 0xff, 0, 0 }; auto blu = Rgb8Pixel { 0, 0, 0xff }; Rgb8Pixel some_data[] = { red, red, blu, red, blu, blu }; img = Image(SharedPixelBuffer(3, 2, some_data)); { auto size = img.size(); REQUIRE(size.width == 3); REQUIRE(size.height == 2); REQUIRE(!img.path().has_value()); } } TEST_CASE("Image buffer access") { using namespace slint; auto img = Image::load_from_path(SOURCE_DIR "/redpixel.png"); REQUIRE(!img.to_rgb8().has_value()); { auto rgb = img.to_rgba8(); REQUIRE(rgb.has_value()); REQUIRE(rgb->width() == 1); REQUIRE(rgb->height() == 1); REQUIRE(*rgb->begin() == Rgba8Pixel { 255, 0, 0, 255 }); } { auto rgb = img.to_rgba8_premultiplied(); REQUIRE(rgb.has_value()); REQUIRE(rgb->width() == 1); REQUIRE(rgb->height() == 1); REQUIRE(*rgb->begin() == Rgba8Pixel { 255, 0, 0, 255 }); } } TEST_CASE("SharedVector") { using namespace slint; SharedVector vec; vec.clear(); vec.push_back("Hello"); vec.push_back("World"); vec.push_back("of"); vec.push_back("Vectors"); auto copy = vec; REQUIRE(vec.size() == 4); auto orig_cap = vec.capacity(); REQUIRE(orig_cap >= vec.size()); vec.clear(); REQUIRE(vec.size() == 0); REQUIRE(vec.capacity() == 0); // vec was shared, so start with new empty vector. vec.push_back("Welcome back"); REQUIRE(vec.size() == 1); REQUIRE(vec.capacity() >= vec.size()); REQUIRE(copy.size() == 4); REQUIRE(copy.capacity() == orig_cap); SharedVector vec2 { "Hello", "World", "of", "Vectors" }; REQUIRE(copy == vec2); REQUIRE(copy != vec); copy.clear(); // copy is not shared (anymore), retain capacity. REQUIRE(copy.capacity() == orig_cap); SharedVector vec3(2, "Welcome back"); REQUIRE(vec3.size() == 2); REQUIRE(vec3[1] == "Welcome back"); REQUIRE(vec3 != vec); vec.push_back("Welcome back"); REQUIRE(vec3 == vec); SharedVector vec4(5); REQUIRE(vec4.size() == 5); REQUIRE(vec4[3] == 0); std::vector std_v(vec2.begin(), vec2.end()); SharedVector vec6(std_v.begin(), std_v.end()); REQUIRE(vec6 == vec2); } TEST_CASE("StyledText") { auto empty_arguments = std::array {}; auto text = slint::private_api::parse_markdown( "Hello *world*", slint::private_api::make_slice(std::span(empty_arguments))); auto text_argument = std::array { text }; auto text2 = slint::private_api::parse_markdown( "Text: {}", slint::private_api::make_slice(std::span(text_argument))); } ================================================ FILE: api/cpp/tests/eventloop.cpp ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 // cSpell: ignore singleshot #define CATCH_CONFIG_MAIN #include "catch2/catch_all.hpp" #include #include TEST_CASE("C++ Singleshot Timers") { using namespace slint; int called = 0; Timer testTimer(std::chrono::milliseconds(16), [&]() { slint::quit_event_loop(); called += 10; }); REQUIRE(called == 0); slint::run_event_loop(); REQUIRE(called == 10); } TEST_CASE("C++ Repeated Timer") { int timer_triggered = 0; slint::Timer timer; timer.start(slint::TimerMode::Repeated, std::chrono::milliseconds(30), [&]() { timer_triggered++; }); REQUIRE(timer_triggered == 0); bool timer_was_running = false; slint::Timer::single_shot(std::chrono::milliseconds(500), [&]() { timer_was_running = timer.running(); slint::quit_event_loop(); }); slint::run_event_loop(); REQUIRE(timer_triggered > 1); REQUIRE(timer_was_running); } TEST_CASE("C++ Restart Singleshot Timer") { int timer_triggered = 0; slint::Timer timer; timer.start(slint::TimerMode::SingleShot, std::chrono::milliseconds(30), [&]() { timer_triggered++; }); REQUIRE(timer_triggered == 0); REQUIRE(timer.running()); bool timer_was_running = true; slint::Timer::single_shot(std::chrono::milliseconds(500), [&]() { timer_was_running = timer.running(); slint::quit_event_loop(); }); slint::run_event_loop(); REQUIRE(!timer.running()); REQUIRE(timer_triggered == 1); REQUIRE(!timer_was_running); // At that point the timer is already considered stopped! timer_triggered = 0; timer_was_running = true; timer.restart(); REQUIRE(timer.running()); slint::Timer::single_shot(std::chrono::milliseconds(500), [&]() { timer_was_running = timer.running(); slint::quit_event_loop(); }); slint::run_event_loop(); REQUIRE(timer_triggered == 1); REQUIRE(!timer_was_running); REQUIRE(!timer.running()); } TEST_CASE("C++ Restart Repeated Timer") { int timer_triggered = 0; slint::Timer timer; timer.start(slint::TimerMode::Repeated, std::chrono::milliseconds(30), [&]() { timer_triggered++; }); REQUIRE(timer_triggered == 0); bool timer_was_running = false; slint::Timer::single_shot(std::chrono::milliseconds(500), [&]() { timer_was_running = timer.running(); slint::quit_event_loop(); }); slint::run_event_loop(); REQUIRE(timer_triggered > 1); REQUIRE(timer_was_running); timer_was_running = false; timer_triggered = 0; timer.stop(); slint::Timer::single_shot(std::chrono::milliseconds(500), [&]() { timer_was_running = timer.running(); slint::quit_event_loop(); }); slint::run_event_loop(); REQUIRE(timer_triggered == 0); REQUIRE(!timer_was_running); timer_was_running = false; timer_triggered = 0; timer.restart(); slint::Timer::single_shot(std::chrono::milliseconds(500), [&]() { timer_was_running = timer.running(); slint::quit_event_loop(); }); slint::run_event_loop(); REQUIRE(timer_triggered > 1); REQUIRE(timer_was_running); } TEST_CASE("Quit from event") { int called = 0; slint::invoke_from_event_loop([&] { slint::quit_event_loop(); called += 10; }); REQUIRE(called == 0); slint::run_event_loop(); REQUIRE(called == 10); } TEST_CASE("Event from thread") { std::atomic called = 0; auto t = std::thread([&] { called += 10; slint::invoke_from_event_loop([&] { called += 100; slint::quit_event_loop(); }); }); slint::run_event_loop(); REQUIRE(called == 110); t.join(); } TEST_CASE("Blocking Event from thread") { std::atomic called = 0; auto t = std::thread([&] { // test returning a, unique_ptr because it is movable-only std::unique_ptr foo = slint::blocking_invoke_from_event_loop([&] { return std::make_unique(42); }); called = *foo; int xxx = 123; slint::blocking_invoke_from_event_loop([&] { slint::quit_event_loop(); xxx = 888999; }); REQUIRE(xxx == 888999); }); slint::run_event_loop(); REQUIRE(called == 42); t.join(); } ================================================ FILE: api/cpp/tests/interpreter.cpp ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #define CATCH_CONFIG_MAIN #include "catch2/catch_all.hpp" #include #include SCENARIO("Value API") { using namespace slint::interpreter; Value value; REQUIRE(value.type() == Value::Type::Void); SECTION("Construct a string") { REQUIRE(!value.to_string().has_value()); slint::SharedString cpp_str("Hello World"); value = Value(cpp_str); REQUIRE(value.type() == Value::Type::String); auto string_opt = value.to_string(); REQUIRE(string_opt.has_value()); REQUIRE(string_opt.value() == "Hello World"); } SECTION("Construct a number") { REQUIRE(!value.to_number().has_value()); const double number = 42.0; value = Value(number); REQUIRE(value.type() == Value::Type::Number); auto number_opt = value.to_number(); REQUIRE(number_opt.has_value()); REQUIRE(number_opt.value() == number); Value v2 = 42; REQUIRE(v2.type() == Value::Type::Number); REQUIRE(v2 == value); REQUIRE(*v2.to_number() == number); } SECTION("Construct a bool") { REQUIRE(!value.to_bool().has_value()); value = Value(true); REQUIRE(value.type() == Value::Type::Bool); auto bool_opt = value.to_bool(); REQUIRE(bool_opt.has_value()); REQUIRE(bool_opt.value() == true); } SECTION("Construct an array") { REQUIRE(!value.to_array().has_value()); slint::SharedVector array { Value(42.0), Value(true) }; value = Value(array); REQUIRE(value.type() == Value::Type::Model); auto array_opt = value.to_array(); REQUIRE(array_opt.has_value()); auto extracted_array = array_opt.value(); REQUIRE(extracted_array.size() == 2); REQUIRE(extracted_array[0].to_number().value() == 42); REQUIRE(extracted_array[1].to_bool().value()); } SECTION("Construct a brush") { REQUIRE(!value.to_brush().has_value()); slint::Brush brush(slint::Color::from_rgb_uint8(255, 0, 255)); value = Value(brush); REQUIRE(value.type() == Value::Type::Brush); auto brush_opt = value.to_brush(); REQUIRE(brush_opt.has_value()); REQUIRE(brush_opt.value() == brush); } SECTION("Construct a struct") { REQUIRE(!value.to_struct().has_value()); slint::interpreter::Struct struc; value = Value(struc); REQUIRE(value.type() == Value::Type::Struct); auto struct_opt = value.to_struct(); REQUIRE(struct_opt.has_value()); } SECTION("Construct an image") { REQUIRE(!value.to_image().has_value()); slint::Image image = slint::Image::load_from_path( SOURCE_DIR "/../../../logo/slint-logo-square-light-128x128.png"); REQUIRE(image.size().width == 128); value = Value(image); REQUIRE(value.type() == Value::Type::Image); auto image2 = value.to_image(); REQUIRE(image2.has_value()); REQUIRE(image2->size().width == 128); REQUIRE(image == *image2); } SECTION("Construct a model") { // And test that it is properly destroyed when the value is destroyed struct M : slint::VectorModel { bool *destroyed; explicit M(bool *destroyed) : destroyed(destroyed) { } void play() { this->push_back(Value(4.)); this->set_row_data(0, Value(9.)); } ~M() { *destroyed = true; } }; bool destroyed = false; auto m = std::make_shared(&destroyed); { Value value(m); REQUIRE(value.type() == Value::Type::Model); REQUIRE(!destroyed); m->play(); m = nullptr; REQUIRE(!destroyed); // play a bit with the value to test the copy and move Value v2 = value; Value v3 = std::move(v2); REQUIRE(!destroyed); } REQUIRE(destroyed); } SECTION("Compare Values") { Value str1 { slint::SharedString("Hello1") }; Value str2 { slint::SharedString("Hello2") }; Value fl1 { 10. }; Value fl2 { 12. }; REQUIRE(str1 == str1); REQUIRE(str1 != str2); REQUIRE(str1 != fl2); REQUIRE(fl1 == fl1); REQUIRE(fl1 != fl2); REQUIRE(Value() == Value()); REQUIRE(Value() != str1); REQUIRE(str1 == slint::SharedString("Hello1")); REQUIRE(str1 != slint::SharedString("Hello2")); REQUIRE(slint::SharedString("Hello2") == str2); REQUIRE(fl1 != slint::SharedString("Hello2")); REQUIRE(fl2 == 12.); } } SCENARIO("Struct API") { using namespace slint::interpreter; Struct struc; REQUIRE(!struc.get_field("not_there")); struc.set_field("field_a", Value(slint::SharedString("Hallo"))); auto value_opt = struc.get_field("field_a"); REQUIRE(value_opt.has_value()); auto value = value_opt.value(); REQUIRE(value.to_string().has_value()); REQUIRE(value.to_string().value() == "Hallo"); int count = 0; for (auto [k, value] : struc) { REQUIRE(count == 0); count++; REQUIRE(k == "field-a"); REQUIRE(value.to_string().value() == "Hallo"); } struc.set_field("field_b", Value(slint::SharedString("World"))); std::map map; for (auto [k, value] : struc) map[std::string(k)] = *value.to_string(); REQUIRE(map == std::map { { "field-a", slint::SharedString("Hallo") }, { "field-b", slint::SharedString("World") } }); } SCENARIO("Struct Iterator Constructor") { using namespace slint::interpreter; std::vector> values = { { "field_a", Value(true) }, { "field_b", Value(42.0) } }; Struct struc(values.begin(), values.end()); REQUIRE(!struc.get_field("foo").has_value()); REQUIRE(struc.get_field("field_a").has_value()); REQUIRE(struc.get_field("field_a").value().to_bool().value()); REQUIRE(struc.get_field("field_b").value().to_number().value() == 42.0); } SCENARIO("Struct Initializer List Constructor") { using namespace slint::interpreter; Struct struc({ { "field_a", Value(true) }, { "field_b", Value(42.0) } }); REQUIRE(!struc.get_field("foo").has_value()); REQUIRE(struc.get_field("field_a").has_value()); REQUIRE(struc.get_field("field_a").value().to_bool().value()); REQUIRE(struc.get_field("field_b").value().to_number().value() == 42.0); } SCENARIO("Struct empty field iteration") { using namespace slint::interpreter; Struct struc; REQUIRE(struc.begin() == struc.end()); } SCENARIO("Struct field iteration") { using namespace slint::interpreter; Struct struc({ { "field_a", Value(true) }, { "field_b", Value(42.0) } }); auto it = struc.begin(); auto end = struc.end(); REQUIRE(it != end); auto check_valid_entry = [](const auto &key, const auto &value) -> bool { if (key == "field-a") return value == Value(true); if (key == "field-b") return value == Value(42.0); return false; }; std::set seen_fields; for (; it != end; ++it) { const auto [key, value] = *it; REQUIRE(check_valid_entry(key, value)); auto value_inserted = seen_fields.insert(std::string(key)).second; REQUIRE(value_inserted); } } SCENARIO("Component Compiler") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; SECTION("configure include paths") { SharedVector in_paths; in_paths.push_back("path1"); in_paths.push_back("path2"); compiler.set_include_paths(in_paths); auto out_paths = compiler.include_paths(); REQUIRE(out_paths.size() == 2); REQUIRE(out_paths[0] == "path1"); REQUIRE(out_paths[1] == "path2"); } SECTION("configure style") { REQUIRE(compiler.style() == ""); compiler.set_style("fluent"); REQUIRE(compiler.style() == "fluent"); } SECTION("configure translation domain") { // Make sure this compiles. compiler.set_translation_domain("cpptests"); } SECTION("Compile failure from source") { auto result = compiler.build_from_source("Syntax Error!!", ""); REQUIRE_FALSE(result.has_value()); } SECTION("Compile from source") { auto result = compiler.build_from_source("export component Dummy {}", ""); REQUIRE(result.has_value()); } SECTION("Compile failure from path") { auto result = compiler.build_from_path(SOURCE_DIR "/file-not-there.slint"); REQUIRE_FALSE(result.has_value()); auto diags = compiler.diagnostics(); REQUIRE(diags.size() == 1); REQUIRE(diags[0].message.starts_with("Could not load")); REQUIRE(diags[0].line == 0); REQUIRE(diags[0].column == 0); } SECTION("Compile from path") { auto result = compiler.build_from_path(SOURCE_DIR "/test.slint"); REQUIRE(result.has_value()); } } SCENARIO("Component Definition Properties") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; auto comp_def = *compiler.build_from_source("export component Dummy { in property test; " "callback dummy; public function my-fun() {} }", ""); auto properties = comp_def.properties(); REQUIRE(properties.size() == 1); REQUIRE(properties[0].property_name == "test"); REQUIRE(properties[0].property_type == Value::Type::String); auto callback_names = comp_def.callbacks(); REQUIRE(callback_names.size() == 1); REQUIRE(callback_names[0] == "dummy"); auto function_names = comp_def.functions(); REQUIRE(function_names.size() == 1); REQUIRE(function_names[0] == "my-fun"); auto instance = comp_def.create(); ComponentDefinition new_comp_def = instance->definition(); auto new_props = new_comp_def.properties(); REQUIRE(new_props.size() == 1); REQUIRE(new_props[0].property_name == "test"); REQUIRE(new_props[0].property_type == Value::Type::String); } SCENARIO("Component Definition Properties / Two-way bindings") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; auto comp_def = *compiler.build_from_source( "export component Dummy { in-out property test <=> sub_object.test; " " sub_object := Rectangle { property test; }" "}", ""); auto properties = comp_def.properties(); REQUIRE(properties.size() == 1); REQUIRE(properties[0].property_name == "test"); REQUIRE(properties[0].property_type == Value::Type::String); } SCENARIO("Invoke callback") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; SECTION("valid") { auto result = compiler.build_from_source( "export component Dummy { callback some_callback(string, int) -> string; }", ""); REQUIRE(result.has_value()); auto instance = result->create(); std::string local_string = "_string_on_the_stack_"; REQUIRE(instance->set_callback("some_callback", [local_string](auto args) { SharedString arg1 = *args[0].to_string(); int arg2 = int(*args[1].to_number()); std::string res = std::string(arg1) + ":" + std::to_string(arg2) + local_string; return Value(SharedString(res)); })); Value args[] = { SharedString("Hello"), 42. }; auto res = instance->invoke("some_callback", args); REQUIRE(res.has_value()); REQUIRE(*res->to_string() == SharedString("Hello:42_string_on_the_stack_")); } SECTION("invalid") { auto result = compiler.build_from_source( "export component Dummy { callback foo(string, int) -> string; }", ""); REQUIRE(result.has_value()); auto instance = result->create(); REQUIRE(!instance->set_callback("bar", [](auto) { return Value(); })); Value args[] = { SharedString("Hello"), 42. }; auto res = instance->invoke("bar", args); REQUIRE(!res.has_value()); } } SCENARIO("Array between .slint and C++") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; auto result = compiler.build_from_source( "export component Dummy { in-out property <[int]> array: [1, 2, 3]; }", ""); REQUIRE(result.has_value()); auto instance = result->create(); SECTION(".slint to C++") { auto maybe_array = instance->get_property("array"); REQUIRE(maybe_array.has_value()); REQUIRE(maybe_array->type() == Value::Type::Model); auto array = *maybe_array; REQUIRE(array.to_array() == slint::SharedVector { Value(1.), Value(2.), Value(3.) }); } SECTION("C++ to .slint") { slint::SharedVector cpp_array { Value(4.), Value(5.), Value(6.) }; instance->set_property("array", Value(cpp_array)); auto maybe_array = instance->get_property("array"); REQUIRE(maybe_array.has_value()); REQUIRE(maybe_array->type() == Value::Type::Model); auto actual_array = *maybe_array; REQUIRE(actual_array.to_array() == cpp_array); } } SCENARIO("Angle between .slint and C++") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; auto result = compiler.build_from_source( "export component Dummy { in-out property the_angle: " "0.25turn; out property test: the_angle == 0.5turn; }", ""); REQUIRE(result.has_value()); auto instance = result->create(); SECTION("Read property") { auto angle_value = instance->get_property("the-angle"); REQUIRE(angle_value.has_value()); REQUIRE(angle_value->type() == Value::Type::Number); auto angle = angle_value->to_number(); REQUIRE(angle.has_value()); REQUIRE(*angle == 90); } SECTION("Write property") { REQUIRE(!*instance->get_property("test")->to_bool()); bool ok = instance->set_property("the_angle", 180.); REQUIRE(ok); REQUIRE(*instance->get_property("the_angle")->to_number() == 180); REQUIRE(*instance->get_property("test")->to_bool()); } } SCENARIO("Component Definition Name") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; auto comp_def = *compiler.build_from_source("export component IHaveAName { }", ""); REQUIRE(comp_def.name() == "IHaveAName"); } SCENARIO("Send key events") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; auto comp_def = compiler.build_from_source(R"( export component Dummy { forward-focus: scope; out property result; scope := FocusScope { key-pressed(event) => { if (event.text != Key.Shift && event.text != Key.Control) { result += event.text; } return accept; } } } )", ""); REQUIRE(comp_def.has_value()); auto instance = comp_def->create(); slint::private_api::testing::send_keyboard_string_sequence(&*instance, "Hello keys!"); REQUIRE(*instance->get_property("result")->to_string() == "Hello keys!"); } SCENARIO("Global properties") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; auto result = compiler.build_from_source( R"( export global The-Global { in-out property the-property: "€€€"; pure callback to_uppercase(string)->string; public function ff() -> string { return the-property; } } export component Dummy { out property result: The-Global.to_uppercase("abc"); } )", ""); for (auto &&x : compiler.diagnostics()) std::cerr << x.message << std::endl; REQUIRE(result.has_value()); auto component_definition = *result; SECTION("Globals introspection") { auto globals = component_definition.globals(); REQUIRE(globals.size() == 1); REQUIRE(globals[0] == "The-Global"); REQUIRE(!component_definition.global_properties("not there").has_value()); REQUIRE(component_definition.global_properties("The_Global").has_value()); REQUIRE(component_definition.global_properties("The-Global").has_value()); auto properties = *component_definition.global_properties("The-Global"); REQUIRE(properties.size() == 1); REQUIRE(properties[0].property_name == "the-property"); REQUIRE(properties[0].property_type == Value::Type::String); auto callbacks = *component_definition.global_callbacks("The-Global"); REQUIRE(callbacks.size() == 1); REQUIRE(callbacks[0] == "to_uppercase"); auto functions = *component_definition.global_functions("The-Global"); REQUIRE(functions.size() == 1); REQUIRE(functions[0] == "ff"); } auto instance = component_definition.create(); SECTION("Invalid read") { REQUIRE(!instance->get_global_property("the - global", "the-property").has_value()); REQUIRE(!instance->get_global_property("The-Global", "the property").has_value()); } SECTION("Invalid set") { REQUIRE(!instance->set_global_property("the - global", "the-property", 5.)); REQUIRE(!instance->set_global_property("The-Global", "the property", 5.)); REQUIRE(!instance->set_global_property("The-Global", "the-property", 5.)); } SECTION("get property") { auto value = instance->get_global_property("The_Global", "the-property"); REQUIRE(value.has_value()); REQUIRE(value->to_string().has_value()); REQUIRE(value->to_string().value() == "€€€"); } SECTION("set property") { REQUIRE(instance->set_global_property("The-Global", "the-property", SharedString("§§§"))); auto value = instance->get_global_property("The-Global", "the_property"); REQUIRE(value.has_value()); REQUIRE(value->to_string().has_value()); REQUIRE(value->to_string().value() == "§§§"); } SECTION("set/invoke callback") { REQUIRE(instance->set_global_callback("The-Global", "to_uppercase", [](auto args) { std::string arg1(*args[0].to_string()); std::transform(arg1.begin(), arg1.end(), arg1.begin(), toupper); return SharedString(arg1); })); auto result = instance->get_property("result"); REQUIRE(result.has_value()); REQUIRE(result->to_string().has_value()); REQUIRE(result->to_string().value() == "ABC"); Value args[] = { SharedString("Hello") }; auto res = instance->invoke_global("The_Global", "to-uppercase", args); REQUIRE(res.has_value()); REQUIRE(*res->to_string() == SharedString("HELLO")); } SECTION("callback errors") { REQUIRE(!instance->set_global_callback("TheGlobal", "to_uppercase", [](auto) { return Value {}; })); REQUIRE(!instance->set_global_callback("The-Global", "touppercase", [](auto) { return Value {}; })); REQUIRE(!instance->invoke_global("TheGlobal", "touppercase", {})); REQUIRE(!instance->invoke_global("The-Global", "touppercase", {})); } SECTION("invoke function") { REQUIRE(instance->set_global_property("The-Global", "the-property", SharedString("&&&"))); auto res = instance->invoke_global("The_Global", "ff", {}); REQUIRE(res.has_value()); REQUIRE(*res->to_string() == SharedString("&&&")); } } ================================================ FILE: api/cpp/tests/libraries/CMakeLists.txt ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 add_executable(libraries main.cpp) target_link_libraries(libraries PRIVATE Slint::Slint) slint_target_sources(libraries app-window.slint LIBRARY_PATHS helper_components=${CMAKE_CURRENT_SOURCE_DIR}/../../../../tests/helper_components/ helper_buttons=${CMAKE_CURRENT_SOURCE_DIR}/../../../../tests/helper_components/test_button.slint ) ================================================ FILE: api/cpp/tests/libraries/README.md ================================================ This is a test making sure that slint_target_sources accepts slint component library paths. ================================================ FILE: api/cpp/tests/libraries/app-window.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { TestButton } from "@helper_components/test_button.slint"; import { ColorButton } from "@helper_buttons"; export component App inherits Window { TestButton { } ColorButton { } } ================================================ FILE: api/cpp/tests/libraries/main.cpp ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #include "app-window.h" int main(int argc, char **argv) { auto my_ui = App::create(); my_ui->run(); } ================================================ FILE: api/cpp/tests/models.cpp ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #include #include #define CATCH_CONFIG_MAIN #include "catch2/catch_all.hpp" #include struct ModelObserver : public slint::private_api::ModelChangeListener { void row_added(size_t index, size_t count) override { added_rows.push_back(Range { index, count }); } void row_changed(size_t index) override { changed_rows.push_back(index); } void row_removed(size_t index, size_t count) override { removed_rows.push_back(Range { index, count }); } void reset() override { model_reset = true; } void clear() { added_rows.clear(); changed_rows.clear(); removed_rows.clear(); model_reset = false; } struct Range { size_t row_index; size_t count; bool operator==(const Range &) const = default; }; std::vector added_rows; std::vector changed_rows; std::vector removed_rows; bool model_reset = false; }; std::ostream &operator<<(std::ostream &os, const ModelObserver::Range &value) { os << "{ row_index: " << value.row_index << "; count: " << value.count << " }"; return os; } SCENARIO("Filtering Model") { auto vec_model = std::make_shared>(std::vector { 1, 2, 3, 4, 5, 6 }); auto even_rows = std::make_shared>( vec_model, [](auto value) { return value % 2 == 0; }); REQUIRE(even_rows->row_count() == 3); REQUIRE(even_rows->row_data(0) == 2); REQUIRE(even_rows->row_data(1) == 4); REQUIRE(even_rows->row_data(2) == 6); } SCENARIO("Filtering Insert") { auto vec_model = std::make_shared>(std::vector { 1, 2, 3, 4, 5, 6 }); auto even_rows = std::make_shared>( vec_model, [](auto value) { return value % 2 == 0; }); auto observer = std::make_shared(); even_rows->attach_peer(observer); REQUIRE(even_rows->row_count() == 3); REQUIRE(even_rows->row_data(0) == 2); REQUIRE(even_rows->row_data(1) == 4); REQUIRE(even_rows->row_data(2) == 6); vec_model->insert(2, 10); REQUIRE(observer->added_rows.size() == 1); REQUIRE(observer->added_rows[0] == ModelObserver::Range { 1, 1 }); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.empty()); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(even_rows->row_count() == 4); REQUIRE(even_rows->row_data(0) == 2); REQUIRE(even_rows->row_data(1) == 10); REQUIRE(even_rows->row_data(2) == 4); REQUIRE(even_rows->row_data(3) == 6); // insert odd number -> no change vec_model->insert(0, 1); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.empty()); REQUIRE(!observer->model_reset); observer->clear(); } SCENARIO("Filtering Change") { auto vec_model = std::make_shared>(std::vector { 1, 2, 3, 4, 5, 6 }); auto even_rows = std::make_shared>( vec_model, [](auto value) { return value % 2 == 0; }); auto observer = std::make_shared(); even_rows->attach_peer(observer); REQUIRE(even_rows->row_count() == 3); REQUIRE(even_rows->row_data(0) == 2); REQUIRE(even_rows->row_data(1) == 4); REQUIRE(even_rows->row_data(2) == 6); // change leading odd 1 to odd 3 -> no change vec_model->set_row_data(0, 3); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.empty()); REQUIRE(!observer->model_reset); REQUIRE(even_rows->row_count() == 3); REQUIRE(even_rows->row_data(0) == 2); REQUIRE(even_rows->row_data(1) == 4); REQUIRE(even_rows->row_data(2) == 6); // change trailing 6 to odd 1 -> one row less vec_model->set_row_data(5, 1); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.size() == 1); REQUIRE(observer->removed_rows[0] == ModelObserver::Range { 2, 1 }); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(even_rows->row_count() == 2); REQUIRE(even_rows->row_data(0) == 2); REQUIRE(even_rows->row_data(1) == 4); // change leading odd 3 to even 0 -> one new row vec_model->set_row_data(0, 0); REQUIRE(observer->added_rows.size() == 1); REQUIRE(observer->added_rows[0] == ModelObserver::Range { 0, 1 }); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.empty()); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(even_rows->row_count() == 3); REQUIRE(even_rows->row_data(0) == 0); REQUIRE(even_rows->row_data(1) == 2); REQUIRE(even_rows->row_data(2) == 4); // change trailing filtered 4 to even 0 -> one changed row vec_model->set_row_data(3, 0); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.size() == 1); REQUIRE(observer->changed_rows[0] == 2); REQUIRE(observer->removed_rows.empty()); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(even_rows->row_count() == 3); REQUIRE(even_rows->row_data(0) == 0); REQUIRE(even_rows->row_data(1) == 2); REQUIRE(even_rows->row_data(2) == 0); } SCENARIO("Filtering Model Remove") { auto vec_model = std::make_shared>(std::vector { 1, 2, 3, 4, 5, 6 }); auto even_rows = std::make_shared>( vec_model, [](auto value) { return value % 2 == 0; }); auto observer = std::make_shared(); even_rows->attach_peer(observer); REQUIRE(even_rows->row_count() == 3); REQUIRE(even_rows->row_data(0) == 2); REQUIRE(even_rows->row_data(1) == 4); REQUIRE(even_rows->row_data(2) == 6); // Erase unrelated row vec_model->erase(0); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.empty()); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(even_rows->row_count() == 3); REQUIRE(even_rows->row_data(0) == 2); REQUIRE(even_rows->row_data(1) == 4); REQUIRE(even_rows->row_data(2) == 6); // Erase trailing even 6 vec_model->erase(4); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.size() == 1); REQUIRE(observer->removed_rows[0] == ModelObserver::Range { 2, 1 }); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(even_rows->row_count() == 2); REQUIRE(even_rows->row_data(0) == 2); REQUIRE(even_rows->row_data(1) == 4); } SCENARIO("Filtering Model Reset") { auto vec_model = std::make_shared>(std::vector { 1, 2, 3, 4, 5, 6 }); bool even = true; auto even_rows = std::make_shared>( vec_model, [&even](auto value) { return value % 2 == !even; }); auto observer = std::make_shared(); even_rows->attach_peer(observer); REQUIRE(even_rows->row_count() == 3); REQUIRE(even_rows->row_data(0) == 2); REQUIRE(even_rows->row_data(1) == 4); REQUIRE(even_rows->row_data(2) == 6); even = false; even_rows->reset(); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.empty()); REQUIRE(observer->model_reset); observer->clear(); REQUIRE(even_rows->row_count() == 3); REQUIRE(even_rows->row_data(0) == 1); REQUIRE(even_rows->row_data(1) == 3); REQUIRE(even_rows->row_data(2) == 5); } template class TestDeferredFilterModel : public slint::FilterModel { public: TestDeferredFilterModel(bool &initialized, bool &filtered, std::shared_ptr> source_model) : slint::FilterModel { std::move(source_model), [&filtered]([[maybe_unused]] const ModelData &) { if (!filtered) { filtered = true; } return true; } } { initialized = true; } }; SCENARIO("Filtering Model Ensure Deferred") { auto source_model = std::make_shared>(std::vector { 0, 1, 2, 3, 4 }); bool initialized = false; bool filtered = false; auto filter_model = std::make_shared>(initialized, filtered, source_model); REQUIRE(initialized); REQUIRE_FALSE(filtered); filter_model->row_data(0); REQUIRE(filtered); } SCENARIO("Mapped Model") { auto vec_model = std::make_shared>(std::vector { 1, 2, 3, 4 }); int to_add = 1; auto plus_one_model = std::make_shared>( vec_model, [&to_add](auto value) { return value + to_add; }); auto observer = std::make_shared(); plus_one_model->attach_peer(observer); REQUIRE(plus_one_model->row_count() == 4); REQUIRE(plus_one_model->row_data(0) == 2); REQUIRE(plus_one_model->row_data(1) == 3); REQUIRE(plus_one_model->row_data(2) == 4); REQUIRE(plus_one_model->row_data(3) == 5); vec_model->insert(0, 100); REQUIRE(observer->added_rows.size() == 1); REQUIRE(observer->added_rows[0] == ModelObserver::Range { 0, 1 }); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.empty()); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(plus_one_model->row_count() == 5); REQUIRE(plus_one_model->row_data(0) == 101); REQUIRE(plus_one_model->row_data(1) == 2); REQUIRE(plus_one_model->row_data(2) == 3); REQUIRE(plus_one_model->row_data(3) == 4); REQUIRE(plus_one_model->row_data(4) == 5); vec_model->set_row_data(1, 3); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.size() == 1); REQUIRE(observer->changed_rows[0] == 1); REQUIRE(observer->removed_rows.empty()); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(plus_one_model->row_count() == 5); REQUIRE(plus_one_model->row_data(0) == 101); REQUIRE(plus_one_model->row_data(1) == 4); REQUIRE(plus_one_model->row_data(2) == 3); REQUIRE(plus_one_model->row_data(3) == 4); REQUIRE(plus_one_model->row_data(4) == 5); vec_model->erase(3); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.size() == 1); REQUIRE(observer->removed_rows[0] == ModelObserver::Range { 3, 1 }); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(plus_one_model->row_count() == 4); REQUIRE(plus_one_model->row_data(0) == 101); REQUIRE(plus_one_model->row_data(1) == 4); REQUIRE(plus_one_model->row_data(2) == 3); REQUIRE(plus_one_model->row_data(3) == 5); to_add = 51; plus_one_model->reset(); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.empty()); REQUIRE(observer->model_reset); observer->clear(); REQUIRE(plus_one_model->row_count() == 4); REQUIRE(plus_one_model->row_data(0) == 151); REQUIRE(plus_one_model->row_data(1) == 54); REQUIRE(plus_one_model->row_data(2) == 53); REQUIRE(plus_one_model->row_data(3) == 55); } SCENARIO("Sorted Model Insert") { auto vec_model = std::make_shared>(std::vector { 3, 4, 1, 2 }); auto sorted_model = std::make_shared>( vec_model, [](auto lhs, auto rhs) { return lhs < rhs; }); auto observer = std::make_shared(); sorted_model->attach_peer(observer); REQUIRE(sorted_model->row_count() == 4); REQUIRE(sorted_model->row_data(0) == 1); REQUIRE(sorted_model->row_data(1) == 2); REQUIRE(sorted_model->row_data(2) == 3); REQUIRE(sorted_model->row_data(3) == 4); vec_model->insert(0, 10); REQUIRE(observer->added_rows.size() == 1); REQUIRE(observer->added_rows[0] == ModelObserver::Range { 4, 1 }); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.empty()); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(sorted_model->row_count() == 5); REQUIRE(sorted_model->row_data(0) == 1); REQUIRE(sorted_model->row_data(1) == 2); REQUIRE(sorted_model->row_data(2) == 3); REQUIRE(sorted_model->row_data(3) == 4); REQUIRE(sorted_model->row_data(4) == 10); } SCENARIO("Sorted Model Remove") { auto vec_model = std::make_shared>(std::vector { 3, 4, 1, 2 }); auto sorted_model = std::make_shared>( vec_model, [](auto lhs, auto rhs) { return lhs < rhs; }); auto observer = std::make_shared(); sorted_model->attach_peer(observer); REQUIRE(sorted_model->row_count() == 4); REQUIRE(sorted_model->row_data(0) == 1); REQUIRE(sorted_model->row_data(1) == 2); REQUIRE(sorted_model->row_data(2) == 3); REQUIRE(sorted_model->row_data(3) == 4); /// Remove the entry with the value 4 vec_model->erase(1); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.size() == 1); REQUIRE(observer->removed_rows[0] == ModelObserver::Range { 3, 1 }); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(sorted_model->row_count() == 3); REQUIRE(sorted_model->row_data(0) == 1); REQUIRE(sorted_model->row_data(1) == 2); REQUIRE(sorted_model->row_data(2) == 3); } SCENARIO("Sorted Model Change") { auto vec_model = std::make_shared>(std::vector { 3, 4, 1, 2 }); auto sorted_model = std::make_shared>( vec_model, [](auto lhs, auto rhs) { return lhs < rhs; }); auto observer = std::make_shared(); sorted_model->attach_peer(observer); REQUIRE(sorted_model->row_count() == 4); REQUIRE(sorted_model->row_data(0) == 1); REQUIRE(sorted_model->row_data(1) == 2); REQUIRE(sorted_model->row_data(2) == 3); REQUIRE(sorted_model->row_data(3) == 4); /// Change the entry with the value 4 to 10 -> maintain order vec_model->set_row_data(1, 10); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.size() == 1); REQUIRE(observer->changed_rows[0] == 3); REQUIRE(observer->removed_rows.empty()); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(sorted_model->row_count() == 4); REQUIRE(sorted_model->row_data(0) == 1); REQUIRE(sorted_model->row_data(1) == 2); REQUIRE(sorted_model->row_data(2) == 3); REQUIRE(sorted_model->row_data(3) == 10); /// Change the entry with the value 10 to 0 -> new order with remove and insert vec_model->set_row_data(1, 0); REQUIRE(observer->added_rows.size() == 1); REQUIRE(observer->added_rows[0] == ModelObserver::Range { 0, 1 }); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.size() == 1); REQUIRE(observer->removed_rows[0] == ModelObserver::Range { 3, 1 }); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(sorted_model->row_count() == 4); REQUIRE(sorted_model->row_data(0) == 0); REQUIRE(sorted_model->row_data(1) == 1); REQUIRE(sorted_model->row_data(2) == 2); REQUIRE(sorted_model->row_data(3) == 3); } SCENARIO("Sorted Model Reset") { auto vec_model = std::make_shared>(std::vector { 3, 4, 1, 2 }); bool ascending = true; auto sorted_model = std::make_shared>(vec_model, [&ascending](auto lhs, auto rhs) { return ascending ? lhs < rhs : rhs < lhs; }); auto observer = std::make_shared(); sorted_model->attach_peer(observer); REQUIRE(sorted_model->row_count() == 4); REQUIRE(sorted_model->row_data(0) == 1); REQUIRE(sorted_model->row_data(1) == 2); REQUIRE(sorted_model->row_data(2) == 3); REQUIRE(sorted_model->row_data(3) == 4); ascending = false; sorted_model->reset(); REQUIRE(sorted_model->row_count() == 4); REQUIRE(sorted_model->row_data(0) == 4); REQUIRE(sorted_model->row_data(1) == 3); REQUIRE(sorted_model->row_data(2) == 2); REQUIRE(sorted_model->row_data(3) == 1); REQUIRE(observer->model_reset); } template class TestDeferredSortModel : public slint::SortModel { public: TestDeferredSortModel(bool &initialized, bool &sorted, std::shared_ptr> source_model) : slint::SortModel { std::move(source_model), [&sorted](const ModelData &first, const ModelData &second) { if (!sorted) { sorted = true; } return first > second; } } { initialized = true; } }; SCENARIO("Sorted Model Ensure Deferred") { auto source_model = std::make_shared>(std::vector { 0, 1, 2, 3, 4 }); bool initialized = false; bool sorted = false; auto sort_model = std::make_shared>(initialized, sorted, source_model); REQUIRE(initialized); REQUIRE_FALSE(sorted); sort_model->row_data(0); REQUIRE(sorted); } SCENARIO("Reverse Model Insert") { auto vec_model = std::make_shared>(std::vector { 3, 4, 1, 2 }); auto reverse_model = std::make_shared>(vec_model); auto observer = std::make_shared(); reverse_model->attach_peer(observer); REQUIRE(reverse_model->row_count() == 4); REQUIRE(reverse_model->row_data(0) == 2); REQUIRE(reverse_model->row_data(1) == 1); REQUIRE(reverse_model->row_data(2) == 4); REQUIRE(reverse_model->row_data(3) == 3); vec_model->insert(0, 10); REQUIRE(observer->added_rows.size() == 1); REQUIRE(observer->added_rows[0] == ModelObserver::Range { 4, 1 }); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.empty()); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(reverse_model->row_count() == 5); REQUIRE(reverse_model->row_data(0) == 2); REQUIRE(reverse_model->row_data(1) == 1); REQUIRE(reverse_model->row_data(2) == 4); REQUIRE(reverse_model->row_data(3) == 3); REQUIRE(reverse_model->row_data(4) == 10); } SCENARIO("Reverse Model Remove") { auto vec_model = std::make_shared>(std::vector { 3, 4, 1, 2 }); auto reverse_model = std::make_shared>(vec_model); auto observer = std::make_shared(); reverse_model->attach_peer(observer); REQUIRE(reverse_model->row_count() == 4); REQUIRE(reverse_model->row_data(0) == 2); REQUIRE(reverse_model->row_data(1) == 1); REQUIRE(reverse_model->row_data(2) == 4); REQUIRE(reverse_model->row_data(3) == 3); /// Remove the entry with the value 4 vec_model->erase(1); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.size() == 1); REQUIRE(observer->removed_rows[0] == ModelObserver::Range { 2, 1 }); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(reverse_model->row_count() == 3); REQUIRE(reverse_model->row_data(0) == 2); REQUIRE(reverse_model->row_data(1) == 1); REQUIRE(reverse_model->row_data(2) == 3); } SCENARIO("Reverse Model Change") { auto vec_model = std::make_shared>(std::vector { 3, 4, 1, 2 }); auto reverse_model = std::make_shared>(vec_model); auto observer = std::make_shared(); reverse_model->attach_peer(observer); REQUIRE(reverse_model->row_count() == 4); REQUIRE(reverse_model->row_data(0) == 2); REQUIRE(reverse_model->row_data(1) == 1); REQUIRE(reverse_model->row_data(2) == 4); REQUIRE(reverse_model->row_data(3) == 3); /// Change the entry with the value 4 to 10 -> maintain order vec_model->set_row_data(1, 10); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.size() == 1); REQUIRE(observer->changed_rows[0] == 2); REQUIRE(observer->removed_rows.empty()); REQUIRE(!observer->model_reset); observer->clear(); REQUIRE(reverse_model->row_count() == 4); REQUIRE(reverse_model->row_data(0) == 2); REQUIRE(reverse_model->row_data(1) == 1); REQUIRE(reverse_model->row_data(2) == 10); REQUIRE(reverse_model->row_data(3) == 3); vec_model->clear(); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.empty()); REQUIRE(observer->model_reset); observer->clear(); REQUIRE(reverse_model->row_count() == 0); } TEST_CASE("VectorModel clear and replace") { using namespace slint::private_api; auto model = std::make_shared>(std::vector { 0, 1, 2, 3, 4 }); auto observer = std::make_shared(); model->attach_peer(observer); REQUIRE(model->row_count() == 5); model->clear(); REQUIRE(model->row_count() == 0); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.empty()); REQUIRE(observer->model_reset); observer->clear(); model->clear(); REQUIRE(!observer->model_reset); observer->clear(); model->set_vector({ 2, 3, 4 }); REQUIRE(model->row_count() == 3); REQUIRE(model->row_data(1) == 3); REQUIRE(observer->added_rows.empty()); REQUIRE(observer->changed_rows.empty()); REQUIRE(observer->removed_rows.empty()); REQUIRE(observer->model_reset); // Test that taking a vector by value compiles std::vector new_data { 5, 6, 7, 8 }; model->set_vector(new_data); } ================================================ FILE: api/cpp/tests/multiple-includes/CMakeLists.txt ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 add_executable(multiple-includes main.cpp logic.cpp) target_link_libraries(multiple-includes PRIVATE Slint::Slint) slint_target_sources(multiple-includes app-window.slint COMPILATION_UNITS 0) slint_target_sources(multiple-includes another-window.slint NAMESPACE other COMPILATION_UNITS 0) ================================================ FILE: api/cpp/tests/multiple-includes/README.md ================================================ This is a test making sure that the header can be included in several compilation without causing multiple definitions And also that we can include two different generated headers generated different namespaces in the same compilation unit ================================================ FILE: api/cpp/tests/multiple-includes/another-window.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import {Button, AboutSlint} from "std-widgets.slint"; export global Logic { callback decrement(int) -> int; } export struct FooBar { qrs: int, tuv: string } export component AnotherWindow inherits Window { preferred-width: 800px; preferred-height: 600px; property count: 100; in property xxx; VerticalLayout { AboutSlint { } Button { text: "Hello"; clicked => { count = Logic.decrement(count); } } Text { text: count; } } } ================================================ FILE: api/cpp/tests/multiple-includes/app-window.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import {Button, AboutSlint} from "std-widgets.slint"; export global Logic { callback increment(int) -> int; } export struct FooBar { abc: int, def: string } component R { property foo; Rectangle { TouchArea {} } } export component App inherits Window { preferred-width: 800px; preferred-height: 600px; property count; VerticalLayout { AboutSlint { } Button { text: "Hello"; clicked => { count = Logic.increment(count); } } Text { text: count; } R { } } } ================================================ FILE: api/cpp/tests/multiple-includes/logic.cpp ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #include "logic.h" #include "app-window.h" // Test that it's ok to include twice #include "app-window.h" void setup_logic(const Logic &logic) { logic.on_increment([](int x) { return x + 1; }); } ================================================ FILE: api/cpp/tests/multiple-includes/logic.h ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #pragma once struct Logic; void setup_logic(const Logic &); ================================================ FILE: api/cpp/tests/multiple-includes/main.cpp ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #include "logic.h" #include "app-window.h" #include "another-window.h" int main(int argc, char **argv) { auto my_ui = App::create(); setup_logic(my_ui->global()); auto my_ui2 = other::AnotherWindow::create(); my_ui2->global().on_decrement([](int x) { return x - 1; }); my_ui2->show(); my_ui->run(); } ================================================ FILE: api/cpp/tests/platform_eventloop.cpp ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 // cSpell: ignore singleshot #define CATCH_CONFIG_MAIN #include "catch2/catch_all.hpp" #include #include #include #include #include #include #include struct TestPlatform : slint::platform::Platform { std::mutex the_mutex; std::deque queue; bool quit = false; std::condition_variable cv; std::chrono::time_point start = std::chrono::steady_clock::now(); /// Returns a new WindowAdapter virtual std::unique_ptr create_window_adapter() override { #ifdef SLINT_FEATURE_RENDERER_SOFTWARE struct TestWindowAdapter : slint::platform::WindowAdapter { slint::platform::SoftwareRenderer r { { } }; slint::PhysicalSize size() override { return slint::PhysicalSize({ 0, 0 }); } slint::platform::AbstractRenderer &renderer() override { return r; } }; return std::make_unique(); #else assert(!"creating window in this test"); return nullptr; #endif }; /// Spins an event loop and renders the visible windows. virtual void run_event_loop() override { quit = false; while (true) { slint::platform::update_timers_and_animations(); std::optional event; { std::unique_lock lock(the_mutex); if (queue.empty()) { if (quit) { quit = false; break; } if (auto duration = slint::platform::duration_until_next_timer_update()) { cv.wait_for(lock, *duration); } else { cv.wait(lock); } continue; } else { event = std::move(queue.front()); queue.pop_front(); } } if (event) { std::move(*event).run(); event.reset(); } } } virtual void quit_event_loop() override { const std::unique_lock lock(the_mutex); quit = true; cv.notify_all(); } virtual void run_in_event_loop(slint::platform::Platform::Task event) override { const std::unique_lock lock(the_mutex); queue.push_back(std::move(event)); cv.notify_all(); } #ifdef SLINT_FEATURE_FREESTANDING virtual std::chrono::milliseconds duration_since_start() override { return std::chrono::duration_cast( std::chrono::steady_clock::now() - start); } #endif }; bool init_platform = (slint::platform::set_platform(std::make_unique()), true); TEST_CASE("C++ Singleshot Timers") { using namespace slint; int called = 0; Timer testTimer(std::chrono::milliseconds(16), [&]() { slint::quit_event_loop(); called += 10; }); REQUIRE(called == 0); slint::run_event_loop(); REQUIRE(called == 10); } TEST_CASE("C++ Repeated Timer") { int timer_triggered = 0; slint::Timer timer; timer.start(slint::TimerMode::Repeated, std::chrono::milliseconds(3), [&]() { timer_triggered++; }); REQUIRE(timer_triggered == 0); bool timer_was_running = false; slint::Timer::single_shot(std::chrono::milliseconds(100), [&]() { timer_was_running = timer.running(); slint::quit_event_loop(); }); slint::run_event_loop(); REQUIRE(timer_triggered > 1); REQUIRE(timer_was_running); } TEST_CASE("C++ Restart Singleshot Timer") { int timer_triggered = 0; slint::Timer timer; timer.start(slint::TimerMode::SingleShot, std::chrono::milliseconds(3), [&]() { timer_triggered++; }); REQUIRE(timer.running()); REQUIRE(timer_triggered == 0); bool timer_was_running = true; slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() { timer_was_running = timer.running(); slint::quit_event_loop(); }); slint::run_event_loop(); REQUIRE(timer_triggered == 1); REQUIRE(!timer_was_running); // Timer is already stopped at this point timer_was_running = true; timer_triggered = 0; timer.restart(); REQUIRE(timer.running()); slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() { timer_was_running = timer.running(); slint::quit_event_loop(); }); slint::run_event_loop(); REQUIRE(timer_triggered == 1); REQUIRE(!timer_was_running); } TEST_CASE("C++ Restart Repeated Timer") { int timer_triggered = 0; slint::Timer timer; timer.start(slint::TimerMode::Repeated, std::chrono::milliseconds(3), [&]() { timer_triggered++; }); REQUIRE(timer_triggered == 0); bool timer_was_running = false; slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() { timer_was_running = timer.running(); slint::quit_event_loop(); }); slint::run_event_loop(); REQUIRE(timer_triggered > 1); REQUIRE(timer_was_running); timer_was_running = false; timer_triggered = 0; timer.stop(); slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() { timer_was_running = timer.running(); slint::quit_event_loop(); }); slint::run_event_loop(); REQUIRE(timer_triggered == 0); REQUIRE(!timer_was_running); timer_was_running = false; timer_triggered = 0; timer.restart(); slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() { timer_was_running = timer.running(); slint::quit_event_loop(); }); slint::run_event_loop(); REQUIRE(timer_triggered > 1); REQUIRE(timer_was_running); } TEST_CASE("Quit from event") { int called = 0; slint::invoke_from_event_loop([&] { slint::quit_event_loop(); called += 10; }); REQUIRE(called == 0); slint::run_event_loop(); REQUIRE(called == 10); } TEST_CASE("Event from thread") { std::atomic called = 0; auto t = std::thread([&] { called += 10; slint::invoke_from_event_loop([&] { called += 100; slint::quit_event_loop(); }); }); slint::run_event_loop(); REQUIRE(called == 110); t.join(); } TEST_CASE("Blocking Event from thread") { std::atomic called = 0; auto t = std::thread([&] { // test returning a, unique_ptr because it is movable-only std::unique_ptr foo = slint::blocking_invoke_from_event_loop([&] { return std::make_unique(42); }); called = *foo; int xxx = 123; slint::blocking_invoke_from_event_loop([&] { slint::quit_event_loop(); xxx = 888999; }); REQUIRE(xxx == 888999); }); slint::run_event_loop(); REQUIRE(called == 42); t.join(); } #if defined(SLINT_FEATURE_INTERPRETER) && defined(SLINT_FEATURE_RENDERER_SOFTWARE) # include TEST_CASE("Quit on last window closed") { using namespace slint::interpreter; using namespace slint; int ok = 0; ComponentCompiler compiler; auto comp_def = compiler.build_from_source("export component App inherits Window { }", ""); REQUIRE(comp_def.has_value()); auto instance = comp_def->create(); instance->hide(); // hide before show should mess the counter REQUIRE(instance->window().is_visible() == false); instance->show(); REQUIRE(instance->window().is_visible() == true); slint::Timer::single_shot(std::chrono::milliseconds(10), [&]() { REQUIRE(instance->window().is_visible() == true); instance->hide(); REQUIRE(instance->window().is_visible() == false); ok = 1; slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() { // event loop should be stopped ok = -1; }); }); slint::run_event_loop(); REQUIRE(ok == 1); REQUIRE(instance->window().is_visible() == false); ok = 0; slint::Timer::single_shot(std::chrono::milliseconds(5), [&]() { REQUIRE(ok == -1); // the event we started previously should have been ran first ok = 1; REQUIRE(instance->window().is_visible() == false); instance->show(); instance->show(); // two show shouldn't make the loop alive slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() { REQUIRE(instance->window().is_visible() == true); instance->hide(); ok = 2; slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() { // event loop should be stopped ok = -2; }); }); }); slint::run_event_loop(); REQUIRE(ok == 2); ok = 0; auto instance2 = comp_def->create(); instance2->show(); slint::Timer::single_shot(std::chrono::milliseconds(5), [&]() { REQUIRE(ok == -2); // the event we started previously should have been ran first instance->show(); instance2->hide(); slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() { instance2->show(); instance->hide(); slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() { instance2->hide(); ok = 3; }); }); }); slint::run_event_loop(); REQUIRE(ok == 3); ok = 0; slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() { REQUIRE(ok == 0); instance->show(); slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() { instance2->hide(); instance->hide(); slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() { instance2->show(); slint::quit_event_loop(); ok = 4; }); }); }); slint::run_event_loop(slint::EventLoopMode::RunUntilQuit); REQUIRE(ok == 4); } #endif ================================================ FILE: api/cpp/tests/properties.cpp ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #include #define CATCH_CONFIG_MAIN #include "catch2/catch_all.hpp" #include #include using slint::private_api::Property; SCENARIO("Basic usage") { Property prop; REQUIRE(prop.get() == 0); prop.set(42); REQUIRE(prop.get() == 42); { Property prop2; prop2.set_binding([&] { return prop.get() + 4; }); REQUIRE(prop2.get() == 42 + 4); prop.set(55); REQUIRE(prop2.get() == 55 + 4); } REQUIRE(prop.get() == 55); prop.set(33); REQUIRE(prop.get() == 33); } SCENARIO("Set after binding") { Property prop; REQUIRE(prop.get() == 0); prop.set_binding([] { return 55; }); prop.set(0); REQUIRE(prop.get() == 0); } ================================================ FILE: api/cpp/tests/test.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 export componentTest := Rectangle {} ================================================ FILE: api/cpp/tests/testing.cpp ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #define CATCH_CONFIG_MAIN #include "catch2/catch_all.hpp" #include #include #include SCENARIO("ElementHandle") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; auto result = compiler.build_from_source( R"( component ButtonBase { @children } component PushButton inherits ButtonBase { accessible-role: button; in property text <=> label.text; label := Text {} } export component App { VerticalLayout { PushButton { text: "first"; } second := PushButton { text: "second"; } } } )", ""); for (auto &&x : compiler.diagnostics()) std::cerr << x.message << std::endl; REQUIRE(result.has_value()); auto component_definition = *result; auto instance = component_definition.create(); SECTION("Find by accessible label") { auto elements = slint::testing::ElementHandle::find_by_accessible_label(instance, "first"); REQUIRE(elements.size() == 1); REQUIRE(*elements[0].accessible_label() == "first"); REQUIRE(*elements[0].id() == "PushButton::label"); REQUIRE(*elements[0].type_name() == "Text"); REQUIRE((*elements[0].bases()).size() == 0); } SECTION("Find by id") { auto elements = slint::testing::ElementHandle::find_by_element_id(instance, "App::second"); REQUIRE(elements.size() == 1); REQUIRE(*elements[0].id() == "App::second"); REQUIRE(*elements[0].type_name() == "PushButton"); REQUIRE((*elements[0].bases()).size() == 1); REQUIRE((*elements[0].bases())[0] == "ButtonBase"); REQUIRE(*elements[0].accessible_role() == slint::testing::AccessibleRole::Button); } SECTION("Find by type name") { auto elements = slint::testing::ElementHandle::find_by_element_type_name(instance, "PushButton"); REQUIRE(elements.size() == 2); REQUIRE(*elements[0].id() == ""); REQUIRE(*elements[1].id() == "App::second"); } } SCENARIO("LayoutKind") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; auto result = compiler.build_from_source( R"( export component App { hl := HorizontalLayout { vl := VerticalLayout { Rectangle {} } } gl := GridLayout { Rectangle {} } rect := Rectangle {} } )", ""); REQUIRE(result.has_value()); auto component_definition = *result; auto instance = component_definition.create(); SECTION("HorizontalLayout") { auto elements = slint::testing::ElementHandle::find_by_element_id(instance, "App::hl"); REQUIRE(elements.size() == 1); const auto lk = elements[0].layout_kind(); REQUIRE(lk.has_value()); REQUIRE(*lk == slint::testing::LayoutKind::HorizontalLayout); REQUIRE(*elements[0].type_name() == "HorizontalLayout"); } SECTION("VerticalLayout nested in HorizontalLayout") { auto elements = slint::testing::ElementHandle::find_by_element_id(instance, "App::vl"); REQUIRE(elements.size() == 1); REQUIRE(*elements[0].layout_kind() == slint::testing::LayoutKind::VerticalLayout); } SECTION("GridLayout") { auto elements = slint::testing::ElementHandle::find_by_element_id(instance, "App::gl"); REQUIRE(elements.size() == 1); REQUIRE(*elements[0].layout_kind() == slint::testing::LayoutKind::GridLayout); } SECTION("Non-layout element") { auto elements = slint::testing::ElementHandle::find_by_element_id(instance, "App::rect"); REQUIRE(elements.size() == 1); REQUIRE_FALSE(elements[0].layout_kind().has_value()); } } ================================================ FILE: api/cpp/tests/translator.cpp ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #define CATCH_CONFIG_MAIN #include "catch2/catch_all.hpp" #include #include static int destructor_call_count = 0; struct TestTranslator : public slint::Translator { ~TestTranslator() { ++destructor_call_count; } slint::SharedString translate(std::string_view string, std::string_view context) const override { return slint::SharedString("translate(string=") + string + ", context=" + context + ")"; } slint::SharedString ntranslate(uint64_t n, std::string_view singular, std::string_view plural, std::string_view context) const override { return slint::SharedString("ntranslate(n=") + slint::SharedString::from_number(n) + ", singular=" + singular + ", plural=" + plural + ", context=" + context + ")"; } }; TEST_CASE("Translate Properties") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; auto comp_def = compiler.build_from_source(R"( export App := Window { out property singular: @tr("singular"); out property plural: @tr("singular" | "plural-{n}" % 42); } )", ""); REQUIRE(comp_def.has_value()); auto instance = comp_def->create(); REQUIRE(instance->get_property("singular").has_value()); REQUIRE(instance->get_property("plural").has_value()); // Check before registering a translator, should return the original string. REQUIRE(*instance->get_property("singular")->to_string() == "singular"); REQUIRE(*instance->get_property("plural")->to_string() == "plural-42"); // Registering a translator should automatically update the properties. slint::set_translator(std::make_unique()); REQUIRE(*instance->get_property("singular")->to_string() == "translate(string=singular, context=App)"); REQUIRE(*instance->get_property("plural")->to_string() == "ntranslate(n=42, singular=singular, plural=plural-42, context=App)"); // Unregistering the translator should restore the untranslated strings. slint::set_translator(nullptr); REQUIRE(*instance->get_property("singular")->to_string() == "singular"); REQUIRE(*instance->get_property("plural")->to_string() == "plural-42"); } TEST_CASE("Set/Unset Translator") { destructor_call_count = 0; // Set nullptr should be allowed but has no effect. slint::set_translator(nullptr); // Registering a translator should not call the destructor. slint::set_translator(std::make_unique()); REQUIRE(destructor_call_count == 0); // Registering another translator should destroy the first translator. slint::set_translator(std::make_unique()); REQUIRE(destructor_call_count == 1); // Unregistering the translator should destroy the second translator. slint::set_translator(nullptr); REQUIRE(destructor_call_count == 2); } ================================================ FILE: api/cpp/tests/window.cpp ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #define CATCH_CONFIG_MAIN #include "catch2/catch_all.hpp" #include #include #include TEST_CASE("Basic Window Visibility") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; auto comp_def = compiler.build_from_source(R"( export App := Window { } )", ""); REQUIRE(comp_def.has_value()); auto instance = comp_def->create(); REQUIRE(instance->window().is_visible() == false); instance->show(); REQUIRE(instance->window().is_visible() == true); instance->hide(); REQUIRE(instance->window().is_visible() == false); } TEST_CASE("Window Scale Factory Existence") { using namespace slint::interpreter; using namespace slint; ComponentCompiler compiler; auto comp_def = compiler.build_from_source(R"( export App := Window { } )", ""); REQUIRE(comp_def.has_value()); auto instance = comp_def->create(); REQUIRE(instance->window().scale_factor() > 0); } ================================================ FILE: api/node/.gitignore ================================================ rust-module.cjs rust-module.d.cts index.js docs/ npm/ dist/ build/ ================================================ FILE: api/node/.npmignore ================================================ target Cargo.lock .cargo .github npm .eslintrc .prettierignore rustfmt.toml yarn.lock *.node .yarn __test__ renovate.json *.tgz ================================================ FILE: api/node/.yarnrc.yml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 nodeLinker: node-modules ================================================ FILE: api/node/Cargo.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 [package] name = "slint-node" description = "Internal Slint Runtime Library for NodeJS API." authors.workspace = true documentation.workspace = true edition.workspace = true homepage.workspace = true keywords.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true version.workspace = true categories = ["gui", "development-tools"] build = "build.rs" publish = false [lib] crate-type = ["cdylib"] path = "rust/lib.rs" [features] default = ["backend-winit", "renderer-femtovg", "renderer-software", "backend-qt", "accessibility"] # Keep in sync with features in nightly_snapshot.yaml, cpp_package.yaml, slint_tool_binary.yaml, and api/python/slint/Cargo.toml # binaries: default = ["backend-linuxkms-noseat", "backend-winit", "renderer-femtovg", "renderer-skia", "accessibility"] backend-qt = ["slint-interpreter/backend-qt"] backend-winit = ["slint-interpreter/backend-winit"] backend-winit-x11 = ["slint-interpreter/backend-winit-x11"] backend-winit-wayland = ["slint-interpreter/backend-winit-wayland"] backend-linuxkms = ["slint-interpreter/backend-linuxkms"] backend-linuxkms-noseat = ["slint-interpreter/backend-linuxkms-noseat"] renderer-femtovg = ["slint-interpreter/renderer-femtovg"] renderer-femtovg-wgpu = ["slint-interpreter/renderer-femtovg-wgpu"] renderer-skia = ["slint-interpreter/renderer-skia"] renderer-skia-opengl = ["slint-interpreter/renderer-skia-opengl"] renderer-skia-vulkan = ["slint-interpreter/renderer-skia-vulkan"] renderer-software = ["slint-interpreter/renderer-software"] accessibility = ["slint-interpreter/accessibility"] # Removed by node_package xtask testing = ["dep:i-slint-backend-testing"] [dependencies] napi = { version = "3", default-features = false, features = ["napi8"] } napi-derive = "3" i-slint-compiler = { workspace = true, features = ["default"] } i-slint-core = { workspace = true, features = ["default", "gettext-rs"] } i-slint-backend-selector = { workspace = true } slint-interpreter = { workspace = true, default-features = false, features = ["display-diagnostics", "internal", "compat-1-2"] } spin_on = { workspace = true } css-color-parser2 = { workspace = true } itertools = { workspace = true } send_wrapper = { workspace = true } # Removed by node_package xtask i-slint-backend-testing = { workspace = true, optional = true } smol_str = { workspace = true } [build-dependencies] napi-build = "2" ================================================ FILE: api/node/README.md ================================================ # Slint-node (Beta) [![npm](https://img.shields.io/npm/v/slint-ui)](https://www.npmjs.com/package/slint-ui) [Slint](https://slint.dev/) is a UI toolkit that supports different programming languages. Slint-node is the integration with Node.js. To get started you use the [walk-through tutorial](https://slint.dev/docs/slint/tutorial/quickstart). We also have a [Getting Started Template](https://github.com/slint-ui/slint-nodejs-template) repository with the code of a minimal application using Slint that can be used as a starting point to your program. **Warning: Beta** Slint-node is still in the early stages of development: APIs will change and important features are still being developed. ## Slint Language Manual The [Slint Language Documentation](../slint) covers the Slint UI description language in detail. ## Installing Slint Slint is available via NPM, so you can install by running the following command: ```sh npm install slint-ui ``` ### Dependencies You need to install the following components: * **[Node.js](https://nodejs.org/download/release/)** (v20 or newer) * **[pnpm](https://www.pnpm.io/)** * **[Rust compiler](https://www.rust-lang.org/tools/install)** You will also need a few more dependencies, see ## Using Slint First, import the API from the `slint-ui` module. In the following examples we're using [ECMAScript module syntax](https://nodejs.org/api/esm.html#modules-ecmascript-modules), but if you prefer you can also import the API using [CommonJS](https://nodejs.org/api/modules.html#modules-commonjs-modules) syntax. To initialize the API, you first need to import the `slint-ui` module in our code: ```js import * as slint from "slint-ui"; ``` Next, load a slint file with the `loadFile` function: ```js let ui = slint.loadFile("ui/main.slint"); ``` Combining these two steps leads us to the obligatory "Hello World" example: ```js import * as slint from "slint-ui"; let ui = slint.loadFile(new URL(".ui/main.slint", import.meta.url)); let main = new ui.Main(); main.run(); ``` For a full example, see [/examples/todo/node](https://github.com/slint-ui/slint/tree/master/examples/todo/node). ## API Overview ### Instantiating a Component The following example shows how to instantiate a Slint component from JavaScript. **`ui/main.slint`** ```slint export component MainWindow inherits Window { callback clicked <=> i-touch-area.clicked; in property counter; width: 400px; height: 200px; i-touch-area := TouchArea {} } ``` Each exported Window component is exposed as a type constructor. The type constructor takes as parameter an object which allow to initialize the value of public properties or callbacks. **`main.js`** ```js import * as slint from "slint-ui"; // In this example, the main.slint file exports a module which // has a counter property and a clicked callback let ui = slint.loadFile(new URL("ui/main.slint", import.meta.url)); let component = new ui.MainWindow({ counter: 42, clicked: function() { console.log("hello"); } }); ``` ### Accessing a property Properties declared as `out` or `in-out` in `.slint` files are visible as JavaScript on the component instance. ```js component.counter = 42; console.log(component.counter); ``` ### Callbacks Callbacks in Slint can be defined using the `callback` keyword and can be connected to a callback of an other component using the `<=>` syntax. **`ui/my-component.slint`** ```slint export component MyComponent inherits Window { callback clicked <=> i-touch-area.clicked; width: 400px; height: 200px; i-touch-area := TouchArea {} } ``` The callbacks in Slint are exposed as properties in JavaScript and that can be called as a function. **`main.js`** ```js import * as slint from "slint-ui"; let ui = slint.loadFile(new URL("ui/my-component.slint", import.meta.url)); let component = new ui.MyComponent(); // connect to a callback component.clicked = function() { console.log("hello"); }; // emit a callback component.clicked(); ``` ### Functions Functions in Slint can be defined using the `function` keyword. **`ui/my-component.slint`** ```slint export component MyComponent inherits Window { width: 400px; height: 200px; public function my-function() -> int { return 42; } } ``` If the function is marked `public`, it can also be called from JavaScript. **`main.js`** ```js import * as slint from "slint-ui"; let ui = slint.loadFile(new URL("ui/my-component.slint", import.meta.url)); let component = new ui.MyComponent(); // call a public function let result = component.my_function(); ``` ### Type Mappings The types used for properties in .slint design markup each translate to specific types in JavaScript. The follow table summarizes the entire mapping: | `.slint` Type | JavaScript Type | Notes | | --- | --- | --- | | `int` | `Number` | | | `float` | `Number` | | | `string` | `String` | | | `color` | `RgbaColor` | | | `brush` | `Brush` | | | `image` | `ImageData` | | | `length` | `Number` | | | `physical_length` | `Number` | | | `duration` | `Number` | The number of milliseconds | | `angle` | `Number` | The angle in degrees | | structure | `Object` | Structures are mapped to JavaScript objects where each structure field is a property. | | array | `Array` or any implementation of Model | | | enumeration | `String` | The value of an enum | ### Arrays and Models For property of array type, they can either be set using an array. In that case, getting the property also return an array. If the array was set within the .slint file, the array can be obtained ```js component.model = [1, 2, 3]; // component.model.push(4); // does not work, because it operate on a copy // but re-assigning works component.model = component.model.concat(4); ``` Another option is to set a model object. A model object has the following function: * `rowCount()`: returns the number of element in the model. * `rowData(index)`: return the row at the given index * `setRowData(index, data)`: called when the model need to be changed. `this.notify.rowDataChanged` must be called if successful. As an example, here is the implementation of the `ArrayModel` (which is available as `slint.ArrayModel`) ```js import * as slint from "slint-ui"; let array = [1, 2, 3]; export class ArrayModel extends slint.Model { private a: Array constructor(arr: Array) { super(); this.a = arr; } rowCount() { return this.a.length; } rowData(row: number) { return this.a[row]; } setRowData(row: number, data: T) { this.a[row] = data; this.notify.rowDataChanged(row); } push(...values: T[]) { let size = this.a.length; Array.prototype.push.apply(this.a, values); this.notify.rowAdded(size, arguments.length); } remove(index: number, size: number) { let r = this.a.splice(index, size); this.notify.rowRemoved(index, size); } get length(): number { return this.a.length; } values(): IterableIterator { return this.a.values(); } entries(): IterableIterator<[number, T]> { return this.a.entries() } } let model = new ArrayModel(array); component.model = model; model.push(4); // this works // does NOT work, getting the model does not return the right object // component.model.push(5); ``` ### structs An exported struct can be created either by defing of an object literal or by using the new keyword. **`my-component.slint`** ```slint export struct Person { name: string, age: int } export component MyComponent inherits Window { in-out property person; } ``` **`main.js`** ```js import * as slint from "slint-ui"; let ui = slint.loadFile(new URL("my-component.slint", import.meta.url)); let component = new ui.MyComponent(); // object literal component.person = { name: "Peter", age: 22 }; // new keyword (sets property values to default e.g. '' for string) component.person = new ui.Person(); // new keyword with parameters component.person = new ui.Person({ name: "Tim", age: 30 }); ``` ### enums A value of an exported enum can be set as string or by using the value from the exported enum. **`my-component.slint`** ```slint export enum Position { top, bottom } export component MyComponent inherits Window { in-out property position; } ``` **`main.js`** ```js import * as slint from "slint-ui"; let ui = slint.loadFile(new URL("my-component.slint", import.meta.url)); let component = new ui.MyComponent(); // set enum value as string component.position = "top"; // use the value of the enum component.position = ui.Position.bottom; ``` ================================================ FILE: api/node/__test__/api.spec.mts ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { test, expect } from "vitest"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; import { loadFile, loadSource, CompileError } from "../dist/index.js"; const dirname = path.dirname( fileURLToPath(import.meta.url).replace("build", "__test__"), ); // loadFile api test("loadFile", () => { // Test the URL variant here, to ensure that it works (esp. on Windows) const demo = loadFile( new URL( "resources/test.slint", import.meta.url.replace("build", "__test__"), ), ) as any; const test = new demo.Test(); expect(test.check).toBe("Test"); const errorPath = path.join(dirname, "resources/error.slint"); let error: any; try { loadFile(errorPath); } catch (e) { error = e; } expect(error).toBeDefined(); expect(error).toBeInstanceOf(CompileError); const formattedDiagnostics = error.diagnostics .map( (d) => `[${d.fileName}:${d.lineNumber}:${d.columnNumber}] ${d.message}`, ) .join("\n"); expect(error.message).toBe( "Could not compile " + errorPath + `\nDiagnostics:\n${formattedDiagnostics}`, ); expect(error.diagnostics).toStrictEqual([ { columnNumber: 18, level: 0, lineNumber: 5, message: "Missing type. The syntax to declare a property is `property name;`. Only two way bindings can omit the type", fileName: errorPath, }, { columnNumber: 22, level: 0, lineNumber: 5, message: "Syntax error: expected ';'", fileName: errorPath, }, { columnNumber: 22, level: 0, lineNumber: 5, message: "Parse error", fileName: errorPath, }, ]); }); test("loadFile constructor parameters", () => { const demo = loadFile( path.join(dirname, "resources/test-constructor.slint"), ) as any; let hello = ""; const test = new demo.Test({ say_hello: function () { hello = "hello"; }, check: "test", }); test.say_hello(); expect(test.check).toBe("test"); expect(hello).toBe("hello"); }); test("loadFile component instances and modules are sealed", () => { const demo = loadFile(path.join(dirname, "resources/test.slint")) as any; { let thrownError: any; try { demo.no_such_property = 42; } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError).toBeInstanceOf(TypeError); } const test = new demo.Test(); expect(test.check).toBe("Test"); { let thrownError: any; try { test.no_such_callback = () => {}; } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError).toBeInstanceOf(TypeError); } }); // loadSource api test("loadSource", () => { const source = `export component Test { out property check: "Test"; }`; const path = "api.spec.ts"; const demo = loadSource(source, path) as any; const test = new demo.Test(); expect(test.check).toBe("Test"); const errorSource = `export component Error { out property bool> check: "Test"; }`; let error: any; try { loadSource(errorSource, path); } catch (e) { error = e; } expect(error).toBeDefined(); expect(error).toBeInstanceOf(CompileError); const formattedDiagnostics = error.diagnostics .map( (d) => `[${d.fileName}:${d.lineNumber}:${d.columnNumber}] ${d.message}`, ) .join("\n"); expect(error.message).toBe( "Could not compile " + path + `\nDiagnostics:\n${formattedDiagnostics}`, ); // console.log(error?.diagnostics) expect(error.diagnostics).toStrictEqual([ { columnNumber: 22, level: 0, lineNumber: 2, message: "Missing type. The syntax to declare a property is `property name;`. Only two way bindings can omit the type", fileName: path, }, { columnNumber: 26, level: 0, lineNumber: 2, message: "Syntax error: expected ';'", fileName: path, }, { columnNumber: 26, level: 0, lineNumber: 2, message: "Parse error", fileName: path, }, ]); }); test("loadSource constructor parameters", () => { const source = `export component Test { callback say_hello(); in-out property check; }`; const path = "api.spec.ts"; const demo = loadSource(source, path) as any; let hello = ""; const test = new demo.Test({ say_hello: function () { hello = "hello"; }, check: "test", }); test.say_hello(); expect(test.check).toBe("test"); expect(hello).toBe("hello"); }); test("loadSource component instances and modules are sealed", () => { const source = `export component Test { out property check: "Test"; }`; const path = "api.spec.ts"; const demo = loadSource(source, path) as any; { let thrownError: any; try { demo.no_such_property = 42; } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError).toBeInstanceOf(TypeError); } const test = new demo.Test(); expect(test.check).toBe("Test"); { let thrownError: any; try { test.no_such_callback = () => {}; } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError).toBeInstanceOf(TypeError); } }); test("loadFile struct", () => { const demo = loadFile( path.join(dirname, "resources/test-struct.slint"), ) as any; const test = new demo.Test({ check: new demo.TestStruct(), }); expect(test.check).toStrictEqual({ text: "", flag: false, value: 0 }); }); test("loadFile struct constructor parameters", () => { const demo = loadFile( path.join(dirname, "resources/test-struct.slint"), ) as any; const test = new demo.Test({ check: new demo.TestStruct({ text: "text", flag: true, value: 12 }), }); expect(test.check).toStrictEqual({ text: "text", flag: true, value: 12 }); test.check = new demo.TestStruct({ text: "hello world", flag: false, value: 8, }); expect(test.check).toStrictEqual({ text: "hello world", flag: false, value: 8, }); }); test("loadFile struct constructor more parameters", () => { const demo = loadFile( path.join(dirname, "resources/test-struct.slint"), ) as any; const test = new demo.Test({ check: new demo.TestStruct({ text: "text", flag: true, value: 12, noProp: "hello", }), }); expect(test.check).toStrictEqual({ text: "text", flag: true, value: 12 }); }); test("loadFile enum", () => { const demo = loadFile( path.join(dirname, "resources/test-enum.slint"), ) as any; const test = new demo.Test({ check: demo.TestEnum.b, }); expect(test.check).toStrictEqual("b"); test.check = demo.TestEnum.c; expect(test.check).toStrictEqual("c"); }); test("file loader", () => { const testSource = `export component Test { in-out property text: "Hello World"; }`; const demo = loadFile( path.join(dirname, "resources/test-fileloader.slint"), { fileLoader: (path) => { if (path.includes("lib.slint")) { return testSource; } return ""; }, }, ) as any; const test = new demo.App(); expect(test.test_text).toStrictEqual("Hello World"); }); ================================================ FILE: api/node/__test__/compiler.spec.mts ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { test, expect } from "vitest"; import { private_api } from "../dist/index.js"; import * as napi from "../rust-module.cjs"; test("get/set include paths", () => { const compiler = new private_api.ComponentCompiler(); expect(compiler.includePaths.length).toBe(0); compiler.includePaths = ["path/one/", "path/two/", "path/three/"]; expect(compiler.includePaths).toStrictEqual([ "path/one/", "path/two/", "path/three/", ]); }); test("get/set library paths", () => { const compiler = new private_api.ComponentCompiler(); compiler.libraryPaths = { "libfile.slint": "third_party/libfoo/ui/lib.slint", libdir: "third_party/libbar/ui/", }; expect(compiler.libraryPaths).toStrictEqual({ "libfile.slint": "third_party/libfoo/ui/lib.slint", libdir: "third_party/libbar/ui/", }); }); test("get/set style", () => { const compiler = new private_api.ComponentCompiler(); expect(compiler.style).toBeNull(); compiler.style = "fluent"; expect(compiler.style).toBe("fluent"); }); test("get/set build from source", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource(`export component App {}`, ""); expect(definition.App).not.toBeNull(); expect(definition.App!.name).toBe("App"); }); test("constructor error ComponentDefinition and ComponentInstance", () => { let componentDefinitionError: any; try { new private_api.ComponentDefinition(); } catch (error) { componentDefinitionError = error; } expect(componentDefinitionError).toBeDefined(); expect(componentDefinitionError.message).toBe( "ComponentDefinition can only be created by using ComponentCompiler.", ); let componentInstanceError: any; try { new private_api.ComponentInstance(); } catch (error) { componentInstanceError = error; } expect(componentInstanceError).toBeDefined(); expect(componentInstanceError.message).toBe( "ComponentInstance can only be created by using ComponentCompiler.", ); }); test("properties ComponentDefinition", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( `export struct Struct {} export component App { in-out property bool-property; in-out property brush-property; in-out property color-property; in-out property float-property; in-out property image-property; in-out property int-property; in-out property <[string]> model-property; in-out property string-property; in-out property struct-property; }`, "", ); expect(definition.App).not.toBeNull(); const properties = definition.App!.properties; expect(properties.length).toBe(9); properties.sort((a, b) => { const nameA = a.name.toUpperCase(); // ignore upper and lowercase const nameB = b.name.toUpperCase(); // ignore upper and lowercase if (nameA < nameB) { return -1; } if (nameA > nameB) { return 1; } return 0; }); expect(properties[0].name).toBe("bool-property"); expect(properties[0].valueType).toBe(napi.ValueType.Bool); expect(properties[1].name).toBe("brush-property"); expect(properties[1].valueType).toBe(napi.ValueType.Brush); expect(properties[2].name).toBe("color-property"); expect(properties[2].valueType).toBe(napi.ValueType.Brush); expect(properties[3].name).toBe("float-property"); expect(properties[3].valueType).toBe(napi.ValueType.Number); expect(properties[4].name).toBe("image-property"); expect(properties[4].valueType).toBe(napi.ValueType.Image); expect(properties[5].name).toBe("int-property"); expect(properties[5].valueType).toBe(napi.ValueType.Number); expect(properties[6].name).toBe("model-property"); expect(properties[6].valueType).toBe(napi.ValueType.Model); expect(properties[7].name).toBe("string-property"); expect(properties[7].valueType).toBe(napi.ValueType.String); expect(properties[8].name).toBe("struct-property"); expect(properties[8].valueType).toBe(napi.ValueType.Struct); }); test("callbacks ComponentDefinition", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App { callback first-callback(); callback second-callback(); }`, "", ); expect(definition.App).not.toBeNull(); const callbacks = definition.App!.callbacks; expect(callbacks.length).toBe(2); callbacks.sort(); expect(callbacks[0]).toBe("first-callback"); expect(callbacks[1]).toBe("second-callback"); }); test("globalProperties ComponentDefinition", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( `export struct Struct {} export global TestGlobal { in-out property bool-property; in-out property brush-property; in-out property color-property; in-out property float-property; in-out property image-property; in-out property int-property; in-out property <[string]> model-property; in-out property string-property; in-out property struct-property; } export component App { }`, "", ); expect(definition.App).not.toBeNull(); expect(definition.App!.globalProperties("NonExistent")).toBeNull(); const properties = definition.App!.globalProperties("TestGlobal"); expect(properties).not.toBeNull(); expect(properties!.length).toBe(9); properties!.sort((a, b) => { const nameA = a.name.toUpperCase(); // ignore upper and lowercase const nameB = b.name.toUpperCase(); // ignore upper and lowercase if (nameA < nameB) { return -1; } if (nameA > nameB) { return 1; } return 0; }); expect(properties![0].name).toBe("bool-property"); expect(properties![0].valueType).toBe(napi.ValueType.Bool); expect(properties![1].name).toBe("brush-property"); expect(properties![1].valueType).toBe(napi.ValueType.Brush); expect(properties![2].name).toBe("color-property"); expect(properties![2].valueType).toBe(napi.ValueType.Brush); expect(properties![3].name).toBe("float-property"); expect(properties![3].valueType).toBe(napi.ValueType.Number); expect(properties![4].name).toBe("image-property"); expect(properties![4].valueType).toBe(napi.ValueType.Image); expect(properties![5].name).toBe("int-property"); expect(properties![5].valueType).toBe(napi.ValueType.Number); expect(properties![6].name).toBe("model-property"); expect(properties![6].valueType).toBe(napi.ValueType.Model); expect(properties![7].name).toBe("string-property"); expect(properties![7].valueType).toBe(napi.ValueType.String); expect(properties![8].name).toBe("struct-property"); expect(properties![8].valueType).toBe(napi.ValueType.Struct); }); test("globalCallbacks ComponentDefinition", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export global TestGlobal { callback first-callback(); callback second-callback(); } export component App { }`, "", ); expect(definition.App).not.toBeNull(); expect(definition.App!.globalCallbacks("NonExistent")).toBeNull(); const callbacks = definition.App!.globalCallbacks("TestGlobal"); expect(callbacks).not.toBeNull(); expect(callbacks!.length).toBe(2); callbacks!.sort(); expect(callbacks![0]).toBe("first-callback"); expect(callbacks![1]).toBe("second-callback"); }); test("compiler diagnostics", () => { const compiler = new private_api.ComponentCompiler(); expect( compiler.buildFromSource( `export component App { garbage }`, "testsource.slint", ), ).toStrictEqual({}); const diags = compiler.diagnostics; expect(diags.length).toBe(1); expect(diags[0]).toStrictEqual({ level: 0, message: "Parse error", lineNumber: 2, columnNumber: 12, fileName: "testsource.slint", }); }); test("non-existent properties and callbacks", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App { }`, "", ); expect(definition.App).not.toBeNull(); const instance = definition.App!.create(); expect(instance).not.toBeNull(); let prop_err: any; try { instance!.setProperty("non-existent", 42); } catch (error) { prop_err = error; } expect(prop_err).toBeDefined(); expect(prop_err.code).toBe("GenericFailure"); expect(prop_err.message).toBe( "Property non-existent not found in the component", ); let callback_err: any; try { instance!.setCallback("non-existent-callback", () => {}); } catch (error) { callback_err = error; } expect(callback_err).toBeDefined(); expect(callback_err.code).toBe("GenericFailure"); expect(callback_err.message).toBe( "Callback non-existent-callback not found in the component", ); }); ================================================ FILE: api/node/__test__/eventloop.spec.mts ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 // Test that the Slint event loop processes libuv's events. import { test, expect, afterEach } from "vitest"; import * as http from "node:http"; import { runEventLoop, quitEventLoop, private_api } from "../dist/index.js"; afterEach(() => { quitEventLoop(); }); test.sequential("merged event loops with timer", async () => { let invoked = false; await runEventLoop(() => { setTimeout(() => { invoked = true; quitEventLoop(); }, 2); }); expect(invoked).toBe(true); }); test.sequential("merged event loops with networking", async () => { const listener = (request, result) => { result.writeHead(200); result.end("Hello World"); }; let received_response = ""; await runEventLoop(() => { const server = http.createServer(listener); server.listen(() => { const host = "localhost"; const port = (server.address() as any).port; console.log(`server ready at ${host}:${port}`); (fetch as any)(`http://${host}:${port}/`) .then((response: Response) => { return response.text(); }) .then((text: string) => { received_response = text; //console.log("received ", text); quitEventLoop(); server.close(); }); }); }); expect(received_response).toBe("Hello World"); }); test.sequential("quit event loop on last window closed with callback", async () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App inherits Window { width: 300px; height: 300px; }`, "", ); expect(definition.App).not.toBeNull(); const instance = definition.App!.create() as any; expect(instance).not.toBeNull(); instance.window().show(); await runEventLoop(() => { setTimeout(() => { instance.window().hide(); }, 2); }); }); ================================================ FILE: api/node/__test__/globals.spec.mts ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { test, expect } from "vitest"; import { private_api } from "../dist/index.js"; test("get/set global properties", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export global Global { in-out property name: "Initial"; } export component App {}`, "", ); expect(definition.App).not.toBeNull(); const instance = definition.App!.create(); expect(instance).not.toBeNull(); expect(instance!.getGlobalProperty("Global", "name")).toBe("Initial"); instance!.setGlobalProperty("Global", "name", "Hello"); expect(instance!.getGlobalProperty("Global", "name")).toBe("Hello"); { let thrownError: any; try { instance!.getGlobalProperty("MyGlobal", "name"); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe("Global MyGlobal not found"); } { let thrownError: any; try { instance!.setGlobalProperty("MyGlobal", "name", "hello"); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe("Global MyGlobal not found"); } { let thrownError: any; try { instance!.getGlobalProperty("Global", "age"); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe("no such property"); } { let thrownError: any; try { instance!.setGlobalProperty("Global", "age", 42); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe( "Property age of global Global not found in the component", ); } { let thrownError: any; try { instance!.setGlobalProperty("Global", "name", 42); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("StringExpected"); expect(thrownError.message).toContain("String"); } { let thrownError: any; try { instance!.setGlobalProperty("Global", "name", { blah: "foo" }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("StringExpected"); expect(thrownError.message).toContain("String"); } }); test("invoke global callback", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export struct Person { name: string } export global Global { callback great(string, string, string, string, string); callback great-person(Person); callback person() -> Person; callback get-string() -> string; person => { { name: "florian" } } get-string => { "string" } } export component App {} `, "", ); expect(definition.App).not.toBeNull(); const instance = definition.App!.create(); expect(instance).not.toBeNull(); { let thrownError: any; try { instance!.setGlobalCallback("MyGlobal", "great", () => {}); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe("Global MyGlobal not found"); } { let thrownError: any; try { instance!.invokeGlobal("MyGlobal", "great", []); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe("Global MyGlobal not found"); } let speakTest: string; instance!.setGlobalCallback( "Global", "great", (a: string, b: string, c: string, d: string, e: string) => { speakTest = "hello " + a + ", " + b + ", " + c + ", " + d + " and " + e; }, ); { let thrownError: any; try { instance!.setGlobalCallback("Global", "bye", () => {}); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe( "Callback bye of global Global not found in the component", ); } { let thrownError: any; try { instance!.invokeGlobal("Global", "bye", []); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe( "Callback bye of global Global not found in the component", ); } instance!.invokeGlobal("Global", "great", [ "simon", "olivier", "auri", "tobias", "florian", ]); expect(speakTest).toStrictEqual( "hello simon, olivier, auri, tobias and florian", ); instance!.setGlobalCallback("Global", "great-person", (p: any) => { speakTest = "hello " + p.name; }); instance!.invokeGlobal("Global", "great-person", [{ name: "simon" }]); expect(speakTest).toStrictEqual("hello simon"); instance!.invokeGlobal("Global", "great-person", [{ hello: "simon" }]); expect(speakTest).toStrictEqual("hello "); expect(instance!.invokeGlobal("Global", "get-string", [])).toStrictEqual( "string", ); expect(instance!.invokeGlobal("Global", "person", [])).toStrictEqual({ name: "florian", }); }); ================================================ FILE: api/node/__test__/helpers/utils.ts ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { hook } from "capture-console"; export function captureAsyncStderr() { const chunks: string[] = []; const streams = new Set(); streams.add(process.stderr); const consoleStderr = (globalThis.console as any)?._stderr; if (consoleStderr && consoleStderr !== process.stderr) { streams.add(consoleStderr); } const unhooks = Array.from(streams).map((stream) => hook(stream, { quiet: true }, (chunk) => { chunks.push(chunk); }), ); return { output() { return chunks.join(""); }, restore() { while (unhooks.length) { const unhook = unhooks.pop(); unhook && unhook(); } }, }; } ================================================ FILE: api/node/__test__/js_value_conversion.spec.mts ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { test, expect } from "vitest"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; import { read, ImageColorModel } from "image-js"; import { captureAsyncStderr } from "./helpers/utils.js"; import { private_api, type ImageData, ArrayModel, type Model, } from "../dist/index.js"; const filename = fileURLToPath(import.meta.url).replace("build", "__test__"); const dirname = path.dirname(filename); function createNonNullInstance(definition: { App?: { create(): private_api.ComponentInstance | null }; }): private_api.ComponentInstance { const app = definition.App; if (!app) { throw new Error("Expected App to be defined"); } const instance = app.create(); if (!instance) { throw new Error("Expected non-null instance from App.create()"); } return instance; } test("get/set string properties", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( `export component App { in-out property name: "Initial"; }`, "", ); const instance = createNonNullInstance(definition); expect(instance.getProperty("name")).toBe("Initial"); instance.setProperty("name", "Hello"); expect(instance.getProperty("name")).toBe("Hello"); { let thrownError: any; try { instance.setProperty("name", 42); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("StringExpected"); expect(thrownError.message).toContain("String"); } { let thrownError: any; try { instance.setProperty("name", { blah: "foo" }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("StringExpected"); expect(thrownError.message).toContain("String"); } }); test("get/set number properties", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App { in-out property age: 42; }`, "", ); const instance = createNonNullInstance(definition); expect(instance.getProperty("age")).toBe(42); instance.setProperty("age", 100); expect(instance.getProperty("age")).toBe(100); { let thrownError: any; try { instance.setProperty("age", "Hello"); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("NumberExpected"); expect(thrownError.message).toContain("Number"); } { let thrownError: any; try { instance.setProperty("age", { blah: "foo" }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("NumberExpected"); expect(thrownError.message).toContain("Number"); } }); test("get/set bool properties", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( `export component App { in-out property ready: true; }`, "", ); const instance = createNonNullInstance(definition); expect(instance.getProperty("ready")).toBe(true); instance.setProperty("ready", false); expect(instance.getProperty("ready")).toBe(false); { let thrownError: any; try { instance.setProperty("ready", "Hello"); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("BooleanExpected"); expect(thrownError.message).toContain("Boolean"); } { let thrownError: any; try { instance.setProperty("ready", { blah: "foo" }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("BooleanExpected"); expect(thrownError.message).toContain("Boolean"); } }); test("set struct properties", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export struct Player { name: string, age: int, energy_level: float } export component App { in-out property player: { name: "Florian", age: 20, energy_level: 40% }; } `, "", ); const instance = createNonNullInstance(definition); expect(instance.getProperty("player")).toStrictEqual({ name: "Florian", age: 20, energy_level: 0.4, }); instance.setProperty("player", { name: "Simon", age: 22, energy_level: 0.8, }); expect(instance.getProperty("player")).toStrictEqual({ name: "Simon", age: 22, energy_level: 0.8, }); // Extra properties are thrown away instance.setProperty("player", { name: "Excessive Player", age: 100, energy_level: 0.8, weight: 200, }); expect(instance.getProperty("player")).toStrictEqual({ name: "Excessive Player", age: 100, energy_level: 0.8, }); // Missing properties are defaulted instance.setProperty("player", { age: 39 }); expect(instance.getProperty("player")).toStrictEqual({ name: "", age: 39, energy_level: 0.0, }); }); test("get/set image properties", async () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App { in-out property image: @image-url("resources/rgb.png"); in property external-image; out property external-image-ok: self.external-image.width == 64 && self.external-image.height == 64; }`, filename, ); const instance = createNonNullInstance(definition); const slintImage = instance.getProperty("image"); if (slintImage instanceof private_api.SlintImageData) { expect((slintImage as private_api.SlintImageData).width).toStrictEqual( 64, ); expect((slintImage as private_api.SlintImageData).height).toStrictEqual( 64, ); expect((slintImage as ImageData).path.endsWith("rgb.png")).toBe(true); const image = await read(path.join(dirname, "resources/rgb.png")); const rgbaImage = image.colorModel === ImageColorModel.RGBA ? image : image.convertColor(ImageColorModel.RGBA); const raw = rgbaImage.getRawImage(); // Sanity check: setProperty fails when passed definitely a non-image { let thrownError: any; try { instance.setProperty("external-image", 42); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.message).toBe( "Cannot convert object to image, because the provided object does not have an u32 `width` property", ); } { let thrownError: any; try { instance.setProperty("external-image", { garbage: true }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.message).toBe( "Cannot convert object to image, because the provided object does not have an u32 `width` property", ); } { let thrownError: any; try { instance.setProperty("external-image", { width: [1, 2, 3] }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.message).toBe( "Cannot convert object to image, because the provided object does not have an u32 `height` property", ); } { let thrownError: any; try { instance.setProperty("external-image", { width: 1, height: 1, data: new Uint8ClampedArray(1), }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.message).toBe( "data property does not have the correct size; expected 1 (width) * 1 (height) * 4 = 1; got 4", ); } expect(raw.width).toBe(64); expect(raw.height).toBe(64); // Duck typing: object with width, height, data has the shape of ImageData, so // it should be possible to use it with Slint: instance.setProperty("external-image", raw); expect(instance.getProperty("external-image-ok")).toBe(true); expect(raw.data.length).toBe((slintImage as ImageData).data.length); expect(Buffer.from(raw.data as Uint8Array)).toStrictEqual( (slintImage as ImageData).data, ); expect( (instance.getProperty("external-image") as ImageData).path, ).toStrictEqual(undefined); } }); test("get/set brush properties", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App { in-out property black: #000000; in-out property trans: transparent; in-out property ref: transparent; in-out property linear-gradient: @linear-gradient(90deg, #3f87a6 0%, #ebf8e1 50%, #f69d3c 100%); in-out property radial-gradient: @radial-gradient(circle, #f00 0%, #0f0 50%, #00f 100%); in-out property ref-color; } `, "", ); const instance = createNonNullInstance(definition); const black = instance.getProperty("black"); expect((black as private_api.SlintBrush).toString()).toBe("#000000"); if (black instanceof private_api.SlintBrush) { const blackSlintRgbaColor = (black as private_api.SlintBrush).color; expect(blackSlintRgbaColor.red).toStrictEqual(0); expect(blackSlintRgbaColor.green).toStrictEqual(0); expect(blackSlintRgbaColor.blue).toStrictEqual(0); } instance.setProperty("black", "#ffffff"); const white = instance.getProperty("black"); if (white instanceof private_api.SlintBrush) { const whiteSlintRgbaColor = (white as private_api.SlintBrush).color; expect(whiteSlintRgbaColor.red).toStrictEqual(255); expect(whiteSlintRgbaColor.green).toStrictEqual(255); expect(whiteSlintRgbaColor.blue).toStrictEqual(255); } const transparent = instance.getProperty("trans"); if (black instanceof private_api.SlintBrush) { expect((transparent as private_api.SlintBrush).isTransparent).toBe( true, ); } const ref = new private_api.SlintBrush({ red: 100, green: 110, blue: 120, alpha: 255, }); instance.setProperty("ref", ref); let instance_ref = instance.getProperty("ref"); if (instance_ref instanceof private_api.SlintBrush) { const ref_color = (instance_ref as private_api.SlintBrush).color; expect(ref_color.red).toStrictEqual(100); expect(ref_color.green).toStrictEqual(110); expect(ref_color.blue).toStrictEqual(120); expect(ref_color.alpha).toStrictEqual(255); } instance.setProperty("ref", { color: { red: 110, green: 120, blue: 125, alpha: 255 }, }); instance_ref = instance.getProperty("ref"); if (instance_ref instanceof private_api.SlintBrush) { const ref_color = (instance_ref as private_api.SlintBrush).color; expect(ref_color.red).toStrictEqual(110); expect(ref_color.green).toStrictEqual(120); expect(ref_color.blue).toStrictEqual(125); expect(ref_color.alpha).toStrictEqual(255); } instance.setProperty("ref", { red: 110, green: 120, blue: 125, alpha: 255, }); instance_ref = instance.getProperty("ref"); if (instance_ref instanceof private_api.SlintBrush) { const ref_color = (instance_ref as private_api.SlintBrush).color; expect(ref_color.red).toStrictEqual(110); expect(ref_color.green).toStrictEqual(120); expect(ref_color.blue).toStrictEqual(125); expect(ref_color.alpha).toStrictEqual(255); } instance.setProperty("ref", {}); instance_ref = instance.getProperty("ref"); if (instance_ref instanceof private_api.SlintBrush) { const ref_color = (instance_ref as private_api.SlintBrush).color; expect(ref_color.red).toStrictEqual(0); expect(ref_color.green).toStrictEqual(0); expect(ref_color.blue).toStrictEqual(0); expect(ref_color.alpha).toStrictEqual(0); } const radialGradient = instance.getProperty("radial-gradient"); if (radialGradient instanceof private_api.SlintBrush) { expect((radialGradient as private_api.SlintBrush).toString()).toBe( "radial-gradient(circle, rgba(255, 0, 0, 255) 0%, rgba(0, 255, 0, 255) 50%, rgba(0, 0, 255, 255) 100%)", ); } const linearGradient = instance.getProperty("linear-gradient"); if (linearGradient instanceof private_api.SlintBrush) { expect((linearGradient as private_api.SlintBrush).toString()).toBe( "linear-gradient(90deg, rgba(63, 135, 166, 255) 0%, rgba(235, 248, 225, 255) 50%, rgba(246, 157, 60, 255) 100%)", ); } { let thrownError: any; try { instance.setProperty("ref-color", { red: "abc", blue: 0, green: 0, alpha: 0, }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("NumberExpected"); expect(thrownError.message).toBe( "Failed to convert napi value String into rust type `f64`", ); } { let thrownError: any; try { instance.setProperty("ref-color", { red: 0, blue: true, green: 0, alpha: 0, }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("NumberExpected"); expect(thrownError.message).toBe( "Failed to convert napi value Boolean into rust type `f64`", ); } { let thrownError: any; try { instance.setProperty("ref-color", { red: 0, blue: 0, green: true, alpha: 0, }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("NumberExpected"); expect(thrownError.message).toBe( "Failed to convert napi value Boolean into rust type `f64`", ); } { let thrownError: any; try { instance.setProperty("ref-color", { red: 0, blue: 0, green: 0, alpha: new private_api.SlintRgbaColor(), }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("NumberExpected"); expect(thrownError.message).toBe( "Failed to convert napi value Object into rust type `f64`", ); } { let thrownError: any; try { instance.setProperty("ref-color", { blue: 0, green: 0, alpha: 0 }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe("Property red is missing"); } { let thrownError: any; try { instance.setProperty("ref-color", { red: 0, green: 0, alpha: 0 }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe("Property blue is missing"); } instance.setProperty("ref-color", { red: 0, green: 0, blue: 0 }); instance_ref = instance.getProperty("ref-color"); if (instance_ref instanceof private_api.SlintBrush) { const ref_color = (instance_ref as private_api.SlintBrush).color; expect(ref_color.red).toStrictEqual(0); expect(ref_color.green).toStrictEqual(0); expect(ref_color.blue).toStrictEqual(0); expect(ref_color.alpha).toStrictEqual(255); } // ref is a brush, but setting to a color should not throw, but take the brush's color. instance.setProperty("ref-color", ref); instance_ref = instance.getProperty("ref-color"); if (instance_ref instanceof private_api.SlintBrush) { const ref_color = (instance_ref as private_api.SlintBrush).color; expect(ref_color.red).toStrictEqual(ref.color.red); expect(ref_color.green).toStrictEqual(ref.color.green); expect(ref_color.blue).toStrictEqual(ref.color.blue); expect(ref_color.alpha).toStrictEqual(ref.color.alpha); } }); test("get/set enum properties", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( `export enum Direction { up, down } export component App { in-out property direction: up; }`, "", ); const instance = createNonNullInstance(definition); expect(instance.getProperty("direction")).toBe("up"); instance.setProperty("direction", "down"); expect(instance.getProperty("direction")).toBe("down"); { let thrownError: any; try { instance.setProperty("direction", 42); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe("42 is not a value of enum Direction"); } { let thrownError: any; try { instance.setProperty("direction", { blah: "foo" }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe( "[object Object] is not a value of enum Direction", ); } { let thrownError: any; try { instance.setProperty("direction", "left"); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe( "left is not a value of enum Direction", ); } }); test("ArrayModel", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export struct Player { name: string, age: int } export component App { in-out property <[int]> int-model; in-out property <[string]> string-model; in-out property <[Player]> struct-model; }`, "", ); const instance = createNonNullInstance(definition); expect(Array.from(new ArrayModel([3, 2, 1]))).toStrictEqual([3, 2, 1]); instance.setProperty("int-model", new ArrayModel([10, 9, 8])); const intArrayModel = instance.getProperty( "int-model", ) as ArrayModel; expect(intArrayModel.rowCount()).toStrictEqual(3); expect(intArrayModel.values()).toStrictEqual( new ArrayModel([10, 9, 8]).values(), ); instance.setProperty( "string-model", new ArrayModel(["Simon", "Olivier", "Auri", "Tobias", "Florian"]), ); const stringArrayModel = instance.getProperty( "string-model", ) as ArrayModel; expect(stringArrayModel.values()).toStrictEqual( new ArrayModel([ "Simon", "Olivier", "Auri", "Tobias", "Florian", ]).values(), ); instance.setProperty( "struct-model", new ArrayModel([ { name: "simon", age: 22 }, { name: "florian", age: 22 }, ]), ); const structArrayModel = instance.getProperty( "struct-model", ) as ArrayModel; expect(structArrayModel.values()).toStrictEqual( new ArrayModel([ { name: "simon", age: 22 }, { name: "florian", age: 22 }, ]).values(), ); }); test("MapModel", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App { in-out property <[string]> model; }`, "", ); const instance = createNonNullInstance(definition); interface Name { first: string; last: string; } const nameModel: ArrayModel = new ArrayModel([ { first: "Hans", last: "Emil" }, { first: "Max", last: "Mustermann" }, { first: "Roman", last: "Tisch" }, ]); const mapModel = new private_api.MapModel(nameModel, (data) => { return data.last + ", " + data.first; }); instance.setProperty("model", mapModel); nameModel.setRowData(0, { first: "Simon", last: "Hausmann" }); nameModel.setRowData(1, { first: "Olivier", last: "Goffart" }); const checkModel = instance.getProperty("model") as Model; expect(checkModel.rowData(0)).toBe("Hausmann, Simon"); expect(checkModel.rowData(1)).toBe("Goffart, Olivier"); expect(checkModel.rowData(2)).toBe("Tisch, Roman"); }); test("MapModel undefined rowData sourcemodel", () => { const nameModel: ArrayModel = new ArrayModel([1, 2, 3]); let mapFunctionCallCount = 0; const mapModel = new private_api.MapModel( nameModel, (data) => { mapFunctionCallCount++; return data.toString(); }, ); for (let i = 0; i < mapModel.rowCount(); ++i) { mapModel.rowData(i); } expect(mapFunctionCallCount).toStrictEqual(mapModel.rowCount()); mapFunctionCallCount = 0; expect(nameModel.rowData(nameModel.rowCount())).toBeUndefined(); expect(mapFunctionCallCount).toStrictEqual(0); expect(mapModel.rowData(mapModel.rowCount())).toBeUndefined(); expect(mapFunctionCallCount).toStrictEqual(0); }); test("ArrayModel rowCount", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App { out property model-length: model.length; in-out property <[int]> model; }`, "", ); const instance = createNonNullInstance(definition); const model = new ArrayModel([10, 9, 8]); instance.setProperty("model", model); expect(model.rowCount()).toBe(3); expect(instance.getProperty("model-length") as number).toBe(3); }); test("ArrayModel rowData/setRowData", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App { callback data(int) -> int; in-out property <[int]> model; data(row) => { model[row] } }`, "", ); const instance = createNonNullInstance(definition); const model = new ArrayModel([10, 9, 8]); instance.setProperty("model", model); expect(model.rowData(1)).toBe(9); expect(instance.invoke("data", [1])).toStrictEqual(9); model.setRowData(1, 4); expect(model.rowData(1)).toBe(4); expect(instance.invoke("data", [1])).toStrictEqual(4); }); test("Model notify", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App { width: 300px; height: 300px; out property layout-height: layout.height; in-out property<[length]> fixed-height-model; VerticalLayout { alignment: start; layout := VerticalLayout { for fixed-height in fixed-height-model: Rectangle { background: blue; height: fixed-height; } } } }`, "", ); const instance = createNonNullInstance(definition); const model = new ArrayModel([100, 0]); instance.setProperty("fixed-height-model", model); expect(instance.getProperty("layout-height") as number).toBe(100); model.setRowData(1, 50); expect(instance.getProperty("layout-height") as number).toBe(150); model.push(75); expect(instance.getProperty("layout-height") as number).toBe(225); model.remove(1, 2); expect(instance.getProperty("layout-height") as number).toBe(100); }); test("model from array", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App { in-out property <[int]> int-array; in-out property <[string]> string-array; }`, "", ); const instance = createNonNullInstance(definition); if (!instance) { throw new Error("Expected non-null instance from App.create()"); } instance.setProperty("int-array", [10, 9, 8]); const wrapped_int_model = instance.getProperty( "int-array", ) as Model; expect(Array.from(wrapped_int_model)).toStrictEqual([10, 9, 8]); expect(wrapped_int_model.rowCount()).toStrictEqual(3); expect(wrapped_int_model.rowData(0)).toStrictEqual(10); expect(wrapped_int_model.rowData(1)).toStrictEqual(9); expect(wrapped_int_model.rowData(2)).toStrictEqual(8); expect(Array.from(wrapped_int_model)).toStrictEqual([10, 9, 8]); instance.setProperty("string-array", [ "Simon", "Olivier", "Auri", "Tobias", "Florian", ]); const wrapped_string_model = instance.getProperty( "string-array", ) as Model; expect(wrapped_string_model.rowCount()).toStrictEqual(5); expect(wrapped_string_model.rowData(0)).toStrictEqual("Simon"); expect(wrapped_string_model.rowData(1)).toStrictEqual("Olivier"); expect(wrapped_string_model.rowData(2)).toStrictEqual("Auri"); expect(wrapped_string_model.rowData(3)).toStrictEqual("Tobias"); expect(wrapped_string_model.rowData(4)).toStrictEqual("Florian"); }); test("invoke callback", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export struct Person { name: string } export component App { callback great(string, string, string, string, string); callback great-person(Person); callback person() -> Person; callback get-string() -> string; person => { { name: "florian" } } get-string => { "string" } } `, "", ); const instance = createNonNullInstance(definition); let speakTest: string; instance.setCallback( "great", (a: string, b: string, c: string, d: string, e: string) => { speakTest = "hello " + a + ", " + b + ", " + c + ", " + d + " and " + e; }, ); instance.invoke("great", ["simon", "olivier", "auri", "tobias", "florian"]); expect(speakTest).toStrictEqual( "hello simon, olivier, auri, tobias and florian", ); instance.setCallback("great-person", (p: any) => { speakTest = "hello " + p.name; }); instance.invoke("great-person", [{ name: "simon" }]); expect(speakTest).toStrictEqual("hello simon"); instance.invoke("great-person", [{ hello: "simon" }]); expect(speakTest).toStrictEqual("hello "); expect(instance.invoke("get-string", [])).toStrictEqual("string"); expect(instance.invoke("person", [])).toStrictEqual({ name: "florian" }); }); test("wrong callback return type ", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export struct Person { name: string, age: int, } export component App { callback get-string() -> string; callback get-int() -> int; callback get-bool() -> bool; callback get-person() -> Person; } `, "", ); const instance = createNonNullInstance(definition); let speakTest: string; instance.setCallback("get-string", () => { return 20; }); const string = instance.invoke("get-string", []); expect(string).toStrictEqual(""); instance.setCallback("get-int", () => { return "string"; }); const int = instance.invoke("get-int", []); expect(int).toStrictEqual(0); instance.setCallback("get-bool", () => { return "string"; }); const bool = instance.invoke("get-bool", []); expect(bool).toStrictEqual(false); instance.setCallback("get-person", () => { return "string"; }); const person = instance.invoke("get-person", []); expect(person).toStrictEqual({ name: "", age: 0 }); }); test("wrong global callback return type ", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export struct Person { name: string, age: int, } export global Global { callback get-string() -> string; callback get-int() -> int; callback get-bool() -> bool; callback get-person() -> Person; } export component App { } `, "", ); const instance = createNonNullInstance(definition); let speakTest: string; instance.setGlobalCallback("Global", "get-string", () => { return 20; }); const string = instance.invokeGlobal("Global", "get-string", []); expect(string).toStrictEqual(""); instance.setGlobalCallback("Global", "get-bool", () => { return "string"; }); const bool = instance.invokeGlobal("Global", "get-bool", []); expect(bool).toStrictEqual(false); instance.setGlobalCallback("Global", "get-person", () => { return "string"; }); const person = instance.invokeGlobal("Global", "get-person", []); expect(person).toStrictEqual({ name: "", age: 0 }); }); test("throw exception in callback", async () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App { callback throw-something(); } `, "", ); const instance = createNonNullInstance(definition); instance.setCallback("throw-something", () => { throw new Error("I'm an error"); }); const stderrCapture = captureAsyncStderr(); try { instance.invoke("throw-something", []); // Vitest runs these tests in workers and the native binding writes to // stderr on the next macrotask, so yield once before restoring writers. await new Promise((resolve) => setTimeout(resolve, 0)); } finally { stderrCapture.restore(); } const output = stderrCapture.output(); expect( output.includes("Node.js: Invoking callback 'throw-something' failed"), ).toBe(true); expect(output.includes("I'm an error")).toBe(true); }); test("throw exception set color", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App { in-out property test; } `, "", ); const instance = createNonNullInstance(definition); { let thrownError: any; try { instance.setProperty("test", { garbage: true }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe("Property red is missing"); } }); ================================================ FILE: api/node/__test__/models.spec.mts ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { test, expect } from "vitest"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; import { loadFile, loadSource, CompileError, ArrayModel, private_api, Model, } from "../dist/index.js"; test("MapModel notify rowChanged", () => { const source = ` export component App { in-out property <[string]> model; in-out property changed-items; for item in root.model : Text { text: item; changed text => { root.changed-items += self.text; } } }`; const path = "api.spec.ts"; private_api.initTesting(); const demo = loadSource(source, path) as any; const instance = new demo.App(); interface Name { first: string; last: string; } const nameModel: ArrayModel = new ArrayModel([ { first: "Hans", last: "Emil" }, { first: "Max", last: "Mustermann" }, { first: "Roman", last: "Tisch" }, ]); const mapModel = new private_api.MapModel(nameModel, (data) => { return data.last + ", " + data.first; }); instance.model = mapModel; private_api.send_mouse_click(instance, 5, 5); nameModel.setRowData(0, { first: "Simon", last: "Hausmann" }); nameModel.setRowData(1, { first: "Olivier", last: "Goffart" }); private_api.send_mouse_click(instance, 5, 5); expect(instance.changed_items).toBe("Goffart, OlivierHausmann, Simon"); }); ================================================ FILE: api/node/__test__/resources/error.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 export component Error { out property bool> check: "Test"; } ================================================ FILE: api/node/__test__/resources/test-constructor.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 export component Test { callback say_hello(); in-out property check; } ================================================ FILE: api/node/__test__/resources/test-enum.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 export enum TestEnum { a, b, c } export component Test { in-out property check; } ================================================ FILE: api/node/__test__/resources/test-fileloader.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { Test } from "lib.slint"; export component App inherits Window { out property test-text <=> test.text; test := Test {} } ================================================ FILE: api/node/__test__/resources/test-struct.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 export struct TestStruct { text: string, flag: bool, value: float } export component Test { in-out property check; } ================================================ FILE: api/node/__test__/resources/test.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 export component Test { out property check: "Test"; } ================================================ FILE: api/node/__test__/tsconfig.json ================================================ { "compilerOptions": { "module": "nodenext", "target": "esnext", "outDir": "../build", "skipLibCheck": true }, "include": [ "*.mts" ], } ================================================ FILE: api/node/__test__/types.spec.mts ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { test, expect } from "vitest"; import { private_api, ArrayModel } from "../dist/index.js"; test("SlintColor from fromRgb", () => { const color = private_api.SlintRgbaColor.fromRgb(100, 110, 120); expect(color.red).toStrictEqual(100); expect(color.green).toStrictEqual(110); expect(color.blue).toStrictEqual(120); }); test("SlintColor from fromArgb", () => { const color = private_api.SlintRgbaColor.fromArgb(120, 100, 110, 120); expect(color.red).toStrictEqual(100); expect(color.green).toStrictEqual(110); expect(color.blue).toStrictEqual(120); }); test("SlintColor brighter", () => { const color = private_api.SlintRgbaColor.fromRgb(100, 110, 120).brighter( 0.1, ); expect(color.red).toStrictEqual(110); expect(color.green).toStrictEqual(121); expect(color.blue).toStrictEqual(132); }); test("SlintColor darker", () => { const color = private_api.SlintRgbaColor.fromRgb(100, 110, 120).darker(0.1); expect(color.red).toStrictEqual(91); expect(color.green).toStrictEqual(100); expect(color.blue).toStrictEqual(109); }); test("private_api.SlintBrush from RgbaColor", () => { const brush = new private_api.SlintBrush({ red: 100, green: 110, blue: 120, alpha: 255, }); expect(brush.color.red).toStrictEqual(100); expect(brush.color.green).toStrictEqual(110); expect(brush.color.blue).toStrictEqual(120); let thrownError: any; try { new private_api.SlintBrush({ red: -100, green: 110, blue: 120, alpha: 255, }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe("A channel of Color cannot be negative"); }); test("private_api.SlintBrush from Brush", () => { const brush = private_api.SlintBrush.fromBrush({ color: { red: 100, green: 110, blue: 120, alpha: 255 }, }); expect(brush.color.red).toStrictEqual(100); expect(brush.color.green).toStrictEqual(110); expect(brush.color.blue).toStrictEqual(120); let thrownError: any; try { private_api.SlintBrush.fromBrush({ color: { red: -100, green: 110, blue: 120, alpha: 255 }, }); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe("A channel of Color cannot be negative"); }); test("ArrayModel push", () => { const arrayModel = new ArrayModel([0]); expect(arrayModel.rowCount()).toBe(1); expect(arrayModel.rowData(0)).toBe(0); arrayModel.push(2); expect(arrayModel.rowCount()).toBe(2); expect(arrayModel.rowData(1)).toBe(2); }); test("ArrayModel setRowData", () => { const arrayModel = new ArrayModel([0]); expect(arrayModel.rowCount()).toBe(1); expect(arrayModel.rowData(0)).toBe(0); arrayModel.setRowData(0, 2); expect(arrayModel.rowCount()).toBe(1); expect(arrayModel.rowData(0)).toBe(2); }); test("ArrayModel remove", () => { const arrayModel = new ArrayModel([0, 2, 1]); expect(arrayModel.rowCount()).toBe(3); expect(arrayModel.rowData(0)).toBe(0); expect(arrayModel.rowData(1)).toBe(2); arrayModel.remove(0, 2); expect(arrayModel.rowCount()).toBe(1); expect(arrayModel.rowData(0)).toBe(1); }); ================================================ FILE: api/node/__test__/window.spec.mts ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { test, expect } from "vitest"; import { private_api, Window } from "../dist/index.js"; test("Window constructor", () => { let thrownError: any; try { new private_api.Window(); } catch (error) { thrownError = error; } expect(thrownError).toBeDefined(); expect(thrownError.code).toBe("GenericFailure"); expect(thrownError.message).toBe( "Window can only be created by using a Component.", ); }); test("Window show / hide", () => { const compiler = new private_api.ComponentCompiler(); const definition = compiler.buildFromSource( ` export component App inherits Window { width: 300px; height: 300px; }`, "", ); expect(definition.App).not.toBeNull(); const instance = definition.App!.create(); expect(instance).not.toBeNull(); const window = instance!.window(); expect(window.visible).toBe(false); window.show(); expect(window.visible).toBe(true); window.hide(); expect(window.visible).toBe(false); }); ================================================ FILE: api/node/binaries.json ================================================ { "binaryName": "slint-ui", "packageName": "@slint-ui/slint-ui-binary", "targets": [ "x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc" ] } ================================================ FILE: api/node/biome.json ================================================ { "root": true, "extends": ["../../biome.json"], "files": { "includes": [ "**", "!**/docs/", "!rust-module.d.cts", "!rust-module.cjs" ] }, "linter": { "rules": { "complexity": { "useArrowFunction": "off", "noForEach": "off", "noArguments": "off" }, "style": { "noNonNullAssertion": "off", "noUnusedTemplateLiteral": "off", "useTemplate": "off", "noInferrableTypes": "off", "noParameterAssign": "off", "useAsConstAssertion": "error", "useDefaultParameterLast": "error", "useEnumInitializers": "error", "useSelfClosingElements": "error", "useSingleVarDeclarator": "error", "useNumberNamespace": "error", "noUselessElse": "error" }, "suspicious": { "noExplicitAny": "off", "noAssignInExpressions": "off", "noVar": "off" } } } } ================================================ FILE: api/node/build-on-demand.mjs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 // This file checks if a binary package was installed (through architecture dependencies), and // builds slint if no binary was found. import { Worker } from "node:worker_threads"; import { spawn } from "node:child_process"; import { existsSync } from "node:fs"; const worker = new Worker("./rust-module.cjs"); // Define dummy error handler to prevent node from aborting on errors worker.on("error", (error) => { //console.log(`Error loading rust-module.cjs: {error}`); }); worker.on("exit", (code) => { if (code !== 0) { // HACK: npm package removes .npmignore. If the file is present, then it means that we're in the Slint git repo, // and we don't want to automatically build (see https://github.com/slint-ui/slint/pull/6780). if (!existsSync("./.npmignore")) { console.log( "slint-ui: loading rust-module.cjs failed, building now", ); spawn("npm", ["run", "build"], { stdio: "inherit", }); } } }); ================================================ FILE: api/node/build.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 fn main() { napi_build::setup(); // workaround bug that the `#[napi]` macro generate some invalid `#[cfg(feature="...")]` println!("cargo:rustc-check-cfg=cfg(feature,values(\"noop\", \"used_linker\"))"); } ================================================ FILE: api/node/cover.md ================================================ # Slint-node (Beta) [![npm](https://img.shields.io/npm/v/slint-ui)](https://www.npmjs.com/package/slint-ui) [Slint](https://slint.dev/) is a UI toolkit that supports different programming languages. Slint-node is the integration with [Node.js](https://nodejs.org/en) and [Deno](https://deno.com). To get started you use the [walk-through tutorial](https://slint.dev/docs/slint/tutorial/quickstart). We also have a [Getting Started Template](https://github.com/slint-ui/slint-nodejs-template) repository with the code of a minimal application using Slint that can be used as a starting point to your program. **Warning: Beta** Slint-node is still in the early stages of development: APIs will change and important features are still being developed. ## Slint Language Manual The [Slint Language Documentation](http://slint.dev/docs/slint) covers the Slint UI description language in detail. ## Prerequisites To use Slint with Node.js, ensure the following programs are installed: * **[Node.js](https://nodejs.org/download/release/)** (v20 or newer) * **[npm](https://www.npmjs.com/)** To use Slint with Deno, ensure the following programs are installed: * **[Deno](https://docs.deno.com/runtime/manual)** ### Building from Source Slint-node comes with pre-built binaries for macOS, Linux, and Windows. If you'd like to use Slint-node on a system without pre-built binaries, you need to additional software: * **[Rust compiler](https://www.rust-lang.org/tools/install)** * Depending on your operating system, you may need additional components. For a list of required system libraries, see . ## Getting Started (Node.js) 1. In a new directory, create a new Node.js project by calling [`npm init`](https://docs.npmjs.com/cli/v10/commands/npm-init). 2. Install Slint for your project using [`npm install slint-ui`](https://docs.npmjs.com/cli/v10/commands/npm-install). 3. Create a new file called `main.slint` with the following contents: ``` import { AboutSlint, Button, VerticalBox } from "std-widgets.slint"; export component Demo inherits Window { in-out property greeting <=> label.text; VerticalBox { alignment: start; label := Text { text: "Hello World!"; font-size: 24px; horizontal-alignment: center; } AboutSlint { preferred-height: 150px; } HorizontalLayout { alignment: center; Button { text: "OK!"; } } } } ``` This file declares the user interface. 4. Create a new file called `index.mjs` with the following contents: ```js import * as slint from "slint-ui"; let ui = slint.loadFile(new URL("main.slint", import.meta.url)); let demo = new ui.Demo(); await demo.run(); ``` This is your main JavaScript entry point: * Import the Slint API as an [ECMAScript module](https://nodejs.org/api/esm.html#modules-ecmascript-modules) module. If you prefer you can also import it as [CommonJS](https://nodejs.org/api/modules.html#modules-commonjs-modules) module. * Invoke `loadFile()` to compile and load the `.slint` file. * Instantiate the `Demo` component declared in `main.slint`. * Run it by showing it on the screen and reacting to user input. 5. Run the example with `node index.mjs` For a complete example, see [/examples/todo/node](https://github.com/slint-ui/slint/tree/master/examples/todo/node). ## Getting Started (Deno) 1. Create a new file called `main.slint` with the following contents: ``` import { AboutSlint, Button, VerticalBox } from "std-widgets.slint"; export component Demo inherits Window { in-out property greeting <=> label.text; VerticalBox { alignment: start; label := Text { text: "Hello World!"; font-size: 24px; horizontal-alignment: center; } AboutSlint { preferred-height: 150px; } HorizontalLayout { alignment: center; Button { text: "OK!"; } } } } ``` This file declares the user interface. 2. Create a new file called `deno.json` (a [Deno Import Map](https://docs.deno.com/runtime/manual/basics/import_maps)) with the following contents: ```json { "imports": { "slint-ui": "npm:slint-ui" } } ``` 3. Create a new file called `index.ts` with the following contents: ```ts import * as slint from "slint-ui"; let ui = slint.loadFile(new URL("main.slint", import.meta.url)); let demo = new ui.Demo(); await demo.run(); ``` This is your main JavaScript entry point: * Import the Slint API as an [ECMAScript module](https://nodejs.org/api/esm.html#modules-ecmascript-modules) module through Deno's NPM compatibility layer. * Invoke `loadFile()` to compile and load the `.slint` file. * Instantiate the `Demo` component declared in `main.slint`. * Run it by showing it on the screen and reacting to user input. 1. Run the example with `deno run --allow-read --allow-ffi --allow-sys index.ts` ## Getting Started (bun) 1. In a new directory, create a new `bun` project by calling [`bun init`](https://bun.sh/docs/cli/init). 2. Install Slint for your project using [`bun install slint-ui`](https://bun.sh/docs/cli/install). 3. Create a new file called `main.slint` with the following contents: ``` import { AboutSlint, Button, VerticalBox } from "std-widgets.slint"; export component Demo inherits Window { in-out property greeting <=> label.text; VerticalBox { alignment: start; label := Text { text: "Hello World!"; font-size: 24px; horizontal-alignment: center; } AboutSlint { preferred-height: 150px; } HorizontalLayout { alignment: center; Button { text: "OK!"; } } } } ``` This file declares the user interface. 4. Clear the content of `index.ts` and add the following code: ```ts import * as slint from "slint-ui"; let ui = slint.loadFile(new URL("main.slint", import.meta.url)) as any; let demo = new ui.Demo(); await demo.run(); ``` This is your main TypeScript entry point: * Import the Slint API as an [ECMAScript module](https://nodejs.org/api/esm.html#modules-ecmascript-modules) module. * Invoke `loadFile()` to compile and load the `.slint` file. * Instantiate the `Demo` component declared in `main.slint`. * Run it by showing it on the screen and reacting to user input. 5. Run the example with `bun run index.ts` ## API Overview ### Instantiating a Component Use the {@link loadFile} function to load a `.slint` file. Instantiate the [exported component](http://slint.dev/docs/slint/guide/language/coding/file/) with the new operator. Access exported callbacks and properties as JavaScript properties on the instantiated component. In addition, the returned object implements the {@link ComponentHandle} interface, to show/hide the instance or access the window. The following example shows how to instantiating a Slint component from JavaScript. **`ui/main.slint`** ``` export component MainWindow inherits Window { callback clicked <=> i-touch-area.clicked; in property counter; width: 400px; height: 200px; i-touch-area := TouchArea {} } ``` The exported component is exposed as a type constructor. The type constructor takes as parameter an object which allow to initialize the value of public properties or callbacks. **`main.mjs`** ```js import * as slint from "slint-ui"; // In this example, the main.slint file exports a module which // has a counter property and a clicked callback let ui = slint.loadFile(new URL("ui/main.slint", import.meta.url)); let component = new ui.MainWindow({ counter: 42, clicked: function() { console.log("hello"); } }); ``` ### Accessing a Properties [Properties](http://slint.dev/docs/slint/guide/language/coding/properties/) declared as `out` or `in-out` in `.slint` files are visible as JavaScript properties on the component instance. **`main.slint`** export component MainWindow { in-out property name; in-out property age: 42; } ```js let ui = slint.loadFile(new URL("main.slint", import.meta.url)); let instance = new ui.MainWindow(); console.log(instance.age); // Prints 42 instance.name = "Joe"; ``` ### Setting and Invoking Callbacks [Callbacks](http://slint.dev/docs/slint/guide/language/coding/functions-and-callbacks/) declared in `.slint` files are visible as JavaScript function properties on the component instance. Invoke them as function to invoke the callback, and assign JavaScript functions to set the callback handler. **`ui/my-component.slint`** ``` export component MyComponent inherits Window { callback clicked <=> i-touch-area.clicked; width: 400px; height: 200px; i-touch-area := TouchArea {} } ``` **`main.mjs`** ```js import * as slint from "slint-ui"; let ui = slint.loadFile(new URL("ui/my-component.slint", import.meta.url)); let component = new ui.MyComponent(); // connect to a callback component.clicked = function() { console.log("hello"); }; // emit a callback component.clicked(); ``` ### Type Mappings The types used for properties in .slint design markup each translate to specific types in JavaScript. The follow table summarizes the entire mapping: | `.slint` Type | JavaScript Type | Notes | | --- | --- | --- | | `int` | `Number` | | | `bool` | `Boolean` | | | `float` | `Number` | | | `string` | `String` | | | `color` | {@link RgbaColor} | | | `brush` | {@link Brush} | | | `image` | {@link ImageData} | | | `length` | `Number` | | | `physical_length` | `Number` | | | `duration` | `Number` | The number of milliseconds | | `angle` | `Number` | The angle in degrees | | `relative-font-size` | `Number` | Relative font size factor that is multiplied with the `Window.default-font-size` and can be converted to a `length`. | | structure | `Object` | Structures are mapped to JavaScript objects where each structure field is a property. | | array | {@link Model} | | ### Arrays and Models [Array properties](http://slint.dev/docs/slint/guide/language/coding/repetition-and-data-models#arrays-and-models) can be set from JavaScript by passing either `Array` objects or implementations of the {@link Model} interface. When passing a JavaScript `Array` object, the contents of the array are copied. Any changes to the JavaScript afterwards will not be visible on the Slint side. Reading a Slint array property from JavaScript will always return a @{link Model}. ```js component.model = [1, 2, 3]; // component.model.push(4); // does not work, because assignment creates a copy. // Use re-assignment instead. component.model = component.model.concat(4); ``` Another option is to set an object that implements the {@link Model} interface. ### structs An exported struct can be created either by defing of an object literal or by using the new keyword. **`my-component.slint`** ``` export struct Person { name: string, age: int } export component MyComponent inherits Window { in-out property person; } ``` **`main.js`** ```js import * as slint from "slint-ui"; let ui = slint.loadFile(new URL("my-component.slint", import.meta.url)); let component = new ui.MyComponent(); // object literal component.person = { name: "Peter", age: 22 }; // new keyword (sets property values to default e.g. '' for string) component.person = new ui.Person(); // new keyword with parameters component.person = new ui.Person({ name: "Tim", age: 30 }); ``` ### enums A value of an exported enum can be set as string or by using the value from the exported enum. **`my-component.slint`** ``` export enum Position { top, bottom } export component MyComponent inherits Window { in-out property position; } ``` **`main.js`** ```js import * as slint from "slint-ui"; let ui = slint.loadFile(new URL("my-component.slint", import.meta.url)); let component = new ui.MyComponent(); // set enum value as string component.position = "top"; // use the value of the enum component.position = ui.Position.bottom; ``` ### Globals You can declare [globally available singletons](http://slint.dev/docs/slint/guide/language/coding/globals) in your `.slint` files. If exported, these singletons are accessible as properties on your main componen instance. Each global singleton is represented by an object with properties and callbacks, similar to API that's created for your `.slint` component. For example the following `.slint` markup defines a global `Logic` singleton that's also exported: ``` export global Logic { callback to_uppercase(string) -> string; } ``` Assuming this global is used together with the `MyComponent` from the previous section, you can access `Logic` like this: ```js import * as slint from "slint-ui"; let ui = slint.loadFile(new URL("ui/my-component.slint", import.meta.url)); let component = new ui.MyComponent(); component.Logic.to_upper_case = (str) => { return str.toUpperCase(); }; ``` **Note**: Global singletons are instantiated once per component. When declaring multiple components for `export` to JavaScript, each instance will have their own instance of associated globals singletons. ## Third-Party Licenses For a list of the third-party licenses of all dependencies, see the separate [Third-Party Licenses page](thirdparty.html). ================================================ FILE: api/node/package.json ================================================ { "name": "slint-ui", "version": "1.16.0", "main": "dist/index.js", "types": "dist/index.d.ts", "homepage": "https://github.com/slint-ui/slint", "license": "SEE LICENSE IN LICENSE.md", "repository": { "type": "git", "url": "https://github.com/slint-ui/slint" }, "keywords": [ "GUI", "UI", "native", "node", "Slint", "desktop", "embedded" ], "description": "Slint is a declarative GUI toolkit to build native user interfaces for desktop and embedded applications.", "devDependencies": { "@biomejs/biome": "catalog:", "@types/capture-console": "1.0.5", "@types/node": "catalog:", "capture-console": "1.0.2", "image-js": "1.4.0", "typedoc": "0.28.17", "typescript": "catalog:", "vitest": "catalog:" }, "engines": { "node": ">= 10" }, "scripts": { "artifacts": "napi artifacts", "compile": "tsc --build", "build": "napi build --platform --release --js rust-module.cjs --dts rust-module.d.cts -c binaries.json", "build:debug": "napi build --platform --js rust-module.cjs --dts rust-module.d.cts -c binaries.json && pnpm compile", "build:testing": "napi build --platform --js rust-module.cjs --dts rust-module.d.cts -c binaries.json --features testing && pnpm compile", "install": "node build-on-demand.mjs", "docs": "pnpm build && typedoc --hideGenerator --readme cover.md typescript/index.ts && cargo about generate thirdparty.hbs -o docs/thirdparty.html", "docs:debug": "pnpm build:debug && typedoc --hideGenerator --readme cover.md typescript/index.ts", "check": "biome check", "format": "biome format", "format:fix": "biome format --write", "lint": "biome lint", "lint:fix": "biome lint --fix", "test": "vitest run" }, "dependencies": { "@napi-rs/cli": "^3.5.1" } } ================================================ FILE: api/node/rust/interpreter/component_compiler.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use super::JsComponentDefinition; use super::JsDiagnostic; use i_slint_compiler::langtype::Type; use itertools::Itertools; use napi::bindgen_prelude::*; use napi::{Env, JsValue}; use slint_interpreter::Compiler; use slint_interpreter::Value; use smol_str::StrExt; use std::collections::HashMap; use std::path::PathBuf; /// ComponentCompiler is the entry point to the Slint interpreter that can be used /// to load .slint files or compile them on-the-fly from a string. #[napi(js_name = "ComponentCompiler")] pub struct JsComponentCompiler { internal: Compiler, structs_and_enums: Vec, diagnostics: Vec, } #[napi] impl JsComponentCompiler { /// Returns a new ComponentCompiler. #[napi(constructor)] pub fn new() -> Self { let mut compiler = Compiler::default(); let include_paths = match std::env::var_os("SLINT_INCLUDE_PATH") { Some(paths) => { std::env::split_paths(&paths).filter(|path| !path.as_os_str().is_empty()).collect() } None => Vec::new(), }; let library_paths = match std::env::var_os("SLINT_LIBRARY_PATH") { Some(paths) => std::env::split_paths(&paths) .filter_map(|entry| { entry .to_str() .unwrap_or_default() .split('=') .collect_tuple() .map(|(k, v)| (k.into(), v.into())) }) .collect(), None => HashMap::new(), }; compiler.set_include_paths(include_paths); compiler.set_library_paths(library_paths); Self { internal: compiler, diagnostics: Vec::new(), structs_and_enums: vec![] } } #[napi(setter)] pub fn set_include_paths(&mut self, include_paths: Vec) { self.internal.set_include_paths(include_paths.iter().map(PathBuf::from).collect()); } #[napi(getter)] pub fn include_paths(&self) -> Vec { self.internal .include_paths() .iter() .map(|p| p.to_str().unwrap_or_default().to_string()) .collect() } #[napi(setter)] pub fn set_library_paths(&mut self, paths: HashMap) { let mut library_paths = HashMap::new(); for (key, path) in paths { library_paths.insert(key, PathBuf::from(path)); } self.internal.set_library_paths(library_paths); } #[napi(getter)] pub fn library_paths(&self) -> HashMap { let mut library_paths = HashMap::new(); for (key, path) in self.internal.library_paths() { library_paths.insert(key.clone(), path.to_str().unwrap_or_default().to_string()); } library_paths } #[napi(setter)] pub fn set_style(&mut self, style: String) { self.internal.set_style(style); } #[napi(getter)] pub fn style(&self) -> Option { self.internal.style().cloned() } #[napi(getter)] pub fn diagnostics(&self) -> Vec { self.diagnostics.iter().map(|d| JsDiagnostic::from(d.clone())).collect() } #[napi(getter)] pub fn structs<'a>(&self, env: &'a Env) -> HashMap> { fn convert_type<'a>(env: &'a Env, ty: &Type) -> Option<(String, Unknown<'a>)> { match ty { Type::Struct(s) if s.node().is_some() => { let name = s.name.slint_name().unwrap(); let struct_instance = crate::to_js_unknown( env, &Value::Struct(slint_interpreter::Struct::from_iter(s.fields.iter().map( |(name, field_type)| { ( name.to_string(), slint_interpreter::default_value_for_type(field_type), ) }, ))), ); Some((name.to_string(), struct_instance.ok()?)) } _ => None, } } self.structs_and_enums .iter() .filter_map(|ty| convert_type(env, ty)) .collect::>>() } #[napi(getter)] pub fn enums<'a>(&self, env: &'a Env) -> HashMap> { fn convert_type<'a>(env: &'a Env, ty: &Type) -> Option<(String, Unknown<'a>)> { match ty { Type::Enumeration(en) => { let mut o = Object::new(env).ok()?; for value in en.values.iter() { let value = value.replace_smolstr("-", "_"); let str_val = env.create_string(&value).ok()?; o.set_named_property(&value, str_val).ok()?; } Some((en.name.to_string(), o.into_unknown(env).ok()?)) } _ => None, } } self.structs_and_enums .iter() .filter_map(|ty| convert_type(env, ty)) .collect::>>() } #[napi(setter)] pub fn set_file_loader( &mut self, env: &Env, callback: crate::DynFunction<'_>, ) -> napi::Result<()> { let stored_fn = std::rc::Rc::new(crate::StoredFunction::new(&callback)?); let env = *env; self.internal.set_file_loader(move |path| { let path = PathBuf::from(path); let stored_fn = stored_fn.clone(); Box::pin({ async move { let Ok(path_str) = env.create_string(path.display().to_string().as_str()) else { return Some(Err(std::io::Error::other( "Node.js: wrong argument for callback file_loader.", ))); }; let Ok(result) = stored_fn.call(&env, vec![path_str.raw()]) else { return Some(Err(std::io::Error::other( "Node.js: file loader callback failed.", ))); }; let js_string = result.coerce_to_string(); let Ok(js_string) = js_string else { return Some(Err(std::io::Error::other( "Node.js: cannot read return value of file loader callback as js string.", ))); }; let Ok(utf8_string) = js_string.into_utf8() else { return Some(Err(std::io::Error::other( "Node.js: cannot convert return value of file loader callback into utf8.", ))); }; if let Ok(str) = utf8_string.as_str() { let string = str.to_string(); return Some(Ok(string)); }; Some(Err(std::io::Error::other( "Node.js: cannot convert return value of file loader callback into string.", ))) } }) }); Ok(()) } /// Compile a .slint file into a ComponentDefinition /// /// Returns the compiled `ComponentDefinition` if there were no errors. #[napi] pub fn build_from_path(&mut self, path: String) -> HashMap { let r = spin_on::spin_on(self.internal.build_from_path(PathBuf::from(path))); self.structs_and_enums = r.structs_and_enums(i_slint_core::InternalToken {}).cloned().collect::>(); self.diagnostics = r.diagnostics().collect(); r.components().map(|c| (c.name().to_owned(), c.into())).collect() } /// Compile some .slint code into a ComponentDefinition #[napi] pub fn build_from_source( &mut self, source_code: String, path: String, ) -> HashMap { let r = spin_on::spin_on(self.internal.build_from_source(source_code, PathBuf::from(path))); self.diagnostics = r.diagnostics().collect(); self.structs_and_enums = r.structs_and_enums(i_slint_core::InternalToken {}).cloned().collect::>(); r.components().map(|c| (c.name().to_owned(), c.into())).collect() } } ================================================ FILE: api/node/rust/interpreter/component_definition.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use napi::Result; use slint_interpreter::ComponentDefinition; use super::{JsComponentInstance, JsProperty}; #[napi(js_name = "ComponentDefinition")] pub struct JsComponentDefinition { internal: ComponentDefinition, } impl From for JsComponentDefinition { fn from(definition: ComponentDefinition) -> Self { Self { internal: definition } } } #[napi] impl JsComponentDefinition { #[napi(constructor)] pub fn new() -> napi::Result { Err(napi::Error::from_reason( "ComponentDefinition can only be created by using ComponentCompiler.".to_string(), )) } #[napi(getter)] pub fn properties(&self) -> Vec { self.internal .properties() .map(|(name, value_type)| JsProperty { name, value_type: value_type.into() }) .collect() } #[napi(getter)] pub fn callbacks(&self) -> Vec { self.internal.callbacks().collect() } #[napi(getter)] pub fn functions(&self) -> Vec { self.internal.functions().collect() } #[napi(getter)] pub fn globals(&self) -> Vec { self.internal.globals().collect() } #[napi] pub fn global_properties(&self, global_name: String) -> Option> { self.internal.global_properties(global_name.as_str()).map(|iter| { iter.map(|(name, value_type)| JsProperty { name, value_type: value_type.into() }) .collect() }) } #[napi] pub fn global_callbacks(&self, global_name: String) -> Option> { self.internal.global_callbacks(global_name.as_str()).map(|iter| iter.collect()) } #[napi] pub fn global_functions(&self, global_name: String) -> Option> { self.internal.global_functions(global_name.as_str()).map(|iter| iter.collect()) } #[napi] pub fn create(&self) -> Result { Ok(self.internal.create().map_err(|e| napi::Error::from_reason(e.to_string()))?.into()) } #[napi(getter)] pub fn name(&self) -> String { self.internal.name().into() } } ================================================ FILE: api/node/rust/interpreter/component_instance.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use i_slint_compiler::langtype::Type; use i_slint_core::window::WindowInner; use napi::bindgen_prelude::*; use napi::{Env, Result}; use slint_interpreter::{ComponentHandle, ComponentInstance, Value}; use crate::JsWindow; use super::JsComponentDefinition; #[napi(js_name = "ComponentInstance")] pub struct JsComponentInstance { inner: ComponentInstance, } impl From for JsComponentInstance { fn from(instance: ComponentInstance) -> Self { Self { inner: instance } } } #[napi] impl JsComponentInstance { #[napi(constructor)] pub fn new() -> napi::Result { Err(napi::Error::from_reason( "ComponentInstance can only be created by using ComponentCompiler.".to_string(), )) } #[napi] pub fn definition(&self) -> JsComponentDefinition { self.inner.definition().into() } #[napi] pub fn get_property<'a>(&self, env: &'a Env, name: String) -> Result> { let value = self .inner .get_property(name.as_ref()) .map_err(|e| napi::Error::from_reason(e.to_string()))?; super::value::to_js_unknown(env, &value) } #[napi] pub fn set_property(&self, env: &Env, prop_name: String, js_value: Unknown<'_>) -> Result<()> { let (ty, _) = self .inner .definition() .properties_and_callbacks() .find_map(|(name, proptype)| if name == prop_name { Some(proptype) } else { None }) .ok_or(()) .map_err(|_| { napi::Error::from_reason(format!("Property {prop_name} not found in the component")) })?; self.inner .set_property(&prop_name, super::value::to_value(env, js_value, &ty)?) .map_err(|e| napi::Error::from_reason(format!("{e}")))?; Ok(()) } #[napi] pub fn get_global_property<'a>( &self, env: &'a Env, global_name: String, name: String, ) -> Result> { if !self.definition().globals().contains(&global_name) { return Err(napi::Error::from_reason(format!("Global {global_name} not found"))); } let value = self .inner .get_global_property(global_name.as_ref(), name.as_ref()) .map_err(|e| napi::Error::from_reason(e.to_string()))?; super::value::to_js_unknown(env, &value) } #[napi] pub fn set_global_property( &self, env: &Env, global_name: String, prop_name: String, js_value: Unknown<'_>, ) -> Result<()> { let (ty, _) = self .inner .definition() .global_properties_and_callbacks(global_name.as_str()) .ok_or(napi::Error::from_reason(format!("Global {global_name} not found")))? .find_map(|(name, proptype)| if name == prop_name { Some(proptype) } else { None }) .ok_or(()) .map_err(|_| { napi::Error::from_reason(format!( "Property {prop_name} of global {global_name} not found in the component" )) })?; self.inner .set_global_property( global_name.as_str(), &prop_name, super::value::to_value(env, js_value, &ty)?, ) .map_err(|e| napi::Error::from_reason(format!("{e}")))?; Ok(()) } #[napi] pub fn set_callback( &self, env: &Env, callback_name: String, callback: DynFunction<'_>, ) -> Result<()> { let function_ref = StoredFunction::new(&callback)?; let env = *env; let (ty, _) = self .inner .definition() .properties_and_callbacks() .find_map(|(name, proptype)| if name == callback_name { Some(proptype) } else { None }) .ok_or(()) .map_err(|_| { napi::Error::from_reason(format!( "Callback {callback_name} not found in the component" )) })?; if let Type::Callback(callback) = ty { self.inner .set_callback(callback_name.as_str(), { let return_type = callback.return_type.clone(); let callback_name = callback_name.clone(); move |args| { let js_args: Vec = args .iter() .filter_map(|v| Some(super::value::to_js_unknown(&env, v).ok()?.raw())) .collect(); let result = match function_ref.call(&env, js_args) { Ok(result) => result, Err(err) => { crate::console_err!( env, "Node.js: Invoking callback '{callback_name}' failed: {err}" ); return Value::Void; } }; if matches!(return_type, Type::Void) { Value::Void } else if let Ok(value) = super::to_value(&env, result, &return_type) { value } else { eprintln!( "Node.js: cannot convert return type of callback {callback_name}" ); slint_interpreter::default_value_for_type(&return_type) } } }) .map_err(|_| napi::Error::from_reason("Cannot set callback."))?; return Ok(()); } Err(napi::Error::from_reason(format!("{callback_name} is not a callback").as_str())) } #[napi] pub fn set_global_callback( &self, env: &Env, global_name: String, callback_name: String, callback: DynFunction<'_>, ) -> Result<()> { let function_ref = StoredFunction::new(&callback)?; let env = *env; let (ty, _) = self .inner .definition() .global_properties_and_callbacks(global_name.as_str()) .ok_or(napi::Error::from_reason(format!("Global {global_name} not found")))? .find_map(|(name, proptype)| if name == callback_name { Some(proptype) } else { None }) .ok_or(()) .map_err(|_| { napi::Error::from_reason(format!( "Callback {callback_name} of global {global_name} not found in the component" )) })?; if let Type::Callback(callback) = ty { self.inner .set_global_callback(global_name.as_str(), callback_name.as_str(), { let return_type = callback.return_type.clone(); let _global_name = global_name.clone(); let callback_name = callback_name.clone(); move |args| { let js_args: Vec = args .iter() .filter_map(|v| Some(super::value::to_js_unknown(&env, v).ok()?.raw())) .collect(); let result = match function_ref.call(&env, js_args) { Ok(result) => result, Err(err) => { crate::console_err!(env, "Node.js: Invoking global callback '{callback_name}' failed: {err}"); return Value::Void; } }; if matches!(return_type, Type::Void) { Value::Void } else if let Ok(value) = super::to_value(&env, result, &return_type) { value } else { eprintln!("Node.js: cannot convert return type of callback {callback_name}"); slint_interpreter::default_value_for_type(&return_type) } } }) .map_err(|_| napi::Error::from_reason("Cannot set callback."))?; return Ok(()); } Err(napi::Error::from_reason(format!("{callback_name} is not a callback").as_str())) } fn invoke_args( env: &Env, callback_name: &String, arguments: Vec>, args: &[Type], ) -> Result> { let count = args.len(); let args = arguments .into_iter() .zip(args) .map(|(a, ty)| super::value::to_value(env, a, ty)) .collect::, _>>()?; if args.len() != count { return Err(napi::Error::from_reason( format!( "{} expect {} arguments, but {} where provided", callback_name, count, args.len() ) .as_str(), )); } Ok(args) } #[napi] pub fn invoke<'a>( &self, env: &'a Env, callback_name: String, callback_arguments: Vec>, ) -> Result> { let (ty, _) = self .inner .definition() .properties_and_callbacks() .find_map(|(name, proptype)| if name == callback_name { Some(proptype) } else { None }) .ok_or(()) .map_err(|_| { napi::Error::from_reason( format!("Callback {callback_name} not found in the component").as_str(), ) })?; let args = match ty { Type::Callback(function) | Type::Function(function) => { Self::invoke_args(env, &callback_name, callback_arguments, &function.args)? } _ => { return Err(napi::Error::from_reason( format!("{callback_name} is not a callback or a function").as_str(), )); } }; let result = self .inner .invoke(callback_name.as_str(), args.as_slice()) .map_err(|_| napi::Error::from_reason("Cannot invoke callback."))?; super::to_js_unknown(env, &result) } #[napi] pub fn invoke_global<'a>( &self, env: &'a Env, global_name: String, callback_name: String, callback_arguments: Vec>, ) -> Result> { let (ty, _) = self .inner .definition() .global_properties_and_callbacks(global_name.as_str()) .ok_or(napi::Error::from_reason(format!("Global {global_name} not found")))? .find_map(|(name, proptype)| if name == callback_name { Some(proptype) } else { None }) .ok_or(()) .map_err(|_| { napi::Error::from_reason( format!( "Callback {callback_name} of global {global_name} not found in the component" ) .as_str(), ) })?; let args = match ty { Type::Callback(function) | Type::Function(function) => { Self::invoke_args(env, &callback_name, callback_arguments, &function.args)? } _ => { return Err(napi::Error::from_reason( format!( "{callback_name} is not a callback or a function on global {global_name}" ) .as_str(), )); } }; let result = self .inner .invoke_global(global_name.as_str(), callback_name.as_str(), args.as_slice()) .map_err(|_| napi::Error::from_reason("Cannot invoke callback."))?; super::to_js_unknown(env, &result) } #[napi] pub fn send_mouse_click(&self, x: f64, y: f64) { slint_interpreter::testing::send_mouse_click(&self.inner, x as f32, y as f32); } #[napi] pub fn send_keyboard_string_sequence(&self, sequence: String) { slint_interpreter::testing::send_keyboard_string_sequence(&self.inner, sequence.into()); } #[napi] pub fn window(&self) -> Result { Ok(JsWindow { inner: WindowInner::from_pub(self.inner.window()).window_adapter() }) } } /// A reference-counted handle to a JS object, for storing object references /// that outlive the current scope (e.g. model implementations). pub struct RefCountedReference { inner: Option, env: Env, } impl RefCountedReference { pub fn new(env: &Env, value: &Object) -> Result { let unknown = (*value).into_unknown(env)?; Ok(Self { inner: Some(unknown.create_ref()?), env: *env }) } pub fn get_unknown(&self) -> Result> { self.inner .as_ref() .ok_or_else(|| napi::Error::from_reason("Reference already dropped"))? .get_value(&self.env) } } impl Drop for RefCountedReference { fn drop(&mut self) { if let Some(r) = self.inner.take() { let _: napi::Result<()> = r.unref(&self.env); } } } /// A stored reference to a JS function that can be called with dynamic arguments. /// Uses `FunctionRef` for lifecycle management and compat `JsFunction::call` for invocation. /// Type alias for a JS function that accepts dynamic arguments. pub type DynFunction<'a> = Function<'a, crate::DynArgs, Unknown<'static>>; /// A stored reference to a JS function that can be called with dynamic arguments. pub struct StoredFunction { func_ref: FunctionRef>, } impl StoredFunction { pub fn new(func: &DynFunction<'_>) -> Result { Ok(Self { func_ref: func.create_ref()? }) } /// Call the function with dynamic raw JS values. pub fn call(&self, env: &Env, args: Vec) -> Result> { let func = self.func_ref.borrow_back(env)?; func.call(crate::DynArgs(args)) } } ================================================ FILE: api/node/rust/interpreter/diagnostic.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use slint_interpreter::{Diagnostic, DiagnosticLevel}; /// This enum describes the level or severity of a diagnostic message produced by the compiler. #[napi(js_name = "DiagnosticLevel")] pub enum JsDiagnosticLevel { /// The diagnostic found is an error that prevents successful compilation. Error, /// The diagnostic found is a warning. Warning, /// The diagnostic is a note to further help with the error or warning. Note, } impl From for JsDiagnosticLevel { fn from(diagnostic_level: DiagnosticLevel) -> Self { match diagnostic_level { DiagnosticLevel::Error => JsDiagnosticLevel::Error, DiagnosticLevel::Warning => JsDiagnosticLevel::Warning, DiagnosticLevel::Note => JsDiagnosticLevel::Note, _ => unimplemented!(), } } } /// This structure represent a diagnostic emitted while compiling .slint code. /// /// It is basically a message, a level (warning or error), attached to a /// position in the code. #[napi(object, js_name = "Diagnostic")] pub struct JsDiagnostic { /// The level for this diagnostic. pub level: JsDiagnosticLevel, /// Message for this diagnostic. pub message: String, /// The line number in the .slint source file. The line number starts with 1. pub line_number: u32, // The column in the .slint source file. The column number starts with 1. pub column_number: u32, /// The path of the source file where this diagnostic occurred. pub file_name: Option, } impl From for JsDiagnostic { fn from(internal_diagnostic: Diagnostic) -> Self { let (line_number, column) = internal_diagnostic.line_column(); Self { level: internal_diagnostic.level().into(), message: internal_diagnostic.message().into(), line_number: line_number as u32, column_number: column as u32, file_name: internal_diagnostic .source_file() .and_then(|path| path.to_str()) .map(|str| str.into()), } } } ================================================ FILE: api/node/rust/interpreter/value.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use crate::{ ReadOnlyRustModel, RgbaColor, SlintBrush, SlintImageData, js_into_rust_model, rust_into_js_model, }; use i_slint_compiler::langtype::Type; use i_slint_core::graphics::{Image, Rgba8Pixel, SharedPixelBuffer}; use i_slint_core::model::{ModelRc, SharedVectorModel}; use i_slint_core::{Brush, Color, SharedVector}; use napi::bindgen_prelude::*; use napi::{Env, JsValue, Result, ValueType}; use napi_derive::napi; use slint_interpreter::Value; use smol_str::SmolStr; /// A dynamic-length argument list for calling JS functions with a variable /// number of arguments. Implements `JsValuesTupleIntoVec` so it can be used /// directly with `Function::call`. pub struct DynArgs(pub Vec); impl JsValuesTupleIntoVec for DynArgs { fn into_vec(self, _env: napi::sys::napi_env) -> Result> { Ok(self.0) } } /// Safely extract a f64 from an Unknown, failing if the type is wrong. fn expect_number(unknown: Unknown<'_>) -> Result { match unknown.get_type()? { ValueType::Number => unknown.coerce_to_number()?.get_double(), vt => Err(napi::Error::new( napi::Status::NumberExpected, format!("expect Number, got: {vt:?}"), )), } } /// Safely extract a String from an Unknown, failing if the type is wrong. fn expect_string(unknown: Unknown<'_>) -> Result { match unknown.get_type()? { ValueType::String => Ok(unknown.coerce_to_string()?.into_utf8()?.as_str()?.to_owned()), vt => Err(napi::Error::new( napi::Status::StringExpected, format!("expect String, got: {vt:?}"), )), } } /// Safely extract a bool from an Unknown, failing if the type is wrong. fn expect_bool(unknown: Unknown<'_>) -> Result { match unknown.get_type()? { ValueType::Boolean => Ok(unknown.coerce_to_bool()?), vt => Err(napi::Error::new( napi::Status::BooleanExpected, format!("expect Boolean, got: {vt:?}"), )), } } #[napi(js_name = "ValueType")] pub enum JsValueType { Void, Number, String, Bool, Model, Struct, Brush, Image, StyledText, } impl From for JsValueType { fn from(value_type: slint_interpreter::ValueType) -> Self { match value_type { slint_interpreter::ValueType::Number => JsValueType::Number, slint_interpreter::ValueType::String => JsValueType::String, slint_interpreter::ValueType::Bool => JsValueType::Bool, slint_interpreter::ValueType::Model => JsValueType::Model, slint_interpreter::ValueType::Struct => JsValueType::Struct, slint_interpreter::ValueType::Brush => JsValueType::Brush, slint_interpreter::ValueType::Image => JsValueType::Image, slint_interpreter::ValueType::StyledText => JsValueType::StyledText, _ => JsValueType::Void, } } } #[napi(js_name = "Property")] pub struct JsProperty { pub name: String, pub value_type: JsValueType, } pub fn to_js_unknown<'a>(env: &'a Env, value: &Value) -> Result> { match value { Value::Void => Null.into_unknown(env), Value::Number(number) => (*number).into_unknown(env), Value::String(string) => string.as_str().into_unknown(env), Value::Bool(value) => (*value).into_unknown(env), Value::Image(image) => { SlintImageData::from(image.clone()).into_instance(env)?.as_object(env).into_unknown(env) } Value::Struct(struct_value) => { let mut o = Object::new(env)?; for (field_name, field_value) in struct_value.iter() { let key = env.create_string(field_name.replace('-', "_"))?; let val = to_js_unknown(env, field_value)?; o.set_property(key, val)?; } o.into_unknown(env) } Value::Keys(keys) => { // TODO: Make this an actual JS object format!("{keys:?}").as_str().into_unknown(env) } Value::Brush(brush) => { SlintBrush::from(brush.clone()).into_instance(env)?.as_object(env).into_unknown(env) } Value::Model(model) => { if let Some(maybe_js_model) = rust_into_js_model(env, model) { maybe_js_model } else { let model_wrapper: ReadOnlyRustModel = model.clone().into(); model_wrapper.into_js(env) } } Value::EnumerationValue(_, value) => value.as_str().into_unknown(env), _ => ().into_unknown(env), } } pub fn to_value(env: &Env, unknown: Unknown<'_>, typ: &Type) -> Result { match typ { Type::Float32 | Type::Int32 | Type::Duration | Type::Angle | Type::PhysicalLength | Type::LogicalLength | Type::Rem | Type::Percent | Type::UnitProduct(_) => Ok(Value::Number(expect_number(unknown)?)), Type::String => Ok(Value::String(expect_string(unknown)?.into())), Type::Bool => Ok(Value::Bool(expect_bool(unknown)?)), Type::Color => { match unknown.get_type() { Ok(ValueType::String) => { let js_string = unknown.coerce_to_string()?; return string_to_brush(js_string); } Ok(ValueType::Object) => { let obj = unknown.coerce_to_object()?; if let Some(direct_brush) = obj.get::>("brush").ok().flatten() { return Ok(Value::Brush(direct_brush.color().into())); } return brush_from_color(obj); } _ => {} } Err(napi::Error::from_reason( "Cannot convert object to brush, because the given object is neither a brush, color, nor a string".to_string() )) } Type::Brush => { match unknown.get_type() { Ok(ValueType::String) => { let js_string = unknown.coerce_to_string()?; return string_to_brush(js_string); } Ok(ValueType::Object) => { let obj = unknown.coerce_to_object()?; // this is used to make the color property of the `Brush` interface optional. let properties = obj.get_property_names()?; if properties.get_array_length()? == 0 { return Ok(Value::Brush(Brush::default())); } if let Some(color) = obj.get::("color").ok().flatten() { if color.red() < 0. || color.green() < 0. || color.blue() < 0. || color.alpha() < 0. { return Err(napi::Error::from_reason( "A channel of Color cannot be negative", )); } return Ok(Value::Brush(Brush::SolidColor(Color::from_argb_u8( color.alpha() as u8, color.red() as u8, color.green() as u8, color.blue() as u8, )))); } else { return brush_from_color(obj); } } _ => {} } Err(napi::Error::from_reason( "Cannot convert object to brush, because the given object is neither a brush, color, nor a string".to_string() )) } Type::Image => { let object = unknown.coerce_to_object()?; if let Some(direct_image) = object.get::>("image").ok().flatten() { Ok(Value::Image((*direct_image).clone())) } else { let get_size_prop = |name: &str| { object .get::(name) .ok() .flatten() .and_then(|p| { p.coerce_to_number().ok() .and_then(|number| number.get_int64().ok()) .and_then(|i64_num| i64_num.try_into().ok()) }) .ok_or_else( || napi::Error::from_reason( format!("Cannot convert object to image, because the provided object does not have an u32 `{name}` property") )) }; fn try_convert_image + FromNapiValue>( object: &Object, width: u32, height: u32, ) -> Result> { let buffer = object.get::("data").ok().flatten().ok_or_else(|| { napi::Error::from_reason( "data property does not have suitable array buffer type" .to_string(), ) })?; const BPP: usize = core::mem::size_of::(); let actual_size = buffer.as_ref().len(); let expected_size: usize = (width as usize) * (height as usize) * BPP; if actual_size != expected_size { return Err(napi::Error::from_reason(format!( "data property does not have the correct size; expected {width} (width) * {height} (height) * {BPP} = {actual_size}; got {expected_size}" ))); } Ok(SharedPixelBuffer::clone_from_slice(buffer.as_ref(), width, height)) } let width: u32 = get_size_prop("width")?; let height: u32 = get_size_prop("height")?; let pixel_buffer = try_convert_image::(&object, width, height) .or_else(|_| try_convert_image::(&object, width, height))?; Ok(Value::Image(Image::from_rgba8(pixel_buffer))) } } Type::Struct(s) => { let js_object = unknown.coerce_to_object()?; Ok(Value::Struct( s.fields .iter() .map(|(pro_name, pro_ty)| { let prop: Unknown = js_object.get_named_property(&pro_name.replace('-', "_"))?; let prop_value = if prop.get_type()? == napi::ValueType::Undefined { slint_interpreter::default_value_for_type(pro_ty) } else { to_value(env, prop, pro_ty)? }; Ok((pro_name.to_string(), prop_value)) }) .collect::>()?, )) } Type::Array(a) => { if unknown.is_array()? { let array = Array::from_unknown(unknown)?; let mut vec = Vec::new(); for i in 0..array.len() { vec.push(to_value( env, array.get(i)?.ok_or(napi::Error::from_reason(format!( "Cannot access array element at index {i}" )))?, a, )?); } Ok(Value::Model(ModelRc::new(SharedVectorModel::from(SharedVector::from_slice( &vec, ))))) } else { let obj = unknown.coerce_to_object()?; let rust_model = js_into_rust_model(env, &obj, a)?; Ok(Value::Model(rust_model)) } } Type::Enumeration(e) => { let js_string = unknown.coerce_to_string()?; let value: SmolStr = js_string.into_utf8()?.as_str()?.into(); if !e.values.contains(&value) { return Err(napi::Error::from_reason(format!( "{value} is not a value of enum {}", e.name ))); } Ok(Value::EnumerationValue(e.name.to_string(), value.to_string())) } Type::Invalid | Type::Model | Type::Void | Type::InferredProperty | Type::InferredCallback | Type::Function { .. } | Type::Callback { .. } | Type::ComponentFactory | Type::Easing | Type::PathData | Type::LayoutCache | Type::ArrayOfU16 | Type::Keys | Type::ElementReference | Type::StyledText => Err(napi::Error::from_reason("reason")), } } fn string_to_brush(js_string: napi::JsString<'_>) -> Result { let string = js_string.into_utf8()?.as_str()?.to_string(); let c = string .parse::() .map_err(|_| napi::Error::from_reason(format!("Could not convert {string} to Brush.")))?; Ok(Value::Brush(Brush::from(Color::from_argb_u8((c.a * 255.) as u8, c.r, c.g, c.b)))) } fn brush_from_color(rgb_color: Object) -> Result { let red: f64 = rgb_color.get("red")?.ok_or(napi::Error::from_reason("Property red is missing"))?; let green: f64 = rgb_color.get("green")?.ok_or(napi::Error::from_reason("Property green is missing"))?; let blue: f64 = rgb_color.get("blue")?.ok_or(napi::Error::from_reason("Property blue is missing"))?; let alpha: f64 = rgb_color.get("alpha")?.unwrap_or(255.); if red < 0. || green < 0. || blue < 0. || alpha < 0. { return Err(napi::Error::from_reason("A channel of Color cannot be negative")); } Ok(Value::Brush(Brush::SolidColor(Color::from_argb_u8( alpha as u8, red as u8, green as u8, blue as u8, )))) } ================================================ FILE: api/node/rust/interpreter/window.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use crate::types::{SlintPoint, SlintSize}; use i_slint_core::window::WindowAdapterRc; use slint_interpreter::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; /// This type represents a window towards the windowing system, that's used to render the /// scene of a component. It provides API to control windowing system specific aspects such /// as the position on the screen. #[napi(js_name = "Window")] pub struct JsWindow { pub(crate) inner: WindowAdapterRc, } impl From for JsWindow { fn from(instance: WindowAdapterRc) -> Self { Self { inner: instance } } } #[napi] impl JsWindow { /// @hidden #[napi(constructor)] pub fn new() -> napi::Result { Err(napi::Error::from_reason( "Window can only be created by using a Component.".to_string(), )) } /// Shows the window on the screen. An additional strong reference on the /// associated component is maintained while the window is visible. #[napi] pub fn show(&self) -> napi::Result<()> { self.inner .window() .show() .map_err(|_| napi::Error::from_reason("Cannot show window.".to_string())) } /// Hides the window, so that it is not visible anymore. #[napi] pub fn hide(&self) -> napi::Result<()> { self.inner .window() .hide() .map_err(|_| napi::Error::from_reason("Cannot hide window.".to_string())) } /// Returns the visibility state of the window. This function can return false even if you previously called show() /// on it, for example if the user minimized the window. #[napi(getter, js_name = "visible")] pub fn is_visible(&self) -> bool { self.inner.window().is_visible() } /// Returns the logical position of the window on the screen. #[napi(getter)] pub fn get_logical_position(&self) -> SlintPoint { let pos = self.inner.window().position().to_logical(self.inner.window().scale_factor()); SlintPoint { x: pos.x as f64, y: pos.y as f64 } } /// Sets the logical position of the window on the screen. #[napi(setter)] pub fn set_logical_position(&self, position: SlintPoint) { self.inner .window() .set_position(LogicalPosition { x: position.x as f32, y: position.y as f32 }); } /// Returns the physical position of the window on the screen. #[napi(getter)] pub fn get_physical_position(&self) -> SlintPoint { let pos = self.inner.window().position(); SlintPoint { x: pos.x as f64, y: pos.y as f64 } } /// Sets the physical position of the window on the screen. #[napi(setter)] pub fn set_physical_position(&self, position: SlintPoint) { self.inner.window().set_position(PhysicalPosition { x: position.x.floor() as i32, y: position.y.floor() as i32, }); } /// Returns the logical size of the window on the screen, #[napi(getter)] pub fn get_logical_size(&self) -> SlintSize { let size = self.inner.window().size().to_logical(self.inner.window().scale_factor()); SlintSize { width: size.width as f64, height: size.height as f64 } } /// Sets the logical size of the window on the screen, #[napi(setter)] pub fn set_logical_size(&self, size: SlintSize) { self.inner.window().set_size(LogicalSize::from_physical( PhysicalSize { width: size.width.floor() as u32, height: size.height.floor() as u32 }, self.inner.window().scale_factor(), )); } /// Returns the physical size of the window on the screen, #[napi(getter)] pub fn get_physical_size(&self) -> SlintSize { let size = self.inner.window().size(); SlintSize { width: size.width as f64, height: size.height as f64 } } /// Sets the logical size of the window on the screen, #[napi(setter)] pub fn set_physical_size(&self, size: SlintSize) { self.inner.window().set_size(PhysicalSize { width: size.width.floor() as u32, height: size.height.floor() as u32, }); } /// Issues a request to the windowing system to re-render the contents of the window. #[napi(js_name = "requestRedraw")] pub fn request_redraw(&self) { self.inner.request_redraw(); } /// Returns if the window is currently fullscreen #[napi(getter)] pub fn get_fullscreen(&self) -> bool { self.inner.window().is_fullscreen() } /// Set or unset the window to display fullscreen. #[napi(setter)] pub fn set_fullscreen(&self, enable: bool) { self.inner.window().set_fullscreen(enable) } /// Returns if the window is currently maximized #[napi(getter)] pub fn get_maximized(&self) -> bool { self.inner.window().is_maximized() } /// Maximize or unmaximize the window. #[napi(setter)] pub fn set_maximized(&self, maximized: bool) { self.inner.window().set_maximized(maximized) } /// Returns if the window is currently minimized #[napi(getter)] pub fn get_minimized(&self) -> bool { self.inner.window().is_minimized() } /// Minimize or unminimze the window. #[napi(setter)] pub fn set_minimized(&self, minimized: bool) { self.inner.window().set_minimized(minimized) } } ================================================ FILE: api/node/rust/interpreter.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 mod diagnostic; pub use diagnostic::*; mod component_compiler; pub use component_compiler::*; mod component_definition; pub use component_definition::*; mod component_instance; pub use component_instance::*; mod value; pub use value::*; mod window; pub use window::*; ================================================ FILE: api/node/rust/lib.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 mod interpreter; use std::path::PathBuf; pub use interpreter::*; mod types; pub use types::*; use napi::Env; use napi::bindgen_prelude::*; #[macro_use] extern crate napi_derive; #[napi] pub fn mock_elapsed_time(ms: f64) { i_slint_core::tests::slint_mock_elapsed_time(ms as _); } #[napi] pub fn get_mocked_time() -> f64 { i_slint_core::tests::slint_get_mocked_time() as f64 } #[napi] pub enum ProcessEventsResult { Continue, Exited, } #[napi] pub fn process_events() -> napi::Result { i_slint_backend_selector::with_platform(|b| { b.process_events(std::time::Duration::ZERO, i_slint_core::InternalToken) }) .map_err(|e| napi::Error::from_reason(e.to_string())) .map(|result| match result { core::ops::ControlFlow::Continue(()) => ProcessEventsResult::Continue, core::ops::ControlFlow::Break(()) => ProcessEventsResult::Exited, }) } #[napi] pub fn invoke_from_event_loop(env: &Env, callback: DynFunction<'_>) -> napi::Result<()> { i_slint_backend_selector::with_platform(|_b| { // Nothing to do, just make sure a backend was created Ok(()) }) .map_err(|e| napi::Error::from_reason(e.to_string()))?; let stored_fn = StoredFunction::new(&callback)?; let env = *env; let wrapper = send_wrapper::SendWrapper::new((stored_fn, env)); i_slint_core::api::invoke_from_event_loop(move || { let (stored_fn, env) = wrapper.take(); if stored_fn.call(&env, vec![]).is_err() { eprintln!("Node.js: JavaScript invoke_from_event_loop threw an exception"); } }) .map_err(|e| napi::Error::from_reason(e.to_string())) } #[napi] pub fn set_quit_on_last_window_closed(quit_on_last_window_closed: bool) -> napi::Result<()> { if !quit_on_last_window_closed { i_slint_backend_selector::with_platform(|b| { #[allow(deprecated)] b.set_event_loop_quit_on_last_window_closed(false); Ok(()) }) .map_err(|e| napi::Error::from_reason(e.to_string()))?; } Ok(()) } #[napi] pub fn init_testing() { #[cfg(feature = "testing")] i_slint_backend_testing::init_integration_test_with_mock_time(); } #[napi] pub fn init_translations(domain: String, dir_name: String) -> napi::Result<()> { i_slint_core::translations::gettext_bindtextdomain(domain.as_str(), PathBuf::from(dir_name)) .map_err(|e| napi::Error::from_reason(e.to_string())) } #[napi] pub fn set_xdg_app_id(app_id: String) -> napi::Result<()> { i_slint_backend_selector::with_global_context(|ctx| ctx.set_xdg_app_id(app_id.into())) .map_err(|e| napi::Error::from_reason(e.to_string())) } pub fn print_to_console(env: Env, function: &str, arguments: core::fmt::Arguments) { let Ok(global) = env.get_global() else { eprintln!("Unable to obtain global object"); return; }; let console_object: Object = match global.get_named_property("console") { Ok(c) => c, Err(_) => { eprintln!("Unable to obtain console object for logging"); return; } }; let log_fn: Function = match console_object.get_named_property(function) { Ok(f) => f, Err(_) => { eprintln!("Unable to obtain console.{function}"); return; } }; let message = arguments.to_string(); let Ok(js_message) = env.create_string(&message) else { eprintln!("Unable to provide log message to JS env"); return; }; let Ok(js_message_unknown) = js_message.into_unknown(&env) else { eprintln!("Unable to convert log message to unknown"); return; }; if let Err(err) = log_fn.apply(console_object, js_message_unknown) { eprintln!("Unable to invoke console.{function}: {err}"); } } #[macro_export] macro_rules! console_err { ($env:expr, $($t:tt)*) => ($crate::print_to_console($env, "error", format_args!($($t)*))) } ================================================ FILE: api/node/rust/types/brush.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use i_slint_core::{Brush, Color, graphics::GradientStop}; use napi::{Error, Result, bindgen_prelude::External}; /// RgbaColor represents a color in the Slint run-time, represented using 8-bit channels for red, green, blue and the alpha (opacity). #[napi(object)] pub struct RgbaColor { /// Represents the red channel of the color as u8 in the range 0..255. pub red: f64, /// Represents the green channel of the color as u8 in the range 0..255. pub green: f64, /// Represents the blue channel of the color as u8 in the range 0..255. pub blue: f64, /// Represents the alpha channel of the color as u8 in the range 0..255. pub alpha: Option, } impl Default for RgbaColor { fn default() -> Self { Self { red: 0., green: 0., blue: 0., alpha: None } } } // no public api only available internal because in js/ts it's exported as interface impl RgbaColor { pub fn red(&self) -> f64 { self.red } pub fn green(&self) -> f64 { self.green } pub fn blue(&self) -> f64 { self.blue } pub fn alpha(&self) -> f64 { self.alpha.unwrap_or(255.) } } /// SlintRgbaColor implements {@link RgbaColor}. #[napi] pub struct SlintRgbaColor { inner: Color, } impl From for SlintRgbaColor { fn from(color: Color) -> Self { Self { inner: color } } } impl From for RgbaColor { fn from(color: SlintRgbaColor) -> Self { Self { red: color.red() as f64, green: color.green() as f64, blue: color.blue() as f64, alpha: Some(color.alpha() as f64), } } } #[napi] impl SlintRgbaColor { /// Creates a new transparent color. #[napi(constructor)] pub fn new() -> Self { Self { inner: Color::default() } } /// Construct a color from the red, green and blue color channel parameters. The alpha /// channel will have the value 255. #[napi(factory)] pub fn from_rgb(red: u8, green: u8, blue: u8) -> Self { Self { inner: Color::from_rgb_u8(red, green, blue) } } /// Construct a color from the alpha, red, green and blue color channel parameters. #[napi(factory)] pub fn from_argb(alpha: u8, red: u8, green: u8, blue: u8) -> Self { Self { inner: Color::from_argb_u8(alpha, red, green, blue) } } /// Returns the red channel of the color as number in the range 0..255. #[napi(getter)] pub fn red(&self) -> u8 { self.inner.red() } /// Returns the green channel of the color as number in the range 0..255. #[napi(getter)] pub fn green(&self) -> u8 { self.inner.green() } /// Returns the blue channel of the color as number in the range 0..255. #[napi(getter)] pub fn blue(&self) -> u8 { self.inner.blue() } /// Returns the alpha channel of the color as number in the range 0..255. #[napi(getter)] pub fn alpha(&self) -> u8 { self.inner.alpha() } // Returns a new version of this color that has the brightness increased /// by the specified factor. This is done by converting the color to the HSV /// color space and multiplying the brightness (value) with (1 + factor). /// The result is converted back to RGB and the alpha channel is unchanged. /// So for example `brighter(0.2)` will increase the brightness by 20%, and /// calling `brighter(-0.5)` will return a color that's 50% darker. #[napi] pub fn brighter(&self, factor: f64) -> SlintRgbaColor { SlintRgbaColor::from(self.inner.brighter(factor as f32)) } /// Returns a new version of this color that has the brightness decreased /// by the specified factor. This is done by converting the color to the HSV /// color space and dividing the brightness (value) by (1 + factor). The /// result is converted back to RGB and the alpha channel is unchanged. /// So for example `darker(0.3)` will decrease the brightness by 30%. #[napi] pub fn darker(&self, factor: f64) -> SlintRgbaColor { SlintRgbaColor::from(self.inner.darker(factor as f32)) } /// Returns a new version of this color with the opacity decreased by `factor`. /// /// The transparency is obtained by multiplying the alpha channel by `(1 - factor)`. #[napi] pub fn transparentize(&self, amount: f64) -> SlintRgbaColor { SlintRgbaColor::from(self.inner.transparentize(amount as f32)) } /// Returns a new color that is a mix of `this` color and `other`. The specified factor is /// clamped to be between `0.0` and `1.0` and then applied to `this` color, while `1.0 - factor` ///is applied to `other`. #[napi] pub fn mix(&self, other: &SlintRgbaColor, factor: f64) -> SlintRgbaColor { SlintRgbaColor::from(self.inner.mix(&other.inner, factor as f32)) } /// Returns a new version of this color with the opacity set to `alpha`. #[napi] pub fn with_alpha(&self, alpha: f64) -> SlintRgbaColor { SlintRgbaColor::from(self.inner.with_alpha(alpha as f32)) } /// Returns the color as string in hex representation e.g. `#000000` for black. #[napi] pub fn to_string(&self) -> String { let alpha = self.alpha(); if alpha == 255 { format!("#{:02x}{:02x}{:02x}", self.red(), self.green(), self.blue()) } else { format!("#{:02x}{:02x}{:02x}{:02x}", self.red(), self.green(), self.blue(), alpha) } } } /// A brush is a data structure that is used to describe how /// a shape, such as a rectangle, path or even text, shall be filled. /// A brush can also be applied to the outline of a shape, that means /// the fill of the outline itself. #[napi(object, js_name = "Brush")] pub struct JsBrush { /// Defines a solid color brush from rgba. /// /// If no color is set it defaults to transparent. pub color: Option, } /// SlintBrush implements {@link Brush}. #[napi] pub struct SlintBrush { inner: Brush, } impl From for SlintBrush { fn from(brush: Brush) -> Self { Self { inner: brush } } } impl From for SlintBrush { fn from(color: SlintRgbaColor) -> Self { Self::from(Brush::from(color.inner)) } } #[napi] impl SlintBrush { #[napi(constructor)] pub fn new_with_color(color: RgbaColor) -> Result { if color.red() < 0. || color.green() < 0. || color.blue() < 0. || color.alpha() < 0. { return Err(Error::from_reason("A channel of Color cannot be negative")); } Ok(Self { inner: Brush::SolidColor(Color::from_argb_u8( color.alpha().floor() as u8, color.red().floor() as u8, color.green().floor() as u8, color.blue().floor() as u8, )), }) } #[napi(factory)] pub fn from_brush(brush: JsBrush) -> Result { SlintBrush::new_with_color(brush.color.unwrap_or_default()) } /// Creates a brush form a `Color`. pub fn from_slint_color(color: &SlintRgbaColor) -> Self { Self { inner: Brush::SolidColor(color.inner) } } #[napi(getter)] pub fn color(&self) -> RgbaColor { self.slint_color().into() } /// @hidden #[napi(getter)] pub fn slint_color(&self) -> SlintRgbaColor { self.inner.color().into() } /// Returns true if this brush contains a fully transparent color (alpha value is zero) #[napi(getter)] pub fn is_transparent(&self) -> bool { self.inner.is_transparent() } /// Returns true if this brush is fully opaque. #[napi(getter)] pub fn is_opaque(&self) -> bool { self.inner.is_opaque() } /// Returns a new version of this brush that has the brightness increased /// by the specified factor. This is done by calling [`Color::brighter`] on /// all the colors of this brush. #[napi] pub fn brighter(&self, factor: f64) -> SlintBrush { SlintBrush::from(self.inner.brighter(factor as f32)) } /// Returns a new version of this brush that has the brightness decreased /// by the specified factor. This is done by calling [`Color::darker`] on /// all the color of this brush. #[napi] pub fn darker(&self, factor: f64) -> SlintBrush { SlintBrush::from(self.inner.darker(factor as f32)) } /// Returns a new version of this brush with the opacity decreased by `factor`. /// /// The transparency is obtained by multiplying the alpha channel by `(1 - factor)`. #[napi] pub fn transparentize(&self, amount: f64) -> SlintBrush { SlintBrush::from(self.inner.transparentize(amount as f32)) } /// Returns a new version of this brush with the related color's opacities /// set to `alpha`. #[napi] pub fn with_alpha(&self, alpha: f64) -> SlintBrush { SlintBrush::from(self.inner.with_alpha(alpha as f32)) } /// @hidden #[napi(getter)] pub fn brush(&self) -> External { External::new(self.inner.clone()) } /// Returns the color as string in hex representation e.g. `#000000` for black. /// It is only implemented for solid color brushes. #[napi] pub fn to_string(&self) -> String { match &self.inner { Brush::SolidColor(_) => self.slint_color().to_string(), Brush::LinearGradient(gradient) => { format!( "linear-gradient({}deg, {})", gradient.angle(), gradient_stops_to_string(gradient.stops()) ) } Brush::RadialGradient(gradient) => { format!("radial-gradient(circle, {})", gradient_stops_to_string(gradient.stops())) } _ => String::default(), } } } fn gradient_stops_to_string<'a>(stops: impl Iterator) -> String { let stops: Vec = stops .map(|s| { format!( "rgba({}, {}, {}, {}) {}%", s.color.red(), s.color.green(), s.color.blue(), s.color.alpha(), s.position * 100. ) }) .collect(); let mut stops_string = String::default(); let len = stops.len(); for i in 0..len { stops_string.push_str(stops[i].as_str()); if i < len - 1 { stops_string.push_str(", "); } } stops_string } ================================================ FILE: api/node/rust/types/image_data.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use std::vec; use i_slint_core::{ ImageInner, graphics::{Image, SharedImageBuffer, SharedPixelBuffer}, }; use napi::{ Env, bindgen_prelude::{Buffer, External, ToNapiValue, Unknown}, }; // This is needed for typedoc check JsImageData::image pub type ImageData = Image; /// SlintPoint implements {@link ImageData}. #[napi] pub struct SlintImageData { inner: Image, } impl From for SlintImageData { fn from(image: Image) -> Self { Self { inner: image } } } #[napi] impl SlintImageData { /// Constructs a new image with the given width and height. /// Each pixel will set to red = 0, green = 0, blue = 0 and alpha = 0. #[napi(constructor)] pub fn new(width: u32, height: u32) -> Self { Self { inner: Image::from_rgba8(SharedPixelBuffer::new(width, height)) } } /// Returns the width of the image in pixels. #[napi(getter)] pub fn width(&self) -> u32 { self.inner.size().width } /// Returns the height of the image in pixels. #[napi(getter)] pub fn height(&self) -> u32 { self.inner.size().height } /// Returns the image as buffer. /// A Buffer is a subclass of Uint8Array. #[napi(getter)] pub fn data(&self) -> Buffer { let image_inner: &ImageInner = (&self.inner).into(); if let Some(buffer) = image_inner.render_to_buffer(None) { match buffer { SharedImageBuffer::RGB8(buffer) => { return Buffer::from(rgb_to_rgba( buffer.as_bytes(), (self.width() * self.height()) as usize, )); } SharedImageBuffer::RGBA8(buffer) => return Buffer::from(buffer.as_bytes()), SharedImageBuffer::RGBA8Premultiplied(buffer) => { return Buffer::from(rgb_to_rgba( buffer.as_bytes(), (self.width() * self.height()) as usize, )); } } } Buffer::from(vec![0; (self.width() * self.height() * 4) as usize]) } #[napi(getter)] pub fn path<'a>(&self, env: &'a Env) -> napi::Result> { match self.inner.path() { None => ().into_unknown(env), Some(p) => env.create_string(p.to_string_lossy().as_ref())?.into_unknown(env), } } /// @hidden #[napi(getter)] pub fn image(&self) -> External { External::new(self.inner.clone()) } } fn rgb_to_rgba(bytes: &[u8], size: usize) -> Vec { let mut rgba_bytes = Vec::new(); for i in 0..size { if (i * 3) + 2 >= bytes.len() { continue; } rgba_bytes.push(bytes[i * 3]); rgba_bytes.push(bytes[(i * 3) + 1]); rgba_bytes.push(bytes[(i * 3) + 2]); rgba_bytes.push(255); } rgba_bytes } ================================================ FILE: api/node/rust/types/model.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use std::rc::Rc; use i_slint_compiler::langtype::Type; use i_slint_core::model::{Model, ModelNotify, ModelRc}; use napi::bindgen_prelude::*; use napi::{Env, JsValue, Result, ValueType}; use crate::{RefCountedReference, to_js_unknown, to_value}; #[napi] #[derive(Clone, Default)] pub struct SharedModelNotify(Rc); impl core::ops::Deref for SharedModelNotify { type Target = Rc; fn deref(&self) -> &Self::Target { &self.0 } } pub(crate) fn js_into_rust_model( env: &Env, maybe_js_impl: &Object, row_data_type: &Type, ) -> Result> { let shared_model_notify: ExternalRef = maybe_js_impl.get_named_property("modelNotify")?; let shared_model_notify: SharedModelNotify = (*shared_model_notify).clone(); Ok(Rc::new(JsModel { shared_model_notify, env: *env, js_impl: RefCountedReference::new(env, maybe_js_impl)?, row_data_type: row_data_type.clone(), }) .into()) } pub(crate) fn rust_into_js_model<'a>( env: &'a Env, model: &ModelRc, ) -> Option>> { model .as_any() .downcast_ref::() .map(|rust_model| rust_model.js_impl.get_unknown()?.into_unknown(env)) } struct JsModel { shared_model_notify: SharedModelNotify, env: Env, js_impl: RefCountedReference, row_data_type: Type, } #[napi] pub fn js_model_notify_new() -> Result> { Ok(External::new(Default::default())) } #[napi] pub fn js_model_notify_row_data_changed(notify: ExternalRef, row: u32) { notify.row_changed(row as usize); } #[napi] pub fn js_model_notify_row_added(notify: ExternalRef, row: u32, count: u32) { notify.row_added(row as usize, count as usize); } #[napi] pub fn js_model_notify_row_removed(notify: ExternalRef, row: u32, count: u32) { notify.row_removed(row as usize, count as usize); } #[napi] pub fn js_model_notify_reset(notify: ExternalRef) { notify.reset(); } impl Model for JsModel { type Data = slint_interpreter::Value; fn row_count(&self) -> usize { let Ok(model_unknown) = self.js_impl.get_unknown() else { eprintln!("Node.js: JavaScript Model's rowCount threw an exception"); return 0; }; let Ok(model) = model_unknown.coerce_to_object() else { eprintln!("Node.js: JavaScript Model is not an object"); return 0; }; let row_count_fn: Function<(), Unknown> = match model.get_named_property("rowCount") { Ok(f) => f, Err(_) => { eprintln!( "Node.js: JavaScript Model implementation is missing rowCount property" ); return 0; } }; let Ok(row_count_result) = row_count_fn.apply(model, ()) else { eprintln!("Node.js: JavaScript Model's rowCount implementation call failed"); return 0; }; let Ok(row_count_number) = row_count_result.coerce_to_number() else { eprintln!( "Node.js: JavaScript Model's rowCount function returned a value that cannot be coerced to a number" ); return 0; }; let Ok(row_count) = row_count_number.get_uint32() else { eprintln!( "Node.js: JavaScript Model's rowCount function returned a number that cannot be mapped to a uint32" ); return 0; }; row_count as usize } fn row_data(&self, row: usize) -> Option { let Ok(model_unknown) = self.js_impl.get_unknown() else { eprintln!("Node.js: JavaScript Model's rowData threw an exception"); return None; }; let Ok(model) = model_unknown.coerce_to_object() else { eprintln!("Node.js: JavaScript Model is not an object"); return None; }; let row_data_fn: Function = match model.get_named_property("rowData") { Ok(f) => f, Err(_) => { eprintln!( "Node.js: JavaScript Model implementation is missing rowData property" ); return None; } }; let Ok(row_data) = row_data_fn.apply(model, row as f64) else { eprintln!("Node.js: JavaScript Model's rowData function threw an exception"); return None; }; if row_data.get_type().unwrap() == ValueType::Undefined { debug_assert!(row >= self.row_count()); None } else { let Ok(js_value) = to_value(&self.env, row_data, &self.row_data_type) else { eprintln!( "Node.js: JavaScript Model's rowData function returned data type that cannot be represented in Rust" ); return None; }; Some(js_value) } } fn set_row_data(&self, row: usize, data: Self::Data) { let Ok(model_unknown) = self.js_impl.get_unknown() else { eprintln!("Node.js: JavaScript Model's setRowData threw an exception"); return; }; let Ok(model) = model_unknown.coerce_to_object() else { eprintln!("Node.js: JavaScript Model is not an object"); return; }; let set_row_data_fn: Function)>, Unknown> = match model.get_named_property("setRowData") { Ok(f) => f, Err(_) => { eprintln!( "Node.js: JavaScript Model implementation is missing setRowData property" ); return; } }; let Ok(js_data) = to_js_unknown(&self.env, &data) else { eprintln!( "Node.js: Model's set_row_data called by Rust with data type that can't be represented in JavaScript" ); return; }; if let Err(exception) = set_row_data_fn.apply(model, FnArgs::from((row as f64, js_data))) { eprintln!( "Node.js: JavaScript Model's setRowData function threw an exception: {exception}" ); } } fn model_tracker(&self) -> &dyn i_slint_core::model::ModelTracker { &**self.shared_model_notify } fn as_any(&self) -> &dyn core::any::Any { self } } #[napi] pub struct ReadOnlyRustModel(ModelRc); impl From> for ReadOnlyRustModel { fn from(model: ModelRc) -> Self { Self(model) } } // Implement minimal Model interface #[napi] impl ReadOnlyRustModel { #[napi] pub fn row_count(&self) -> u32 { self.0.row_count() as u32 } #[napi] pub fn row_data<'a>(&self, env: &'a Env, row: u32) -> Result> { let Some(data) = self.0.row_data(row as usize) else { return ().into_unknown(env); }; crate::to_js_unknown(env, &data) } #[napi] pub fn set_row_data(&self, _env: &Env, _row: u32, _data: Unknown<'_>) { eprintln!( "setRowData called on a model which does not re-implement this method. This happens when trying to modify a read-only model" ) } pub fn into_js<'a>(self, env: &'a Env) -> Result> { let model = self.0.clone(); let mut obj = self.into_instance(env)?.as_object(env); // Implement Iterator protocol by hand until it's stable in napi-rs let global = env.get_global()?; let symbol_function: Unknown = global.get_named_property("Symbol")?; let symbol_obj = symbol_function.coerce_to_object()?; let iterator_symbol: napi::JsSymbol = symbol_obj.get_named_property("iterator")?; obj.set_property( iterator_symbol, env.create_function_from_closure::<(), ModelIterator, _>( "rust model iterator", move |ctx| Ok(ModelIterator { model: model.clone(), row: 0, env: *ctx.env }), )?, )?; obj.into_unknown(env) } } #[napi] pub struct ModelIterator { model: ModelRc, row: usize, env: Env, } #[napi] impl ModelIterator { // Implements the JS iterator protocol — name must be `next`. #[allow(clippy::should_implement_trait)] #[napi] pub fn next(&mut self) -> Result> { let mut result = Object::new(&self.env)?; if self.row >= self.model.row_count() { result.set_named_property("done", true)?; } else { let row = self.row; self.row += 1; result.set_named_property( "value", self.model.row_data(row).and_then(|value| to_js_unknown(&self.env, &value).ok()), )? } result.into_unknown(&self.env) } } ================================================ FILE: api/node/rust/types/point.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use napi::JsValue; use napi::bindgen_prelude::{FromNapiValue, Object, Unknown}; /// SlintPoint implements {@link Point}. #[napi] pub struct SlintPoint { pub x: f64, pub y: f64, } #[napi] impl SlintPoint { /// Constructs new point from x and y. #[napi(constructor)] pub fn new(x: f64, y: f64) -> Self { Self { x, y } } } impl FromNapiValue for SlintPoint { unsafe fn from_napi_value( env: napi::sys::napi_env, napi_val: napi::sys::napi_value, ) -> napi::Result { let obj = unsafe { Object::from_napi_value(env, napi_val)? }; let x: f64 = obj .get::("x") .ok() .flatten() .and_then(|p| p.coerce_to_number().ok().and_then(|n| n.get_double().ok())) .ok_or_else( || napi::Error::from_reason( "Cannot convert object to Point, because the provided object does not have an f64 x property".to_string() ))?; let y: f64 = obj .get::("y") .ok() .flatten() .and_then(|p| p.coerce_to_number().ok().and_then(|n| n.get_double().ok())) .ok_or_else( || napi::Error::from_reason( "Cannot convert object to Point, because the provided object does not have an f64 y property".to_string() ))?; Ok(SlintPoint { x, y }) } } ================================================ FILE: api/node/rust/types/size.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use napi::JsValue; use napi::bindgen_prelude::{FromNapiValue, Object, Unknown}; /// SlintSize implements {@link Size}. #[napi] pub struct SlintSize { pub width: f64, pub height: f64, } #[napi] impl SlintSize { /// Constructs a size from the given width and height. #[napi(constructor)] pub fn new(width: f64, height: f64) -> napi::Result { if width < 0. { return Err(napi::Error::from_reason("width cannot be negative".to_string())); } if height < 0. { return Err(napi::Error::from_reason("height cannot be negative".to_string())); } Ok(Self { width, height }) } } impl FromNapiValue for SlintSize { unsafe fn from_napi_value( env: napi::sys::napi_env, napi_val: napi::sys::napi_value, ) -> napi::Result { let obj = unsafe { Object::from_napi_value(env, napi_val)? }; let width: f64 = obj .get::("width") .ok() .flatten() .and_then(|p| p.coerce_to_number().ok().and_then(|n| n.get_double().ok())) .ok_or_else( || napi::Error::from_reason( "Cannot convert object to Size, because the provided object does not have an f64 width property".to_string() ))?; let height: f64 = obj .get::("height") .ok() .flatten() .and_then(|p| p.coerce_to_number().ok().and_then(|n| n.get_double().ok())) .ok_or_else( || napi::Error::from_reason( "Cannot convert object to Size, because the provided object does not have an f64 height property".to_string() ))?; Ok(SlintSize { width, height }) } } ================================================ FILE: api/node/rust/types.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 mod brush; pub use brush::*; mod image_data; pub use image_data::*; mod model; pub use model::*; mod point; pub use point::*; mod size; pub use size::*; ================================================ FILE: api/node/thirdparty.hbs ================================================

Third Party Licenses

This page lists the licenses of the dependencies used by Slint.

Overview of licenses:

    {{#each overview}}
  • {{name}} ({{count}})
  • {{/each}}

All license text:

================================================ FILE: api/node/tsconfig.json ================================================ { "compilerOptions": { "module": "CommonJS", "target": "esnext", "declaration": true, "outDir": "dist", "skipLibCheck": true }, "include": [ "typescript/" ], "exclude": ["**/node_modules/**"] } ================================================ FILE: api/node/typescript/index.ts ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import * as napi from "../rust-module.cjs"; export { Diagnostic, DiagnosticLevel, RgbaColor, Brush, } from "../rust-module.cjs"; import { Model } from "./models"; export { Model }; export { ArrayModel } from "./models"; import { Diagnostic } from "../rust-module.cjs"; import { fileURLToPath } from "node:url"; /** * Represents a two-dimensional point. */ export interface Point { /** * Defines the x coordinate of the point. */ x: number; /** * Defines the y coordinate of the point. */ y: number; } /** * Represents a two-dimensional size. */ export interface Size { /** * Defines the width length of the size. */ width: number; /** * Defines the height length of the size. */ height: number; } /** * This type represents a window towards the windowing system, that's used to render the * scene of a component. It provides API to control windowing system specific aspects such * as the position on the screen. */ export interface Window { /** Gets or sets the logical position of the window on the screen. */ logicalPosition: Point; /** Gets or sets the physical position of the window on the screen. */ physicalPosition: Point; /** Gets or sets the logical size of the window on the screen, */ logicalSize: Size; /** Gets or sets the physical size of the window on the screen, */ physicalSize: Size; /** Gets or sets the window's fullscreen state **/ fullscreen: boolean; /** Gets or sets the window's maximized state **/ maximized: boolean; /** Gets or sets the window's minimized state **/ minimized: boolean; /** * Returns the visibility state of the window. This function can return false even if you previously called show() * on it, for example if the user minimized the window. */ get visible(): boolean; /** * Shows the window on the screen. An additional strong reference on the * associated component is maintained while the window is visible. */ show(): void; /** Hides the window, so that it is not visible anymore. */ hide(): void; /** Issues a request to the windowing system to re-render the contents of the window. */ requestRedraw(): void; } /** * An image data type that can be displayed by the Image element. * * This interface is inspired by the web [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) interface. */ export interface ImageData { /** * Returns the path of the image, if it was loaded from disk. Otherwise * the property is undefined. */ readonly path?: string; /** * Returns the image as buffer. */ get data(): Uint8Array; /** * Returns the width of the image in pixels. */ get width(): number; /** * Returns the height of the image in pixels. */ get height(): number; } /** * This interface describes the public API of a Slint component that is common to all instances. Use this to * show() the window on the screen, access the window and subsequent window properties, or start the * Slint event loop with run(). */ export interface ComponentHandle { /** * Shows the window and runs the event loop. The returned promise is resolved when the event loop * is terminated, for example when the last window was closed, or {@link quitEventLoop} was called. * * This function is a convenience for calling {@link show}, followed by {@link runEventLoop}, and * {@link hide} when the event loop's promise is resolved. */ run(): Promise; /** * Shows the component's window on the screen. */ show(); /** * Hides the component's window, so that it is not visible anymore. */ hide(); /** * Returns the {@link Window} associated with this component instance. * The window API can be used to control different aspects of the integration into the windowing system, such as the position on the screen. */ get window(): Window; } /** * @hidden */ class Component implements ComponentHandle { #instance: napi.ComponentInstance; /** * @hidden */ constructor(instance: napi.ComponentInstance) { this.#instance = instance; } get window(): Window { return this.#instance.window(); } /** * @hidden */ get component_instance(): napi.ComponentInstance { return this.#instance; } async run() { this.show(); await runEventLoop(); this.hide(); } show() { this.#instance.window().show(); } hide() { this.#instance.window().hide(); } } /** * Represents an errors that can be emitted by the compiler. */ export class CompileError extends Error { /** * List of {@link Diagnostic} items emitted while compiling .slint code. */ diagnostics: napi.Diagnostic[]; /** * Creates a new CompileError. * * @param message human-readable description of the error. * @param diagnostics represent a list of diagnostic items emitted while compiling .slint code. */ constructor(message: string, diagnostics: napi.Diagnostic[]) { const formattedDiagnostics = diagnostics .map( (d) => `[${d.fileName}:${d.lineNumber}:${d.columnNumber}] ${d.message}`, ) .join("\n"); let formattedMessage = message; if (diagnostics.length > 0) { formattedMessage += `\nDiagnostics:\n${formattedDiagnostics}`; } super(formattedMessage); this.diagnostics = diagnostics; } } /** * LoadFileOptions are used to defines different optional parameters that can be used to configure the compiler. */ export interface LoadFileOptions { /** * If set to true warnings from the compiler will not be printed to the console. */ quiet?: boolean; /** * Sets the widget style the compiler is currently using when compiling .slint files. */ style?: string; /** * Sets the include paths used for looking up `.slint` imports to the specified vector of paths. */ includePaths?: Array; /** * Sets library paths used for looking up `@library` imports to the specified map of library names to paths. */ libraryPaths?: Record; /** * @hidden */ fileLoader?: (path: string) => string; } type LoadData = | { fileData: { filePath: string; options?: LoadFileOptions; }; from: "file"; } | { fileData: { source: string; filePath: string; options?: LoadFileOptions; }; from: "source"; }; function translateName(key: string): string { return key.replace(/-/g, "_"); } function loadSlint(loadData: LoadData): Object { const { filePath, options } = loadData.fileData; const compiler = new napi.ComponentCompiler(); if (typeof options !== "undefined") { if (typeof options.style !== "undefined") { compiler.style = options.style; } if (typeof options.includePaths !== "undefined") { compiler.includePaths = options.includePaths; } if (typeof options.libraryPaths !== "undefined") { compiler.libraryPaths = options.libraryPaths; } if (typeof options.fileLoader !== "undefined") { compiler.fileLoader = options.fileLoader; } } const definitions = loadData.from === "file" ? compiler.buildFromPath(filePath) : compiler.buildFromSource(loadData.fileData.source, filePath); const diagnostics = compiler.diagnostics; if (diagnostics.length > 0) { const warnings = diagnostics.filter( (d) => d.level === napi.DiagnosticLevel.Warning, ); if (typeof options !== "undefined" && options.quiet !== true) { warnings.forEach((w) => console.warn("Warning: " + w)); } const errors = diagnostics.filter( (d) => d.level === napi.DiagnosticLevel.Error, ); if (errors.length > 0) { throw new CompileError("Could not compile " + filePath, errors); } } const slint_module = Object.create({}); // generate structs const structs = compiler.structs; for (const key in compiler.structs) { Object.defineProperty(slint_module, translateName(key), { value: function (properties: any) { const defaultObject = structs[key] as any; const newObject = Object.create({}); for (const propertyKey in defaultObject) { const propertyName = translateName(propertyKey); const propertyValue = properties !== undefined && Object.hasOwn(properties, propertyName) ? properties[propertyName] : defaultObject[propertyKey]; Object.defineProperty(newObject, propertyName, { value: propertyValue, writable: true, enumerable: true, }); } return Object.seal(newObject); }, }); } // generate enums const enums = compiler.enums; for (const key in enums) { Object.defineProperty(slint_module, translateName(key), { value: Object.seal(enums[key]), enumerable: true, }); } Object.keys(definitions).forEach((key) => { const definition = definitions[key]; Object.defineProperty(slint_module, translateName(definition.name), { value: function (properties: any) { const instance = definition.create(); if (instance == null) { throw Error( "Could not create a component handle for" + filePath, ); } for (var key in properties) { const value = properties[key]; if (value instanceof Function) { instance.setCallback(key, value); } else { instance.setProperty(key, properties[key]); } } const componentHandle = new Component(instance!); instance!.definition().properties.forEach((prop) => { const propName = translateName(prop.name); if (componentHandle[propName] !== undefined) { console.warn("Duplicated property name " + propName); } else { Object.defineProperty(componentHandle, propName, { get() { return instance!.getProperty(prop.name); }, set(value) { instance!.setProperty(prop.name, value); }, enumerable: true, }); } }); instance!.definition().callbacks.forEach((cb) => { const callbackName = translateName(cb); if (componentHandle[callbackName] !== undefined) { console.warn( "Duplicated callback name " + callbackName, ); } else { Object.defineProperty( componentHandle, translateName(cb), { get() { return function () { return instance!.invoke( cb, Array.from(arguments), ); }; }, set(callback) { instance!.setCallback(cb, callback); }, enumerable: true, }, ); } }); instance!.definition().functions.forEach((cb) => { const functionName = translateName(cb); if (componentHandle[functionName] !== undefined) { console.warn( "Duplicated function name " + functionName, ); } else { Object.defineProperty( componentHandle, translateName(cb), { get() { return function () { return instance!.invoke( cb, Array.from(arguments), ); }; }, enumerable: true, }, ); } }); // globals instance!.definition().globals.forEach((globalName) => { const jsName = translateName(globalName); if (componentHandle[jsName] !== undefined) { console.warn( "Duplicated property name " + globalName + " (In JS: " + jsName + ")", ); } else { const globalObject = Object.create({}); instance! .definition() .globalProperties(globalName) .forEach((prop) => { const propName = translateName(prop.name); if (globalObject[propName] !== undefined) { console.warn( "Duplicated property name " + propName + " on global " + global, ); } else { Object.defineProperty( globalObject, propName, { get() { return instance!.getGlobalProperty( globalName, prop.name, ); }, set(value) { instance!.setGlobalProperty( globalName, prop.name, value, ); }, enumerable: true, }, ); } }); instance! .definition() .globalCallbacks(globalName) .forEach((cb) => { const callbackName = translateName(cb); if (globalObject[callbackName] !== undefined) { console.warn( "Duplicated property name " + cb + " on global " + global, ); } else { Object.defineProperty( globalObject, translateName(cb), { get() { return function () { return instance!.invokeGlobal( globalName, cb, Array.from(arguments), ); }; }, set(callback) { instance!.setGlobalCallback( globalName, cb, callback, ); }, enumerable: true, }, ); } }); instance! .definition() .globalFunctions(globalName) .forEach((cb) => { const functionName = translateName(cb); if (globalObject[functionName] !== undefined) { console.warn( "Duplicated function name " + cb + " on global " + global, ); } else { Object.defineProperty( globalObject, translateName(cb), { get() { return function () { return instance!.invokeGlobal( globalName, cb, Array.from(arguments), ); }; }, enumerable: true, }, ); } }); Object.defineProperty(componentHandle, jsName, { get() { return globalObject; }, enumerable: true, }); } }); return Object.seal(componentHandle); }, }); }); return Object.seal(slint_module); } /** * Loads the specified Slint file and returns an object containing functions to construct the exported * components defined within the Slint file. * * The following example loads a "Hello World" style Slint file and changes the Text label to a new greeting: * **`main.slint`**: * ``` * export component Main inherits Window { * in-out property greeting <=> label.text; * label := Text { * text: "Hello World"; * } * } * ``` * * **`index.js`**: * ```javascript * import * as slint from "slint-ui"; * let ui = slint.loadFile("main.slint"); * let main = new ui.Main(); * main.greeting = "Hello friends"; * ``` * * @param filePath The path to the file to load as `string` or `URL`. Relative paths are resolved against the process' current working directory. * @param options An optional {@link LoadFileOptions} to configure additional Slint compilation settings, * such as include search paths, library imports, or the widget style. * @returns Returns an object that is immutable and provides a constructor function for each exported Window component found in the `.slint` file. * For instance, in the example above, a `Main` property is available, which can be used to create instances of the `Main` component using the `new` keyword. * These instances offer properties and event handlers, adhering to the {@link ComponentHandle} interface. * For further information on the available properties, refer to [Instantiating A Component](../index.html#instantiating-a-component). * @throws {@link CompileError} if errors occur during compilation. */ export function loadFile( filePath: string | URL, options?: LoadFileOptions, ): Object { const pathname = filePath instanceof URL ? fileURLToPath(filePath) : filePath; return loadSlint({ fileData: { filePath: pathname, options }, from: "file", }); } /** * Loads the given Slint source code and returns an object that contains a functions to construct the exported * components of the Slint source code. * * The following example loads a "Hello World" style Slint source code and changes the Text label to a new greeting: * ```js * import * as slint from "slint-ui"; * const source = `export component Main { * in-out property greeting <=> label.text; * label := Text { * text: "Hello World"; * } * }`; // The content of main.slint * let ui = slint.loadSource(source, "main.js"); * let main = new ui.Main(); * main.greeting = "Hello friends"; * ``` * @param source The Slint source code to load. * @param filePath A path to the file to show log and resolve relative import and images. * Relative paths are resolved against the process' current working directory. * @param options An optional {@link LoadFileOptions} to configure additional Slint compilation settings, * such as include search paths, library imports, or the widget style. * @returns Returns an object that is immutable and provides a constructor function for each exported Window component found in the `.slint` file. * For instance, in the example above, a `Main` property is available, which can be used to create instances of the `Main` component using the `new` keyword. * These instances offer properties and event handlers, adhering to the {@link ComponentHandle} interface. * For further information on the available properties, refer to [Instantiating A Component](../index.html#instantiating-a-component). * @throws {@link CompileError} if errors occur during compilation. */ export function loadSource( source: string, filePath: string, options?: LoadFileOptions, ): Object { return loadSlint({ fileData: { filePath, options, source }, from: "source", }); } class EventLoop { #quit_loop: boolean = false; #terminationPromise: Promise | null = null; #terminateResolveFn: ((_value: unknown) => void) | null; start( running_callback?: Function, quitOnLastWindowClosed: boolean = true, ): Promise { if (this.#terminationPromise != null) { return this.#terminationPromise; } this.#terminationPromise = new Promise((resolve) => { this.#terminateResolveFn = resolve; }); this.#quit_loop = false; napi.setQuitOnLastWindowClosed(quitOnLastWindowClosed); if (running_callback !== undefined) { napi.invokeFromEventLoop(() => { running_callback(); running_callback = undefined; }); } // Give the nodejs event loop 16 ms to tick. This polling is sub-optimal, but it's the best we // can do right now. const nodejsPollInterval = 16; const id = setInterval(() => { if ( napi.processEvents() === napi.ProcessEventsResult.Exited || this.#quit_loop ) { clearInterval(id); this.#terminateResolveFn!(undefined); this.#terminateResolveFn = null; this.#terminationPromise = null; return; } }, nodejsPollInterval); return this.#terminationPromise; } quit() { this.#quit_loop = true; } } var globalEventLoop: EventLoop = new EventLoop(); /** * Spins the Slint event loop and returns a promise that resolves when the loop terminates. * * If the event loop is already running, then this function returns the same promise as from * the earlier invocation. * * @param args As Function it defines a callback that's invoked once when the event loop is running. * @param args.runningCallback Optional callback that's invoked once when the event loop is running. * The function's return value is ignored. * @param args.quitOnLastWindowClosed if set to `true` event loop is quit after last window is closed otherwise * it is closed after {@link quitEventLoop} is called. * This is useful for system tray applications where the application needs to stay alive even if no windows are visible. * (default true). * * Note that the event loop integration with Node.js is slightly imperfect. Due to conflicting * implementation details between Slint's and Node.js' event loop, the two loops are merged * by spinning one after the other, at 16 millisecond intervals. This means that when the * application is idle, it continues to consume a low amount of CPU cycles, checking if either * event loop has any pending events. */ export function runEventLoop( args?: | Function | { runningCallback?: Function; quitOnLastWindowClosed?: boolean }, ): Promise { if (args === undefined) { return globalEventLoop.start(undefined); } if (args instanceof Function) { return globalEventLoop.start(args); } return globalEventLoop.start( args.runningCallback, args.quitOnLastWindowClosed, ); } /** * Stops a spinning event loop. This function returns immediately, and the promise returned from run_event_loop() will resolve in a later tick of the nodejs event loop. */ export function quitEventLoop() { globalEventLoop.quit(); } export namespace private_api { /** * Provides rows that are generated by a map function based on the rows of another Model. * * @template T item type of source model that is mapped to U. * @template U the type of the mapped items * * ## Example * * Here we have a {@link ArrayModel} holding rows of a custom interface `Name` and a {@link MapModel} that maps the name rows * to single string rows. * * ```ts * import { Model, ArrayModel, MapModel } from "./index"; * * interface Name { * first: string; * last: string; * } * * const model = new ArrayModel([ * { * first: "Hans", * last: "Emil", * }, * { * first: "Max", * last: "Mustermann", * }, * { * first: "Roman", * last: "Tisch", * }, * ]); * * const mappedModel = new MapModel( * model, * (data) => { * return data.last + ", " + data.first; * } * ); * * // prints "Emil, Hans" * console.log(mappedModel.rowData(0)); * * // prints "Mustermann, Max" * console.log(mappedModel.rowData(1)); * * // prints "Tisch, Roman" * console.log(mappedModel.rowData(2)); * * // Alternatively you can use the shortcut {@link MapModel.map}. * * const model = new ArrayModel([ * { * first: "Hans", * last: "Emil", * }, * { * first: "Max", * last: "Mustermann", * }, * { * first: "Roman", * last: "Tisch", * }, * ]); * * const mappedModel = model.map( * (data) => { * return data.last + ", " + data.first; * } * ); * * * // prints "Emil, Hans" * console.log(mappedModel.rowData(0)); * * // prints "Mustermann, Max" * console.log(mappedModel.rowData(1)); * * // prints "Tisch, Roman" * console.log(mappedModel.rowData(2)); * * // You can modifying the underlying {@link ArrayModel}: * * const model = new ArrayModel([ * { * first: "Hans", * last: "Emil", * }, * { * first: "Max", * last: "Mustermann", * }, * { * first: "Roman", * last: "Tisch", * }, * ]); * * const mappedModel = model.map( * (data) => { * return data.last + ", " + data.first; * } * ); * * model.setRowData(1, { first: "Minnie", last: "Musterfrau" } ); * * // prints "Emil, Hans" * console.log(mappedModel.rowData(0)); * * // prints "Musterfrau, Minnie" * console.log(mappedModel.rowData(1)); * * // prints "Tisch, Roman" * console.log(mappedModel.rowData(2)); * ``` */ export class MapModel extends Model { readonly sourceModel: Model; #mapFunction: (data: T) => U; /** * Constructs the MapModel with a source model and map functions. * @template T item type of source model that is mapped to U. * @template U the type of the mapped items. * @param sourceModel the wrapped model. * @param mapFunction maps the data from T to U. */ constructor(sourceModel: Model, mapFunction: (data: T) => U) { super(sourceModel.modelNotify); this.sourceModel = sourceModel; this.#mapFunction = mapFunction; } /** * Returns the number of entries in the model. */ rowCount(): number { return this.sourceModel.rowCount(); } /** * Returns the data at the specified row. * @param row index in range 0..(rowCount() - 1). * @returns undefined if row is out of range otherwise the data. */ rowData(row: number): U | undefined { const data = this.sourceModel.rowData(row); if (data === undefined) { return undefined; } return this.#mapFunction(data); } } } /** * Initialize translations. * * Call this with the path where translations are located. This function internally calls the [bindtextdomain](https://man7.org/linux/man-pages/man3/bindtextdomain.3.html) function from gettext. * * Translations are expected to be found at //LC_MESSAGES/.mo, where path is the directory passed as an argument to this function, locale is a locale name (e.g., en, en_GB, fr), and domain is the package name. * * @param domain defines the domain name e.g. name of the package. * @param path specifies the directory as `string` or as `URL` in which gettext should search for translations. * * For example, assuming this is in a package called example and the default locale is configured to be French, it will load translations at runtime from ``/path/to/example/translations/fr/LC_MESSAGES/example.mo`. * * ```js * import * as slint from "slint-ui"; * slint.initTranslations("example", new URL("translations/", import.meta.url)); * ```` */ export function initTranslations(domain: string, path: string | URL) { const pathname = path instanceof URL ? fileURLToPath(path) : path; napi.initTranslations(domain, pathname); } /** * Sets the application id for use on Wayland or X11 with [xdg](https://specifications.freedesktop.org/desktop-entry-spec/latest/) * compliant window managers. This must be set before the window is shown. */ export function setXdgAppId(app_id: string) { napi.setXdgAppId(app_id); } /** * @hidden */ export namespace private_api { export import mock_elapsed_time = napi.mockElapsedTime; export import get_mocked_time = napi.getMockedTime; export import ComponentCompiler = napi.ComponentCompiler; export import ComponentDefinition = napi.ComponentDefinition; export import ComponentInstance = napi.ComponentInstance; export import ValueType = napi.ValueType; export import Window = napi.Window; export import SlintBrush = napi.SlintBrush; export import SlintRgbaColor = napi.SlintRgbaColor; export import SlintSize = napi.SlintSize; export import SlintPoint = napi.SlintPoint; export import SlintImageData = napi.SlintImageData; export function send_mouse_click( component: Component, x: number, y: number, ) { component.component_instance.sendMouseClick(x, y); } export function send_keyboard_string_sequence( component: Component, s: string, ) { component.component_instance.sendKeyboardStringSequence(s); } export import initTesting = napi.initTesting; } ================================================ FILE: api/node/typescript/models.ts ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import * as napi from "../rust-module.cjs"; class ModelIterator implements Iterator { private row: number; private model: Model; constructor(model: Model) { this.model = model; this.row = 0; } public next(): IteratorResult { if (this.row < this.model.rowCount()) { const row = this.row; this.row++; return { done: false, value: this.model.rowData(row), }; } return { done: true, value: undefined, }; } } /** * Model is the interface for feeding dynamic data into * `.slint` views. * * A model is organized like a table with rows of data. The * fields of the data type T behave like columns. * * @template T the type of the model's items. * * ### Example * As an example let's see the implementation of {@link ArrayModel} * * ```js * export class ArrayModel extends Model { * private a: Array * * constructor(arr: Array) { * super(); * this.a = arr; * } * * rowCount() { * return this.a.length; * } * * rowData(row: number) { * return this.a[row]; * } * * setRowData(row: number, data: T) { * this.a[row] = data; * this.notifyRowDataChanged(row); * } * * push(...values: T[]) { * let size = this.a.length; * Array.prototype.push.apply(this.a, values); * this.notifyRowAdded(size, arguments.length); * } * * remove(index: number, size: number) { * let r = this.a.splice(index, size); * this.notifyRowRemoved(index, size); * } * * get length(): number { * return this.a.length; * } * * values(): IterableIterator { * return this.a.values(); * } * * entries(): IterableIterator<[number, T]> { * return this.a.entries() * } *} * ``` */ export abstract class Model implements Iterable { /** * @hidden */ modelNotify: napi.ExternalObject; /** * @hidden */ constructor(modelNotify?: napi.ExternalObject) { this.modelNotify = modelNotify ?? napi.jsModelNotifyNew(); } // /** // * Returns a new Model where all elements are mapped by the function `mapFunction`. // * @template T the type of the source model's items. // * @param mapFunction functions that maps // * @returns a new {@link MapModel} that wraps the current model. // */ // map( // mapFunction: (data: T) => U // ): MapModel { // return new MapModel(this, mapFunction); // } /** * Implementations of this function must return the current number of rows. */ abstract rowCount(): number; /** * Implementations of this function must return the data at the specified row. * @param row index in range 0..(rowCount() - 1). * @returns undefined if row is out of range otherwise the data. */ abstract rowData(row: number): T | undefined; /** * Implementations of this function must store the provided data parameter * in the model at the specified row. * @param _row index in range 0..(rowCount() - 1). * @param _data new data item to store on the given row index */ setRowData(_row: number, _data: T): void { console.log( "setRowData called on a model which does not re-implement this method. This happens when trying to modify a read-only model", ); } [Symbol.iterator](): Iterator { return new ModelIterator(this); } /** * Notifies the view that the data of the current row is changed. * @param row index of the changed row. */ protected notifyRowDataChanged(row: number): void { napi.jsModelNotifyRowDataChanged(this.modelNotify, row); } /** * Notifies the view that multiple rows are added to the model. * @param row index of the first added row. * @param count the number of added items. */ protected notifyRowAdded(row: number, count: number): void { napi.jsModelNotifyRowAdded(this.modelNotify, row, count); } /** * Notifies the view that multiple rows are removed to the model. * @param row index of the first removed row. * @param count the number of removed items. */ protected notifyRowRemoved(row: number, count: number): void { napi.jsModelNotifyRowRemoved(this.modelNotify, row, count); } /** * Notifies the view that the complete data must be reload. */ protected notifyReset(): void { napi.jsModelNotifyReset(this.modelNotify); } } /** * ArrayModel wraps a JavaScript array for use in `.slint` views. The underlying * array can be modified with the [[ArrayModel.push]] and [[ArrayModel.remove]] methods. */ export class ArrayModel extends Model { /** * @hidden */ #array: Array; /** * Creates a new ArrayModel. * * @param arr */ constructor(arr: Array) { super(); this.#array = arr; } /** * Returns the number of entries in the array model. */ get length(): number { return this.#array.length; } /** * Returns the number of entries in the array model. */ rowCount() { return this.#array.length; } /** * Returns the data at the specified row. * @param row index in range 0..(rowCount() - 1). * @returns undefined if row is out of range otherwise the data. */ rowData(row: number) { return this.#array[row]; } /** * Stores the given data on the given row index and notifies run-time about the changed row. * @param row index in range 0..(rowCount() - 1). * @param data new data item to store on the given row index */ setRowData(row: number, data: T) { this.#array[row] = data; this.notifyRowDataChanged(row); } /** * Pushes new values to the array that's backing the model and notifies * the run-time about the added rows. * @param values list of values that will be pushed to the array. */ push(...values: T[]) { const size = this.#array.length; Array.prototype.push.apply(this.#array, values); this.notifyRowAdded(size, arguments.length); } /** * Removes the last element from the array and returns it. * * @returns The removed element or undefined if the array is empty. */ pop(): T | undefined { const last = this.#array.pop(); if (last !== undefined) { this.notifyRowRemoved(this.#array.length, 1); } return last; } // FIXME: should this be named splice and have the splice api? /** * Removes the specified number of element from the array that's backing * the model, starting at the specified index. * @param index index of first row to remove. * @param size number of rows to remove. */ remove(index: number, size: number) { const r = this.#array.splice(index, size); this.notifyRowRemoved(index, size); } /** * Returns an iterable of values in the array. */ values(): IterableIterator { return this.#array.values(); } /** * Returns an iterable of key, value pairs for every entry in the array. */ entries(): IterableIterator<[number, T]> { return this.#array.entries(); } } ================================================ FILE: api/node/vitest.config.ts ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { defineConfig } from "vitest/config"; export default defineConfig({ test: { include: ["**/*.spec.mts"], globals: true, // Enable global test/expect/describe pool: "forks", // Use process forks (required for native modules that need main thread) teardownTimeout: 5000, // Force teardown after 5s to prevent hanging processes reporters: ["verbose"], // Show individual test names }, }); ================================================ FILE: api/python/briefcase/README.md ================================================ # Briefcase Slint Plugin The `briefcasex-slint` package extends the [Briefcase](https://briefcase.beeware.org/en/stable/) tool with support for Slint as toolkit for standalone applications. Install it in your Python environment, and `briefcase new` will offer you Slint as GUI framework for your next application. ## Get Started In a terminal, create a new Python environment and run the following commands: 1. `pip install briefcasex-slint` 2. `briefcase new -Q bootstrap=Slint` Following the instructions on the screen. ================================================ FILE: api/python/briefcase/pyproject.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 [project] name = "briefcasex-slint" version = "1.16.0b1" description = "Plugin for Briefcase to offer Slint application templates" readme = "README.md" requires-python = ">=3.10" dependencies = ["briefcase>=0.3.23"] [project.entry-points."briefcase.bootstraps"] Slint = "briefcasex_slint:SlintGuiBootstrap" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" ================================================ FILE: api/python/briefcase/src/briefcasex_slint/__init__.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 from pathlib import Path from briefcase.bootstraps.base import BaseGuiBootstrap class SlintGuiBootstrap(BaseGuiBootstrap): display_name_annotation = "does not support Android/Web deployment" def app_source(self): return """\ import slint class {{ cookiecutter.class_name }}(slint.loader.{{ cookiecutter.module_name }}.resources.app_window.AppWindow): @slint.callback def request_increase_value(self): self.counter = self.counter + 1 def main(): main_window = {{ cookiecutter.class_name }}() main_window.show() main_window.run() """ def app_start_source(self): return """\ from {{ cookiecutter.module_name }}.app import main if __name__ == "__main__": main() """ def pyproject_table_briefcase_app_extra_content(self): return """ requires = [ ] test_requires = [ {% if cookiecutter.test_framework == "pytest" %} "pytest", {% endif %} ] """ def pyproject_table_macOS(self): return """\ universal_build = false requires = [ "slint", ] """ def pyproject_table_linux(self): return """\ requires = [ "slint", ] """ def pyproject_table_windows(self): return """\ requires = [ "slint", ] """ def pyproject_table_iOS(self): return """\ requires = [ "slint", ] """ def post_generate(self, base_path: Path) -> None: target_dir = base_path / self.context["source_dir"] / "resources" target_dir.mkdir(parents=True, exist_ok=True) with open(target_dir / "app-window.slint", "w") as slint_file: slint_file.write(r""" import { Button, VerticalBox, AboutSlint } from "std-widgets.slint"; export component AppWindow inherits Window { in-out property counter: 42; callback request-increase-value(); VerticalBox { alignment: center; AboutSlint {} Text { text: "Counter: \{root.counter}"; } Button { text: "Increase value"; clicked => { root.request-increase-value(); } } } } """) ================================================ FILE: api/python/slint/.gitignore ================================================ slint/*.so slint/language.pyi uv.lock docs ================================================ FILE: api/python/slint/Cargo.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 [package] name = "slint-python" version.workspace = true authors.workspace = true edition.workspace = true license.workspace = true description = "Slint Python integration" repository.workspace = true homepage.workspace = true publish = false rust-version.workspace = true [lib] path = "lib.rs" crate-type = ["cdylib", "rlib"] [[bin]] name = "stub-gen" path = "stub-gen/main.rs" [features] default = ["backend-winit", "renderer-femtovg", "renderer-software", "backend-qt", "accessibility"] # Keep in sync with features in nightly_snapshot.yaml, cpp_package.yaml, slint_tool_binary.yaml, and api/node/Cargo.toml # binaries: default = ["backend-linuxkms-noseat", "backend-winit", "renderer-femtovg", "renderer-skia", "accessibility"] backend-qt = ["slint-interpreter/backend-qt"] backend-winit = ["slint-interpreter/backend-winit"] backend-winit-x11 = ["slint-interpreter/backend-winit-x11"] backend-winit-wayland = ["slint-interpreter/backend-winit-wayland"] backend-linuxkms = ["slint-interpreter/backend-linuxkms"] backend-linuxkms-noseat = ["slint-interpreter/backend-linuxkms-noseat"] backend-testing = ["i-slint-backend-selector/backend-testing"] renderer-femtovg = ["slint-interpreter/renderer-femtovg"] renderer-femtovg-wgpu = ["slint-interpreter/renderer-femtovg-wgpu"] renderer-skia = ["slint-interpreter/renderer-skia"] renderer-skia-opengl = ["slint-interpreter/renderer-skia-opengl"] renderer-skia-vulkan = ["slint-interpreter/renderer-skia-vulkan"] renderer-software = ["slint-interpreter/renderer-software"] accessibility = ["slint-interpreter/accessibility"] [dependencies] i-slint-backend-selector = { workspace = true } i-slint-common = { workspace = true } i-slint-core = { workspace = true, features = ["tr"] } slint-interpreter = { workspace = true, features = ["default", "display-diagnostics", "internal", "internal-highlight"] } i-slint-compiler = { workspace = true, features = ["python"] } pyo3 = { version = "0.28", features = ["extension-module", "indexmap", "chrono", "abi3-py311"] } indexmap = { version = "2.1.0" } chrono = "0.4" spin_on = { workspace = true } css-color-parser2 = { workspace = true } pyo3-stub-gen = { version = "0.18.0", default-features = false } smol = { version = "2.0.0" } [build-dependencies] i-slint-common = { workspace = true } [package.metadata.maturin] python-source = "slint" ================================================ FILE: api/python/slint/README.md ================================================ # Slint-python (Beta) [Slint](https://slint.dev/) is a UI toolkit that supports different programming languages. Slint-python is the integration with Python. **Warning** Slint-python is in a beta phase of development: The APIs while mostly stable, may be subject to further changes. Any changes will be documented in the ChangeLog. You can track the progress for the Python integration by looking at python-labelled issues at https://github.com/slint-ui/slint/labels/a%3Alanguage-python . ## Slint Language Manual The [Slint Language Documentation](../slint) covers the Slint UI description language in detail. ## Prerequisites * [Python 3](https://python.org/) * [uv](https://docs.astral.sh/uv/) or [pip](https://pypi.org/project/pip/) ## Installation Install Slint with `uv` or `pip` from the [Python Package Index](https://pypi.org): ```bash uv add slint ``` The installation uses binaries provided for macOS, Windows, and Linux for various architectures. If your target platform is not covered by binaries, `uv` will automatically build Slint from source. If that happens, you will then need some software development tools on your machine, as well as [Rust](https://www.rust-lang.org/learn/get-started). ## Quick Start 1. Create a new project with `uv init`. 2. Add the Slint Python package to your Python project: `uv add slint` 3. Create a file called `app-window.slint`: ```slint import { Button, VerticalBox } from "std-widgets.slint"; export component AppWindow inherits Window { in-out property counter: 42; callback request-increase-value(); VerticalBox { Text { text: "Counter: \{root.counter}"; } Button { text: "Increase value"; clicked => { root.request-increase-value(); } } } } ``` 4. Create a file called `main.py`: ```python import slint # slint.loader will look in `sys.path` for `app-window.slint`. class App(slint.loader.app_window.AppWindow): @slint.callback def request_increase_value(self): self.counter = self.counter + 1 app = App() app.run() ``` 5. Run it with `uv run main.py` ## API Overview ### Instantiating a Component The following example shows how to instantiate a Slint component in Python: **`app.slint`** ```slint export component MainWindow inherits Window { callback clicked <=> i-touch-area.clicked; in property counter; width: 400px; height: 200px; i-touch-area := TouchArea {} } ``` The exported component is exposed as a Python class. To access this class, you have two options: 1. Call `slint.load_file("app.slint")`. The returned object is a [namespace](https://docs.python.org/3/library/types.html#types.SimpleNamespace), that provides the `MainWindow` class as well as any other explicitly exported component that inherits `Window`: ```python import slint components = slint.load_file("app.slint") main_window = components.MainWindow() ``` 2. Use Slint's auto-loader, which lazily loads `.slint` files from `sys.path`: ```python import slint # Look for for `app.slint` in `sys.path`: main_window = slint.loader.app.MainWindow() ``` Any attribute lookup in `slint.loader` is searched for in `sys.path`. If a directory with the name exists, it is returned as a loader object, and subsequent attribute lookups follow the same logic. If the name matches a file with the `.slint` extension, it is automatically loaded with `load_file` and the [namespace](https://docs.python.org/3/library/types.html#types.SimpleNamespace) is returned. If the file name contains a dash, like `app-window.slint`, an attribute lookup for `app_window` tries to locate `app_window.slint` and then fall back to `app-window.slint`. ### Accessing Properties [Properties](../slint/src/language/syntax/properties) declared as `out` or `in-out` in `.slint` files are visible as properties on the component instance. ```python main_window.counter = 42 print(main_window.counter) ``` ### Accessing Globals [Global Singletons](https://slint.dev/docs/slint/src/language/syntax/globals#global-singletons) are accessible in Python as properties in the component instance. For example, this Slint code declares a `PrinterJobQueue` singleton: ```slint export global PrinterJobQueue { in-out property job-count; } ``` Access it as a property on the component instance by its name: ```python print("job count:", instance.PrinterJobQueue.job_count) ``` **Note**: Global singletons are instantiated once per component. When declaring multiple components for `export` to Python, each instance has their own associated globals singletons. ### Setting and Invoking Callbacks [Callbacks](src/language/syntax/callbacks) declared in `.slint` files are visible as callable properties on the component instance. Invoke them as functions to invoke the callback, and assign Python callables to set the callback handler. In Slint, callbacks are defined using the `callback` keyword and can be connected to another component's callback using the `<=>` syntax. **`my-component.slint`** ```slint export component MyComponent inherits Window { callback clicked <=> i-touch-area.clicked; width: 400px; height: 200px; i-touch-area := TouchArea {} } ``` The callbacks in Slint are exposed as properties and that can be called as functions. **`main.py`** ```python import slint component = slint.loader.my_component.MyComponent() # connect to a callback def clicked(): print("hello") component.clicked = clicked // invoke a callback component.clicked(); ``` Another way to set callbacks is to sub-class and use the `@slint.callback` decorator: ```python import slint class Component(slint.loader.my_component.MyComponent): @slint.callback def clicked(self): print("hello") component = Component() ``` The `@slint.callback()` decorator accepts a `name` argument, if the name of the method does not match the name of the callback in the `.slint` file. Similarly, a `global_name` argument can be used to bind a method to a callback in a global singleton. ### Type Mappings Each type used for properties in the Slint Language translates to a specific type in Python. The following table summarizes the mapping: | `.slint` Type | Python Type | Notes | | ------------- | ----------- | ----- | | `int` | `int` | | | `float` | `float` | | | `string` | `str` | | | `color` | `slint.Color` | | | `brush` | `slint.Brush` | | | `image` | `slint.Image` | | | `length` | `float` | | | `physical_length` | `float` | | | `duration` | `float` | The number of milliseconds | | `angle` | `float` | The angle in degrees | | structure | `dict`/`Struct` | When reading, structures are mapped to data classes, when writing dicts are also accepted. | | array | `slint.Model` | | ### Arrays and Models You can set [array properties](../slint/src/language/syntax/types#arrays-and-models) from Python by passing subclasses of `slint.Model`. Use the `slint.ListModel` class to construct a model from an iterable: ```python component.model = slint.ListModel([1, 2, 3]); component.model.append(4) del component.model[0] ``` When sub-classing `slint.Model`, provide the following methods: ```python def row_count(self): """Return the number of rows in your model""" def row_data(self, row): """Return data at specified row""" def set_row_data(self, row, data): """For read-write models, store data in the given row. When done call set.notify_row_changed:" ...""" self.notify_row_changed(row) ``` When adding or inserting rows, call `notify_row_added(row, count)` on the super class. Similarly, when removing rows, notify Slint by calling `notify_row_removed(row, count)`. ### Structs Structs declared in Slint and exposed to Python via `export` are then accessible in the namespace that is returned when [instantiating a component](#instantiating-a-component). **`app.slint`** ```slint export struct MyData { name: string, age: int } export component MainWindow inherits Window { in-out property data; } ``` **`main.py`** The exported `MyData` struct can be constructed as follows: ```python import slint # Look for for `app.slint` in `sys.path`: main_window = slint.loader.app.MainWindow() data = slint.loader.app.MyData(name = "Simon") data.age = 10 main_window.data = data ``` ### Enums Enums declared in Slint and exposed to Python via `export` are then accessible in the namespace that is returned when [instantiating a component](#instantiating-a-component). The enums are subclasses of [enum.Enum](https://docs.python.org/3/library/enum.html). **`app.slint`** ```slint export enum MyOption { Variant1, Variant2 } export component MainWindow inherits Window { in-out property data; } ``` **`main.py`** Variants of the exported `MyOption` enum can be constructed as follows: ```python import slint # Look for for `app.slint` in `sys.path`: main_window = slint.loader.app.MainWindow() value = slint.loader.app.MyOption.Variant2 main_window.data = value ``` ## Asynchronous I/O Use Python's [asyncio](https://docs.python.org/3/library/asyncio.html) library to write concurrent Python code with the `async`/`await` syntax. Slint's event loop is a full-featured [asyncio event loop](https://docs.python.org/3/library/asyncio-eventloop.html#asyncio-event-loop). While the event loop is running, [`asyncio.get_event_loop()`](https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.get_running_loop) returns a valid loop. To run an async function when starting the loop, pass a coroutine to `slint.run_event_loop()`. For the common use case of interacting with REST APIs, we recommend the [`aiohttp` library](https://docs.aiohttp.org/en/stable/). ### Known Limitations - Pipes and sub-processes are only supported on Unix-like platforms. ## Type Hints [PEP 484](https://peps.python.org/pep-0484/) introduces a standard syntax for type annotations to Python, enabling static analysis for type checking, refactoring, and code completion. Popular type checkers include [mypy](http://mypy-lang.org/), [Pyre](https://pyre-check.org), and Astral's [ty](https://docs.astral.sh/ty/). Use Slint's [slint-compiler](https://pypi.org/project/slint-compiler/) to generate stub `.py` files for `.slint` files, which are annotated with type information. These replace the need to call `load_file` or any use of `slint.loader`. 1. Create a new project with `uv init`. 2. Add the Slint Python package to your Python project: `uv add slint` 3. Create a file called `app-window.slint`: ```slint import { Button, VerticalBox } from "std-widgets.slint"; export component AppWindow inherits Window { in-out property counter: 42; callback request-increase-value(); VerticalBox { Text { text: "Counter: \{root.counter}"; } Button { text: "Increase value"; clicked => { root.request-increase-value(); } } } } ``` 4. Run the [slint-compiler](https://pypi.org/project/slint-compiler/) to generate `app_window.py`: `uvx slint-compiler -f python -o app_window.py app-window.slint` 5. Create a file called `main.py`: ```python import slint import app_window class App(app_window.AppWindow): @slint.callback def request_increase_value(self): self.counter = self.counter + 1 app = App() app.run() ``` 5. Run it with `uv run main.py` ## Third-Party Licenses For a list of the third-party licenses of all dependencies, see the separate [Third-Party Licenses page](thirdparty.html). ================================================ FILE: api/python/slint/api_match.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use std::path::PathBuf; use pyo3::prelude::*; use pyo3_stub_gen::{derive::gen_stub_pyclass, derive::gen_stub_pymethods}; #[gen_stub_pyclass] #[pyclass(name = "GeneratedAPI", unsendable)] pub struct PyGeneratedAPI { pub(crate) path: PathBuf, pub(crate) module: i_slint_compiler::generator::python::PyModule, } #[gen_stub_pymethods] #[pymethods] impl PyGeneratedAPI { #[new] fn new(path: PathBuf, json: &str) -> PyResult { let module = i_slint_compiler::generator::python::PyModule::load_from_json(json) .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; Ok(Self { path, module }) } #[staticmethod] fn compare_generated_vs_actual(generated: &Self, actual: &Self) -> PyResult<()> { let changed_globals = generated.module.changed_globals(&actual.module); let changed_components = generated.module.changed_components(&actual.module); let changed_structs_or_enums = generated.module.changed_structs_or_enums(&actual.module); let diff = changed_globals.is_some() || changed_components.is_some() || changed_structs_or_enums.is_some(); let incompatible_changes = changed_globals.as_ref().map_or(false, |c| c.incompatible_changes()) || changed_components.as_ref().map_or(false, |c| c.incompatible_changes()) || changed_structs_or_enums.as_ref().map_or(false, |c| c.incompatible_changes()); if diff { let slint_file = actual.path.display(); let python_file = generated.path.display(); eprintln!( r#"Changes detected between {slint_file} and {python_file} Re-run the slint compiler to re-generate the file, for example: uxv slint-compiler -f python -o {slint_file} {python_file} "#, ) } if incompatible_changes { Err(pyo3::exceptions::PyRuntimeError::new_err(format!( "Incompatible API changes detected between {} and {}", generated.path.display(), actual.path.display() ))) } else { Ok(()) } } } ================================================ FILE: api/python/slint/async_adapter.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use std::rc::Rc; use pyo3::prelude::*; use pyo3_stub_gen::{derive::gen_stub_pyclass, derive::gen_stub_pymethods}; #[cfg(unix)] struct PyFdWrapper(std::os::fd::RawFd); #[cfg(unix)] impl std::os::fd::AsFd for PyFdWrapper { fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> { unsafe { std::os::fd::BorrowedFd::borrow_raw(self.0) } } } #[cfg(windows)] struct PyFdWrapper(#[cfg(windows)] std::os::windows::io::RawSocket); #[cfg(windows)] impl std::os::windows::io::AsSocket for PyFdWrapper { fn as_socket(&self) -> std::os::windows::io::BorrowedSocket<'_> { unsafe { std::os::windows::io::BorrowedSocket::borrow_raw(self.0) } } } struct AdapterInner { adapter: smol::Async, readable_callback: Option>, writable_callback: Option>, } #[gen_stub_pyclass] #[pyclass(unsendable)] pub struct AsyncAdapter { inner: Option>, task: Option>, } #[gen_stub_pymethods] #[pymethods] impl AsyncAdapter { #[new] fn py_new(fd: i32) -> Self { #[cfg(windows)] let fd = u64::try_from(fd).unwrap(); AsyncAdapter { inner: Some(Rc::new(AdapterInner { adapter: smol::Async::new(PyFdWrapper(fd)).unwrap(), readable_callback: Default::default(), writable_callback: Default::default(), })), task: None, } } fn wait_for_readable(&mut self, callback: Py) { self.restart_after_mut_inner_access(|inner| { inner.readable_callback.replace(callback); }); } fn wait_for_writable(&mut self, callback: Py) { self.restart_after_mut_inner_access(|inner| { inner.writable_callback.replace(callback); }); } } impl AsyncAdapter { fn restart_after_mut_inner_access(&mut self, callback: impl FnOnce(&mut AdapterInner)) { if let Some(task) = self.task.take() { task.abort(); } // This detaches and basically makes any existing future that might get woke up fail when // trying to upgrade the weak. let mut inner = Rc::into_inner(self.inner.take().unwrap()).unwrap(); callback(&mut inner); let inner = Rc::new(inner); let inner_weak = Rc::downgrade(&inner); self.inner = Some(inner); self.task = Some( slint_interpreter::spawn_local(std::future::poll_fn(move |cx| { loop { let Some(inner) = inner_weak.upgrade() else { return std::task::Poll::Ready(()); }; let readable_poll_status: Option>> = inner.readable_callback.as_ref().map(|callback| { if inner.adapter.poll_readable(cx).is_ready() { std::task::Poll::Ready(Python::attach(|py| callback.clone_ref(py))) } else { std::task::Poll::Pending } }); let writable_poll_status: Option>> = inner.writable_callback.as_ref().map(|callback| { if inner.adapter.poll_writable(cx).is_ready() { std::task::Poll::Ready(Python::attach(|py| callback.clone_ref(py))) } else { std::task::Poll::Pending } }); let fd = inner.adapter.get_ref().0; drop(inner); if let Some(std::task::Poll::Ready(callback)) = &readable_poll_status { Python::attach(|py| { callback.call1(py, (fd,)).expect( "unexpected failure running python async readable adapter callback", ); }); } if let Some(std::task::Poll::Ready(callback)) = &writable_poll_status { Python::attach(|py| { callback.call1(py, (fd,)).expect( "unexpected failure running python async writable adapter callback", ); }); } match &readable_poll_status { Some(std::task::Poll::Ready(..)) => continue, // poll again and then probably return in the next iteration Some(std::task::Poll::Pending) => return std::task::Poll::Pending, // waker registered, come back later None => {} // Nothing to poll } match &writable_poll_status { Some(std::task::Poll::Ready(..)) => continue, // poll again and then probably return in the next iteration Some(std::task::Poll::Pending) => return std::task::Poll::Pending, // waker registered, come back later None => {} // Nothing to poll } return std::task::Poll::Ready(()); } })) .unwrap(), ); } } ================================================ FILE: api/python/slint/brush.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use pyo3::prelude::*; use pyo3_stub_gen::{derive::gen_stub_pyclass, derive::gen_stub_pymethods, impl_stub_type}; use crate::errors::PyColorParseError; #[gen_stub_pyclass] #[pyclass] #[derive(FromPyObject)] struct RgbaColor { #[pyo3(get, set)] red: u8, #[pyo3(get, set)] green: u8, #[pyo3(get, set)] blue: u8, #[pyo3(get, set)] alpha: u8, } #[gen_stub_pyclass] #[pyclass] #[derive(FromPyObject)] struct RgbColor { #[pyo3(get, set)] red: u8, #[pyo3(get, set)] green: u8, #[pyo3(get, set)] blue: u8, } #[derive(FromPyObject)] #[pyclass] enum PyColorInput { ColorStr(String), // This variant must come before RgbColor RgbaColor { #[pyo3(item)] red: u8, #[pyo3(item)] green: u8, #[pyo3(item)] blue: u8, #[pyo3(item)] alpha: u8, }, RgbColor { #[pyo3(item)] red: u8, #[pyo3(item)] green: u8, #[pyo3(item)] blue: u8, }, } impl_stub_type!(PyColorInput = String | RgbaColor | RgbColor); /// A Color object represents a color in the RGB color space with an alpha. Each color channel and the alpha is represented /// as an 8-bit integer. The alpha channel is 0 for fully transparent and 255 for fully opaque. /// /// Construct colors from a CSS color string, or by specifying the red, green, blue, and (optional) alpha channels in a dict. #[gen_stub_pyclass] #[pyclass(name = "Color", from_py_object)] #[derive(Clone)] pub struct PyColor { pub color: slint_interpreter::Color, } #[gen_stub_pymethods] #[pymethods] impl PyColor { #[new] #[pyo3(signature = (maybe_value=None))] fn py_new(maybe_value: Option) -> PyResult { let Some(value) = maybe_value else { return Ok(Self { color: Default::default() }); }; match value { PyColorInput::ColorStr(color_str) => color_str .parse::() .map(|c| Self { color: slint_interpreter::Color::from_argb_u8( (c.a * 255.) as u8, c.r, c.g, c.b, ), }) .map_err(|color_err| PyColorParseError(color_err).into()), PyColorInput::RgbaColor { red, green, blue, alpha } => { Ok(Self { color: slint_interpreter::Color::from_argb_u8(alpha, red, green, blue) }) } PyColorInput::RgbColor { red, green, blue } => { Ok(Self { color: slint_interpreter::Color::from_rgb_u8(red, green, blue) }) } } } /// The red channel. #[getter] fn red(&self) -> u8 { self.color.red() } /// The green channel. #[getter] fn green(&self) -> u8 { self.color.green() } /// The blue channel. #[getter] fn blue(&self) -> u8 { self.color.blue() } /// The alpha channel. #[getter] fn alpha(&self) -> u8 { self.color.alpha() } /// Returns a new color that is brighter than this color by the given factor. fn brighter(&self, factor: f32) -> Self { Self { color: self.color.brighter(factor) } } /// Returns a new color that is darker than this color by the given factor. fn darker(&self, factor: f32) -> Self { Self { color: self.color.darker(factor) } } /// Returns a new version of this color with the opacity decreased by `factor`. /// /// The transparency is obtained by multiplying the alpha channel by `(1 - factor)`. fn transparentize(&self, factor: f32) -> Self { Self { color: self.color.transparentize(factor) } } /// Returns a new color that is a mix of this color and `other`. The specified factor is /// clamped to be between `0.0` and `1.0` and then applied to this color, while `1.0 - factor` /// is applied to `other`. fn mix(&self, other: &Self, factor: f32) -> Self { Self { color: self.color.mix(&other.color, factor) } } /// Returns a new version of this color with the opacity set to `alpha`. fn with_alpha(&self, alpha: f32) -> Self { Self { color: self.color.with_alpha(alpha) } } fn __str__(&self) -> String { self.color.to_string() } fn __eq__(&self, other: &Self) -> bool { self.color == other.color } } impl From for PyColor { fn from(color: slint_interpreter::Color) -> Self { Self { color } } } #[derive(FromPyObject)] #[pyclass] enum PyBrushInput { SolidColor(PyColor), } impl_stub_type!(PyBrushInput = PyColor); /// A brush is a data structure that is used to describe how a shape, such as a rectangle, path or even text, /// shall be filled. A brush can also be applied to the outline of a shape, that means the fill of the outline itself. /// /// Brushes can only be constructed from solid colors. /// /// **Note:** In future, we plan to reduce this constraint and allow for declaring graidient brushes programmatically. #[gen_stub_pyclass] #[pyclass(name = "Brush")] pub struct PyBrush { pub brush: slint_interpreter::Brush, } #[gen_stub_pymethods] #[pymethods] impl PyBrush { #[new] #[pyo3(signature = (maybe_value=None))] fn py_new(maybe_value: Option) -> PyResult { let Some(value) = maybe_value else { return Ok(Self { brush: Default::default() }); }; match value { PyBrushInput::SolidColor(pycol) => Ok(Self { brush: pycol.color.into() }), } } /// The brush's color. #[getter] fn color(&self) -> PyColor { self.brush.color().into() } /// Returns true if this brush contains a fully transparent color (alpha value is zero). fn is_transparent(&self) -> bool { self.brush.is_transparent() } /// Returns true if this brush is fully opaque. fn is_opaque(&self) -> bool { self.brush.is_opaque() } /// Returns a new version of this brush that has the brightness increased /// by the specified factor. This is done by calling `Color.brighter` on /// all the colors of this brush. fn brighter(&self, factor: f32) -> Self { Self { brush: self.brush.brighter(factor) } } /// Returns a new version of this brush that has the brightness decreased /// by the specified factor. This is done by calling `Color.darker` on /// all the color of this brush. fn darker(&self, factor: f32) -> Self { Self { brush: self.brush.darker(factor) } } /// Returns a new version of this brush with the opacity decreased by `factor`. /// /// The transparency is obtained by multiplying the alpha channel by `(1 - factor)`. /// /// See also `Color.transparentize`. fn transparentize(&self, amount: f32) -> Self { Self { brush: self.brush.transparentize(amount) } } /// Returns a new version of this brush with the related color's opacities /// set to `alpha`. fn with_alpha(&self, alpha: f32) -> Self { Self { brush: self.brush.with_alpha(alpha) } } fn __eq__(&self, other: &Self) -> bool { self.brush == other.brush } } impl From for PyBrush { fn from(brush: slint_interpreter::Brush) -> Self { Self { brush } } } ================================================ FILE: api/python/slint/build.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use std::fs::File; use std::io::{BufWriter, Write}; fn map_type(ty: &str) -> &str { match ty { "bool" => "bool", "SharedString" => "str", "i32" => "int", "f32" | "Coord" => "float", _ => "typing.Any", } } fn map_default(ty: &str) -> &str { match ty { "bool" => "False", "SharedString" => "\"\"", "i32" => "0", "f32" | "Coord" => "0.0", _ => "None", } } macro_rules! generate_builtin_structs_pyi { ($( $(#[doc = $struct_doc:literal])* $(#[non_exhaustive])? $(#[derive(Copy, Eq)])? struct $Name:ident { @name = $NameTy:ident :: $Variant:ident, export { $( $(#[doc = $pub_doc:literal])* $pub_field:ident : $pub_type:ident, )* } private { $($private:tt)* } } )*) => { fn generate_pyi(writer: &mut impl Write) { // REUSE-IgnoreStart writeln!(writer, "# Copyright © SixtyFPS GmbH ").unwrap(); writeln!(writer, "# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0").unwrap(); // REUSE-IgnoreEnd writeln!(writer, "").unwrap(); writeln!(writer, "import typing").unwrap(); $( generate_builtin_structs_pyi!(@check writer, $NameTy, $Name, [$($struct_doc),*], [$([$($pub_doc),*] $pub_field : $pub_type),*] ); )* } }; (@check $writer:expr, BuiltinPublicStruct, $Name:ident, [$($struct_doc:literal),*], [$([$($pub_doc:literal),*] $pub_field:ident : $pub_type:ident),*] ) => { writeln!($writer, "\nclass {}(typing.NamedTuple):", stringify!($Name)).unwrap(); let struct_doc = vec![$($struct_doc),*].join("\n").trim().to_string(); if !struct_doc.is_empty() { writeln!($writer, " \"\"\"").unwrap(); for line in struct_doc.lines() { if line.is_empty() { writeln!($writer).unwrap(); } else { writeln!($writer, " {}", line).unwrap(); } } writeln!($writer, " \"\"\"").unwrap(); } writeln!($writer, "").unwrap(); $( writeln!($writer, " {}: {} = {}", stringify!($pub_field), map_type(stringify!($pub_type)), map_default(stringify!($pub_type))).unwrap(); let field_doc = vec![$($pub_doc),*].join("\n").trim().to_string(); if !field_doc.is_empty() { writeln!($writer, " \"\"\"").unwrap(); for line in field_doc.lines() { if line.is_empty() { writeln!($writer).unwrap(); } else { writeln!($writer, " {}", line).unwrap(); } } writeln!($writer, " \"\"\"").unwrap(); } )* }; (@check $writer:expr, BuiltinPrivateStruct, $Name:ident, [$($struct_doc:literal),*], [$([$($pub_doc:literal),*] $pub_field:ident : $pub_type:ident),*] ) => {}; } i_slint_common::for_each_builtin_structs!(generate_builtin_structs_pyi); fn main() { let pyi_path = std::path::Path::new("slint/language.pyi"); if let Some(parent) = pyi_path.parent() { std::fs::create_dir_all(parent).expect("Failed to create slint/ directory"); } let file = File::create(pyi_path).expect("Failed to create language.pyi"); let mut writer = BufWriter::new(file); generate_pyi(&mut writer); } ================================================ FILE: api/python/slint/build_docs.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import slint import pdoc import pathlib import subprocess import typing doc = pdoc.doc.Module(slint) model_cls = typing.cast(pdoc.doc.Class, doc.get("Model")) assert model_cls is not None for method in model_cls.inherited_members[("builtins", "PyModelBase")]: method.is_inherited = False if not method.name.startswith("_") and method.name != "init_self": model_cls.own_members.append(method) all_modules: dict[str, pdoc.doc.Module] = {} def add_modules(m: pdoc.doc.Module): all_modules[m.fullname] = m for submod in m.submodules: add_modules(submod) add_modules(doc) output_directory = pathlib.Path("docs") for module in all_modules.values(): out = pdoc.render.html_module(module, all_modules) outfile = output_directory / f"{module.fullname.replace('.', '/')}.html" outfile.parent.mkdir(parents=True, exist_ok=True) outfile.write_bytes(out.encode()) index = pdoc.render.html_index(all_modules) (output_directory / "index.html").write_bytes(index.encode()) search = pdoc.render.search_index(all_modules) (output_directory / "search.js").write_bytes(search.encode()) subprocess.call( "cargo about generate thirdparty.hbs -o docs/thirdparty.html", shell=True ) ================================================ FILE: api/python/slint/errors.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use pyo3::PyErr; pub struct PyGetPropertyError(pub slint_interpreter::GetPropertyError); impl From for PyErr { fn from(err: PyGetPropertyError) -> Self { pyo3::exceptions::PyValueError::new_err(err.0.to_string()) } } impl From for PyGetPropertyError { fn from(err: slint_interpreter::GetPropertyError) -> Self { Self(err) } } pub struct PySetPropertyError(pub slint_interpreter::SetPropertyError); impl From for PyErr { fn from(err: PySetPropertyError) -> Self { pyo3::exceptions::PyValueError::new_err(err.0.to_string()) } } impl From for PySetPropertyError { fn from(err: slint_interpreter::SetPropertyError) -> Self { Self(err) } } pub struct PyPlatformError(pub slint_interpreter::PlatformError); impl From for PyErr { fn from(err: PyPlatformError) -> Self { pyo3::exceptions::PyRuntimeError::new_err(err.0.to_string()) } } impl From for PyPlatformError { fn from(err: slint_interpreter::PlatformError) -> Self { Self(err) } } pub struct PyEventLoopError(pub slint_interpreter::EventLoopError); impl From for PyErr { fn from(err: PyEventLoopError) -> Self { pyo3::exceptions::PyRuntimeError::new_err(err.0.to_string()) } } impl From for PyEventLoopError { fn from(err: slint_interpreter::EventLoopError) -> Self { Self(err) } } pub struct PyInvokeError(pub slint_interpreter::InvokeError); impl From for PyErr { fn from(err: PyInvokeError) -> Self { pyo3::exceptions::PyRuntimeError::new_err(err.0.to_string()) } } impl From for PyInvokeError { fn from(err: slint_interpreter::InvokeError) -> Self { Self(err) } } pub struct PySetCallbackError(pub slint_interpreter::SetCallbackError); impl From for PyErr { fn from(err: PySetCallbackError) -> Self { pyo3::exceptions::PyRuntimeError::new_err(err.0.to_string()) } } impl From for PySetCallbackError { fn from(err: slint_interpreter::SetCallbackError) -> Self { Self(err) } } pub struct PyLoadImageError(pub slint_interpreter::LoadImageError); impl From for PyErr { fn from(err: PyLoadImageError) -> Self { pyo3::exceptions::PyRuntimeError::new_err(err.0.to_string()) } } impl From for PyLoadImageError { fn from(err: slint_interpreter::LoadImageError) -> Self { Self(err) } } pub struct PyColorParseError(pub css_color_parser2::ColorParseError); impl From for PyErr { fn from(err: PyColorParseError) -> Self { pyo3::exceptions::PyRuntimeError::new_err(err.0.to_string()) } } impl From for PyColorParseError { fn from(err: css_color_parser2::ColorParseError) -> Self { Self(err) } } ================================================ FILE: api/python/slint/image.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use pyo3::prelude::*; use pyo3_stub_gen::{derive::gen_stub_pyclass, derive::gen_stub_pymethods}; use slint_interpreter::SharedPixelBuffer; /// Image objects can be set on Slint Image elements for display. Use `Image.load_from_path` to construct Image /// objects from a path to an image file on disk. #[gen_stub_pyclass] #[pyclass(unsendable, name = "Image")] pub struct PyImage { pub image: slint_interpreter::Image, } #[gen_stub_pymethods] #[pymethods] impl PyImage { #[new] fn py_new() -> PyResult { Ok(Self { image: Default::default() }) } /// The size of the image as tuple of `width` and `height`. #[getter] fn size(&self) -> PyResult<(u32, u32)> { Ok(self.image.size().into()) } /// The width of the image in pixels. #[getter] fn width(&self) -> PyResult { Ok(self.image.size().width) } /// The height of the image in pixels. #[getter] fn height(&self) -> PyResult { Ok(self.image.size().height) } /// The path of the image if it was loaded from disk, or None. #[getter] fn path(&self) -> PyResult> { Ok(self.image.path().map(|p| p.to_path_buf())) } /// Loads the image from the specified path. Returns None if the image can't be loaded. #[staticmethod] fn load_from_path(path: std::path::PathBuf) -> Result { let image = slint_interpreter::Image::load_from_path(&path)?; Ok(Self { image }) } /// Creates a new image from a string that describes the image in SVG format. #[staticmethod] fn load_from_svg_data(data: Vec) -> Result { let image = slint_interpreter::Image::load_from_svg_data(&data)?; Ok(Self { image }) } /// Creates a new image from an array-like object that implements the [Buffer Protocol](https://docs.python.org/3/c-api/buffer.html). /// Use this function to import images created by third-party modules such as matplotlib or Pillow. /// /// The array must satisfy certain contraints to represent an image: /// /// - The buffer's format needs to be `B` (unsigned char) /// - The shape must be a tuple of (height, width, bytes-per-pixel) /// - If a stride is defined, the row stride must be equal to width * bytes-per-pixel, and the column stride must equal the bytes-per-pixel. /// - A value of 3 for bytes-per-pixel is interpreted as RGB image, a value of 4 means RGBA. /// /// The image is created by performing a deep copy of the array's data. Subsequent changes to the buffer are not automatically /// reflected in a previously created Image. /// /// Example of importing a matplot figure into an image: /// ```python /// import slint /// import matplotlib /// /// from matplotlib.backends.backend_agg import FigureCanvasAgg /// from matplotlib.figure import Figure /// /// fig = Figure(figsize=(5, 4), dpi=100) /// canvas = FigureCanvasAgg(fig) /// ax = fig.add_subplot() /// ax.plot([1, 2, 3]) /// canvas.draw() /// /// buffer = canvas.buffer_rgba() /// img = slint.Image.load_from_array(buffer) /// ``` /// /// Example of loading an image with Pillow: /// ```python /// import slint /// from PIL import Image /// import numpy as np /// /// pil_img = Image.open("hello.jpeg") /// array = np.array(pil_img) /// img = slint.Image.load_from_array(array) /// ``` #[staticmethod] fn load_from_array(array: &Bound<'_, PyAny>) -> PyResult { let buffer: pyo3::buffer::PyBuffer = pyo3::buffer::PyBuffer::get(array)?; let shape = buffer.shape(); if shape.len() != 3 { return Err(pyo3::exceptions::PyRuntimeError::new_err( "Arrays must have a shape of (height, width, bpp) for image conversion", )); } let bpp: u32 = shape[2] .try_into() .map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("Image bpp exceeds u32"))?; let width = shape[1] .try_into() .map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("Image width exceeds u32"))?; let height = shape[0] .try_into() .map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("Image height exceeds u32"))?; if buffer.item_size() != 1 { return Err(pyo3::exceptions::PyRuntimeError::new_err(format!( "Item size {} is not valid. Arrays must contain bytes for image conversion", buffer.item_size(), ))); } if buffer.format() != c"B" { return Err(pyo3::exceptions::PyRuntimeError::new_err(format!( "Unexpected buffer format {}, expected 'B' for unsigned char", buffer.format().to_str().unwrap_or_default(), ))); } let strides = buffer.strides(); if strides.len() > 0 { if strides.len() != 3 { return Err(pyo3::exceptions::PyRuntimeError::new_err(format!( "Unexpected strides size {}. Arrays must provides stride tuple of 3 for image conversion", strides.len(), ))); } let row_stride: u32 = strides[0].try_into().map_err(|_| { pyo3::exceptions::PyRuntimeError::new_err("Image row stride cannot be negative") })?; let column_stride: u32 = strides[1].try_into().map_err(|_| { pyo3::exceptions::PyRuntimeError::new_err("Image column stride cannot be negative") })?; let elem_stride: u32 = strides[2].try_into().map_err(|_| { pyo3::exceptions::PyRuntimeError::new_err("Image element stride cannot be negative") })?; if row_stride != width * bpp { return Err(pyo3::exceptions::PyRuntimeError::new_err(format!( "Unexpected row stride {}. Expected {}", row_stride, height * bpp, ))); } if column_stride != bpp { return Err(pyo3::exceptions::PyRuntimeError::new_err(format!( "Unexpected column stride {}. Expected {}", column_stride, bpp, ))); } if elem_stride != 1 { return Err(pyo3::exceptions::PyRuntimeError::new_err(format!( "Unexpected element stride {}. Expected 1", column_stride, ))); } } Ok(Self { image: match bpp { 3 => { let mut pixel_buffer = SharedPixelBuffer::new(width, height); buffer.copy_to_slice(array.py(), pixel_buffer.make_mut_bytes())?; slint_interpreter::Image::from_rgb8(pixel_buffer) } 4 => { let mut pixel_buffer = SharedPixelBuffer::new(width, height); buffer.copy_to_slice(array.py(), pixel_buffer.make_mut_bytes())?; slint_interpreter::Image::from_rgba8(pixel_buffer) } _ => { return Err(pyo3::exceptions::PyRuntimeError::new_err(format!( "Unexpected bits per pixel {}. Expected 3 or 4", bpp, ))); } }, }) } } impl From for PyImage { fn from(image: slint_interpreter::Image) -> Self { Self { image: image } } } ================================================ FILE: api/python/slint/interpreter.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use std::cell::RefCell; use std::collections::HashMap; use std::path::PathBuf; use std::rc::Rc; use i_slint_compiler::generator::python::ident; use pyo3::IntoPyObjectExt; use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pyclass_enum, gen_stub_pymethods}; use slint_interpreter::{ComponentHandle, Value}; use i_slint_compiler::langtype::Type; use i_slint_compiler::parser::normalize_identifier; use indexmap::IndexMap; use pyo3::PyTraverseError; use pyo3::gc::PyVisit; use pyo3::prelude::*; use pyo3::types::PyTuple; use crate::api_match::PyGeneratedAPI; use crate::errors::{ PyGetPropertyError, PyInvokeError, PyPlatformError, PySetCallbackError, PySetPropertyError, }; use crate::value::{SlintToPyValue, TypeCollection}; #[gen_stub_pyclass] #[pyclass(unsendable)] pub struct Compiler { compiler: slint_interpreter::Compiler, } #[gen_stub_pymethods] #[pymethods] impl Compiler { #[new] fn py_new() -> PyResult { Ok(Self { compiler: Default::default() }) } #[getter] fn get_include_paths(&self) -> PyResult> { Ok(self.compiler.include_paths().clone()) } #[setter] fn set_include_paths(&mut self, paths: Vec) { self.compiler.set_include_paths(paths) } #[getter] fn get_style(&self) -> PyResult> { Ok(self.compiler.style().cloned()) } #[setter] fn set_style(&mut self, style: String) { self.compiler.set_style(style) } #[getter] fn get_library_paths(&self) -> PyResult> { Ok(self.compiler.library_paths().clone()) } #[setter] fn set_library_paths(&mut self, libraries: HashMap) { self.compiler.set_library_paths(libraries) } #[setter] fn set_translation_domain(&mut self, domain: String) { self.compiler.set_translation_domain(domain) } fn build_from_path(&mut self, py: Python<'_>, path: PathBuf) -> CompilationResult { let result = spin_on::spin_on(self.compiler.build_from_path(&path)); CompilationResult::new(result, path, py) } fn build_from_source( &mut self, py: Python<'_>, source_code: String, path: PathBuf, ) -> CompilationResult { let result = spin_on::spin_on(self.compiler.build_from_source(source_code, path.clone())); CompilationResult::new(result, path, py) } } #[derive(Debug, Clone)] #[gen_stub_pyclass] #[pyclass(unsendable, from_py_object)] pub struct PyDiagnostic(slint_interpreter::Diagnostic); #[gen_stub_pymethods] #[pymethods] impl PyDiagnostic { #[getter] fn level(&self) -> PyDiagnosticLevel { match self.0.level() { slint_interpreter::DiagnosticLevel::Error => PyDiagnosticLevel::Error, slint_interpreter::DiagnosticLevel::Warning => PyDiagnosticLevel::Warning, slint_interpreter::DiagnosticLevel::Note => PyDiagnosticLevel::Note, _ => unimplemented!(), } } #[getter] fn message(&self) -> &str { self.0.message() } #[getter] fn column_number(&self) -> usize { self.0.line_column().1 } #[getter] fn line_number(&self) -> usize { self.0.line_column().0 } #[getter] fn source_file(&self) -> Option { self.0.source_file().map(|path| path.to_path_buf()) } fn __str__(&self) -> String { self.0.to_string() } } #[gen_stub_pyclass_enum] #[pyclass(name = "DiagnosticLevel", eq, eq_int)] #[derive(PartialEq)] pub enum PyDiagnosticLevel { Error, Warning, Note, } #[gen_stub_pyclass] #[pyclass(unsendable)] pub struct CompilationResult { result: slint_interpreter::CompilationResult, type_collection: TypeCollection, path: PathBuf, } impl CompilationResult { fn new(result: slint_interpreter::CompilationResult, path: PathBuf, py: Python<'_>) -> Self { let type_collection = TypeCollection::new(&result, py); Self { result, type_collection, path } } } #[gen_stub_pymethods] #[pymethods] impl CompilationResult { #[getter] fn component_names(&self) -> Vec { self.result.component_names().map(ToString::to_string).collect() } fn component(&self, name: &str) -> Option { self.result.component(name).map(|definition| ComponentDefinition { definition, type_collection: self.type_collection.clone(), }) } #[getter] fn get_diagnostics(&self) -> Vec { self.result.diagnostics().map(|diag| PyDiagnostic(diag.clone())).collect() } #[getter] fn structs_and_enums<'py>( &self, py: Python<'py>, ) -> (HashMap>, HashMap>) { let mut structs = HashMap::new(); for struct_or_enum in self.result.structs_and_enums(i_slint_core::InternalToken {}) { match struct_or_enum { Type::Struct(s) if s.node().is_some() => { let struct_instance = self.type_collection.struct_to_py(slint_interpreter::Struct::from_iter( s.fields.iter().map(|(name, field_type)| { ( ident(&name).into(), slint_interpreter::default_value_for_type(field_type), ) }), )); structs.insert( ident(&s.name.slint_name().unwrap()).into(), struct_instance.into_bound_py_any(py).unwrap(), ); } _ => {} } } ( structs, self.type_collection .enums() .map(|(name, enum_cls)| (name.clone(), enum_cls.into_bound_py_any(py).unwrap())) .collect(), ) } #[getter] fn named_exports(&self) -> Vec<(String, String)> { self.result.named_exports(i_slint_core::InternalToken {}).cloned().collect::>() } #[getter] fn generated_api(&self) -> PyResult { let type_loader = self .result .components() .next() .ok_or_else(|| { pyo3::exceptions::PyRuntimeError::new_err( "Cannot generated API for empty slint file", ) })? .type_loader(); let doc = type_loader.get_document(&self.path).ok_or_else(|| { pyo3::exceptions::PyRuntimeError::new_err( "Failed to load document from cache for API generation", ) })?; i_slint_compiler::generator::python::generate_py_module( doc, &i_slint_compiler::CompilerConfiguration::new( i_slint_compiler::generator::OutputFormat::Python, ), ) .map_err(|e| { pyo3::exceptions::PyRuntimeError::new_err(format!("Error generating pymodule: {}", e)) }) .map(|module| PyGeneratedAPI { path: self.path.clone(), module }) } } #[gen_stub_pyclass] #[pyclass(unsendable)] pub struct ComponentDefinition { definition: slint_interpreter::ComponentDefinition, type_collection: TypeCollection, } #[pymethods] impl ComponentDefinition { #[getter] fn name(&self) -> &str { self.definition.name() } #[getter] fn properties(&self) -> IndexMap { self.definition .properties_and_callbacks() .filter_map(|(name, (ty, _))| ty.is_property_type().then(|| (name, ty.into()))) .collect() } #[getter] fn callbacks(&self) -> Vec { self.definition.callbacks().collect() } #[getter] fn functions(&self) -> Vec { self.definition.functions().collect() } #[getter] fn globals(&self) -> Vec { self.definition.globals().collect() } fn global_properties(&self, name: &str) -> Option> { self.definition.global_properties_and_callbacks(name).map(|propiter| { propiter .filter_map(|(name, (ty, _))| ty.is_property_type().then(|| (name, ty.into()))) .collect() }) } fn global_callbacks(&self, name: &str) -> Option> { self.definition.global_callbacks(name).map(|callbackiter| callbackiter.collect()) } fn global_functions(&self, name: &str) -> Option> { self.definition.global_functions(name).map(|functioniter| functioniter.collect()) } fn callback_returns_void(&self, callback_name: &str) -> Option { let callback_name = normalize_identifier(callback_name); self.definition.properties_and_callbacks().find_map(|(name, (ty, _))| { if normalize_identifier(&name) == callback_name { if let Type::Callback(signature) = ty { return Some(signature.return_type == Type::Void); } } None }) } fn global_callback_returns_void(&self, global_name: &str, callback_name: &str) -> Option { let global_name = normalize_identifier(global_name); let callback_name = normalize_identifier(callback_name); self.definition.global_properties_and_callbacks(&global_name).and_then(|mut props| { props.find_map(|(name, (ty, _))| { if normalize_identifier(&name) == callback_name { if let Type::Callback(signature) = ty { return Some(signature.return_type == Type::Void); } } None }) }) } fn create(&self) -> Result { Ok(ComponentInstance { instance: self.definition.create()?, callbacks: GcVisibleCallbacks { callables: Default::default(), type_collection: self.type_collection.clone(), }, global_callbacks: Default::default(), type_collection: self.type_collection.clone(), }) } } #[gen_stub_pyclass_enum] #[pyclass(name = "ValueType", eq, eq_int)] #[derive(PartialEq)] pub enum PyValueType { Void, Number, String, Bool, Model, Struct, Brush, Image, Enumeration, } impl From for PyValueType { fn from(ty: i_slint_compiler::langtype::Type) -> Self { use i_slint_compiler::langtype::Type; match ty { Type::Bool => PyValueType::Bool, Type::Void => PyValueType::Void, Type::Float32 | Type::Int32 | Type::Duration | Type::Angle | Type::PhysicalLength | Type::LogicalLength | Type::Percent | Type::Rem | Type::UnitProduct(_) => PyValueType::Number, Type::String => PyValueType::String, Type::Array(..) => PyValueType::Model, Type::Struct { .. } => PyValueType::Struct, Type::Brush => PyValueType::Brush, Type::Color => PyValueType::Brush, Type::Image => PyValueType::Image, Type::Enumeration(..) => PyValueType::Enumeration, Type::Keys => PyValueType::String, _ => unimplemented!(), } } } #[gen_stub_pyclass] #[pyclass(unsendable, weakref)] pub struct ComponentInstance { instance: slint_interpreter::ComponentInstance, callbacks: GcVisibleCallbacks, global_callbacks: HashMap, type_collection: TypeCollection, } #[pymethods] impl ComponentInstance { #[getter] fn definition(&self) -> ComponentDefinition { ComponentDefinition { definition: self.instance.definition(), type_collection: self.type_collection.clone(), } } fn get_property(&self, name: &str) -> Result { Ok(self.type_collection.to_py_value(self.instance.get_property(name)?)) } fn set_property(&self, name: &str, value: Bound<'_, PyAny>) -> PyResult<()> { let pv = TypeCollection::slint_value_from_py_value_bound(&value, Some(&self.type_collection))?; Ok(self.instance.set_property(name, pv).map_err(|e| PySetPropertyError(e))?) } fn get_global_property( &self, global_name: &str, prop_name: &str, ) -> Result { Ok(self .type_collection .to_py_value(self.instance.get_global_property(global_name, prop_name)?)) } fn set_global_property( &self, global_name: &str, prop_name: &str, value: Bound<'_, PyAny>, ) -> PyResult<()> { let pv = TypeCollection::slint_value_from_py_value_bound(&value, Some(&self.type_collection))?; Ok(self .instance .set_global_property(global_name, prop_name, pv) .map_err(|e| PySetPropertyError(e))?) } #[pyo3(signature = (callback_name, *args))] fn invoke(&self, callback_name: &str, args: Bound<'_, PyTuple>) -> PyResult { let mut rust_args = Vec::new(); for arg in args.iter() { let pv = TypeCollection::slint_value_from_py_value_bound(&arg, Some(&self.type_collection))?; rust_args.push(pv) } Ok(self.type_collection.to_py_value( self.instance.invoke(callback_name, &rust_args).map_err(|e| PyInvokeError(e))?, )) } #[pyo3(signature = (global_name, callback_name, *args))] fn invoke_global( &self, global_name: &str, callback_name: &str, args: Bound<'_, PyTuple>, ) -> PyResult { let mut rust_args = Vec::new(); for arg in args.iter() { let pv = TypeCollection::slint_value_from_py_value_bound(&arg, Some(&self.type_collection))?; rust_args.push(pv) } Ok(self.type_collection.to_py_value( self.instance .invoke_global(global_name, callback_name, &rust_args) .map_err(|e| PyInvokeError(e))?, )) } fn set_callback(&self, name: &str, callable: Py) -> Result<(), PySetCallbackError> { let rust_cb = self.callbacks.register(name.to_string(), callable); Ok(self.instance.set_callback(name, rust_cb)?.into()) } fn set_global_callback( &mut self, global_name: &str, callback_name: &str, callable: Py, ) -> Result<(), PySetCallbackError> { let rust_cb = self .global_callbacks .entry(global_name.to_string()) .or_insert_with(|| GcVisibleCallbacks { callables: Default::default(), type_collection: self.type_collection.clone(), }) .register(callback_name.to_string(), callable); Ok(self.instance.set_global_callback(global_name, callback_name, rust_cb)?.into()) } fn show(&self) -> Result<(), PyPlatformError> { Ok(self.instance.show()?) } fn hide(&self) -> Result<(), PyPlatformError> { Ok(self.instance.hide()?) } fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> { self.callbacks.__traverse__(&visit)?; for global_callbacks in self.global_callbacks.values() { global_callbacks.__traverse__(&visit)?; } for value in self.properties_for_gc() { crate::value::traverse_value(&value, &visit)?; } Ok(()) } fn __clear__(&mut self) { self.callbacks.__clear__(); self.global_callbacks.clear(); for value in self.properties_for_gc() { crate::value::clear_strongrefs_in_value(&value) } } } impl ComponentInstance { fn properties_for_gc(&self) -> Vec { let mut props = Vec::new(); props.extend( self.instance .definition() .properties_and_callbacks() .filter_map(|(name, (ty, _))| ty.is_property_type().then(|| name)) .filter_map(|prop_name| self.instance.get_property(&prop_name).ok()), ); for global_name in self.instance.definition().globals() { if let Some(prop_iter) = self.instance.definition().global_properties_and_callbacks(&global_name) { props.extend( prop_iter .filter_map(|(name, (ty, _))| ty.is_property_type().then(|| name)) .filter_map(|prop_name| { self.instance.get_global_property(&global_name, &prop_name).ok() }), ); } } props } } struct GcVisibleCallbacks { callables: Rc>>>, type_collection: TypeCollection, } impl GcVisibleCallbacks { fn register(&self, name: String, callable: Py) -> impl Fn(&[Value]) -> Value + 'static { self.callables.borrow_mut().insert(name.clone(), callable); let callables = self.callables.clone(); let type_collection = self.type_collection.clone(); move |args| { let callables = callables.borrow(); let callable = callables.get(&name).unwrap(); Python::attach(|py| { let py_args = PyTuple::new(py, args.iter().map(|v| type_collection.to_py_value(v.clone()))) .unwrap(); let result = match callable.call(py, py_args, None) { Ok(result) => result, Err(err) => { crate::handle_unraisable( py, format!( "Python: Invoking python callback for {name} threw an exception" ), err, ); return Value::Void; } }; let pv = match TypeCollection::slint_value_from_py_value( py, &result, Some(&type_collection), ) { Ok(value) => value, Err(err) => { crate::handle_unraisable( py, format!( "Python: Unable to convert return value of Python callback for {name} to Slint value" ), err, ); return Value::Void; } }; pv }) } } fn __traverse__(&self, visit: &PyVisit<'_>) -> Result<(), PyTraverseError> { for callback in self.callables.borrow().values() { visit.call(callback)?; } Ok(()) } fn __clear__(&mut self) { self.callables.borrow_mut().clear(); } } ================================================ FILE: api/python/slint/language.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 //! This module generates Python `typing.NamedTuple` bindings for public Slint //! structs using the `for_each_builtin_structs` macro, reusing documentation //! from `builtin_structs.rs`. //! //! The pattern follows `cbindgen.rs`: a macro consumes `for_each_builtin_structs`, //! matches on `BuiltinPublicStruct` variants, and generates NamedTuple classes //! with the original doc comments. Private structs are skipped. use pyo3::IntoPyObjectExt; use pyo3::prelude::*; use pyo3::types::{PyDict, PyTuple}; fn get_default_value<'py>(py: Python<'py>, ty: &str) -> PyResult> { match ty { "bool" => false.into_bound_py_any(py), "SharedString" => "".into_bound_py_any(py), "i32" => 0_i32.into_bound_py_any(py), "f32" | "Coord" => 0.0_f32.into_bound_py_any(py), _ => Ok(py.None().into_bound(py)), } } /// Dynamically creates a Python `typing.NamedTuple` class and registers it /// in the `slint.language` submodule. fn register_named_tuple( py: Python<'_>, m: &Bound<'_, PyModule>, class_name: &str, class_doc: &str, fields: &[(&str, &str, String)], // name, rust_type, doc ) -> PyResult<()> { let collections = py.import("collections")?; let namedtuple = collections.getattr("namedtuple")?; let mut field_names = Vec::new(); let mut defaults = Vec::new(); let mut full_doc = class_doc.to_string(); for (name, rust_ty, doc) in fields { field_names.push(*name); defaults.push(get_default_value(py, rust_ty)?); if !doc.is_empty() { use std::fmt::Write; let _ = write!(full_doc, "\n\n:param {name}: {doc}"); } } let kwargs = PyDict::new(py); kwargs.set_item("defaults", PyTuple::new(py, defaults)?)?; kwargs.set_item("module", "slint.language")?; let class = namedtuple.call((class_name, field_names), Some(&kwargs))?; class.setattr("__doc__", full_doc)?; // Register the class in the "slint.language" submodule. // The submodule is created lazily on the first call and reused for subsequent structs. let language_mod = match m.getattr("language") { Ok(existing) => existing.cast_into::()?, Err(_) => { let sub = PyModule::new(py, "slint.language")?; m.add("language", &sub)?; // Register in sys.modules so "from slint.language import ..." works let sys = py.import("sys")?; let modules = sys.getattr("modules")?; modules.set_item("slint.language", &sub)?; sub } }; language_mod.add(class_name, class)?; Ok(()) } /// This macro processes `for_each_builtin_structs` and generates a single `register_all` /// function that registers all public structs as NamedTuples in the `slint.language` submodule. macro_rules! declare_python_public_structs { // Top-level arm: matches the full list of struct definitions emitted by // `for_each_builtin_structs!`. For each struct, it delegates to the // `@register` arm which decides whether to register or skip it based // on whether it's a BuiltinPublicStruct or BuiltinPrivateStruct. ($( $(#[doc = $struct_doc:literal])* $(#[non_exhaustive])? $(#[derive(Copy, Eq)])? struct $Name:ident { @name = $NameTy:ident :: $NameVariant:ident, export { $( $(#[doc = $pub_doc:literal])* $pub_field:ident : $pub_type:ident, )* } private { $( $(#[doc = $pri_doc:literal])* $pri_field:ident : $pri_type:ty, )* } } )*) => { /// Registers all public builtin structs as NamedTuples in `slint.language`. /// Called once during module initialization from `lib.rs`. pub fn register_all(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { $( declare_python_public_structs!(@register $NameTy, $Name, py, m; docs: [$(#[doc = $struct_doc])*], fields: [$( $(#[doc = $pub_doc])* $pub_field : $pub_type ,)*], ); )* Ok(()) } }; // Public struct arm: collects doc comments and field metadata, then calls // `register_named_tuple` to create and register the NamedTuple class. (@register BuiltinPublicStruct, $Name:ident, $py:ident, $m:ident; docs: [$(#[doc = $struct_doc:literal])*], fields: [$( $(#[doc = $field_doc:literal])* $pub_field:ident : $pub_type:ident ,)*], ) => { { let class_doc = [ $($struct_doc),* ].join("\n"); let fields = vec![ $( (stringify!($pub_field), stringify!($pub_type), [ $($field_doc),* ].join("\n")), )* ]; register_named_tuple($py, $m, stringify!($Name), &class_doc, &fields)?; } }; // Private struct arm: intentionally empty — private structs are not exposed to Python. (@register BuiltinPrivateStruct, $_Name:ident, $py:ident, $m:ident; docs: [$(#[$struct_meta:meta])*], fields: [$( $(#[$field_meta:meta])* $pub_field:ident : $pub_type:ty ,)*], ) => {}; } i_slint_common::for_each_builtin_structs!(declare_python_public_structs); ================================================ FILE: api/python/slint/lib.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use std::cell::{Cell, RefCell}; use pyo3_stub_gen::{define_stub_info_gatherer, derive::gen_stub_pyfunction}; mod image; mod interpreter; mod language; use interpreter::{ CompilationResult, Compiler, ComponentDefinition, ComponentInstance, PyDiagnostic, PyDiagnosticLevel, PyValueType, }; mod api_match; mod async_adapter; mod brush; mod errors; mod models; mod timer; mod value; use i_slint_core::translations::Translator; fn handle_unraisable(py: Python<'_>, context: String, err: PyErr) { let exception = err.value(py); let __notes__ = exception .getattr(pyo3::intern!(py, "__notes__")) .unwrap_or_else(|_| pyo3::types::PyList::empty(py).into_any()); if let Ok(notes_list) = __notes__.cast::() { let _ = notes_list.append(context); let _ = exception.setattr(pyo3::intern!(py, "__notes__"), __notes__); } if EVENT_LOOP_RUNNING.get() && err.is_instance_of::(py) { EVENT_LOOP_EXCEPTION.replace(Some(err)); let _ = slint_interpreter::quit_event_loop(); } else { err.write_unraisable(py, None); } } thread_local! { static EVENT_LOOP_RUNNING: Cell = Cell::new(false); static EVENT_LOOP_EXCEPTION: RefCell> = RefCell::new(None) } #[gen_stub_pyfunction] #[pyfunction] fn run_event_loop(py: Python<'_>) -> Result<(), PyErr> { EVENT_LOOP_EXCEPTION.replace(None); EVENT_LOOP_RUNNING.set(true); // Release the GIL while running the event loop, so that other Python threads can run. let result = py.detach(|| slint_interpreter::run_event_loop()); EVENT_LOOP_RUNNING.set(false); result.map_err(|e| errors::PyPlatformError::from(e))?; EVENT_LOOP_EXCEPTION.take().map_or(Ok(()), |err| Err(err)) } #[gen_stub_pyfunction] #[pyfunction] fn quit_event_loop() -> Result<(), errors::PyEventLoopError> { slint_interpreter::quit_event_loop().map_err(|e| e.into()) } #[gen_stub_pyfunction] #[pyfunction] fn set_xdg_app_id(app_id: String) -> Result<(), errors::PyPlatformError> { slint_interpreter::set_xdg_app_id(app_id).map_err(|e| e.into()) } #[gen_stub_pyfunction] #[pyfunction] fn invoke_from_event_loop(callable: Py) -> Result<(), errors::PyEventLoopError> { slint_interpreter::invoke_from_event_loop(move || { Python::attach(|py| { if let Err(err) = callable.call0(py) { eprintln!("Error invoking python callable from closure invoked via slint::invoke_from_event_loop: {}", err) } }) }) .map_err(|e| e.into()) } #[gen_stub_pyfunction] #[pyfunction] fn init_translations(_py: Python<'_>, translations: Bound) -> PyResult<()> { i_slint_backend_selector::with_global_context(|ctx| { ctx.set_external_translator(if translations.is_none() { None } else { Some(Box::new(PyGettextTranslator(translations.unbind()))) }); }) .map_err(|e| errors::PyPlatformError(e))?; Ok(()) } struct PyGettextTranslator( /// A reference to a `gettext.GNUTranslations` object. Py, ); impl Translator for PyGettextTranslator { fn translate<'a>( &'a self, string: &'a str, context: Option<&'a str>, ) -> std::borrow::Cow<'a, str> { Python::try_attach(|py| { match if let Some(context) = context { self.0.call_method(py, pyo3::intern!(py, "pgettext"), (context, string), None) } else { self.0.call_method(py, pyo3::intern!(py, "gettext"), (string,), None) } { Ok(translation) => Some(translation), Err(err) => { handle_unraisable(py, "calling pgettext/gettext".into(), err); None } } .and_then(|maybe_str| maybe_str.extract::(py).ok()) .map(std::borrow::Cow::Owned) }) .flatten() .unwrap_or(std::borrow::Cow::Borrowed(string)) .into() } fn ntranslate<'a>( &'a self, n: u64, singular: &'a str, plural: &'a str, context: Option<&'a str>, ) -> std::borrow::Cow<'a, str> { Python::try_attach(|py| { match if let Some(context) = context { self.0.call_method( py, pyo3::intern!(py, "npgettext"), (context, singular, plural, n), None, ) } else { self.0.call_method(py, pyo3::intern!(py, "ngettext"), (singular, plural, n), None) } { Ok(translation) => Some(translation), Err(err) => { handle_unraisable(py, "calling npgettext/ngettext".into(), err); None } } .and_then(|maybe_str| maybe_str.extract::(py).ok()) .map(std::borrow::Cow::Owned) }) .flatten() .unwrap_or(std::borrow::Cow::Borrowed(singular)) .into() } } use pyo3::prelude::*; #[pymodule] fn slint(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { i_slint_backend_selector::with_platform(|_b| { // Nothing to do, just make sure a backend was created Ok(()) }) .map_err(|e| errors::PyPlatformError(e))?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_function(wrap_pyfunction!(run_event_loop, m)?)?; m.add_function(wrap_pyfunction!(quit_event_loop, m)?)?; m.add_function(wrap_pyfunction!(set_xdg_app_id, m)?)?; m.add_function(wrap_pyfunction!(invoke_from_event_loop, m)?)?; m.add_function(wrap_pyfunction!(init_translations, m)?)?; language::register_all(m.py(), m)?; Ok(()) } define_stub_info_gatherer!(stub_info); ================================================ FILE: api/python/slint/models.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use std::cell::RefCell; use std::rc::Rc; use i_slint_core::model::{Model, ModelNotify, ModelRc}; use pyo3::PyTraverseError; use pyo3::exceptions::PyIndexError; use pyo3::gc::PyVisit; use pyo3::prelude::*; use crate::value::{SlintToPyValue, TypeCollection}; pub struct PyModelShared { notify: ModelNotify, self_ref: RefCell>>, /// The type collection is needed when calling a Python implementation of set_row_data and /// the model data provided (for example from within a .slint file) contains an enum. Then /// we need to know how to map it to the correct Python enum. This field is lazily set, whenever /// time the Python model is exposed to Slint. type_collection: RefCell>, } impl PyModelShared { pub fn apply_type_collection(&self, type_collection: &TypeCollection) { if let Ok(mut type_collection_ref) = self.type_collection.try_borrow_mut() { *type_collection_ref = Some(type_collection.clone()); } } pub fn __traverse__(&self, visit: &PyVisit<'_>) -> Result<(), PyTraverseError> { if let Some(this) = self.self_ref.borrow().as_ref() { visit.call(this)?; } Ok(()) } pub fn __clear__(&self) { *self.self_ref.borrow_mut() = None; } } #[derive(Clone)] #[pyclass(unsendable, weakref, subclass, skip_from_py_object)] pub struct PyModelBase { inner: Rc, } impl PyModelBase { pub fn as_model(&self) -> ModelRc { self.inner.clone().into() } } #[pymethods] impl PyModelBase { #[new] fn new() -> Self { Self { inner: Rc::new(PyModelShared { notify: Default::default(), self_ref: RefCell::new(None), type_collection: RefCell::new(None), }), } } fn init_self(&self, self_ref: Py) { *self.inner.self_ref.borrow_mut() = Some(self_ref); } fn notify_row_added(&self, index: usize, count: usize) { self.inner.notify.row_added(index, count) } fn notify_row_changed(&self, index: usize) { self.inner.notify.row_changed(index) } fn notify_row_removed(&self, index: usize, count: usize) { self.inner.notify.row_removed(index, count) } fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> { self.inner.__traverse__(&visit) } fn __clear__(&mut self) { self.inner.__clear__(); } } impl i_slint_core::model::Model for PyModelShared { type Data = slint_interpreter::Value; fn row_count(&self) -> usize { Python::try_attach(|py| { let obj = self.self_ref.borrow(); let Some(obj) = obj.as_ref() else { eprintln!("Python: Model implementation is lacking self object (in row_count)"); return 0; }; let result = match obj.call_method0(py, "row_count") { Ok(result) => result, Err(err) => { crate::handle_unraisable( py, "Python: Model implementation of row_count() threw an exception".into(), err, ); return 0; } }; match result.extract::(py) { Ok(count) => count, Err(err) => { crate::handle_unraisable( py, "Python: Model implementation of row_count() returned value that cannot be cast to usize".into(), err, ); 0 } } }).unwrap_or_default() } fn row_data(&self, row: usize) -> Option { Python::try_attach(|py| { let obj = self.self_ref.borrow(); let Some(obj) = obj.as_ref() else { eprintln!("Python: Model implementation is lacking self object (in row_data)"); return None; }; let result = match obj.call_method1(py, "row_data", (row,)) { Ok(result) => result, Err(err) if err.is_instance_of::(py) => return None, Err(err) => { crate::handle_unraisable( py, "Python: Model implementation of row_data() threw an exception".into(), err, ); return None; } }; match TypeCollection::slint_value_from_py_value( py, &result, self.type_collection.borrow().as_ref(), ) { Ok(pv) => Some(pv), Err(err) => { crate::handle_unraisable( py, "Python: Model implementation of row_data() returned value that cannot be cast to usize".into(), err, ); None } } }).flatten() } fn set_row_data(&self, row: usize, data: Self::Data) { Python::try_attach(|py| { let obj = self.self_ref.borrow(); let Some(obj) = obj.as_ref() else { eprintln!("Python: Model implementation is lacking self object (in set_row_data)"); return; }; let Some(type_collection) = self.type_collection.borrow().as_ref().cloned() else { eprintln!( "Python: Model implementation is lacking type collection (in set_row_data)" ); return; }; if let Err(err) = obj.call_method1(py, "set_row_data", (row, type_collection.to_py_value(data))) { crate::handle_unraisable( py, "Python: Model implementation of set_row_data() threw an exception".into(), err, ); }; }); } fn model_tracker(&self) -> &dyn i_slint_core::model::ModelTracker { &self.notify } fn as_any(&self) -> &dyn core::any::Any { self } } impl PyModelShared { pub fn rust_into_py_model<'py>( model: &ModelRc, py: Python<'py>, ) -> Option> { model.as_any().downcast_ref::().and_then(|rust_model| { rust_model.self_ref.borrow().as_ref().map(|obj| obj.clone_ref(py).into_bound(py)) }) } } #[pyclass(unsendable)] pub struct ReadOnlyRustModel { pub model: ModelRc, pub type_collection: TypeCollection, } #[pymethods] impl ReadOnlyRustModel { fn row_count(&self) -> usize { self.model.row_count() } fn row_data(&self, row: usize) -> Option { self.model.row_data(row).map(|value| self.type_collection.to_py_value(value)) } fn __len__(&self) -> usize { self.row_count() } fn __iter__(slf: PyRef<'_, Self>) -> ReadOnlyRustModelIterator { ReadOnlyRustModelIterator { model: slf.model.clone(), row: 0, type_collection: slf.type_collection.clone(), } } fn __getitem__(&self, index: usize) -> Option { self.row_data(index) } } #[pyclass(unsendable)] struct ReadOnlyRustModelIterator { model: ModelRc, row: usize, type_collection: TypeCollection, } #[pymethods] impl ReadOnlyRustModelIterator { fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } fn __next__(&mut self) -> Option { if self.row >= self.model.row_count() { return None; } let row = self.row; self.row += 1; self.model.row_data(row).map(|value| self.type_collection.to_py_value(value)) } } ================================================ FILE: api/python/slint/pyproject.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "slint" version = "1.16.0b1" requires-python = ">= 3.12" authors = [{ name = "Slint Team", email = "info@slint.dev" }] classifiers = [ "Development Status :: 3 - Alpha", "Environment :: MacOS X", "Environment :: X11 Applications", "Environment :: Win32 (MS Windows)", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Rust", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "License :: Other/Proprietary License", "Topic :: Software Development", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: User Interfaces", "Topic :: Software Development :: Widget Sets", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", ] description = "GUI toolkit to efficiently develop fluid graphical user interfaces for embedded devices and desktop applications" readme = "README.md" [project.urls] Homepage = "https://slint.dev" Documentation = "https://slint.dev/docs" Repository = "https://github.com/slint-ui/slint" Changelog = "https://github.com/slint-ui/slint/blob/master/CHANGELOG.md" Tracker = "https://github.com/slint-ui/slint/issues" [project.optional-dependencies] dev = ["pytest", "numpy>=2.3.2", "pillow>=11.3.0", "aiohttp>=3.12.15"] [dependency-groups] dev = ["pdoc>=15.0.1", "pytest>=8.3.4", "ruff>=0.9.6", "pillow>=11.3.0", "numpy>=2.3.2", "aiohttp>=3.12.15"] [tool.uv] # Rebuild package when any rust files change cache-keys = [{ file = "pyproject.toml" }, { file = "Cargo.toml" }, { file = "**/*.rs" }] # Uncomment to build rust code in development mode # config-settings = { build-args = '--profile=dev' } [tool.maturin] editable-profile = "dev" # added so it includes in wheel build to overrite the default behavior of not including untracked files include = ["slint/language.pyi"] ================================================ FILE: api/python/slint/slint/__init__.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 r""" .. include:: ../README.md """ import os import sys from . import slint as native from . import language import types import logging import copy import typing from typing import Any import pathlib from .models import ListModel, Model from .slint import Image, Color, Brush, Timer, TimerMode from .loop import SlintEventLoop from pathlib import Path from collections.abc import Coroutine import asyncio import gettext import gzip import base64 Struct = native.PyStruct class CompileError(Exception): message: str """The error message that produced this compile error.""" diagnostics: list[native.PyDiagnostic] """A list of detailed diagnostics that were produced as part of the compilation.""" def __init__(self, message: str, diagnostics: list[native.PyDiagnostic]): """@private""" super().__init__(message) self.message = message self.diagnostics = diagnostics for diag in self.diagnostics: self.add_note(str(diag)) class Component: """Component is the base class for all instances of Slint components. Use the member functions to show or hide the window, or spin the event loop.""" __instance__: native.ComponentInstance def show(self) -> None: """Shows the window on the screen.""" self.__instance__.show() def hide(self) -> None: """Hides the window from the screen.""" self.__instance__.hide() def run(self) -> None: """Shows the window, runs the event loop, hides it when the loop is quit, and returns.""" self.show() run_event_loop() self.hide() def _normalize_prop(name: str) -> str: return name.replace("-", "_") def _build_global_class(compdef: native.ComponentDefinition, global_name: str) -> Any: properties_and_callbacks = {} for prop_name in compdef.global_properties(global_name).keys(): python_prop = _normalize_prop(prop_name) if python_prop in properties_and_callbacks: logging.warning(f"Duplicated property {prop_name}") continue def mk_setter_getter(prop_or_callback_name: str) -> property: def getter(self: Component) -> Any: return self.__instance__.get_global_property( global_name, prop_or_callback_name ) def setter(self: Component, value: Any) -> None: self.__instance__.set_global_property( global_name, prop_or_callback_name, value ) return property(getter, setter) properties_and_callbacks[python_prop] = mk_setter_getter(prop_name) for callback_name in compdef.global_callbacks(global_name): python_prop = _normalize_prop(callback_name) if python_prop in properties_and_callbacks: logging.warning(f"Duplicated property {prop_name}") continue def mk_setter_getter(prop_or_callback_name: str) -> property: def getter(self: Component) -> typing.Callable[..., Any]: def call(*args: Any) -> Any: return self.__instance__.invoke_global( global_name, prop_or_callback_name, *args ) return call def setter(self: Component, value: typing.Callable[..., Any]) -> None: self.__instance__.set_global_callback( global_name, prop_or_callback_name, value ) return property(getter, setter) properties_and_callbacks[python_prop] = mk_setter_getter(callback_name) for function_name in compdef.global_functions(global_name): python_prop = _normalize_prop(function_name) if python_prop in properties_and_callbacks: logging.warning(f"Duplicated function {prop_name}") continue def mk_getter(function_name: str) -> property: def getter(self: Component) -> typing.Callable[..., Any]: def call(*args: Any) -> Any: return self.__instance__.invoke_global( global_name, function_name, *args ) return call return property(getter) properties_and_callbacks[python_prop] = mk_getter(function_name) return type("SlintGlobalClassWrapper", (), properties_and_callbacks) def _build_class( compdef: native.ComponentDefinition, ) -> typing.Callable[..., Component]: def cls_init(self: Component, **kwargs: Any) -> Any: self.__instance__ = compdef.create() for name, value in self.__class__.__dict__.items(): if hasattr(value, "slint.callback"): callback_info = getattr(value, "slint.callback") name = callback_info["name"] is_async = getattr(value, "slint.async", False) if is_async: if "global_name" in callback_info: global_name = callback_info["global_name"] is_void = compdef.global_callback_returns_void( global_name, name ) if is_void is None: raise AttributeError( f"Callback '{name}' in global '{global_name}' cannot be used with a callback decorator for an async function, as it is not declared in Slint component" ) if not is_void: raise RuntimeError( f"Callback '{name}' in global '{global_name}' cannot be used with a callback decorator for an async function, as it doesn't return void" ) else: is_void = compdef.callback_returns_void(name) if is_void is None: raise AttributeError( f"Callback '{name}' cannot be used with a callback decorator for an async function, as it is not declared in Slint component" ) if not is_void: raise RuntimeError( f"Callback '{name}' cannot be used with a callback decorator for an async function, as it doesn't return void" ) def mk_callback( self: Any, callback: typing.Callable[..., Any] ) -> typing.Callable[..., Any]: def invoke(*args: Any, **kwargs: Any) -> Any: return callback(self, *args, **kwargs) return invoke if "global_name" in callback_info: self.__instance__.set_global_callback( callback_info["global_name"], name, mk_callback(self, value) ) else: self.__instance__.set_callback(name, mk_callback(self, value)) for prop, val in kwargs.items(): setattr(self, prop, val) properties_and_callbacks: dict[Any, Any] = {"__init__": cls_init} for prop_name in compdef.properties.keys(): python_prop = _normalize_prop(prop_name) if python_prop in properties_and_callbacks: logging.warning(f"Duplicated property {prop_name}") continue def mk_setter_getter(prop_or_callback_name: str) -> property: def getter(self: Component) -> Any: return self.__instance__.get_property(prop_or_callback_name) def setter(self: Component, value: Any) -> None: self.__instance__.set_property(prop_or_callback_name, value) return property(getter, setter) properties_and_callbacks[python_prop] = mk_setter_getter(prop_name) for callback_name in compdef.callbacks: python_prop = _normalize_prop(callback_name) if python_prop in properties_and_callbacks: logging.warning(f"Duplicated property {prop_name}") continue def mk_setter_getter(prop_or_callback_name: str) -> property: def getter(self: Component) -> typing.Callable[..., Any]: def call(*args: Any) -> Any: return self.__instance__.invoke(prop_or_callback_name, *args) return call def setter(self: Component, value: typing.Callable[..., Any]) -> None: self.__instance__.set_callback(prop_or_callback_name, value) return property(getter, setter) properties_and_callbacks[python_prop] = mk_setter_getter(callback_name) for function_name in compdef.functions: python_prop = _normalize_prop(function_name) if python_prop in properties_and_callbacks: logging.warning(f"Duplicated function {prop_name}") continue def mk_getter(function_name: str) -> property: def getter(self: Component) -> typing.Callable[..., Any]: def call(*args: Any) -> Any: return self.__instance__.invoke(function_name, *args) return call return property(getter) properties_and_callbacks[python_prop] = mk_getter(function_name) for global_name in compdef.globals: global_class = _build_global_class(compdef, global_name) def mk_global(global_class: typing.Callable[..., Any]) -> property: def global_getter(self: Component) -> Any: wrapper = global_class() setattr(wrapper, "__instance__", self.__instance__) return wrapper return property(global_getter) properties_and_callbacks[global_name] = mk_global(global_class) return type("SlintClassWrapper", (Component,), properties_and_callbacks) def _build_struct(name: str, struct_prototype: native.PyStruct) -> type: def new_struct(cls: Any, *args: Any, **kwargs: Any) -> native.PyStruct: inst = copy.copy(struct_prototype) for prop, val in kwargs.items(): setattr(inst, prop, val) return inst type_dict = { "__new__": new_struct, } return type(name, (), type_dict) def _load_file( path: str | os.PathLike[Any] | pathlib.Path, quiet: bool = False, style: typing.Optional[str] = None, include_paths: typing.Optional[typing.List[os.PathLike[Any] | pathlib.Path]] = None, library_paths: typing.Optional[ typing.Dict[str, os.PathLike[Any] | pathlib.Path] ] = None, translation_domain: typing.Optional[str] = None, ) -> typing.Tuple[types.SimpleNamespace, native.CompilationResult]: """This function is the low-level entry point into Slint for instantiating components. It loads the `.slint` file at the specified `path` and returns a namespace with all exported components as Python classes, as well as enums, and structs. * `quiet`: Set to true to prevent any warnings during compilation from being printed to stderr. * `style`: Specify a widget style. * `include_paths`: Additional include paths used to look up `.slint` files imported from other `.slint` files. * `library_paths`: A dictionary that maps library names to their location in the file system. This is then used to look up library imports, such as `import { MyButton } from "@mylibrary";`. * `translation_domain`: The domain to use for looking up the catalogue run-time translations. This must match the translation domain used when extracting translations with `slint-tr-extractor`. """ compiler = native.Compiler() if style is not None: compiler.style = style if include_paths is not None: compiler.include_paths = include_paths if library_paths is not None: compiler.library_paths = library_paths if translation_domain is not None: compiler.translation_domain = translation_domain result = compiler.build_from_path(Path(path)) diagnostics = result.diagnostics if diagnostics: if not quiet: for diag in diagnostics: if diag.level == native.DiagnosticLevel.Warning: logging.warning(diag) if diag.level == native.DiagnosticLevel.Note: logging.debug(diag) errors = [ diag for diag in diagnostics if diag.level == native.DiagnosticLevel.Error ] if errors: raise CompileError(f"Could not compile {path}", diagnostics) module = types.SimpleNamespace() for comp_name in result.component_names: wrapper_class = _build_class(result.component(comp_name)) setattr(module, comp_name, wrapper_class) structs, enums = result.structs_and_enums for name, struct_prototype in structs.items(): name = _normalize_prop(name) struct_wrapper = _build_struct(name, struct_prototype) setattr(module, name, struct_wrapper) for name, enum_class in enums.items(): name = _normalize_prop(name) setattr(module, name, enum_class) for orig_name, new_name in result.named_exports: orig_name = _normalize_prop(orig_name) new_name = _normalize_prop(new_name) setattr(module, new_name, getattr(module, orig_name)) return (module, result) def load_file( path: str | os.PathLike[Any] | pathlib.Path, quiet: bool = False, style: typing.Optional[str] = None, include_paths: typing.Optional[typing.List[os.PathLike[Any] | pathlib.Path]] = None, library_paths: typing.Optional[ typing.Dict[str, os.PathLike[Any] | pathlib.Path] ] = None, translation_domain: typing.Optional[str] = None, ) -> types.SimpleNamespace: """This function is the low-level entry point into Slint for instantiating components. It loads the `.slint` file at the specified `path` and returns a namespace with all exported components as Python classes, as well as enums, and structs. * `quiet`: Set to true to prevent any warnings during compilation from being printed to stderr. * `style`: Specify a widget style. * `include_paths`: Additional include paths used to look up `.slint` files imported from other `.slint` files. * `library_paths`: A dictionary that maps library names to their location in the file system. This is then used to look up library imports, such as `import { MyButton } from "@mylibrary";`. * `translation_domain`: The domain to use for looking up the catalogue run-time translations. This must match the translation domain used when extracting translations with `slint-tr-extractor`. """ return _load_file( path, quiet, style, include_paths, library_paths, translation_domain )[0] def _load_file_checked( path: str | os.PathLike[Any] | pathlib.Path, expected_api_base64_compressed: str, generated_file: str | os.PathLike[Any] | pathlib.Path, ) -> types.SimpleNamespace: """@private""" module, compilation_result = _load_file(path) expected_api = gzip.decompress( base64.standard_b64decode(expected_api_base64_compressed) ).decode("utf-8") generated_api_module = native.GeneratedAPI(path=generated_file, json=expected_api) actual_api_module = compilation_result.generated_api generated_api_module.compare_generated_vs_actual( generated=generated_api_module, actual=actual_api_module ) return module class SlintAutoLoader: def __init__(self, base_dir: Path | None = None): self.local_dirs: typing.List[Path] | None = None if base_dir: self.local_dirs = [base_dir] def __getattr__(self, name: str) -> Any: for path in self.local_dirs or sys.path: dir_candidate = Path(path) / name if os.path.isdir(dir_candidate): loader = SlintAutoLoader(dir_candidate) setattr(self, name, loader) return loader file_candidate = dir_candidate.with_suffix(".slint") if os.path.isfile(file_candidate): type_namespace = load_file(file_candidate) setattr(self, name, type_namespace) return type_namespace dir_candidate = Path(path) / name.replace("_", "-") file_candidate = dir_candidate.with_suffix(".slint") if os.path.isfile(file_candidate): type_namespace = load_file(file_candidate) setattr(self, name, type_namespace) return type_namespace return None loader = SlintAutoLoader() """Use the global `loader` object to load Slint files from the file system. It exposes two stages of attributes: 1. Any lookup of an attribute in the loader tries to match a file in `sys.path` with the `.slint` extension. For example `loader.my_component` looks for a file `my_component.slint` in the directories in `sys.path`. 2. Any lookup in the object returned by the first stage tries to match an exported component in the loaded file, or a struct, or enum. For example `loader.my_component.MyComponent` looks for an *exported* component named `MyComponent` in the file `my_component.slint`. **Note:** The first entry in the module search path `sys.path` is the directory that contains the input script. Example: ```python import slint # Look for a file `main.slint` in the current directory, # #load & compile it, and instantiate the exported `MainWindow` component main_window = slint.loader.main_window.MainWindow() main_window.show() ... ``` """ def _callback_decorator( callable: typing.Callable[..., Any], info: typing.Dict[str, Any] ) -> typing.Callable[..., Any]: if "name" not in info: info["name"] = typing.cast(Any, callable).__name__ setattr(callable, "slint.callback", info) try: import inspect if inspect.iscoroutinefunction(callable): def run_as_task(*args, **kwargs) -> None: loop = asyncio.get_event_loop() loop.create_task(callable(*args, **kwargs)) setattr(run_as_task, "slint.callback", info) setattr(run_as_task, "slint.async", True) return run_as_task except ImportError: pass return callable def callback( global_name: typing.Callable[..., Any] | str | None = None, name: str | None = None ) -> typing.Callable[..., Any]: """Use the callback decorator to mark a method as a callback that can be invoked from the Slint component. For the decorator to work, the method must be a member of a class that is Slint component. Example: ```python import slint class AppMainWindow(slint.loader.main_window.MainWindow): # Automatically connected to a callback button_clicked() # in main_window.slint's MainWindow. @slint.callback() def button_clicked(self): print("Button clicked") ... ``` If your Python method has a different name from the Slint component's callback, use the `name` parameter to specify the correct name. Similarly, use the `global_name` parameter to specify the name of the correct global singleton in the Slint component. **Note:** The callback decorator can also be used with async functions. They will be run as task in the asyncio event loop. This is only supported for callbacks that don't return any value, and requires Python >= 3.13. """ if callable(global_name): callback = global_name return _callback_decorator(callback, {}) else: info = {} if name: info["name"] = name if global_name: info["global_name"] = global_name return lambda callback: _callback_decorator(callback, info) def set_xdg_app_id(app_id: str) -> None: """Sets the application id for use on Wayland or X11 with [xdg](https://specifications.freedesktop.org/desktop-entry-spec/latest/) compliant window managers. This id must be set before the window is shown; it only applies to Wayland or X11.""" native.set_xdg_app_id(app_id) quit_event = asyncio.Event() def run_event_loop( main_coro: typing.Optional[Coroutine[None, None, None]] = None, ) -> None: """Runs the main Slint event loop. If specified, the coroutine `main_coro` is run in parallel. The event loop doesn't terminate when the coroutine finishes, it terminates when calling `quit_event_loop()`. Example: ```python import slint ... image_model: slint.ListModel[slint.Image] = slint.ListModel() ... async def main_receiver(image_model: slint.ListModel) -> None: async with aiohttp.ClientSession() as session: async with session.get("http://some.server/svg-image") as response: svg = await response.read() image = slint.Image.from_svg_data(svg) image_model.append(image) ... slint.run_event_loop(main_receiver(image_model)) ``` """ async def run_inner() -> None: global quit_event loop = typing.cast(SlintEventLoop, asyncio.get_event_loop()) quit_task = asyncio.ensure_future(quit_event.wait(), loop=loop) tasks: typing.List[asyncio.Task[typing.Any]] = [quit_task] main_task = None if main_coro: main_task = loop.create_task(main_coro) tasks.append(main_task) done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) if main_task is not None and main_task in done: main_task.result() # propagate exception if thrown if quit_task in pending: await quit_event.wait() global quit_event quit_event = asyncio.Event() asyncio.run(run_inner(), debug=False, loop_factory=SlintEventLoop) def quit_event_loop() -> None: """Quits the running event loop in the next event processing cycle. This will make an earlier call to `run_event_loop()` return.""" global quit_event quit_event.set() def init_translations(translations: typing.Optional[gettext.GNUTranslations]) -> None: """Installs the specified translations object to handle translations originating from the Slint code. Example: ```python import gettext import slint translations_dir = os.path.join(os.path.dirname(__file__), "lang") try: translations = gettext.translation("my_app", translations_dir, ["de"]) slint.install_translations(translations) except OSError: pass ``` """ native.init_translations(translations) __all__ = [ "CompileError", "Component", "load_file", "_load_file_checked", "loader", "Image", "Color", "Brush", "Model", "ListModel", "Timer", "TimerMode", "set_xdg_app_id", "callback", "run_event_loop", "quit_event_loop", "init_translations", "language", ] ================================================ FILE: api/python/slint/slint/language.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 from . import slint as native StandardListViewItem = native.language.StandardListViewItem # type: ignore[unresolved-attribute] KeyboardModifiers = native.language.KeyboardModifiers # type: ignore[unresolved-attribute] ================================================ FILE: api/python/slint/slint/loop.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 from . import slint as native import asyncio.selector_events import asyncio import asyncio.events import selectors import typing from collections.abc import Mapping import datetime class HasFileno(typing.Protocol): def fileno(self) -> int: ... def fd_for_fileobj(fileobj: int | HasFileno) -> int: if isinstance(fileobj, int): return fileobj return int(fileobj.fileno()) class _SlintSelectorMapping(Mapping[typing.Any, selectors.SelectorKey]): def __init__(self, slint_selector: "_SlintSelector") -> None: self.slint_selector = slint_selector def __len__(self) -> int: return len(self.slint_selector.fd_to_selector_key) def get(self, fileobj, default=None): fd = fd_for_fileobj(fileobj) return self.slint_selector.fd_to_selector_key.get(fd, default) def __getitem__(self, fileobj: typing.Any) -> selectors.SelectorKey: fd = fd_for_fileobj(fileobj) return self.slint_selector.fd_to_selector_key[fd] def __iter__(self): return iter(self.slint_selector.fd_to_selector_key) class _SlintSelector(selectors.BaseSelector): def __init__(self) -> None: self.fd_to_selector_key: typing.Dict[typing.Any, selectors.SelectorKey] = {} self.mapping = _SlintSelectorMapping(self) self.adapters: typing.Dict[int, native.AsyncAdapter] = {} def register( self, fileobj: typing.Any, events: typing.Any, data: typing.Any = None ) -> selectors.SelectorKey: fd = fd_for_fileobj(fileobj) key = selectors.SelectorKey(fileobj, fd, events, data) self.fd_to_selector_key[fd] = key adapter = native.AsyncAdapter(fd) self.adapters[fd] = adapter if events & selectors.EVENT_READ: adapter.wait_for_readable(self.read_notify) if events & selectors.EVENT_WRITE: adapter.wait_for_writable(self.write_notify) return key def unregister(self, fileobj: typing.Any) -> selectors.SelectorKey: fd = fd_for_fileobj(fileobj) key = self.fd_to_selector_key.pop(fd) try: del self.adapters[fd] except KeyError: pass return key def modify( self, fileobj: typing.Any, events: int, data: typing.Any = None ) -> selectors.SelectorKey: fd = fd_for_fileobj(fileobj) key = self.fd_to_selector_key[fd] if key.events != events: self.unregister(fileobj) key = self.register(fileobj, events, data) elif key.data != data: key._replace(data=data) self.fd_to_selector_key[fd] = key return key def select( self, timeout: float | None = None ) -> typing.List[typing.Tuple[selectors.SelectorKey, int]]: raise NotImplementedError def close(self) -> None: pass def get_map(self) -> Mapping[int | HasFileno, selectors.SelectorKey]: return self.mapping def read_notify(self, fd: int) -> None: key = self.fd_to_selector_key[fd] (reader, writer) = key.data reader._run() def write_notify(self, fd: int) -> None: key = self.fd_to_selector_key[fd] (reader, writer) = key.data writer._run() class SlintEventLoop(asyncio.SelectorEventLoop): def __init__(self) -> None: self._is_running = False self._timers: typing.Set[native.Timer] = set() self.stop_run_forever_event = asyncio.Event() self._soon_tasks: typing.List[asyncio.TimerHandle] = [] super().__init__(_SlintSelector()) def run_forever(self) -> None: async def loop_stopper(event: asyncio.Event) -> None: await event.wait() native.quit_event_loop() asyncio.events._set_running_loop(self) self._is_running = True try: self.stop_run_forever_event = asyncio.Event() self.create_task(loop_stopper(self.stop_run_forever_event)) native.run_event_loop() finally: self._is_running = False asyncio.events._set_running_loop(None) def run_until_complete[T](self, future: typing.Awaitable[T]) -> T | None: def stop_loop(future: typing.Any) -> None: self.stop() future = asyncio.ensure_future(future, loop=self) future.add_done_callback(stop_loop) try: self.run_forever() finally: future.remove_done_callback(stop_loop) if future.done(): return future.result() else: if self.stop_run_forever_event.is_set(): raise RuntimeError("run_until_complete's future isn't done", future) else: # If the loop was quit for example because the user closed the last window, then # don't thrown an error but return a None sentinel. The return value of asyncio.run() # isn't used by slint.run_event_loop() anyway # TODO: see if we can properly cancel the future by calling cancel() and throwing # the task cancellation exception. return None def _run_forever_setup(self) -> None: pass def _run_forever_cleanup(self) -> None: pass def stop(self) -> None: self.stop_run_forever_event.set() def is_running(self) -> bool: return self._is_running def close(self) -> None: super().close() def is_closed(self) -> bool: return False def call_later(self, delay, callback, *args, context=None) -> asyncio.TimerHandle: timer = native.Timer() handle = asyncio.TimerHandle( when=self.time() + delay, callback=callback, args=args, loop=self, context=context, ) timers = self._timers def timer_done_cb() -> None: timers.remove(timer) if not handle._cancelled: handle._run() timer.start( native.TimerMode.SingleShot, interval=datetime.timedelta(seconds=delay), callback=timer_done_cb, ) timers.add(timer) return handle def call_at(self, when, callback, *args, context=None) -> asyncio.TimerHandle: return self.call_later(when - self.time(), callback, *args, context=context) def call_soon(self, callback, *args, context=None) -> asyncio.TimerHandle: # Collect call-soon tasks in a separate list to ensure FIFO order, as there's no guarantee # that multiple single-shot timers in Slint are run in order. handle = asyncio.TimerHandle( when=self.time(), callback=callback, args=args, loop=self, context=context ) self._soon_tasks.append(handle) self.call_later(0, self._flush_soon_tasks) return handle def _flush_soon_tasks(self) -> None: tasks_now = self._soon_tasks self._soon_tasks = [] for handle in tasks_now: if not handle._cancelled: handle._run() def call_soon_threadsafe(self, callback, *args, context=None) -> asyncio.Handle: handle = asyncio.Handle( callback=callback, args=args, loop=self, context=context, ) def run_handle_cb() -> None: if not handle._cancelled: handle._run() native.invoke_from_event_loop(run_handle_cb) return handle def _write_to_self(self) -> None: raise NotImplementedError ================================================ FILE: api/python/slint/slint/models.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 from . import slint as native from collections.abc import Iterable from abc import abstractmethod import typing from typing import Any, cast, Iterator class Model[T](native.PyModelBase, Iterable[T]): """Model is the base class for feeding dynamic data into Slint views. Subclass Model to implement your own models, or use `ListModel` to wrap a list. Models are iterable and can be used in for loops.""" def __new__(cls, *args: Any) -> "Model[T]": return super().__new__(cls) def __init__(self) -> None: self.init_self(self) def __len__(self) -> int: return self.row_count() def __getitem__(self, index: int) -> typing.Optional[T]: return self.row_data(index) def __setitem__(self, index: int, value: T) -> None: self.set_row_data(index, value) def __iter__(self) -> Iterator[T]: return ModelIterator(self) def set_row_data(self, row: int, value: T) -> None: """Call this method on mutable models to change the data for the given row. The UI will also call this method when modifying a model's data. Re-implement this method in a sub-class to handle the change.""" super().set_row_data(row, value) @abstractmethod def row_data(self, row: int) -> typing.Optional[T]: """Returns the data for the given row. Re-implement this method in a sub-class to provide the data.""" return cast(T, super().row_data(row)) def notify_row_changed(self, row: int) -> None: """Call this method from a sub-class to notify the views that a row has changed.""" super().notify_row_changed(row) def notify_row_removed(self, row: int, count: int) -> None: """Call this method from a sub-class to notify the views that `count` rows have been removed starting at `row`.""" super().notify_row_removed(row, count) def notify_row_added(self, row: int, count: int) -> None: """Call this method from a sub-class to notify the views that `count` rows have been added starting at `row`.""" super().notify_row_added(row, count) class ListModel[T](Model[T]): """ListModel is a `Model` that stores its data in a Python list. Construct a ListMode from an iterable (such as a list itself). Use `ListModel.append()` to add items to the model, and use the `del` statement to remove items. Any changes to the model are automatically reflected in the views in UI they're used with. """ def __init__(self, iterable: typing.Optional[Iterable[T]] = None): """Constructs a new ListModel from the give iterable. All the values the iterable produces are stored in a list.""" super().__init__() if iterable is not None: self.list = list(iterable) else: self.list = [] def row_count(self) -> int: return len(self.list) def row_data(self, row: int) -> typing.Optional[T]: return self.list[row] def set_row_data(self, row: int, value: T) -> None: self.list[row] = value super().notify_row_changed(row) def __delitem__(self, key: int | slice) -> None: if isinstance(key, slice): start, stop, step = key.indices(len(self.list)) del self.list[key] count = len(range(start, stop, step)) super().notify_row_removed(start, count) else: del self.list[key] super().notify_row_removed(key, 1) def append(self, value: T) -> None: """Appends the value to the end of the list.""" index = len(self.list) self.list.append(value) super().notify_row_added(index, 1) class ModelIterator[T](Iterator[T]): def __init__(self, model: Model[T]): self.model = model self.index = 0 def __iter__(self) -> "ModelIterator[T]": return self def __next__(self) -> T: if self.index >= self.model.row_count(): raise StopIteration() index = self.index self.index += 1 data = self.model.row_data(index) assert data is not None return data ================================================ FILE: api/python/slint/slint/py.typed ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # This file is just a marker ================================================ FILE: api/python/slint/slint/slint.pyi ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # This file is automatically generated by pyo3_stub_gen # ruff: noqa: E501, F401 import builtins import datetime import os import pathlib import typing from typing import Any, List from collections.abc import Callable, Buffer, Coroutine from enum import Enum, auto import gettext class RgbColor: red: int green: int blue: int class RgbaColor: red: int green: int blue: int alpha: int class Color: red: int green: int blue: int alpha: int def __new__( cls, maybe_value: typing.Optional[ str | RgbaColor | RgbColor | typing.Dict[str, int] ] = None, ) -> "Color": ... def brighter(self, factor: float) -> "Color": ... def darker(self, factor: float) -> "Color": ... def transparentize(self, factor: float) -> "Color": ... def mix(self, other: "Image", factor: float) -> "Color": ... def with_alpha(self, alpha: float) -> "Color": ... def __str__(self) -> str: ... def __eq__(self, other: object) -> bool: ... class Brush: color: Color def __new__(cls, maybe_value: typing.Optional[Color]) -> "Brush": ... def is_transparent(self) -> bool: ... def is_opaque(self) -> bool: ... def brighter(self, factor: float) -> "Brush": ... def darker(self, factor: float) -> "Brush": ... def transparentize(self, amount: float) -> "Brush": ... def with_alpha(self, alpha: float) -> "Brush": ... def __eq__(self, other: object) -> bool: ... class Image: r""" Image objects can be set on Slint Image elements for display. Construct Image objects from a path to an image file on disk, using `Image.load_from_path`. """ size: tuple[int, int] width: int height: int path: typing.Optional[pathlib.Path] def __new__( cls, ) -> "Image": ... @staticmethod def load_from_path(path: str | os.PathLike[Any] | pathlib.Path) -> "Image": r""" Loads the image from the specified path. Returns None if the image can't be loaded. """ ... @staticmethod def load_from_svg_data(data: typing.Sequence[int]) -> "Image": r""" Creates a new image from a string that describes the image in SVG format. """ ... @staticmethod def load_from_array(array: Buffer) -> Image: r""" Creates a new image from an array-like object that implements the [Buffer Protocol](https://docs.python.org/3/c-api/buffer.html). Use this function to import images created by third-party modules such as matplotlib or Pillow. The array must satisfy certain contraints to represent an image: - The buffer's format needs to be `B` (unsigned char) - The shape must be a tuple of (height, width, bytes-per-pixel) - If a stride is defined, the row stride must be equal to width * bytes-per-pixel, and the column stride must equal the bytes-per-pixel. - A value of 3 for bytes-per-pixel is interpreted as RGB image, a value of 4 means RGBA. Example of importing a matplot figure into an image: ```python import slint import matplotlib from matplotlib.backends.backend_agg import FigureCanvasAgg from matplotlib.figure import Figure fig = Figure(figsize=(5, 4), dpi=100) canvas = FigureCanvasAgg(fig) ax = fig.add_subplot() ax.plot([1, 2, 3]) canvas.draw() buffer = canvas.buffer_rgba() img = slint.Image.load_from_array(buffer) ``` Example of loading an image with Pillow: ```python import slint from PIL import Image import numpy as np pil_img = Image.open("hello.jpeg") array = np.array(pil_img) img = slint.Image.load_from_array(array) ``` """ class TimerMode(Enum): SingleShot = auto() Repeated = auto() class Timer: running: bool interval: datetime.timedelta def __new__( cls, ) -> "Timer": ... def start( self, mode: TimerMode, interval: datetime.timedelta, callback: typing.Any ) -> None: ... @staticmethod def single_shot(duration: datetime.timedelta, callback: typing.Any) -> None: ... def stop(self) -> None: ... def restart(self) -> None: ... def set_xdg_app_id(app_id: str) -> None: ... def invoke_from_event_loop(callable: typing.Callable[[], None]) -> None: ... def run_event_loop() -> None: ... def quit_event_loop() -> None: ... def init_translations( translations: typing.Optional[gettext.GNUTranslations], ) -> None: ... class PyModelBase: def init_self(self, *args: Any) -> None: ... def row_count(self) -> int: ... def row_data(self, row: int) -> typing.Optional[Any]: ... def set_row_data(self, row: int, value: Any) -> None: ... def notify_row_changed(self, row: int) -> None: ... def notify_row_removed(self, row: int, count: int) -> None: ... def notify_row_added(self, row: int, count: int) -> None: ... class PyStruct(Any): ... class ValueType(Enum): Void = auto() Number = auto() String = auto() Bool = auto() Model = auto() Struct = auto() Brush = auto() Image = auto() class DiagnosticLevel(Enum): Error = auto() Warning = auto() Note = auto() class PyDiagnostic: level: DiagnosticLevel message: str line_number: int column_number: int source_file: typing.Optional[str] class ComponentInstance: def show(self) -> None: ... def hide(self) -> None: ... def invoke(self, callback_name: str, *args: Any) -> Any: ... def invoke_global( self, global_name: str, callback_name: str, *args: Any ) -> Any: ... def set_property(self, property_name: str, value: Any) -> None: ... def get_property(self, property_name: str) -> Any: ... def set_callback( self, callback_name: str, callback: Callable[..., Any] ) -> None: ... def set_global_callback( self, global_name: str, callback_name: str, callback: Callable[..., Any] ) -> None: ... def set_global_property( self, global_name: str, property_name: str, value: Any ) -> None: ... def get_global_property(self, global_name: str, property_name: str) -> Any: ... class ComponentDefinition: def create(self) -> ComponentInstance: ... name: str globals: list[str] functions: list[str] callbacks: list[str] properties: dict[str, ValueType] def global_functions(self, global_name: str) -> list[str]: ... def global_callbacks(self, global_name: str) -> list[str]: ... def global_properties(self, global_name: str) -> typing.Dict[str, ValueType]: ... def callback_returns_void(self, callback_name: str) -> bool: ... def global_callback_returns_void( self, global_name: str, callback_name: str ) -> bool: ... class CompilationResult: component_names: list[str] diagnostics: list[PyDiagnostic] named_exports: list[typing.Tuple[str, str]] structs_and_enums: typing.Tuple[typing.Dict[str, PyStruct], typing.Dict[str, Enum]] generated_api: GeneratedAPI def component(self, name: str) -> ComponentDefinition: ... class Compiler: include_paths: list[os.PathLike[Any] | pathlib.Path] library_paths: dict[str, os.PathLike[Any] | pathlib.Path] translation_domain: str style: str def build_from_path( self, path: os.PathLike[Any] | pathlib.Path ) -> CompilationResult: ... def build_from_source( self, source: str, path: os.PathLike[Any] | pathlib.Path ) -> CompilationResult: ... class AsyncAdapter: def __new__( cls, fd: int, ) -> "AsyncAdapter": ... def wait_for_readable(self, callback: typing.Callable[[int], None]) -> None: ... def wait_for_writable(self, callback: typing.Callable[[int], None]) -> None: ... class GeneratedAPI: def __new__( cls, path: str | os.PathLike[Any] | pathlib.Path, json: str ) -> "GeneratedAPI": ... @staticmethod def compare_generated_vs_actual( generated: "GeneratedAPI", actual: "GeneratedAPI" ) -> None: ... ================================================ FILE: api/python/slint/stub-gen/main.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use pyo3_stub_gen::Result; fn main() -> Result<()> { // `stub_info` is a function defined by `define_stub_info_gatherer!` macro. let stub = slint_python::stub_info()?; stub.generate()?; Ok(()) } ================================================ FILE: api/python/slint/tests/api-match.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 export component Test inherits Window { in-out property name; } ================================================ FILE: api/python/slint/tests/test-file-error.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 export component App inherits Window { property foo; NewType { } } ================================================ FILE: api/python/slint/tests/test-load-file.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 export global MyGlobal { in-out property global-prop: "This is global"; callback global-callback(string) -> string; public function minus-one(value: int) { return value - 1; } } export global SecondGlobal { out property second: "second"; } export struct MyData { name: string, age: int, } struct Secret-Struct { balance: int, } export { Secret-Struct as Public-Struct } export enum TestEnum { Variant1, Variant2, Variant-three, } export component App inherits Window { in-out property hello: "World"; callback say-hello(string) -> string; callback say_hello_again(string) -> string; callback invoke-say-hello(string) -> string; invoke-say-hello(arg) => { return self.say-hello(arg); } callback invoke-say-hello-again(string) -> string; invoke-say-hello-again(arg) => { return self.say-hello-again(arg); } callback invoke-global-callback(string) -> string; invoke-global-callback(arg) => { return MyGlobal.global-callback(arg); } public function plus-one(value: int) { return value + 1; } callback invoke-call-void(); invoke-call-void => { call-void(); } callback call-void(); callback invoke-call-void2(); invoke-call-void2 => { call_void2(); } callback call_void2(); in-out property enum-property: TestEnum.Variant2; in-out property <[TestEnum]> model-with-enums: [TestEnum.Variant2, TestEnum.Variant1]; Rectangle { color: red; } in-out property translated: @tr("Yes"); } component Diag inherits Window { } export { Diag as MyDiag } ================================================ FILE: api/python/slint/tests/test_api_match.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import pytest from slint import _load_file_checked from pathlib import Path import base64 import gzip def base_dir() -> Path: assert __spec__ origin = __spec__.origin assert origin is not None base_dir = Path(origin).parent assert base_dir is not None return base_dir def compress_and_encode(json: str) -> str: return base64.standard_b64encode(gzip.compress(json.encode("utf-8"))).decode( "utf-8" ) def test_no_change() -> None: _load_file_checked( base_dir() / "api-match.slint", expected_api_base64_compressed=compress_and_encode(r""" { "version":"1.0", "globals":[], "components":[ { "name":"Test", "properties":[ { "name": "name", "ty": "str" } ], "aliases":[] } ], "structs_and_enums":[] }"""), generated_file="/some/path.py", ) def test_incompatible_changes() -> None: with pytest.raises(RuntimeError) as excinfo: _load_file_checked( base_dir() / "api-match.slint", expected_api_base64_compressed=compress_and_encode(r""" { "version":"1.0", "globals":[], "components":[ { "name":"Test", "properties":[ { "name": "name", "ty": "str" }, { "name": "not_there_anymore", "ty": "str" } ], "aliases":[] } ], "structs_and_enums":[] }"""), generated_file="/some/path.py", ) assert ( f"Incompatible API changes detected between /some/path.py and {base_dir() / 'api-match.slint'}" == str(excinfo.value) ) ================================================ FILE: api/python/slint/tests/test_async.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import slint from slint import slint as native import asyncio import typing import aiohttp from aiohttp import web import socket import threading import pytest import sys import platform from datetime import timedelta def test_async_basic() -> None: async def quit_soon(call_check: typing.List[bool]) -> None: await asyncio.sleep(1) call_check[0] = True slint.quit_event_loop() call_check = [False] slint.run_event_loop(quit_soon(call_check)) assert call_check[0] def test_async_aiohttp() -> None: def probe_port() -> int: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("127.0.0.1", 0)) port = typing.cast(int, s.getsockname()[1]) # This is a race condition, but should be good enough for test environments s.close() return port async def hello(request: web.Request) -> web.Response: return web.Response(text="Hello, world") async def run_network_requests( port: int, exceptions: typing.List[Exception] ) -> None: try: app = web.Application() app.add_routes([web.get("/", hello)]) runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, "127.0.0.1", port) await site.start() async with aiohttp.ClientSession() as session: async with session.get(f"http://127.0.0.1:{port}") as response: # print("Status:", response.status) print("Content-type:", response.headers["content-type"]) # html = await response.text() print("Body:", html[:15], "...") assert html == "Hello, world" await runner.cleanup() except Exception as e: exceptions.append(e) finally: slint.quit_event_loop() exceptions: typing.List[Exception] = [] slint.run_event_loop(run_network_requests(probe_port(), exceptions)) assert len(exceptions) == 0 def test_basic_socket() -> None: def server_thread(server_socket: socket.socket) -> None: server_socket.listen(1) conn, _ = server_socket.accept() try: data = conn.recv(1024) if data == b"ping": conn.sendall(b"pong") else: conn.sendall(b"error") finally: conn.close() server_socket.close() server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(("127.0.0.1", 0)) port = server_socket.getsockname()[1] thread = threading.Thread(target=server_thread, args=(server_socket,)) thread.start() async def run_network_request(port: int) -> None: reader, writer = await asyncio.open_connection("127.0.0.1", port) writer.write(b"ping") await writer.drain() response = [] while chunk := await reader.read(1024): response.append(chunk) writer.close() await writer.wait_closed() assert response[0] == b"pong" slint.quit_event_loop() slint.run_event_loop(run_network_request(port)) thread.join() def test_server_socket() -> None: async def handle_client( reader: asyncio.StreamReader, writer: asyncio.StreamWriter ) -> None: data = await reader.read(1024) if data == b"ping": writer.write(b"pong") else: writer.write(b"error") await writer.drain() writer.close() await writer.wait_closed() async def run_network_request(port: int) -> None: try: reader, writer = await asyncio.open_connection("127.0.0.1", port) writer.write(b"ping") await writer.drain() response = [] while chunk := await reader.read(1024): response.append(chunk) writer.close() await writer.wait_closed() assert response[0] == b"pong" finally: slint.quit_event_loop() async def run_server_and_client(exception_check: typing.List[Exception]) -> None: try: server = await asyncio.start_server(handle_client, "127.0.0.1", 0) port = server.sockets[0].getsockname()[1] async with server: await asyncio.gather( server.serve_forever(), run_network_request(port), ) except Exception as e: exception_check.append(e) raise exception_check: typing.List[Exception] = [] slint.run_event_loop(run_server_and_client(exception_check)) if len(exception_check) > 0: raise exception_check[0] def test_loop_close_while_main_future_runs() -> None: def q() -> None: native.quit_event_loop() async def never_quit() -> None: loop = asyncio.get_running_loop() # Call native.quit_event_loop() directly as if the user closed the last window. We should gracefully # handle that the future that this function represents isn't terminated. loop.call_later(0.1, q) while True: await asyncio.sleep(1) try: slint.run_event_loop(never_quit()) except Exception: pytest.fail("Should not throw a run-time error") def test_loop_continues_when_main_coro_finished() -> None: async def quit_later(quit_event: asyncio.Event) -> None: await quit_event.wait() slint.quit_event_loop() async def simple(quit_event: asyncio.Event) -> None: loop = asyncio.get_event_loop() loop.create_task(quit_later(quit_event)) quit_event = asyncio.Event() slint.Timer.single_shot( duration=timedelta(milliseconds=100), callback=lambda: quit_event.set() ) slint.run_event_loop(simple(quit_event)) assert quit_event.is_set() @pytest.mark.skipif(platform.system() == "Windows", reason="pipes aren't supported yet") def test_subprocess() -> None: async def launch_process(exception_check: typing.List[Exception]) -> None: try: proc = await asyncio.create_subprocess_exec( sys.executable, "--version", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT, ) stdout, _ = await proc.communicate() output = stdout.decode().strip() print(f"Process output: {output}") assert proc.returncode == 0 assert output != "" slint.quit_event_loop() except Exception as e: exception_check[0] = e raise exception_check: typing.List[Exception] = [] slint.run_event_loop(launch_process(exception_check)) if len(exception_check) > 0: raise exception_check[0] def test_exception_thrown() -> None: async def throws() -> None: raise RuntimeError("Boo") with pytest.raises(RuntimeError, match="Boo"): slint.run_event_loop(throws()) ================================================ FILE: api/python/slint/tests/test_brush.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 from slint import Color, Brush def test_col_default() -> None: col = Color() assert col.red == 0 assert col.green == 0 assert col.blue == 0 assert col.alpha == 0 def test_col_from_str() -> None: col = Color("#123456") assert col.red == 0x12 assert col.green == 0x34 assert col.blue == 0x56 assert col.alpha == 255 assert str(col) == "argb(255, 18, 52, 86)" def test_col_from_rgb_dict() -> None: coldict = {"red": 0x12, "green": 0x34, "blue": 0x56} col = Color(coldict) assert col.red == 0x12 assert col.green == 0x34 assert col.blue == 0x56 assert col.alpha == 255 def test_col_from_rgba_dict() -> None: coldict = {"red": 0x12, "green": 0x34, "blue": 0x56, "alpha": 128} col = Color(coldict) assert col.red == 0x12 assert col.green == 0x34 assert col.blue == 0x56 assert col.alpha == 128 def test_from_col() -> None: col = Color("#123456") brush = Brush(col) assert brush.color == col ================================================ FILE: api/python/slint/tests/test_callback_decorators.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 from slint import load_file import slint import pytest from pathlib import Path import asyncio def base_dir() -> Path: assert __spec__ origin = __spec__.origin assert origin is not None base_dir = Path(origin).parent assert base_dir is not None return base_dir def test_callback_decorators(caplog: pytest.LogCaptureFixture) -> None: module = load_file(base_dir() / "test-load-file.slint", quiet=False) class SubClass(module.App): @slint.callback() def say_hello_again(self, arg: str) -> str: return "say_hello_again:" + arg @slint.callback(name="say-hello") def renamed(self, arg: str) -> str: return "renamed:" + arg @slint.callback(global_name="MyGlobal", name="global-callback") def global_callback(self, arg: str) -> str: return "global:" + arg instance = SubClass() assert instance.invoke_say_hello("ok") == "renamed:ok" assert instance.invoke_say_hello_again("ok") == "say_hello_again:ok" assert instance.invoke_global_callback("ok") == "global:ok" del instance def test_callback_decorators_async() -> None: module = load_file(base_dir() / "test-load-file.slint", quiet=False) class SubClass(module.App): def __init__(self, in_queue: asyncio.Queue[int], out_queue: asyncio.Queue[int]): super().__init__() self.in_queue = in_queue self.out_queue = out_queue @slint.callback() async def call_void(self) -> None: value = await self.in_queue.get() await self.out_queue.put(value + 1) @slint.callback() async def call_void2(self) -> None: value = await self.in_queue.get() await self.out_queue.put(value + 2) async def main( instance: SubClass, in_queue: asyncio.Queue[int], out_queue: asyncio.Queue[int] ) -> None: await in_queue.put(42) instance.invoke_call_void() assert await out_queue.get() == 43 await in_queue.put(43) instance.invoke_call_void2() assert await out_queue.get() == 45 slint.quit_event_loop() in_queue: asyncio.Queue[int] = asyncio.Queue() out_queue: asyncio.Queue[int] = asyncio.Queue() instance = SubClass(in_queue, out_queue) slint.run_event_loop(main(instance, in_queue, out_queue)) def test_callback_decorators_async_err() -> None: module = load_file(base_dir() / "test-load-file.slint", quiet=False) class SubClass(module.App): def __init__(self) -> None: super().__init__() @slint.callback() async def say_hello(self, msg: str) -> str: return msg with pytest.raises(RuntimeError) as excinfo: SubClass() err = excinfo.value assert ( str(err) == "Callback 'say_hello' cannot be used with a callback decorator for an async function, as it doesn't return void" ) ================================================ FILE: api/python/slint/tests/test_compiler.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 from slint import slint as native from slint.slint import ValueType from pathlib import Path def test_basic_compiler() -> None: compiler = native.Compiler() assert compiler.include_paths == [] compiler.include_paths = [Path("testing")] assert compiler.include_paths == [Path("testing")] assert len(compiler.build_from_source("Garbage", Path("")).component_names) == 0 result = compiler.build_from_source( """ export global TestGlobal { in property theglobalprop; callback globallogic(); public function globalfun() {} } export component Test { in property strprop; in property intprop; in property floatprop; in property boolprop; in property imgprop; in property brushprop; in property colprop; in property <[string]> modelprop; callback test-callback(); public function ff() {} } """, Path(""), ) assert result.component_names == ["Test"] compdef = result.component("Test") assert compdef is not None assert compdef.name == "Test" props = [(name, type) for name, type in compdef.properties.items()] assert props == [ ("boolprop", ValueType.Bool), ("brushprop", ValueType.Brush), ("colprop", ValueType.Brush), ("floatprop", ValueType.Number), ("imgprop", ValueType.Image), ("intprop", ValueType.Number), ("modelprop", ValueType.Model), ("strprop", ValueType.String), ] assert compdef.callbacks == ["test-callback"] assert compdef.functions == ["ff"] assert compdef.globals == ["TestGlobal"] assert compdef.global_properties("Garbage") is None assert [ (name, type) for name, type in compdef.global_properties("TestGlobal").items() ] == [("theglobalprop", ValueType.String)] assert compdef.global_callbacks("Garbage") is None assert compdef.global_callbacks("TestGlobal") == ["globallogic"] assert compdef.global_functions("Garbage") is None assert compdef.global_functions("TestGlobal") == ["globalfun"] instance = compdef.create() assert instance is not None def test_compiler_build_from_path() -> None: compiler = native.Compiler() result = compiler.build_from_path(Path("Nonexistent.slint")) assert len(result.component_names) == 0 diags = result.diagnostics assert len(diags) == 1 assert diags[0].level == native.DiagnosticLevel.Error assert diags[0].message.startswith("Could not load Nonexistent.slint:") ================================================ FILE: api/python/slint/tests/test_enums.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import pytest from slint import load_file, ListModel from pathlib import Path def base_dir() -> Path: assert __spec__ origin = __spec__.origin assert origin is not None base_dir = Path(origin).parent assert base_dir is not None return base_dir def test_enums() -> None: module = load_file(base_dir() / "test-load-file.slint", quiet=False) TestEnum = module.TestEnum assert TestEnum.Variant1.name == "Variant1" assert TestEnum.Variant1.value == "Variant1" assert TestEnum.Variant2.name == "Variant2" assert TestEnum.Variant2.value == "Variant2" with pytest.raises( AttributeError, match="type object 'TestEnum' has no attribute 'Variant3'" ): TestEnum.Variant3 instance = module.App() assert instance.enum_property == TestEnum.Variant2 assert instance.enum_property.__class__ is TestEnum instance.enum_property = TestEnum.Variant1 assert instance.enum_property == TestEnum.Variant1 assert instance.enum_property.__class__ is TestEnum instance.enum_property = TestEnum.Variant_three assert instance.enum_property == TestEnum.Variant_three assert instance.enum_property.__class__ is TestEnum model_with_enums = instance.model_with_enums assert len(model_with_enums) == 2 assert model_with_enums[0] == TestEnum.Variant2 assert model_with_enums[1] == TestEnum.Variant1 assert model_with_enums[0].__class__ is TestEnum model_with_enums = None instance.model_with_enums = ListModel([TestEnum.Variant1, TestEnum.Variant2]) assert len(instance.model_with_enums) == 2 assert instance.model_with_enums[0] == TestEnum.Variant1 assert instance.model_with_enums[1] == TestEnum.Variant2 assert instance.model_with_enums[0].__class__ is TestEnum ================================================ FILE: api/python/slint/tests/test_gc.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 from slint import slint as native import slint import weakref import gc import typing from pathlib import Path def test_callback_gc() -> None: compiler = native.Compiler() compdef = compiler.build_from_source( """ export component Test { out property test-value: "Ok"; callback test-callback(string) -> string; } """, Path(""), ).component("Test") assert compdef is not None instance: native.ComponentInstance | None = compdef.create() assert instance is not None class Handler: def __init__(self, instance: native.ComponentInstance) -> None: self.instance = instance def python_callback(self, input: str) -> str: return input + typing.cast(str, self.instance.get_property("test-value")) handler: Handler | None = Handler(instance) assert handler is not None instance.set_callback("test-callback", handler.python_callback) handler = None assert instance.invoke("test-callback", "World") == "WorldOk" wr = weakref.ref(instance) assert wr() is not None instance = None gc.collect() assert wr() is None def test_struct_gc() -> None: compiler = native.Compiler() compdef = compiler.build_from_source( """ export struct Foo { data: [int] } export component Test { out property test-value; } """, Path(""), ).component("Test") assert compdef is not None instance: native.ComponentInstance | None = compdef.create() assert instance is not None model: typing.Optional[slint.ListModel[int]] = slint.ListModel([1, 2, 3]) assert model assert model.row_count() == 3 test_value = instance.get_property("test-value") test_value.data = model model = None # test_value as a struct should hold a strong reference to the model field within gc.collect() assert test_value.data.row_count() == 3 def test_properties_gc() -> None: compiler = native.Compiler() compdef = compiler.build_from_source( """ export component Test { in-out property <[int]> test-value; } """, Path(""), ).component("Test") assert compdef is not None instance: native.ComponentInstance | None = compdef.create() assert instance is not None model: typing.Optional[slint.ListModel[int]] = slint.ListModel([1, 2, 3]) assert model assert model.row_count() == 3 instance.set_property("test-value", model) model = None gc.collect() assert instance.get_property("test-value").row_count() == 3 ================================================ FILE: api/python/slint/tests/test_image.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import slint import numpy as np from PIL import Image from pathlib import Path def base_dir() -> Path: assert __spec__ origin = __spec__.origin assert origin is not None base_dir = Path(origin).parent assert base_dir is not None return base_dir def test_image_loading() -> None: image = Image.open( base_dir() / ".." / ".." / ".." / ".." / "logo" / "slint-logo-simple-dark.png" ) assert image.size == (282, 84) array = np.array(image) slint_image = slint.Image.load_from_array(array) assert slint_image.width == 282 assert slint_image.height == 84 ================================================ FILE: api/python/slint/tests/test_instance.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import pytest from slint import slint as native from slint.slint import Image, Color, Brush import os from pathlib import Path def test_property_access() -> None: compiler = native.Compiler() compdef = compiler.build_from_source( """ export global TestGlobal { in property theglobalprop: "Hey"; callback globallogic(); } export struct MyStruct { title: string, finished: bool, dash-prop: bool, } export component Test { in property strprop: "Hello"; in property intprop: 42; in property floatprop: 100; in property boolprop: true; in property imgprop; in property brushprop: Colors.rgb(255, 0, 255); in property colprop: Colors.rgb(0, 255, 0); in property <[string]> modelprop; in property structprop: { title: "builtin", finished: true, dash-prop: true, }; in property imageprop: @image-url("../../../../demos/printerdemo/ui/images/cat.jpg"); callback test-callback(); } """, Path(__file__).parent / "main.slint", ).component("Test") assert compdef is not None instance = compdef.create() assert instance is not None with pytest.raises(ValueError, match="no such property"): instance.set_property("nonexistent", 42) assert instance.get_property("strprop") == "Hello" instance.set_property("strprop", "World") assert instance.get_property("strprop") == "World" with pytest.raises(ValueError, match="wrong type"): instance.set_property("strprop", 42) assert instance.get_property("intprop") == 42 instance.set_property("intprop", 100) assert instance.get_property("intprop") == 100 with pytest.raises(ValueError, match="wrong type"): instance.set_property("intprop", False) assert instance.get_property("floatprop") == 100 instance.set_property("floatprop", 42) assert instance.get_property("floatprop") == 42 with pytest.raises(ValueError, match="wrong type"): instance.set_property("floatprop", "Blah") assert instance.get_property("boolprop") instance.set_property("boolprop", False) assert not instance.get_property("boolprop") with pytest.raises(ValueError, match="wrong type"): instance.set_property("boolprop", 0) structval = instance.get_property("structprop") assert isinstance(structval, native.PyStruct) assert structval.title == "builtin" assert structval.finished assert structval.dash_prop instance.set_property( "structprop", {"title": "new", "finished": False, "dash_prop": False} ) structval = instance.get_property("structprop") assert structval.title == "new" assert not structval.finished assert not structval.dash_prop imageval = instance.get_property("imageprop") assert imageval.width == 320 assert imageval.height == 480 assert "cat.jpg" in imageval.path.name with pytest.raises(RuntimeError, match="The image cannot be loaded"): Image.load_from_path("non-existent.png") instance.set_property( "imageprop", Image.load_from_path( os.path.join( os.path.dirname(__file__), "../../../../examples/iot-dashboard/images/humidity.png", ) ), ) imageval = instance.get_property("imageprop") assert imageval.size == (36, 36) assert "humidity.png" in str(imageval.path) with pytest.raises(TypeError, match="'int' object is not an instance of 'str'"): instance.set_property("structprop", {42: "wrong"}) brushval = instance.get_property("brushprop") assert str(brushval.color) == "argb(255, 255, 0, 255)" instance.set_property("brushprop", Brush(Color("rgb(128, 128, 128)"))) brushval = instance.get_property("brushprop") assert str(brushval.color) == "argb(255, 128, 128, 128)" brushval = instance.get_property("colprop") assert str(brushval.color) == "argb(255, 0, 255, 0)" instance.set_property("colprop", Color("rgb(128, 128, 128)")) brushval = instance.get_property("colprop") assert str(brushval.color) == "argb(255, 128, 128, 128)" with pytest.raises(ValueError, match="no such property"): instance.set_global_property("nonexistent", "theglobalprop", 42) with pytest.raises(ValueError, match="no such property"): instance.set_global_property("TestGlobal", "nonexistent", 42) assert instance.get_global_property("TestGlobal", "theglobalprop") == "Hey" instance.set_global_property("TestGlobal", "theglobalprop", "Ok") assert instance.get_global_property("TestGlobal", "theglobalprop") == "Ok" def test_callbacks() -> None: compiler = native.Compiler() compdef = compiler.build_from_source( """ export global TestGlobal { callback globallogic(string) -> string; globallogic(value) => { return "global " + value; } } export component Test { callback test-callback(string) -> string; test-callback(value) => { return "local " + value; } callback void-callback(); } """, Path(""), ).component("Test") assert compdef is not None instance = compdef.create() assert instance is not None assert instance.invoke("test-callback", "foo") == "local foo" assert instance.invoke_global("TestGlobal", "globallogic", "foo") == "global foo" with pytest.raises(RuntimeError, match="no such callback"): instance.set_callback("non-existent", lambda x: x) instance.set_callback("test-callback", lambda x: "python " + x) assert instance.invoke("test-callback", "foo") == "python foo" with pytest.raises(RuntimeError, match="no such callback"): instance.set_global_callback("TestGlobal", "non-existent", lambda x: x) instance.set_global_callback( "TestGlobal", "globallogic", lambda x: "python global " + x ) assert ( instance.invoke_global("TestGlobal", "globallogic", "foo") == "python global foo" ) instance.set_callback("void-callback", lambda: None) instance.invoke("void-callback") if __name__ == "__main__": import slint module = slint.load_file(Path("../../../demos/printerdemo/ui/printerdemo.slint")) instance = module.MainWindow() instance.PrinterQueue.start_job = lambda title: print(f"new print job {title}") instance.run() ================================================ FILE: api/python/slint/tests/test_invoke_from_event_loop.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 from slint import slint as native import threading from datetime import timedelta was_here = False def test_threads() -> None: global was_here was_here = False def invoked_from_event_loop() -> None: global was_here was_here = True native.quit_event_loop() def quit() -> None: native.invoke_from_event_loop(invoked_from_event_loop) thr = threading.Thread(target=quit) native.Timer.single_shot(timedelta(milliseconds=10), lambda: thr.start()) fallback_timer = native.Timer() fallback_timer.start( native.TimerMode.Repeated, timedelta(milliseconds=100), native.quit_event_loop ) native.run_event_loop() thr.join() fallback_timer.stop() assert was_here ================================================ FILE: api/python/slint/tests/test_load_file.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import pytest from slint import load_file, CompileError from pathlib import Path def base_dir() -> Path: assert __spec__ origin = __spec__.origin assert origin is not None base_dir = Path(origin).parent assert base_dir is not None return base_dir def test_load_file(caplog: pytest.LogCaptureFixture) -> None: module = load_file(base_dir() / "test-load-file.slint", quiet=False) assert ( "The property 'color' has been deprecated. Please use 'background' instead" in caplog.text ) assert len(list(module.__dict__.keys())) == 7 assert "App" in module.__dict__ assert "Diag" in module.__dict__ assert "MyDiag" in module.__dict__ assert "MyData" in module.__dict__ assert "Secret_Struct" in module.__dict__ assert "Public_Struct" in module.__dict__ assert "TestEnum" in module.__dict__ instance = module.App() del instance instance = module.MyDiag() del instance struct_instance = module.MyData() struct_instance.name = "Test" struct_instance.age = 42 struct_instance = module.MyData(name="testing") assert struct_instance.name == "testing" assert module.Public_Struct is module.Secret_Struct assert module.MyDiag is module.Diag def test_load_file_fail() -> None: with pytest.raises(CompileError, match="Could not compile non-existent.slint"): load_file("non-existent.slint") def test_compile_error() -> None: with pytest.raises(CompileError) as excinfo: load_file(base_dir() / "test-file-error.slint") err = excinfo.value diagnostics = err.diagnostics assert len(diagnostics) == 2 assert diagnostics[0].message == "Unknown type 'garbage'" assert diagnostics[0].line_number == 6 assert diagnostics[0].column_number == 15 assert diagnostics[1].message == "Unknown element 'NewType'" assert diagnostics[1].line_number == 7 assert diagnostics[1].column_number == 5 path = base_dir() / "test-file-error.slint" assert str(err) == f"Could not compile {path}" assert len(err.__notes__) == 2 assert err.__notes__[0] == f"{path}:6: Unknown type 'garbage'" assert err.__notes__[1] == f"{path}:7: Unknown element 'NewType'" def test_load_file_wrapper() -> None: module = load_file(base_dir() / "test-load-file.slint", quiet=False) instance = module.App() assert instance.hello == "World" instance.hello = "Ok" assert instance.hello == "Ok" instance.say_hello = lambda x: "from here: " + x assert instance.say_hello("wohoo") == "from here: wohoo" assert instance.plus_one(42) == 43 assert instance.MyGlobal.global_prop == "This is global" assert instance.MyGlobal.minus_one(100) == 99 assert instance.SecondGlobal.second == "second" del instance def test_constructor_kwargs() -> None: module = load_file(base_dir() / "test-load-file.slint", quiet=False) def early_say_hello(arg: str) -> str: return "early:" + arg instance = module.App(hello="Set early", say_hello=early_say_hello) assert instance.hello == "Set early" assert instance.invoke_say_hello("test") == "early:test" del instance ================================================ FILE: api/python/slint/tests/test_loader.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 from slint import loader import sys import os def test_magic_import() -> None: instance = loader.test_load_file.App() del instance def test_magic_import_path() -> None: oldsyspath = sys.path assert loader.printerdemo is None try: sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")) instance = loader.demos.printerdemo.ui.printerdemo.MainWindow() del instance finally: sys.path = oldsyspath ================================================ FILE: api/python/slint/tests/test_loop.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import slint from slint import slint as native from datetime import timedelta import pytest import sys def test_sysexit_exception() -> None: def call_sys_exit() -> None: sys.exit(42) slint.Timer.single_shot(timedelta(milliseconds=100), call_sys_exit) with pytest.raises(SystemExit) as exc_info: native.run_event_loop() assert ( "unexpected failure running python singleshot timer callback" in exc_info.value.__notes__ ) ================================================ FILE: api/python/slint/tests/test_models.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 from slint import slint as native from slint import models as models import typing from pathlib import Path def test_model_notify() -> None: compiler = native.Compiler() compdef = compiler.build_from_source( """ export component App { width: 300px; height: 300px; out property layout-height: layout.height; in-out property<[length]> fixed-height-model; VerticalLayout { alignment: start; layout := VerticalLayout { for fixed-height in fixed-height-model: Rectangle { background: blue; height: fixed-height; } } } } """, Path(""), ).component("App") assert compdef is not None instance = compdef.create() assert instance is not None model = models.ListModel([100, 0]) instance.set_property("fixed-height-model", model) assert instance.get_property("layout-height") == 100 model.set_row_data(1, 50) assert instance.get_property("layout-height") == 150 model.append(75) assert instance.get_property("layout-height") == 225 del model[1:] assert instance.get_property("layout-height") == 100 assert isinstance(instance.get_property("fixed-height-model"), models.ListModel) def test_model_from_list() -> None: compiler = native.Compiler() compdef = compiler.build_from_source( """ export component App { in-out property<[int]> data: [1, 2, 3, 4]; } """, Path(""), ).component("App") assert compdef is not None instance = compdef.create() assert instance is not None model = instance.get_property("data") assert model.row_count() == 4 assert model.row_data(2) == 3 instance.set_property("data", models.ListModel([0])) instance.set_property("data", model) assert list(instance.get_property("data")) == [1, 2, 3, 4] def test_python_model_sequence() -> None: model = models.ListModel([1, 2, 3, 4, 5]) assert len(model) == 5 assert list(model) == [1, 2, 3, 4, 5] model[0] = 100 assert list(model) == [100, 2, 3, 4, 5] assert model[2] == 3 def test_python_model_iterable() -> None: def test_generator(max: int) -> typing.Iterator[int]: i = 0 while i < max: yield i i += 1 model = models.ListModel(test_generator(5)) assert len(model) == 5 assert list(model) == [0, 1, 2, 3, 4] def test_rust_model_sequence() -> None: compiler = native.Compiler() compdef = compiler.build_from_source( """ export component App { in-out property<[int]> data: [1, 2, 3, 4, 5]; } """, Path(""), ).component("App") assert compdef is not None instance = compdef.create() assert instance is not None model = instance.get_property("data") assert len(model) == 5 assert list(model) == [1, 2, 3, 4, 5] assert model[2] == 3 def test_model_writeback() -> None: compiler = native.Compiler() compdef = compiler.build_from_source( """ export component App { width: 300px; height: 300px; in-out property<[int]> model; callback write-to-model(int, int); write-to-model(index, value) => { self.model[index] = value } } """, Path(""), ).component("App") assert compdef is not None instance = compdef.create() assert instance is not None model = models.ListModel([100, 0]) instance.set_property("model", model) instance.invoke("write-to-model", 1, 42) assert list(instance.get_property("model")) == [100, 42] instance.invoke("write-to-model", 0, 25) assert list(instance.get_property("model")) == [25, 42] ================================================ FILE: api/python/slint/tests/test_named_tuples.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import pytest from slint.language import StandardListViewItem, KeyboardModifiers NAMED_TUPLES = [ (StandardListViewItem, {"text": ""}), ( KeyboardModifiers, {"shift": False, "control": False, "alt": False, "meta": False}, ), ] @pytest.fixture(params=NAMED_TUPLES, ids=lambda t: t[0].__name__) def named_tuple_info(request): return request.param def test_is_tuple_subclass(named_tuple_info): cls, _ = named_tuple_info assert issubclass(cls, tuple) def test_default_values(named_tuple_info): cls, defaults = named_tuple_info instance = cls() for field, expected in defaults.items(): assert getattr(instance, field) == expected def test_instance_is_tuple(named_tuple_info): cls, _ = named_tuple_info assert isinstance(cls(), tuple) def test_asdict(named_tuple_info): cls, defaults = named_tuple_info d = cls()._asdict() assert isinstance(d, dict) for field in defaults: assert field in d def test_has_docstring(named_tuple_info): cls, _ = named_tuple_info assert cls.__doc__ is not None assert len(cls.__doc__) > 0 def test_fields_attribute(named_tuple_info): cls, defaults = named_tuple_info assert hasattr(cls, "_fields") assert set(cls._fields) == set(defaults.keys()) def test_keyword_init(named_tuple_info): cls, defaults = named_tuple_info first_field = next(iter(defaults)) instance = cls(**{first_field: defaults[first_field]}) assert getattr(instance, first_field) == defaults[first_field] def test_replace(named_tuple_info): cls, defaults = named_tuple_info instance = cls() first_field = next(iter(defaults)) replaced = instance._replace(**{first_field: defaults[first_field]}) assert getattr(replaced, first_field) == defaults[first_field] def test_StandardListViewItem() -> None: item = StandardListViewItem() assert item.text == "" item = item._replace(text="Test") assert item.text == "Test" def test_KeyboardModifiers() -> None: # Test initialization with default values mods = KeyboardModifiers() assert mods.shift is False assert mods.control is False assert mods.alt is False assert mods.meta is False # Test initialization with arguments mods = KeyboardModifiers(shift=True, control=True, alt=True, meta=True) assert mods.shift is True assert mods.control is True assert mods.alt is True assert mods.meta is True # Test setters (_replace for NamedTuple) mods = mods._replace(shift=False) assert mods.shift is False mods = mods._replace(control=False) assert mods.control is False mods = mods._replace(alt=False) assert mods.alt is False mods = mods._replace(meta=False) assert mods.meta is False # Test equality mods2 = KeyboardModifiers() assert mods == mods2 mods3 = mods2._replace(shift=True) assert mods2 != mods3 ================================================ FILE: api/python/slint/tests/test_timers.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 from slint import slint as native from datetime import timedelta counter: int def test_timer() -> None: global counter counter = 0 def quit_after_two_invocations() -> None: global counter counter = min(counter + 1, 2) if counter == 2: native.quit_event_loop() test_timer = native.Timer() test_timer.start( native.TimerMode.Repeated, timedelta(milliseconds=100), quit_after_two_invocations, ) native.run_event_loop() test_timer.stop() assert counter == 2 def test_single_shot() -> None: native.Timer.single_shot(timedelta(milliseconds=100), native.quit_event_loop) native.run_event_loop() ================================================ FILE: api/python/slint/tests/test_translations.py ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 from slint import load_file, init_translations from pathlib import Path import gettext import typing def base_dir() -> Path: assert __spec__ origin = __spec__.origin assert origin is not None base_dir = Path(origin).parent assert base_dir is not None return base_dir class DummyTranslation: def gettext(self, message: str) -> str: if message == "Yes": return "Ja" return message def pgettext(self, context: str, message: str) -> str: return self.gettext(message) def test_load_file() -> None: module = load_file(base_dir() / "test-load-file.slint") testcase = module.App() assert testcase.translated == "Yes" init_translations(typing.cast(gettext.GNUTranslations, DummyTranslation())) try: assert testcase.translated == "Ja" finally: init_translations(None) assert testcase.translated == "Yes" ================================================ FILE: api/python/slint/thirdparty.hbs ================================================

Third Party Licenses

This page lists the licenses of the dependencies used by Slint.

Overview of licenses:

    {{#each overview}}
  • {{name}} ({{count}})
  • {{/each}}

All license text:

================================================ FILE: api/python/slint/timer.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use pyo3::prelude::*; use pyo3_stub_gen::{ derive::gen_stub_pyclass, derive::gen_stub_pyclass_enum, derive::gen_stub_pymethods, }; /// The TimerMode specifies what should happen after the timer fired. /// /// Used by the `Timer.start()` function. #[derive(Copy, Clone, PartialEq)] #[gen_stub_pyclass_enum] #[pyclass(name = "TimerMode", eq, eq_int, from_py_object)] pub enum PyTimerMode { /// A SingleShot timer is fired only once. SingleShot, /// A Repeated timer is fired repeatedly until it is stopped or dropped. Repeated, } impl From for i_slint_core::timers::TimerMode { fn from(value: PyTimerMode) -> Self { match value { PyTimerMode::SingleShot => i_slint_core::timers::TimerMode::SingleShot, PyTimerMode::Repeated => i_slint_core::timers::TimerMode::Repeated, } } } /// Timer is a handle to the timer system that triggers a callback after a specified /// period of time. /// /// Use `Timer.start()` to create a timer that that repeatedly triggers a callback, or /// `Timer.single_shot()` to trigger a callback only once. /// /// The timer will automatically stop when garbage collected. You must keep the Timer object /// around for as long as you want the timer to keep firing. /// /// ```python /// class AppWindow(...) /// def __init__(self): /// super().__init__() /// self.my_timer = None /// /// @slint.callback /// def button_clicked(self): /// self.my_timer = slint.Timer() /// self.my_timer.start(timedelta(seconds=1), self.do_something) /// /// def do_something(self): /// pass /// ``` /// /// Timers can only be used in the thread that runs the Slint event loop. They don't /// fire if used in another thread. #[gen_stub_pyclass] #[pyclass(name = "Timer", unsendable)] pub struct PyTimer { timer: i_slint_core::timers::Timer, } #[gen_stub_pymethods] #[pymethods] impl PyTimer { #[new] fn py_new() -> Self { PyTimer { timer: Default::default() } } /// Starts the timer with the given mode and interval, in order for the callback to called when the /// timer fires. If the timer has been started previously and not fired yet, then it will be restarted. /// /// Arguments: /// * `mode`: The timer mode to apply, i.e. whether to repeatedly fire the timer or just once. /// * `interval`: The duration from now until when the timer should firethe first time, and subsequently /// for `TimerMode.Repeated` timers. /// * `callback`: The function to call when the time has been reached or exceeded. fn start( &self, mode: PyTimerMode, interval: chrono::Duration, callback: Py, ) -> PyResult<()> { let interval = interval .to_std() .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; self.timer.start(mode.into(), interval, move || { Python::attach(|py| { if let Err(err) = callback.call0(py) { crate::handle_unraisable( py, "unexpected failure running python timer callback".into(), err, ); } }); }); Ok(()) } /// Starts the timer with the duration and the callback to called when the /// timer fires. It is fired only once and then deleted. /// /// Arguments: /// * `duration`: The duration from now until when the timer should fire. /// * `callback`: The function to call when the time has been reached or exceeded. #[staticmethod] fn single_shot(duration: chrono::Duration, callback: Py) -> PyResult<()> { let duration = duration .to_std() .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; i_slint_core::timers::Timer::single_shot(duration, move || { Python::attach(|py| { if let Err(err) = callback.call0(py) { crate::handle_unraisable( py, "unexpected failure running python singleshot timer callback".into(), err, ); } }); }); Ok(()) } /// Stops the previously started timer. Does nothing if the timer has never been started. fn stop(&self) { self.timer.stop(); } /// Restarts the timer. If the timer was previously started by calling `Timer.start()` /// with a duration and callback, then the time when the callback will be next invoked /// is re-calculated to be in the specified duration relative to when this function is called. /// /// Does nothing if the timer was never started. fn restart(&self) { self.timer.restart(); } /// Set to true if the timer is running; false otherwise. #[getter] fn running(&self) -> bool { self.timer.running() } /// The duration of timer. /// /// When setting this property and the timer is running (see `Timer.running`), /// then the time when the callback will be next invoked is re-calculated to be in the /// specified duration relative to when this property is set. #[setter] fn set_interval(&self, interval: chrono::Duration) -> PyResult<()> { let interval = interval .to_std() .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; self.timer.set_interval(interval); Ok(()) } #[getter] fn interval(&self) -> core::time::Duration { self.timer.interval() } } ================================================ FILE: api/python/slint/value.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use i_slint_compiler::generator::python::ident; use pyo3::types::PyDict; use pyo3::{IntoPyObjectExt, PyTraverseError}; use pyo3::{PyVisit, prelude::*}; use pyo3_stub_gen::{derive::gen_stub_pyclass, derive::gen_stub_pymethods}; use std::cell::OnceCell; use std::collections::HashMap; use std::rc::Rc; use i_slint_compiler::langtype::Type; use i_slint_core::model::{Model, ModelRc}; #[gen_stub_pyclass] pub struct SlintToPyValue { pub slint_value: slint_interpreter::Value, pub type_collection: TypeCollection, } impl<'py> IntoPyObject<'py> for SlintToPyValue { type Target = PyAny; type Output = Bound<'py, Self::Target>; type Error = PyErr; fn into_pyobject(self, py: Python<'py>) -> Result { let type_collection = self.type_collection; use slint_interpreter::Value; match self.slint_value { Value::Void => ().into_bound_py_any(py), Value::Number(num) => num.into_bound_py_any(py), Value::String(str) => str.into_bound_py_any(py), Value::Bool(b) => b.into_bound_py_any(py), Value::Image(image) => crate::image::PyImage::from(image).into_bound_py_any(py), Value::Model(model) => crate::models::PyModelShared::rust_into_py_model(&model, py) .map_or_else( || type_collection.model_to_py(&model).into_bound_py_any(py), |m| Ok(m), ), Value::Struct(structval) => { type_collection.struct_to_py(structval).into_bound_py_any(py) } Value::Brush(brush) => crate::brush::PyBrush::from(brush).into_bound_py_any(py), Value::EnumerationValue(enum_name, enum_value) => { type_collection.enum_to_py(&enum_name, &enum_value, py)?.into_bound_py_any(py) } Value::Keys(keys) => format!("{keys:?}").into_bound_py_any(py), v @ _ => { eprintln!( "Python: conversion from slint to python needed for {v:#?} and not implemented yet" ); ().into_bound_py_any(py) } } } } pub fn traverse_value( value: &slint_interpreter::Value, visit: &PyVisit<'_>, ) -> Result<(), PyTraverseError> { match value { slint_interpreter::Value::Model(model) => { if let Some(rust_model) = model.as_any().downcast_ref::() { rust_model.__traverse__(&visit)? } } slint_interpreter::Value::Struct(structval) => traverse_struct(&structval, visit)?, _ => {} } Ok(()) } fn traverse_struct( structval: &slint_interpreter::Struct, visit: &PyVisit<'_>, ) -> Result<(), PyTraverseError> { for (_, value) in structval.iter() { traverse_value(value, visit)?; } Ok(()) } pub fn clear_strongrefs_in_value(value: &slint_interpreter::Value) { match value { slint_interpreter::Value::Model(model) => { if let Some(rust_model) = model.as_any().downcast_ref::() { rust_model.__clear__(); } } slint_interpreter::Value::Struct(structval) => clear_strongrefs_in_struct(&structval), _ => {} } } fn clear_strongrefs_in_struct(structval: &slint_interpreter::Struct) { for (_, value) in structval.iter() { clear_strongrefs_in_value(value); } } #[gen_stub_pyclass] #[pyclass(subclass, unsendable, skip_from_py_object)] #[derive(Clone)] pub struct PyStruct { pub data: slint_interpreter::Struct, pub type_collection: TypeCollection, } #[pymethods] impl PyStruct { fn __getattr__(&self, key: &str) -> PyResult { self.data.get_field(key).map_or_else( || { Err(pyo3::exceptions::PyAttributeError::new_err(format!( "Python: No such field {key} on PyStruct" ))) }, |value| Ok(self.type_collection.to_py_value(value.clone())), ) } fn __setattr__(&mut self, py: Python<'_>, key: String, value: Py) -> PyResult<()> { let pv = TypeCollection::slint_value_from_py_value(py, &value, Some(&self.type_collection))?; self.data.set_field(key, pv); Ok(()) } fn __iter__(slf: PyRef<'_, Self>) -> PyStructFieldIterator { PyStructFieldIterator { inner: slf .data .iter() .map(|(name, val)| (name.to_string(), val.clone())) .collect::>() .into_iter(), type_collection: slf.type_collection.clone(), } } fn __copy__(&self) -> Self { self.clone() } fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> { traverse_struct(&self.data, &visit) } fn __clear__(&mut self) { for (_, value) in self.data.iter() { clear_strongrefs_in_value(&value); } } } #[gen_stub_pyclass] #[pyclass(unsendable)] struct PyStructFieldIterator { inner: std::collections::hash_map::IntoIter, type_collection: TypeCollection, } #[gen_stub_pymethods] #[pymethods] impl PyStructFieldIterator { fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<(String, SlintToPyValue)> { slf.inner.next().map(|(name, val)| (name, slf.type_collection.to_py_value(val))) } } thread_local! { static ENUM_CLASS: OnceCell> = OnceCell::new(); } pub fn enum_class(py: Python) -> Py { ENUM_CLASS.with(|cls| { cls.get_or_init(|| -> Py { let enum_module = py.import("enum").unwrap(); enum_module.getattr("Enum").unwrap().into() }) .clone_ref(py) }) } #[derive(Clone)] /// Struct that knows about the enums (and maybe other types) exported by /// a `.slint` file loaded with load_file. This is used to map enums /// provided by Slint to the correct python enum classes. pub struct TypeCollection { enum_classes: Rc>>, } impl TypeCollection { pub fn new(result: &slint_interpreter::CompilationResult, py: Python<'_>) -> Self { let mut enum_classes = HashMap::new(); let enum_ctor = crate::value::enum_class(py); for struct_or_enum in result.structs_and_enums(i_slint_core::InternalToken {}) { match struct_or_enum { Type::Enumeration(en) => { let enum_type = enum_ctor .call( py, ( en.name.to_string(), en.values .iter() .map(|val| (ident(&val).to_string(), val.to_string())) .collect::>(), ), None, ) .unwrap(); enum_classes.insert(ident(&en.name).into(), enum_type); } _ => {} } } let enum_classes = Rc::new(enum_classes); Self { enum_classes } } pub fn to_py_value(&self, value: slint_interpreter::Value) -> SlintToPyValue { SlintToPyValue { slint_value: value, type_collection: self.clone() } } pub fn struct_to_py(&self, s: slint_interpreter::Struct) -> PyStruct { PyStruct { data: s, type_collection: self.clone() } } pub fn enum_to_py( &self, enum_name: &str, enum_value: &str, py: Python<'_>, ) -> Result, PyErr> { let enum_cls = self.enum_classes.get(ident(enum_name).as_str()).ok_or_else(|| { PyErr::new::(format!( "Slint provided enum {enum_name} is unknown" )) })?; enum_cls.getattr(py, enum_value) } pub fn model_to_py( &self, model: &ModelRc, ) -> crate::models::ReadOnlyRustModel { crate::models::ReadOnlyRustModel { model: model.clone(), type_collection: self.clone() } } pub fn enums(&self) -> impl Iterator)> { self.enum_classes.iter() } pub fn slint_value_from_py_value( py: Python<'_>, ob: &Py, type_collection: Option<&Self>, ) -> PyResult { Self::slint_value_from_py_value_bound(&ob.bind(py), type_collection) } pub fn slint_value_from_py_value_bound( ob: &Bound<'_, PyAny>, type_collection: Option<&Self>, ) -> PyResult { if ob.is_none() { return Ok(slint_interpreter::Value::Void); } let interpreter_val = ob .extract::() .map(|b| slint_interpreter::Value::Bool(b)) .or_else(|_| { ob.extract::<&'_ str>().map(|s| slint_interpreter::Value::String(s.into())) }) .or_else(|_| ob.extract::().map(|num| slint_interpreter::Value::Number(num))) .or_else(|_| { ob.extract::>() .map(|pyimg| slint_interpreter::Value::Image(pyimg.image.clone())) }) .or_else(|_| { ob.extract::>() .map(|pybrush| slint_interpreter::Value::Brush(pybrush.brush.clone())) }) .or_else(|_| { ob.extract::>() .map(|pycolor| slint_interpreter::Value::Brush(pycolor.color.clone().into())) }) .or_else(|_| { ob.extract::>().map(|pymodel| { slint_interpreter::Value::Model(Self::apply( type_collection, pymodel.as_model(), )) }) }) .or_else(|_| { ob.extract::>().map(|rustmodel| { slint_interpreter::Value::Model(Self::apply( type_collection, rustmodel.model.clone(), )) }) }) .or_else(|_| { ob.extract::>().and_then(|pystruct| { Ok(slint_interpreter::Value::Struct(pystruct.data.clone())) }) }) .or_else(|_| { ob.is_instance(&enum_class(ob.py()).into_bound(ob.py())).and_then(|r| { r.then(|| { let enum_name = ob.getattr("__class__").and_then(|cls| cls.getattr("__name__"))?; let enum_value = ob.getattr("name")?; Ok(slint_interpreter::Value::EnumerationValue( enum_name.to_string(), enum_value.to_string(), )) }) .unwrap_or_else(|| { Err(PyErr::new::( "Object to convert is not an enum", )) }) }) }) .or_else(|_| { // Try NamedTuple conversion first, then fall back to direct PyDict cast. // NamedTuples (e.g. StandardListViewItem) are tuple subclasses registered // as `typing.NamedTuple` in language.rs. We guard with an isinstance(ob, tuple) // check to avoid false positives from unrelated types that also have `_asdict`. let dict = if ob.is_instance_of::() && ob.hasattr(pyo3::intern!(ob.py(), "_fields")).unwrap_or(false) { let asdict = ob.call_method0(pyo3::intern!(ob.py(), "_asdict"))?; asdict.cast::().cloned().map_err(|e| -> PyErr { e.into() }) } else { ob.cast::().cloned().map_err(|_| { pyo3::exceptions::PyTypeError::new_err("Object is not a dict or NamedTuple") }) }?; let dict_items: Result, PyErr> = dict .iter() .map(|(name, pyval)| { let name = name.extract::<&str>()?.to_string(); let slintval = Self::slint_value_from_py_value_bound(&pyval, type_collection)?; Ok((name, slintval)) }) .collect::, PyErr>>(); Ok::<_, PyErr>(slint_interpreter::Value::Struct( slint_interpreter::Struct::from_iter(dict_items?.into_iter()), )) })?; Ok(interpreter_val) } fn apply( type_collection: Option<&Self>, model: ModelRc, ) -> ModelRc { let Some(type_collection) = type_collection else { return model; }; if let Some(rust_model) = model.as_any().downcast_ref::() { rust_model.apply_type_collection(type_collection); } model } } ================================================ FILE: api/rs/build/Cargo.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 [package] name = "slint-build" description = "Helper for Slint build script" authors.workspace = true edition = "2024" homepage = "https://slint.rs" keywords.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true version.workspace = true categories = ["gui", "rendering::engine", "development-tools::build-utils"] [lib] path = "lib.rs" [features] default = [] ## Enable the [`CompilerConfiguration::with_sdf_fonts`] function that encodes the font's glyphs as scalable signed distance fields, to reduce the size. sdf-fonts = ["i-slint-compiler/sdf-fonts"] ## Enable this feature when building `.slint` libraries. ## This feature is experimental and may change or be removed in the future, ## enabling it implies opting out of [semver](https://semver.org). ## For details of this experiment, see: experimental-module-builds = ["i-slint-compiler/experimental-library-module"] [dependencies] i-slint-compiler = { workspace = true, features = ["default", "rust", "display-diagnostics", "software-renderer", "bundle-translations"] } spin_on = { workspace = true } toml_edit = { workspace = true } derive_more = { workspace = true, features = ["std", "error"] } [target.'cfg(target_os = "linux")'.dependencies] # Workaround cross compiling on linux where here in slint-build we depend on fontique through the software # renderer compiler feature but fontconfig's build script find the target library via pkg-config and causes link errors fontique = { workspace = true, features = ["fontconfig-dlopen"] } [package.metadata.docs.rs] features = ["sdf-fonts"] rustdoc-args = ["--generate-link-to-definition"] ================================================ FILE: api/rs/build/lib.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 /*! This crate serves as a companion crate of the slint crate. It is meant to allow you to compile the `.slint` files from your `build.rs` script. The main entry point of this crate is the [`compile()`] function The generated code must be included in your crate by using the `slint::include_modules!()` macro. ## Example In your Cargo.toml: ```toml [package] ... build = "build.rs" [dependencies] slint = "1.15" ... [build-dependencies] slint-build = "1.15" ``` In the `build.rs` file: ```ignore fn main() { slint_build::compile("ui/hello.slint").unwrap(); } ``` Then in your main file ```ignore slint::include_modules!(); fn main() { HelloWorld::new().run(); } ``` */ #![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")] #![warn(missing_docs)] #[cfg(not(feature = "default"))] compile_error!( "The feature `default` must be enabled to ensure \ forward compatibility with future version of this crate" ); use std::collections::HashMap; use std::env; use std::io::{BufWriter, Write}; use std::path::Path; use i_slint_compiler::diagnostics::BuildDiagnostics; /// Argument of [`CompilerConfiguration::with_default_translation_context()`] /// pub use i_slint_compiler::DefaultTranslationContext; /// The structure for configuring aspects of the compilation of `.slint` markup files to Rust. #[derive(Clone)] pub struct CompilerConfiguration { config: i_slint_compiler::CompilerConfiguration, } /// How should the slint compiler embed images and fonts /// /// Parameter of [`CompilerConfiguration::embed_resources()`] #[derive(Clone, PartialEq)] pub enum EmbedResourcesKind { /// Paths specified in .slint files are made absolute and the absolute /// paths will be used at run-time to load the resources from the file system. AsAbsolutePath, /// The raw files in .slint files are embedded in the application binary. EmbedFiles, /// File names specified in .slint files will be loaded by the Slint compiler, /// optimized for use with the software renderer and embedded in the application binary. EmbedForSoftwareRenderer, } impl Default for CompilerConfiguration { fn default() -> Self { Self { config: i_slint_compiler::CompilerConfiguration::new( i_slint_compiler::generator::OutputFormat::Rust, ), } } } impl CompilerConfiguration { /// Creates a new default configuration. pub fn new() -> Self { Self::default() } /// Create a new configuration that includes sets the include paths used for looking up /// `.slint` imports to the specified vector of paths. #[must_use] pub fn with_include_paths(self, include_paths: Vec) -> Self { let mut config = self.config; config.include_paths = include_paths; Self { config } } /// Create a new configuration that sets the library paths used for looking up /// `@library` imports to the specified map of paths. /// /// Each library path can either be a path to a `.slint` file or a directory. /// If it's a file, the library is imported by its name prefixed by `@` (e.g. /// `@example`). The specified file is the only entry-point for the library /// and other files from the library won't be accessible from the outside. /// If it's a directory, a specific file in that directory must be specified /// when importing the library (e.g. `@example/widgets.slint`). This allows /// exposing multiple entry-points for a single library. /// /// Compile `ui/main.slint` and specify an "example" library path: /// ```rust,no_run /// let manifest_dir = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()); /// let library_paths = std::collections::HashMap::from([( /// "example".to_string(), /// manifest_dir.join("third_party/example/ui/lib.slint"), /// )]); /// let config = slint_build::CompilerConfiguration::new().with_library_paths(library_paths); /// slint_build::compile_with_config("ui/main.slint", config).unwrap(); /// ``` /// /// Import the "example" library in `ui/main.slint`: /// ```slint,ignore /// import { Example } from "@example"; /// ``` #[must_use] pub fn with_library_paths(self, library_paths: HashMap) -> Self { let mut config = self.config; config.library_paths = library_paths; Self { config } } /// Create a new configuration that selects the style to be used for widgets. #[must_use] pub fn with_style(self, style: String) -> Self { let mut config = self.config; config.style = Some(style); Self { config } } /// Selects how the resources such as images and font are processed. /// /// See [`EmbedResourcesKind`] #[must_use] pub fn embed_resources(self, kind: EmbedResourcesKind) -> Self { let mut config = self.config; config.embed_resources = match kind { EmbedResourcesKind::AsAbsolutePath => { i_slint_compiler::EmbedResourcesKind::OnlyBuiltinResources } EmbedResourcesKind::EmbedFiles => { i_slint_compiler::EmbedResourcesKind::EmbedAllResources } EmbedResourcesKind::EmbedForSoftwareRenderer => { i_slint_compiler::EmbedResourcesKind::EmbedTextures } }; Self { config } } /// Sets the scale factor to be applied to all `px` to `phx` conversions /// as constant value. This is only intended for MCU environments. Use /// in combination with [`Self::embed_resources`] to pre-scale images and glyphs /// accordingly. /// /// If this is set, changing the scale factor at runtime will not have any effect. #[must_use] pub fn with_scale_factor(mut self, factor: f32) -> Self { self.config.const_scale_factor = Some(factor); self } /// Configures the compiler to bundle translations when compiling Slint code. /// /// It expects the path to be the root directory of the translation files. /// /// If given a relative path, it will be resolved relative to `$CARGO_MANIFEST_DIR`. /// /// The translation files should be in the gettext `.po` format and follow this pattern: /// `//LC_MESSAGES/.po` #[must_use] pub fn with_bundled_translations( self, path: impl Into, ) -> CompilerConfiguration { let mut config = self.config; config.translation_path_bundle = Some(path.into()); Self { config } } /// Unless explicitly specified with the `@tr("context" => ...)`, the default translation context is the component name. /// Use this option with [`DefaultTranslationContext::None`] to disable the default translation context. /// /// The translation file must also not have context /// (`--no-default-translation-context` argument of `slint-tr-extractor`) #[must_use] pub fn with_default_translation_context( mut self, default_translation_context: DefaultTranslationContext, ) -> Self { self.config.default_translation_context = default_translation_context; self } /// Configures the compiler to emit additional debug info when compiling Slint code. /// /// This is the equivalent to setting `SLINT_EMIT_DEBUG_INFO=1` and using the `slint!()` macro /// and is primarily used by `i-slint-backend-testing`. #[doc(hidden)] #[must_use] pub fn with_debug_info(self, enable: bool) -> Self { let mut config = self.config; config.debug_info = enable; Self { config } } /// Configures the compiler to treat the Slint as part of a library. /// /// Use this when the components and types of the Slint code need /// to be accessible from other modules. /// /// **Note**: This feature is experimental and may change or be removed in the future. #[cfg(feature = "experimental-module-builds")] #[must_use] pub fn as_library(self, library_name: &str) -> Self { let mut config = self.config; config.library_name = Some(library_name.to_string()); Self { config } } /// Specify the Rust module to place the generated code in. /// /// **Note**: This feature is experimental and may change or be removed in the future. #[cfg(feature = "experimental-module-builds")] #[must_use] pub fn rust_module(self, rust_module: &str) -> Self { let mut config = self.config; config.rust_module = Some(rust_module.to_string()); Self { config } } /// Configures the compiler to use Signed Distance Field (SDF) encoding for fonts. /// /// This flag only takes effect when `embed_resources` is set to [`EmbedResourcesKind::EmbedForSoftwareRenderer`], /// and requires the `sdf-fonts` cargo feature to be enabled. /// /// [SDF](https://en.wikipedia.org/wiki/Signed_distance_function) reduces the binary size by /// using an alternative representation for fonts, trading off some rendering quality /// for a smaller binary footprint. /// Rendering is slower and may result in slightly inferior visual output. /// Use this on systems with limited flash memory. #[cfg(feature = "sdf-fonts")] #[must_use] pub fn with_sdf_fonts(self, enable: bool) -> Self { let mut config = self.config; config.use_sdf_fonts = enable; Self { config } } /// Converts any relative include_paths or library_paths to absolute paths relative to the manifest_dir. #[must_use] fn with_absolute_paths(self, manifest_dir: &std::path::Path) -> Self { let mut config = self.config; let to_absolute_path = |path: &mut std::path::PathBuf| { if path.is_relative() { *path = manifest_dir.join(&path); } }; for path in config.library_paths.values_mut() { to_absolute_path(path); } for path in config.include_paths.iter_mut() { to_absolute_path(path); } Self { config } } } /// Error returned by the `compile` function #[derive(derive_more::Error, derive_more::Display, Debug)] #[non_exhaustive] pub enum CompileError { /// Cannot read environment variable CARGO_MANIFEST_DIR or OUT_DIR. The build script need to be run via cargo. #[display( "Cannot read environment variable CARGO_MANIFEST_DIR or OUT_DIR. The build script need to be run via cargo." )] NotRunViaCargo, /// Parse error. The error are printed in the stderr, and also are in the vector #[display("{_0:?}")] CompileError(#[error(not(source))] Vec), /// Cannot write the generated file #[display("Cannot write the generated file: {_0}")] SaveError(std::io::Error), } struct CodeFormatter { indentation: usize, /// We are currently in a string in_string: bool, /// number of bytes after the last `'`, 0 if there was none in_char: usize, /// In string or char, and the previous character was `\\` escaped: bool, sink: Sink, } impl CodeFormatter { pub fn new(sink: Sink) -> Self { Self { indentation: 0, in_string: false, in_char: 0, escaped: false, sink } } } impl Write for CodeFormatter { fn write(&mut self, mut s: &[u8]) -> std::io::Result { let len = s.len(); while let Some(idx) = s.iter().position(|c| match c { b'{' if !self.in_string && self.in_char == 0 => { self.indentation += 1; true } b'}' if !self.in_string && self.in_char == 0 => { self.indentation -= 1; true } b';' if !self.in_string && self.in_char == 0 => true, b'"' if !self.in_string && self.in_char == 0 => { self.in_string = true; self.escaped = false; false } b'"' if self.in_string && !self.escaped => { self.in_string = false; false } b'\'' if !self.in_string && self.in_char == 0 => { self.in_char = 1; self.escaped = false; false } b'\'' if !self.in_string && self.in_char > 0 && !self.escaped => { self.in_char = 0; false } b' ' | b'>' if self.in_char > 2 && !self.escaped => { // probably a lifetime self.in_char = 0; false } b'\\' if (self.in_string || self.in_char > 0) && !self.escaped => { self.escaped = true; // no need to increment in_char since \ isn't a single character false } _ if self.in_char > 0 => { self.in_char += 1; self.escaped = false; false } _ => { self.escaped = false; false } }) { let idx = idx + 1; self.sink.write_all(&s[..idx])?; self.sink.write_all(b"\n")?; for _ in 0..self.indentation { self.sink.write_all(b" ")?; } s = &s[idx..]; } self.sink.write_all(s)?; Ok(len) } fn flush(&mut self) -> std::io::Result<()> { self.sink.flush() } } #[test] fn formatter_test() { fn format_code(code: &str) -> String { let mut res = Vec::new(); let mut formatter = CodeFormatter::new(&mut res); formatter.write_all(code.as_bytes()).unwrap(); String::from_utf8(res).unwrap() } assert_eq!( format_code("fn main() { if ';' == '}' { return \";\"; } else { panic!() } }"), r#"fn main() { if ';' == '}' { return ";"; } else { panic!() } } "# ); assert_eq!( format_code(r#"fn xx<'lt>(foo: &'lt str) { println!("{}", '\u{f700}'); return Ok(()); }"#), r#"fn xx<'lt>(foo: &'lt str) { println!("{}", '\u{f700}'); return Ok(()); } "# ); assert_eq!( format_code(r#"fn main() { ""; "'"; "\""; "{}"; "\\"; "\\\""; }"#), r#"fn main() { ""; "'"; "\""; "{}"; "\\"; "\\\""; } "# ); assert_eq!( format_code(r#"fn main() { '"'; '\''; '{'; '}'; '\\'; }"#), r#"fn main() { '"'; '\''; '{'; '}'; '\\'; } "# ); } /// Compile the `.slint` file and generate rust code for it. /// /// The generated code code will be created in the directory specified by /// the `OUT` environment variable as it is expected for build script. /// /// The following line need to be added within your crate in order to include /// the generated code. /// ```ignore /// slint::include_modules!(); /// ``` /// /// The path is relative to the `CARGO_MANIFEST_DIR`. /// /// In case of compilation error, the errors are shown in `stderr`, the error /// are also returned in the [`CompileError`] enum. You must `unwrap` the returned /// result to make sure that cargo make the compilation fail in case there were /// errors when generating the code. /// /// Please check out the documentation of the `slint` crate for more information /// about how to use the generated code. /// /// This function can only be called within a build script run by cargo. /// /// See also [`compile_with_config()`] if you want to specify a configuration. pub fn compile(path: impl AsRef) -> Result<(), CompileError> { compile_with_config(path, CompilerConfiguration::default()) } /// Same as [`compile`], but allow to specify a configuration. /// /// Compile `ui/hello.slint` and select the "material" style: /// ```rust,no_run /// let config = /// slint_build::CompilerConfiguration::new() /// .with_style("material".into()); /// slint_build::compile_with_config("ui/hello.slint", config).unwrap(); /// ``` pub fn compile_with_config( relative_slint_file_path: impl AsRef, config: CompilerConfiguration, ) -> Result<(), CompileError> { let manifest_path = std::path::PathBuf::from( env::var_os("CARGO_MANIFEST_DIR").ok_or(CompileError::NotRunViaCargo)?, ); let config = config.with_absolute_paths(&manifest_path); let path = manifest_path.join(relative_slint_file_path.as_ref()); let absolute_rust_output_file_path = Path::new(&env::var_os("OUT_DIR").ok_or(CompileError::NotRunViaCargo)?).join( path.file_stem() .map(Path::new) .unwrap_or_else(|| Path::new("slint_out")) .with_extension("rs"), ); #[cfg(feature = "experimental-module-builds")] if let Some(library_name) = config.config.library_name.clone() { println!("cargo::metadata=SLINT_LIBRARY_NAME={}", library_name); println!( "cargo::metadata=SLINT_LIBRARY_PACKAGE={}", std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default() ); println!("cargo::metadata=SLINT_LIBRARY_SOURCE={}", path.display()); if let Some(rust_module) = &config.config.rust_module { println!("cargo::metadata=SLINT_LIBRARY_MODULE={}", rust_module); } } let paths_dependencies = compile_with_output_path(path, absolute_rust_output_file_path.clone(), config)?; for path_dependency in paths_dependencies { println!("cargo:rerun-if-changed={}", path_dependency.display()); } println!("cargo:rerun-if-env-changed=SLINT_STYLE"); println!("cargo:rerun-if-env-changed=SLINT_FONT_SIZES"); println!("cargo:rerun-if-env-changed=SLINT_SCALE_FACTOR"); println!("cargo:rerun-if-env-changed=SLINT_ASSET_SECTION"); println!("cargo:rerun-if-env-changed=SLINT_EMBED_RESOURCES"); println!("cargo:rerun-if-env-changed=SLINT_EMIT_DEBUG_INFO"); println!("cargo:rerun-if-env-changed=SLINT_LIVE_PREVIEW"); println!( "cargo:rustc-env=SLINT_INCLUDE_GENERATED={}", absolute_rust_output_file_path.display() ); Ok(()) } /// Similar to [`compile_with_config`], but meant to be used independently of cargo. /// /// Will compile the input file and write the result in the given output file. /// /// Both input_slint_file_path and output_rust_file_path should be absolute paths. /// /// Doesn't print any cargo messages. /// /// Returns a list of all input files that were used to generate the output file. (dependencies) pub fn compile_with_output_path( input_slint_file_path: impl AsRef, output_rust_file_path: impl AsRef, config: CompilerConfiguration, ) -> Result, CompileError> { let mut diag = BuildDiagnostics::default(); let syntax_node = i_slint_compiler::parser::parse_file(&input_slint_file_path, &mut diag); if diag.has_errors() { let vec = diag.to_string_vec(); diag.print(); return Err(CompileError::CompileError(vec)); } let mut compiler_config = config.config; compiler_config.translation_domain = std::env::var("CARGO_PKG_NAME").ok(); let syntax_node = syntax_node.expect("diags contained no compilation errors"); // 'spin_on' is ok here because the compiler in single threaded and does not block if there is no blocking future let (doc, diag, loader) = spin_on::spin_on(i_slint_compiler::compile_syntax_node(syntax_node, diag, compiler_config)); if diag.has_errors() || (!diag.is_empty() && std::env::var("SLINT_COMPILER_DENY_WARNINGS").is_ok()) { let vec = diag.to_string_vec(); diag.print(); return Err(CompileError::CompileError(vec)); } let output_file = std::fs::File::create(&output_rust_file_path).map_err(CompileError::SaveError)?; let mut code_formatter = CodeFormatter::new(BufWriter::new(output_file)); let generated = i_slint_compiler::generator::rust::generate(&doc, &loader.compiler_config) .map_err(|e| CompileError::CompileError(vec![e.to_string()]))?; let mut dependencies: Vec = Vec::new(); for x in &diag.all_loaded_files { if x.is_absolute() { dependencies.push(x.clone()); } } // print warnings diag.diagnostics_as_string().lines().for_each(|w| { if !w.is_empty() { println!("cargo:warning={}", w.strip_prefix("warning: ").unwrap_or(w)) } }); write!(code_formatter, "{generated}").map_err(CompileError::SaveError)?; dependencies.push(input_slint_file_path.as_ref().to_path_buf()); for resource in doc.embedded_file_resources.borrow().keys() { if !resource.starts_with("builtin:") { dependencies.push(Path::new(resource).to_path_buf()); } } code_formatter.sink.flush().map_err(CompileError::SaveError)?; Ok(dependencies) } /// This function is for use the application's build script, in order to print any device specific /// build flags reported by the backend pub fn print_rustc_flags() -> std::io::Result<()> { if let Some(board_config_path) = std::env::var_os("DEP_MCU_BOARD_SUPPORT_BOARD_CONFIG_PATH").map(std::path::PathBuf::from) { let config = std::fs::read_to_string(board_config_path.as_path())?; let toml = config.parse::().expect("invalid board config toml"); for link_arg in toml.get("link_args").and_then(toml_edit::Item::as_array).into_iter().flatten() { if let Some(option) = link_arg.as_str() { println!("cargo:rustc-link-arg={option}"); } } for link_search_path in toml.get("link_search_path").and_then(toml_edit::Item::as_array).into_iter().flatten() { if let Some(mut path) = link_search_path.as_str().map(std::path::PathBuf::from) { if path.is_relative() { path = board_config_path.parent().unwrap().join(path); } println!("cargo:rustc-link-search={}", path.to_string_lossy()); } } println!("cargo:rerun-if-env-changed=DEP_MCU_BOARD_SUPPORT_MCU_BOARD_CONFIG_PATH"); println!("cargo:rerun-if-changed={}", board_config_path.display()); } Ok(()) } #[cfg(test)] fn root_path_prefix() -> std::path::PathBuf { #[cfg(windows)] return std::path::PathBuf::from("C:/"); #[cfg(not(windows))] return std::path::PathBuf::from("/"); } #[test] fn with_absolute_library_paths_test() { use std::path::PathBuf; let library_paths = std::collections::HashMap::from([ ("relative".to_string(), PathBuf::from("some/relative/path")), ("absolute".to_string(), root_path_prefix().join("some/absolute/path")), ]); let config = CompilerConfiguration::new().with_library_paths(library_paths); let manifest_path = root_path_prefix().join("path/to/manifest"); let absolute_config = config.clone().with_absolute_paths(&manifest_path); let relative = &absolute_config.config.library_paths["relative"]; assert!(relative.is_absolute()); assert!(relative.starts_with(&manifest_path)); assert!(!absolute_config.config.library_paths["absolute"].starts_with(&manifest_path)); } #[test] fn with_absolute_include_paths_test() { use std::path::PathBuf; let config = CompilerConfiguration::new().with_include_paths(Vec::from([ root_path_prefix().join("some/absolute/path"), PathBuf::from("some/relative/path"), ])); let manifest_path = root_path_prefix().join("path/to/manifest"); let absolute_config = config.clone().with_absolute_paths(&manifest_path); assert_eq!( absolute_config.config.include_paths, Vec::from([ root_path_prefix().join("some/absolute/path"), manifest_path.join("some/relative/path"), ]) ) } ================================================ FILE: api/rs/macros/Cargo.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 [package] name = "slint-macros" description = "Macro helper for slint crate" authors.workspace = true edition = "2024" homepage = "https://slint.rs" keywords.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true version.workspace = true categories = ["gui", "development-tools"] [lib] proc-macro = true path = "lib.rs" [features] default = [] [dependencies] i-slint-compiler = { workspace = true, features = ["default", "proc_macro_span", "rust", "display-diagnostics"] } proc-macro2 = "1.0.17" quote = "1.0" spin_on = { workspace = true } [package.metadata.docs.rs] rustdoc-args = ["--generate-link-to-definition"] ================================================ FILE: api/rs/macros/README.md ================================================ **NOTE**: This library is an **internal** crate for the [Slint project](https://slint.rs). This crate should **not be used directly** by applications using Slint. You should use the `slint` crate instead. **WARNING**: This crate does not follow the semver convention for versioning and can only be used with `version = "=x.y.z"` in Cargo.toml. ================================================ FILE: api/rs/macros/lib.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 // cSpell:ignore punct #![doc = include_str!("README.md")] #![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")] extern crate proc_macro; use i_slint_compiler::diagnostics::BuildDiagnostics; use i_slint_compiler::parser::SyntaxKind; use i_slint_compiler::*; use proc_macro::{Spacing, TokenStream, TokenTree}; use quote::quote; use std::path::PathBuf; /// Returns true if the two token are touching. For example the two token `foo`and `-` are touching if /// it was written like so in the source code: `foo-` but not when written like so `foo -` /// /// Returns None if we couldn't detect whether they are touching (eg, our heuristics don't work with rust-analyzer) fn are_token_touching(token1: proc_macro::Span, token2: proc_macro::Span) -> Option { let t1 = token1.end(); let t2 = token2.start(); let t1_column = t1.column(); if t1_column == 1 && t1.line() == 1 && t2.end().line() == 1 && t2.end().column() == 1 { // If everything is 1, this means that Span::line and Span::column are not working properly // (eg, rust-analyzer) return None; } Some(t1.line() == t2.line() && t1_column == t2.column()) } fn fill_token_vec(stream: impl Iterator, vec: &mut Vec) { let mut prev_spacing = Spacing::Alone; let mut prev_span = proc_macro::Span::call_site(); for t in stream { let span = t.span(); match t { TokenTree::Ident(i) => { if let Some(last) = vec.last_mut() && ((last.kind == SyntaxKind::ColorLiteral && last.text.len() == 1) || (last.kind == SyntaxKind::Identifier && are_token_touching(prev_span, span) .unwrap_or_else(|| last.text.ends_with('-')))) { last.text = format!("{}{}", last.text, i).into(); prev_span = span; continue; } vec.push(parser::Token { kind: SyntaxKind::Identifier, text: i.to_string().into(), span: Some(i.span()), ..Default::default() }); } TokenTree::Punct(p) => { let kind = match p.as_char() { ':' => SyntaxKind::Colon, '=' => { if let Some(last) = vec.last_mut() { let kt = match last.kind { SyntaxKind::Star => Some((SyntaxKind::StarEqual, "*=")), SyntaxKind::Colon => Some((SyntaxKind::ColonEqual, ":=")), SyntaxKind::Plus => Some((SyntaxKind::PlusEqual, "+=")), SyntaxKind::Minus => Some((SyntaxKind::MinusEqual, "-=")), SyntaxKind::Div => Some((SyntaxKind::DivEqual, "/=")), SyntaxKind::LAngle => Some((SyntaxKind::LessEqual, "<=")), SyntaxKind::RAngle => Some((SyntaxKind::GreaterEqual, ">=")), SyntaxKind::Equal => Some((SyntaxKind::EqualEqual, "==")), SyntaxKind::Bang => Some((SyntaxKind::NotEqual, "!=")), _ => None, }; if let Some((k, t)) = kt && prev_spacing == Spacing::Joint { last.kind = k; last.text = t.into(); continue; } } SyntaxKind::Equal } ';' => SyntaxKind::Semicolon, '!' => SyntaxKind::Bang, '.' => { // `4..log` is lexed as `4 . . log` in rust, but should be `4. . log` in slint if let Some(last) = vec.last_mut() && last.kind == SyntaxKind::NumberLiteral && are_token_touching(prev_span, p.span()).unwrap_or(false) && !last.text.contains('.') && !last.text.ends_with(char::is_alphabetic) { last.text = format!("{}.", last.text).into(); prev_span = span; continue; } SyntaxKind::Dot } '+' => SyntaxKind::Plus, '-' => { if let Some(last) = vec.last_mut() && last.kind == SyntaxKind::Identifier && are_token_touching(prev_span, p.span()).unwrap_or(true) { last.text = format!("{}-", last.text).into(); prev_span = span; continue; } SyntaxKind::Minus } '*' => SyntaxKind::Star, '/' => SyntaxKind::Div, '<' => SyntaxKind::LAngle, '>' => { if let Some(last) = vec.last_mut() { if last.kind == SyntaxKind::LessEqual && prev_spacing == Spacing::Joint { last.kind = SyntaxKind::DoubleArrow; last.text = "<=>".into(); continue; } else if last.kind == SyntaxKind::Equal && prev_spacing == Spacing::Joint { last.kind = SyntaxKind::FatArrow; last.text = "=>".into(); continue; } else if last.kind == SyntaxKind::Minus && prev_spacing == Spacing::Joint { last.kind = SyntaxKind::Arrow; last.text = "->".into(); continue; } } SyntaxKind::RAngle } '#' => SyntaxKind::ColorLiteral, '?' => SyntaxKind::Question, ',' => SyntaxKind::Comma, '&' => { // Since the '&' alone does not exist or cannot be part of any other token that && // just consider it as '&&' and skip the joint ones. FIXME. do that properly if let Some(last) = vec.last_mut() && last.kind == SyntaxKind::AndAnd && prev_spacing == Spacing::Joint { continue; } SyntaxKind::AndAnd } '|' => { // Since the '|' alone does not exist or cannot be part of any other token that || // just consider it as '||' and skip the joint ones. if let Some(last) = vec.last_mut() && last.kind == SyntaxKind::Pipe && prev_spacing == Spacing::Joint { last.kind = SyntaxKind::OrOr; continue; } SyntaxKind::Pipe } '%' => { // handle % as a unit if let Some(last) = vec.last_mut() && last.kind == SyntaxKind::NumberLiteral { last.text = format!("{}%", last.text).into(); continue; } SyntaxKind::Percent } '$' => SyntaxKind::Dollar, '@' => SyntaxKind::At, _ => SyntaxKind::Error, }; prev_spacing = p.spacing(); vec.push(parser::Token { kind, text: p.to_string().into(), span: Some(p.span()), ..Default::default() }); } TokenTree::Literal(l) => { let s = l.to_string(); // Why can't the rust API give me the type of the literal let f = s.chars().next().unwrap(); let kind = if f == '"' { SyntaxKind::StringLiteral } else if f.is_ascii_digit() { if let Some(last) = vec.last_mut() && ((last.kind == SyntaxKind::ColorLiteral && last.text.len() == 1) || (last.kind == SyntaxKind::Identifier && are_token_touching(prev_span, span) .unwrap_or_else(|| last.text.ends_with('-')))) { last.text = format!("{}{}", last.text, s).into(); prev_span = span; continue; } SyntaxKind::NumberLiteral } else { SyntaxKind::Error }; vec.push(parser::Token { kind, text: s.into(), span: Some(l.span()), ..Default::default() }); } TokenTree::Group(g) => { use SyntaxKind::*; use proc_macro::Delimiter::*; let (l, r, sl, sr) = match g.delimiter() { Parenthesis => (LParent, RParent, "(", ")"), Brace => (LBrace, RBrace, "{", "}"), Bracket => (LBracket, RBracket, "[", "]"), None => todo!(), }; vec.push(parser::Token { kind: l, text: sl.into(), span: Some(g.span()), // span_open is not stable ..Default::default() }); fill_token_vec(g.stream().into_iter(), vec); vec.push(parser::Token { kind: r, text: sr.into(), span: Some(g.span()), // span_clone is not stable ..Default::default() }); } } prev_span = span; } } fn extract_path(literal: proc_macro::Literal) -> std::path::PathBuf { let path_with_quotes = literal.to_string(); let path_with_quotes_stripped = if let Some(p) = path_with_quotes.strip_prefix('r') { let hash_removed = p.trim_matches('#'); hash_removed.strip_prefix('\"').unwrap().strip_suffix('\"').unwrap() } else { // FIXME: unescape path_with_quotes.trim_matches('\"') }; path_with_quotes_stripped.into() } fn extract_compiler_config( mut stream: proc_macro::token_stream::IntoIter, compiler_config: &mut CompilerConfiguration, ) -> impl Iterator { let mut remaining_stream; loop { remaining_stream = stream.clone(); match (stream.next(), stream.next()) { (Some(TokenTree::Punct(p)), Some(TokenTree::Group(group))) if p.as_char() == '#' && group.delimiter() == proc_macro::Delimiter::Bracket => { let mut attr_stream = group.stream().into_iter(); match attr_stream.next() { Some(TokenTree::Ident(include_ident)) if include_ident.to_string() == "include_path" => { match (attr_stream.next(), attr_stream.next()) { ( Some(TokenTree::Punct(equal_punct)), Some(TokenTree::Literal(path)), ) if equal_punct.as_char() == '=' => { compiler_config.include_paths.push(extract_path(path)); } _ => break, } } Some(TokenTree::Ident(library_ident)) if library_ident.to_string() == "library_path" => { match (attr_stream.next(), attr_stream.next(), attr_stream.next()) { ( Some(TokenTree::Group(group)), Some(TokenTree::Punct(equal_punct)), Some(TokenTree::Literal(path)), ) if group.delimiter() == proc_macro::Delimiter::Parenthesis && equal_punct.as_char() == '=' => { let library_name = group.stream().into_iter().next().unwrap(); compiler_config .library_paths .insert(library_name.to_string(), extract_path(path)); } _ => break, } } Some(TokenTree::Ident(style_ident)) if style_ident.to_string() == "style" => { match (attr_stream.next(), attr_stream.next()) { ( Some(TokenTree::Punct(equal_punct)), Some(TokenTree::Literal(requested_style)), ) if equal_punct.as_char() == '=' => { compiler_config.style = requested_style .to_string() .strip_prefix('\"') .unwrap() .strip_suffix('\"') .unwrap() .to_string() .into(); } _ => break, } } _ => break, } } _ => break, } } remaining_stream } /// This macro allows you to use the Slint design markup language inline in Rust code. Within the braces of the macro /// you can use place Slint code and the named exported components will be available for instantiation. /// /// For the documentation about the syntax of the language, see #[doc = concat!("[The Slint Language Documentation](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/slint)")] /// /// When Rust 1.88 or later is used, the paths for loading images with `@image-url` and importing `.slint` files /// are relative to the `.rs` file that contains the macro. /// For compatibility with older rust version, the files are also searched in the manifest directory that contains `Cargo.toml`. /// /// ### Limitations /// /// Within `.slint` files, you can interpolate string literals using `\{...}` syntax. /// This is not possible in this macro as this wouldn't parse as a Rust string. #[proc_macro] pub fn slint(stream: TokenStream) -> TokenStream { let token_iter = stream.into_iter(); let mut compiler_config = CompilerConfiguration::new(i_slint_compiler::generator::OutputFormat::Rust); let token_iter = extract_compiler_config(token_iter, &mut compiler_config); let mut tokens = Vec::new(); fill_token_vec(token_iter, &mut tokens); fn local_file(tokens: &[parser::Token]) -> Option { tokens.first()?.span?.local_file() } let source_file = if let Some(path) = local_file(&tokens) { diagnostics::SourceFileInner::from_path_only(path) } else if let Some(cargo_manifest) = std::env::var_os("CARGO_MANIFEST_DIR") { let mut path: std::path::PathBuf = cargo_manifest.into(); path.push("Cargo.toml"); diagnostics::SourceFileInner::from_path_only(path) } else { diagnostics::SourceFileInner::from_path_only(Default::default()) }; let mut diag = BuildDiagnostics::default(); let syntax_node = parser::parse_tokens(tokens.clone(), source_file, &mut diag); if diag.has_errors() { return diag.report_macro_diagnostic(&tokens); } //println!("{syntax_node:#?}"); compiler_config.translation_domain = std::env::var("CARGO_PKG_NAME").ok(); let (root_component, diag, loader) = spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config)); //println!("{tree:#?}"); if diag.has_errors() { return diag.report_macro_diagnostic(&tokens); } if std::env::var("RUST_ANALYZER_INTERNALS_DO_NOT_USE").is_ok() { // When running on rust-analyzer, only generate the API (using the live preview) to make rust-analyzer faster and use less memory // (This uses an unstable env variable, but it is just an optimization) return generator::rust_live_preview::generate(&root_component, &loader.compiler_config) .unwrap_or_else(|e| { let e_str = e.to_string(); quote!(compile_error!(#e_str)) }) .into(); } let mut result = generator::rust::generate(&root_component, &loader.compiler_config) .unwrap_or_else(|e| { let e_str = e.to_string(); quote!(compile_error!(#e_str)) }); // Make sure to recompile if any of the external files changes let reload = diag .all_loaded_files .iter() .filter(|path| path.is_absolute() && !path.ends_with("Cargo.toml")) .filter_map(|p| p.to_str()) .map(|p| quote! {const _ : &'static [u8] = ::core::include_bytes!(#p);}); result.extend(reload); result.extend(quote! {const _ : ::core::option::Option<&'static str> = ::core::option_env!("SLINT_STYLE");}); let mut result = TokenStream::from(result); if !diag.is_empty() { result.extend(diag.report_macro_diagnostic(&tokens)); } result } ================================================ FILE: api/rs/slint/Cargo.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 [package] name = "slint" description = "GUI toolkit to efficiently develop fluid graphical user interfaces for embedded devices and desktop applications" authors.workspace = true documentation.workspace = true edition = "2024" homepage = "https://slint.rs" keywords.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true version.workspace = true categories = ["gui", "rendering::engine", "no-std"] [lib] path = "lib.rs" [features] default = ["std", "backend-default", "renderer-femtovg", "renderer-software", "accessibility", "compat-1-2"] ## Mandatory feature: ## This feature is required to keep the compatibility with Slint 1.2 ## Newer patch version may put current functionality behind a new feature ## that would be enabled by default only if this feature was added. ## [More info in this blog post](https://slint.dev/blog/rust-adding-default-cargo-feature.html) "compat-1-2" = [] "compat-1-0" = ["compat-1-2", "renderer-software"] ## Enable use of the Rust standard library. std = ["i-slint-core/std", "i-slint-renderer-software?/std"] ## Enable the translations using [gettext](https://www.gnu.org/software/gettext/gettext) ## ## the `@tr(...)` code from .slint files will be transformed into call to `dgettext` ## with the crate name as domain name ## ## translations must be enabled with the [`init_translations!`] macro gettext = ["i-slint-core/gettext-rs"] ## This feature enables floating point arithmetic emulation using the [libm](https://crates.io/crates/libm) crate. Use this ## in MCU environments where the processor does not support floating point arithmetic. libm = ["i-slint-core/libm", "i-slint-renderer-software?/libm"] ## If enabled, calls of `debug()` in `.slint` files use to the [`log::debug!()`] macro ## of the [log](https://crates.io/crates/log) crate instead of just `println!()`. log = ["dep:log"] ## Implement the `serde::Serialize` and `serde::Deserialize` for some of the base types ## such as `SharedString` and `SharedVector`. serde = ["i-slint-core/serde"] # Deprecated feature, this is now automatically enabled when the `renderer-software` and `std` features are enabled software-renderer-systemfonts = ["renderer-software"] # Enable support for Path element rendering with the software renderer. This is implicitly enabled by the # `std` feature, but can be enabled without `std` if you want to use the software renderer in a `#![no_std]` environment and need support for Path elements. software-renderer-path = ["i-slint-renderer-software/path"] ## Slint uses internally some `thread_local` state. ## ## When the `std` feature is enabled, Slint can use [`std::thread_local!`], but when in a `#![no_std]` ## environment, we need a replacement. Using this feature, Slint will just use static variable ## disregarding Rust's Send and Sync safety ## ## **Safety** : You must ensure that there is only one single thread that call into the Slint API unsafe-single-threaded = ["i-slint-core/unsafe-single-threaded"] ## Enable integration with operating system provided accessibility APIs ## ## Enabling this feature will try to expose the tree of UI elements to OS provided accessibility ## APIs to support screen readers and other assistive technologies. accessibility = ["i-slint-backend-selector/accessibility"] ## Enable integration with [raw-window-handle](raw_window_handle_06) version 0.6. This provides a ## [`Window::window_handle()`] function that returns a struct that implements ## [HasWindowHandle](raw_window_handle_06::HasWindowHandle) and ## [HasDisplayHandle](raw_window_handle_06::HasDisplayHandle) implementation. raw-window-handle-06 = ["dep:raw-window-handle-06", "i-slint-backend-selector/raw-window-handle-06"] ## Enable the default image formats from the `image` crate, to support additional image formats in [`Image::load_from_path`] ## and `@image-url`. When this feature is disabled, only PNG and JPEG are supported. When enabled, ## the following image formats are supported: ## AVIF, BMP, DDS, Farbfeld, GIF, HDR, ICO, JPEG, EXR, PNG, PNM, QOI, TGA, TIFF, WebP. image-default-formats = ["i-slint-core/image-default-formats"] ## Enable the live preview feature ## ## Enable this feature to reload the .slint files at runtime and reload it whenever the files are modified on disk. ## For this feature to work, it's also required to set the `SLINT_LIVE_PREVIEW` environment variable during ## application compilation. ## ## This is a feature for debugging and development. It's not recommended to add this feature to your Cargo.toml. ## Instead, use the `--features` command line argument like so when building your app: ## ```bash ## SLINT_LIVE_PREVIEW=1 cargo build --features slint/live-preview ## ``` live-preview = ["dep:slint-interpreter"] #! ### Backends #! Slint needs a backend that will act as liaison between Slint and the OS. #! By default, Slint will use the Qt backend, if Qt is installed, otherwise, it #! will use [Winit](https://crates.io/crates/winit) with [FemtoVG](https://crates.io/crates/femtovg). #! Both backends are compiled in. If you want to not compile one of these you need #! to disable the default feature and re-enable one backend. It is also possible #! to use Slint without backend if you provide the platform abstraction yourself #! with [`platform::set_platform()`]. #! #! If you enable the Winit backend, you need to also include a renderer. #! `renderer-femtovg` is the default renderer. #! #! It is also possible to select the backend and renderer at runtime when several #! are enabled, using the `SLINT_BACKEND` environment variable. #! * `SLINT_BACKEND=Qt` selects the Qt backend #! * `SLINT_BACKEND=winit` selects the winit backend #! * `SLINT_BACKEND=winit-femtovg` selects the winit backend with the FemtoVG renderer #! * `SLINT_BACKEND=winit-skia` selects the winit backend with the skia renderer #! * `SLINT_BACKEND=winit-software` selects the winit backend with the software renderer #! #! If the selected backend is not available, the default will be used. #! #! Here are the cargo features controlling the backend: ## The Qt backend feature uses Qt for the windowing system integration and rendering. ## This backend also provides the `native` style. ## It requires Qt 5.15 or later to be installed. If Qt is not installed, the ## backend will not be operational backend-qt = ["i-slint-backend-selector/backend-qt", "std", "i-slint-backend-qt"] ## The [winit](https://crates.io/crates/winit) crate is used for the event loop and windowing system integration. ## It supports Windows, macOS, web browsers, X11 and Wayland. X11 and wayland are only available when ## compiling for Linux or other Unix-like operating systems. With this feature, both X11 and Wayland are supported. ## For a smaller build, omit this feature and select one of the other specific `backend-winit-XX` features. backend-winit = ["i-slint-backend-selector/backend-winit", "std"] ## Simliar to `backend-winit` this enables the winit based event loop but only ## with support for the X Window System on Unix. backend-winit-x11 = ["i-slint-backend-selector/backend-winit-x11", "std"] ## Simliar to `backend-winit` this enables the winit based event loop but only ## with support for the Wayland window system on Unix. backend-winit-wayland = ["i-slint-backend-selector/backend-winit-wayland", "std"] ## Alias to a backend and renderer that depends on the platform. ## Will select the Qt backend on linux if present, and the winit otherwise backend-default = ["i-slint-backend-selector/default", "i-slint-backend-qt"] # deprecated aliases renderer-winit-femtovg = ["renderer-femtovg"] renderer-winit-skia = ["renderer-skia"] renderer-winit-skia-opengl = ["renderer-skia-opengl"] renderer-winit-skia-vulkan = ["renderer-skia-vulkan"] renderer-winit-software = ["renderer-software"] ## Render using the [FemtoVG](https://crates.io/crates/femtovg) crate. renderer-femtovg = ["i-slint-backend-selector/renderer-femtovg", "dep:i-slint-renderer-femtovg", "std"] ## Render using the [FemtoVG](https://crates.io/crates/femtovg) crate and [WGPU](https://crates.io/crates/wgpu). renderer-femtovg-wgpu = ["i-slint-backend-selector/renderer-femtovg-wgpu", "i-slint-renderer-femtovg/wgpu", "std"] ## Render using [Skia](https://skia.org/). renderer-skia = ["i-slint-backend-selector/renderer-skia", "std"] ## Same as `renderer-skia`, but Skia will always use OpenGL. ## Note: This is not supported on iOS. Use `renderer-skia` on iOS to enable Meta based rendering. renderer-skia-opengl = ["i-slint-backend-selector/renderer-skia-opengl", "std"] ## Same as `renderer-skia`, but Skia will always use Vulkan. renderer-skia-vulkan = ["i-slint-backend-selector/renderer-skia-vulkan", "std"] ## Render using the software renderer. renderer-software = ["i-slint-backend-selector/renderer-software", "dep:i-slint-renderer-software"] ## KMS with Vulkan or EGL and libinput on Linux are used to render the application in full screen mode, without any ## windowing system. Requires libseat. If you don't have libseat, select `backend-linuxkms-noseat` instead. backend-linuxkms = ["i-slint-backend-selector/backend-linuxkms", "std"] ## KMS with Vulkan or EGL and libinput on Linux are used to render the application in full screen mode, without any ## windowing system. backend-linuxkms-noseat = ["i-slint-backend-selector/backend-linuxkms-noseat", "std"] ## Use the backend based on the [android-activity](https://docs.rs/android-activity) crate. (Using it's native activity feature) backend-android-activity-06 = [ "i-slint-backend-android-activity/native-activity", "i-slint-backend-android-activity/aa-06", "i-slint-backend-selector/backend-android-activity", ] ## **Deprecated** Use previous version of android-activity. This is mutually exclusive with `backend-android-activity-06`. backend-android-activity-05 = [ "i-slint-backend-android-activity/native-activity", "i-slint-backend-android-activity/aa-05", ] ## Enable support for [WGPU](http://wgpu.rs) based rendering and expose WGPU based APIs based on WGPU version 27.x. ## ## APIs guarded with this feature are *NOT* subject to the usual Slint API stability guarantees, because WGPU releases new major ## versions frequently. This feature as well as the APIs changed or removed in future minor releases of Slint, likely to be replaced ## by a feature with a similar name but the WGPU version suffix being bumped. ## ## To avoid unintended compilation failures, we recommend to use the [tilde requirement](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements) ## in your `Cargo.toml` when enabling this feature: ## ## ```toml ## slint = { version = "~1.16", features = ["unstable-wgpu-27"] } ## ``` unstable-wgpu-27 = [ "i-slint-core/unstable-wgpu-27", "i-slint-backend-selector/unstable-wgpu-27", "i-slint-backend-android-activity?/unstable-wgpu-27", "dep:wgpu-27", ] ## Enable support for [WGPU](http://wgpu.rs) based rendering and expose WGPU based APIs based on WGPU version 28.x. ## ## APIs guarded with this feature are *NOT* subject to the usual Slint API stability guarantees, because WGPU releases new major ## versions frequently. This feature as well as the APIs changed or removed in future minor releases of Slint, likely to be replaced ## by a feature with a similar name but the WGPU version suffix being bumped. ## ## To avoid unintended compilation failures, we recommend to use the [tilde requirement](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements) ## in your `Cargo.toml` when enabling this feature: ## ## ```toml ## slint = { version = "~1.16", features = ["unstable-wgpu-28"] } ## ``` unstable-wgpu-28 = [ "i-slint-core/unstable-wgpu-28", "i-slint-backend-selector/unstable-wgpu-28", "i-slint-backend-android-activity?/unstable-wgpu-28", "dep:wgpu-28", ] ## APIs guarded with this feature are *NOT* subject to the usual Slint API stability guarantees, because winit releases new major ## versions more frequently than Slint. This feature as well as the APIs changed or removed in future minor releases of Slint, likely to be replaced ## by a feature with a similar name but the winit version suffix being bumped. ## ## To avoid unintended compilation failures, we recommend to use the [tilde requirement](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements) ## in your `Cargo.toml` when enabling this feature: ## ## ```toml ## slint = { version = "~1.16", features = ["unstable-winit-030"] } ## ``` unstable-winit-030 = ["backend-winit", "dep:i-slint-backend-winit", "i-slint-backend-selector/unstable-winit-030"] ## Enable support for exposing [libinput](https://docs.rs/input/latest/input/index.html) related APIs in the LinuxKMS backend. ## ## APIs guarded with this feature are *NOT* subject to the usual Slint API stability guarantees. This feature as well as the APIs changed or removed ## in future minor releases of Slint, likely to be replaced by a feature with a similar name but the input version suffix being bumped. ## ## To avoid unintended compilation failures, we recommend to use the [tilde requirement](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements) ## in your `Cargo.toml` when enabling this feature: ## ## ```toml ## slint = { version = "~1.16", features = ["unstable-libinput-09"] } ## ``` unstable-libinput-09 = ["i-slint-backend-selector/unstable-libinput-09"] ## Enable support for exposing [fontique](https://docs.rs/fontique/0.7.0/fontique/) related APIs. ## ## APIs guarded with this feature are *NOT* subject to the usual Slint API stability guarantees. This feature as well as the APIs changed or removed ## in future minor releases of Slint, likely to be replaced by a feature with a similar name but the fontique version suffix being bumped. ## ## To avoid unintended compilation failures, we recommend to use the [tilde requirement](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements) ## in your `Cargo.toml` when enabling this feature: ## ## ```toml ## slint = { version = "~1.16", features = ["unstable-fontique-07"] } ## ``` unstable-fontique-07 = ["i-slint-common/shared-fontique"] [dependencies] i-slint-core = { workspace = true } slint-macros = { workspace = true } i-slint-backend-selector = { workspace = true } i-slint-core-macros = { workspace = true } i-slint-common = { workspace = true } i-slint-renderer-software = { workspace = true, optional = true } slint-interpreter = { workspace = true, optional = true, default-features = false, features = ["display-diagnostics", "compat-1-2", "internal-live-preview"] } const-field-offset = { version = "0.1.2", path = "../../../helper_crates/const-field-offset" } document-features = { version = "0.2.0", optional = true } vtable = { workspace = true } once_cell = { version = "1.5", default-features = false, features = ["alloc"] } pin-weak = { version = "1.1", default-features = false } num-traits = { version = "0.2", default-features = false } log = { workspace = true, optional = true } raw-window-handle-06 = { workspace = true, optional = true } unicode-segmentation = { workspace = true } wgpu-27 = { workspace = true, optional = true } wgpu-28 = { workspace = true, optional = true } i-slint-backend-winit = { workspace = true, optional = true } [target.'cfg(not(target_os = "android"))'.dependencies] # FemtoVG is disabled on android because it doesn't compile without setting RUST_FONTCONFIG_DLOPEN=on # end even then wouldn't work because it can't load fonts i-slint-renderer-femtovg = { workspace = true, optional = true } [target.'cfg(target_os = "android")'.dependencies] i-slint-backend-android-activity = { workspace = true, optional = true } [dev-dependencies] slint-build = { path = "../build" } # The next can not be a workspace dependency because it may not have a version i-slint-backend-testing = { path = "../../../internal/backends/testing", features = ["internal"] } i-slint-renderer-skia = { path = "../../../internal/renderers/skia" } serde_json = { workspace = true } serde = { workspace = true } tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "net", "io-util"] } async-compat = { version = "0.2.4" } bytemuck = { workspace = true } i-slint-renderer-software = { path = "../../../internal/renderers/software", features = ["testing"] } [target.'cfg(target_os = "linux")'.dependencies] # this line is there to add the "enable" feature by default, but only on linux i-slint-backend-qt = { workspace = true, features = ["enable"], optional = true } [package.metadata.docs.rs] features = [ "document-features", "log", "gettext", "renderer-software", "renderer-femtovg", "raw-window-handle-06", "unstable-wgpu-28", "unstable-winit-030", "unstable-libinput-09", "unstable-fontique-07", ] rustdoc-args = ["--generate-link-to-definition"] ================================================ FILE: api/rs/slint/README.md ================================================ # Slint [![Crates.io](https://img.shields.io/crates/v/slint)](https://crates.io/crates/slint) [![Docs.rs](https://docs.rs/slint/badge.svg)](https://docs.rs/slint) # A Rust UI toolkit [Slint](https://slint.rs) is a Rust based UI toolkit to build native user interfaces on desktop platforms and for embedded devices. This crate provides the Rust APIs to interact with the user interface implemented in Slint. The complete Rust documentation for Slint can be viewed online at https://slint.rs/docs/rust/slint/. ## Getting Started The [crate documentation](https://slint.dev/docs/rust/slint/) shows how to use this crate. ### Hello World The most basic "Hello world" application can be achieved with a few lines of code: In your `Cargo.toml` add: ```toml [dependencies] slint = "1.15" ``` And in your `main.rs`: ```rust,no_run slint::slint!{ export component HelloWorld { Text { text: "hello world"; color: green; } } } fn main() { HelloWorld::new().unwrap().run().unwrap(); } ``` The [`slint` crate documentation](https://slint.dev/docs/rust/slint/) contains more advanced examples and alternative ways to use this crate. To quickly get started, use the [Template Repository](https://github.com/slint-ui/slint-rust-template) with the code of a minimal application using Slint as a starting point for your program. 1. Download and extract the [ZIP archive of the Rust Template](https://github.com/slint-ui/slint-rust-template/archive/refs/heads/main.zip). 2. Rename the extracted directory and change into it: ```bash mv slint-rust-template-main my-project cd my-project ``` ## More examples You can quickly try out the [examples](/examples) by cloning this repo and running them with `cargo run` ```sh # Runs the "printerdemo" example cargo run --release --bin printerdemo ``` ================================================ FILE: api/rs/slint/android.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 //! Android backend. //! //! **Note:** This module is only available on Android with the "backend-android-activity-06" feature //! //! Slint uses the [android-activity crate](https://github.com/rust-mobile/android-activity) as a backend. //! //! For convenience, Slint re-exports the content of the [`android-activity`](https://docs.rs/android-activity) under `slint::android::android_activity`. //! //! As with every application using the android-activity crate, the entry point to your app will be the `android_main` function. //! From that function, you can call [`slint::android::init`](init()) or [`slint::android::init_with_event_listener`](init_with_event_listener) //! //! # Example //! //! This is a basic example of an Android application. //! Do not forget the `#[unsafe(no_mangle)]` //! //! ```rust //! # #[cfg(target_os = "android")] //! #[unsafe(no_mangle)] //! fn android_main(app: slint::android::AndroidApp) { //! slint::android::init(app).unwrap(); //! //! // ... rest of your code ... //! slint::slint!{ //! export component MainWindow inherits Window { //! Text { text: "Hello World"; } //! } //! } //! MainWindow::new().unwrap().run().unwrap(); //! } //! ``` //! //! That function must be in a `cdylib` library, and you should enable the "backend-android-activity-06" //! feature of the slint crate in your Cargo.toml: //! //! ```toml //! [lib] //! crate-type = ["cdylib"] //! //! [dependencies] //! slint = { version = "1.6", features = ["backend-android-activity-06"] } //! ``` //! //! ## Building and Deploying //! //! Building a Rust application requires the target toolchain to be installed. You can install it via `rustup`. For example, to target AArch64 Android, use the following command: //! //! ```sh //! rustup target add aarch64-linux-android //! ``` //! //! Make sure that you have the Android NDK and SDK installed and set up in your development environment. //! For detailed instructions on how to set up the Android NDK and SDK, please refer to the [Android Developer's guide](https://developer.android.com/studio/projects/install-ndk). //! The following environment variables need to be set: //! * `ANDROID_HOME`: The directory in which your Android SDK is located. Usually `$HOME/Android/Sdk`. //! * `ANDROID_NDK_ROOT`: The directory in which your Android NDK is located. Usually `$HOME/Android/Sdk/ndk/${NDK_VERSION}`. ${NDK_VERSION} is the version of the NDK you have installed. //! * `JAVA_HOME`: The directory in which your Java compiler (`javac`) is located. This variable is optional if a `javac` is found in your `$PATH`. //! Otherwise you can set `JAVA_HOME` to the `javac` installation shipped with Android Studio in `android-studio/jbr`. //! //! To build and deploy your application, we suggest the usage of [cargo-apk](https://github.com/rust-mobile/cargo-apk), //! a cargo subcommand that allows you to build, sign, and deploy Android APKs made in Rust. //! //! You can install it and use it with the following command: //! //! ```sh //! cargo install cargo-apk //! ``` //! //! Build and run your application with the following command: //! //! ```sh //! cargo apk run --target aarch64-linux-android --lib //! ``` //! //! //! Note Slint does not require a specific build tool and can work with others, such as [xbuild](https://github.com/rust-mobile/xbuild). /// Re-export of the android-activity crate. #[cfg(all( target_os = "android", any(feature = "backend-android-activity-05", feature = "backend-android-activity-06") ))] pub use i_slint_backend_android_activity::android_activity; #[cfg(not(all( target_os = "android", any(feature = "backend-android-activity-05", feature = "backend-android-activity-06") )))] /// Re-export of the [android-activity](https://docs.rs/android-activity) crate. pub mod android_activity { #[doc(hidden)] pub struct AndroidApp; #[doc(hidden)] pub struct PollEvent<'a>(&'a ()); } /// Re-export of AndroidApp from the [android-activity](https://docs.rs/android-activity) crate. #[doc(no_inline)] pub use android_activity::AndroidApp; use crate::platform::SetPlatformError; /// Initializes the Android backend. /// /// **Note:** This function is only available on Android with the "backend-android-activity-06" feature /// /// This function must be called from the `android_main` function before any call to Slint that needs a backend. /// /// See the [module documentation](self) for an example on how to create Android application. /// /// See also [`init_with_event_listener`] pub fn init(app: android_activity::AndroidApp) -> Result<(), SetPlatformError> { #[cfg(not(target_os = "android"))] unreachable!(); #[cfg(target_os = "android")] { crate::platform::set_platform(Box::new( i_slint_backend_android_activity::AndroidPlatform::new(app), )) } } /// Similar to [`init()`], which allow to listen to android-activity's event /// /// **Note:** This function is only available on Android with the "backend-android-activity-06" feature /// /// The listener argument is a function that takes a [`android_activity::PollEvent`](https://docs.rs/android-activity/latest/android_activity/enum.PollEvent.html) /// /// # Example /// /// ```rust /// # #[cfg(target_os = "android")] /// #[unsafe(no_mangle)] /// fn android_main(app: slint::android_activity::AndroidApp) { /// slint::android::init_with_event_listener( /// app, /// |event| { eprintln!("got event {event:?}") } /// ).unwrap(); /// /// // ... rest of your application ... /// /// } /// ``` /// /// Check out the [module documentation](self) for a more complete example on how to write an android application pub fn init_with_event_listener( app: android_activity::AndroidApp, listener: impl Fn(&android_activity::PollEvent<'_>) + 'static, ) -> Result<(), SetPlatformError> { #[cfg(not(target_os = "android"))] unreachable!(); #[cfg(target_os = "android")] { crate::platform::set_platform(Box::new( i_slint_backend_android_activity::AndroidPlatform::new_with_event_listener( app, listener, ), )) } } ================================================ FILE: api/rs/slint/compile_fail_tests.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 /** Test that the tokenizer properly rejects tokens with spaces. This should work: ``` mod x { use slint::*; slint!{ Hello := Rectangle { } } } ``` But his not: ```compile_fail mod x { use slint::*; slint!{ Hello : = Rectangle { } } } ``` */ #[cfg(doctest)] const basic: u32 = 0; /** Test that invalid rust-attr are compilation error This should result in a `compile_error!("Error parsing @rust-attr for struct 'Foo' declared at tests/invalid_rust_attr.slint:4:12"` ```compile_fail use slint::*; slint!{ export { Foo } from "tests/invalid_rust_attr.slint"; export component Hello inherits Window { } } ``` But Foo is not used/generated, then we do not detect the error (Having the test here to test that the previous test would otherwise work, but it would also be ok to detect the error and make it an actual slint compile error) ``` use slint::*; slint!{ import { Foo } from "tests/invalid_rust_attr.slint"; export component Hello inherits Window { } } ``` */ const INVALID_RUST_ATTR: () = (); #[cfg(doctest)] #[doc = include_str!("README.md")] const CHECK_README_EXAMPLES: () = (); ================================================ FILE: api/rs/slint/docs.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #![cfg(doc)] /*! This is a pseudo module which only exist for documentation purposes as a way to show the Slint documentation as part of rustdoc. - The [`generated_code`] module contains an [commented example](generated_code::SampleComponent) of what is generated from the `.slint` file */ // cSpell: ignore rustdoc /// This module exists only to explain the API of the code generated from `.slint` design markup. Its described structure /// is not really contained in the compiled crate. pub mod generated_code { use crate::ComponentHandle; use crate::Global; use crate::Weak; use crate::Window; /// This an example of the API that is generated for a component in `.slint` design markup. This may help you understand /// what functions you can call and how you can pass data in and out. /// /// This is the source code: /// /// ```slint,no-preview /// export component SampleComponent inherits Window { /// in-out property counter; /// // note that dashes will be replaced by underscores in the generated code /// in-out property user-name; /// callback hello; /// public function do-something(x: int) -> bool { return x > 0; } /// // ... maybe more elements here /// } /// ``` #[derive(Clone)] pub struct SampleComponent { _marker: core::marker::PhantomData<*mut ()>, } impl SampleComponent { /// Creates a new instance that is reference counted and pinned in memory. pub fn new() -> Result { unimplemented!() } /// A getter is generated for each property declared at the root of the component. /// In this case, this is the getter that returns the value of the `counter` /// property declared in the `.slint` design markup. pub fn get_counter(&self) -> i32 { unimplemented!() } /// A setter is generated for each property declared at the root of the component, /// In this case, this is the setter that sets the value of the `counter` property /// declared in the `.slint` design markup. pub fn set_counter(&self, value: i32) {} /// Returns the value of the `user_name` property declared in the `.slint` design markup. pub fn get_user_name(&self) -> crate::SharedString { unimplemented!() } /// Assigns a new value to the `user_name` property. pub fn set_user_name(&self, value: crate::SharedString) {} /// For each callback declared at the root of the component, a function to synchronously call that /// callback is generated. This is the function that calls the `hello` callback declared /// in the `.slint` design markup. pub fn invoke_hello(&self) {} /// For each callback declared at the root of the component, a function connect to that callback /// is generated. This is the function that registers the function f as callback when the /// callback `hello` is emitted. In order to access /// the component in the callback, you'd typically capture a weak reference obtained using /// [`ComponentHandle::as_weak`] /// and then upgrade it to a strong reference when the callback is run: /// ```ignore /// let sample = SampleComponent::new().unwrap(); /// let sample_weak = sample.as_weak(); /// sample.on_hello(move || { /// let sample = sample_weak.unwrap(); /// sample.set_counter(42); /// }); /// ``` pub fn on_hello(&self, f: impl Fn() + 'static) {} /// For each public function declared at the root of the component, a function to synchronously call /// that function is generated. This is the function that calls the `do-something` function /// declared in the `.slint` design markup. pub fn invoke_do_something(&self, d: i32) -> bool { unimplemented!() } } impl ComponentHandle for SampleComponent { #[doc(hidden)] type WeakInner = (); /// Returns a new weak pointer. fn as_weak(&self) -> Weak { unimplemented!() } /// Returns a clone of this handle that's a strong reference. fn clone_strong(&self) -> Self { unimplemented!(); } #[doc(hidden)] fn upgrade_from_weak_inner(_: &Self::WeakInner) -> Option { unimplemented!(); } /// Convenience function for [`crate::Window::show()`]. This shows the window on the screen /// and maintains an extra strong reference while the window is visible. To react /// to events from the windowing system, such as draw requests or mouse/touch input, it is /// still necessary to spin the event loop, using [`crate::run_event_loop`]. fn show(&self) -> Result<(), crate::PlatformError> { unimplemented!(); } /// Convenience function for [`crate::Window::hide()`]. Hides the window, so that it is not /// visible anymore. The additional strong reference on the associated component, that was /// created when show() was called, is dropped. fn hide(&self) -> Result<(), crate::PlatformError> { unimplemented!(); } /// Returns the Window associated with this component. The window API can be used /// to control different aspects of the integration into the windowing system, /// such as the position on the screen. fn window(&self) -> &Window { unimplemented!() } /// This is a convenience function that first calls [`Self::show`], followed by [`crate::run_event_loop()`] /// and [`Self::hide`]. fn run(&self) -> Result<(), crate::PlatformError> { unimplemented!(); } /// This function provides access to instances of global singletons exported in `.slint`. fn global<'a, T: Global<'a, Self>>(&'a self) -> T { unimplemented!() } } } pub mod mcu { #![doc = include_str!("mcu.md")] #[cfg(feature = "renderer-software")] use crate::platform::software_renderer::*; use crate::platform::*; mod slint { pub use crate::*; } } #[i_slint_core_macros::slint_doc] pub mod cargo_features { //! # Feature flags and backend selection. //! Use the following feature flags in your Cargo.toml to enable additional features. //! #![cfg_attr(feature = "document-features", doc = document_features::document_features!())] //! //! More information about the backend and renderers is available in the //![Slint Documentation](slint:backends_and_renderers)")] use crate::*; } pub mod type_mappings { #![doc = include_str!("type-mappings.md")] use crate::*; } ================================================ FILE: api/rs/slint/lib.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 // cSpell: ignore buildrs #![cfg_attr(docsrs, feature(doc_cfg))] /*! # Slint This crate is the main entry point for embedding user interfaces designed with [Slint](https://slint.rs/) in Rust programs. */ #![doc = i_slint_core_macros::slint_doc_str!("If you are new to Slint, start with the [Walk-through **tutorial**](slint:quickstart).")] /*! If you are already familiar with Slint, the following topics provide related information. ## Topics */ #![doc = i_slint_core_macros::slint_doc_str!("- [The Slint Language Documentation](slint:index)")] /*! - [Type mappings between .slint and Rust](docs::type_mappings) - [Feature flags and backend selection](docs::cargo_features) - [Slint on Microcontrollers](docs::mcu) ## How to use this crate: Designs of user interfaces are described in the `.slint` design markup language. There are three ways of including them in Rust: - The `.slint` code is [inline in a macro](#the-slint-code-in-a-macro). - The `.slint` code in [external files compiled with `build.rs`](#the-slint-code-in-external-files-is-compiled-with-buildrs) */ #![doc = i_slint_core_macros::slint_doc_str!(" - The `.slint` code is loaded dynamically at run-time from the file system, by using the [interpreter API](slint:rust:slint_interpreter/).")] /*! With the first two methods, the markup code is translated to Rust code and each component is turned into a Rust struct with functions. Use these functions to instantiate and show the component, and to access declared properties. Check out our [sample component](docs::generated_code::SampleComponent) for more information about the generation functions and how to use them. ### The .slint code in a macro This method combines your Rust code with the `.slint` design markup in one file, using a macro: ```rust,no_run slint::slint!{ export component HelloWorld inherits Window { Text { text: "hello world"; color: green; } } } fn main() { HelloWorld::new().unwrap().run().unwrap(); } ``` ### The .slint code in external files is compiled with `build.rs` When your design becomes bigger in terms of markup code, you may want move it to a dedicated `.slint` file. */ #![doc = i_slint_core_macros::slint_doc_str!("It's also possible to split a `.slint` file into multiple files using [modules](slint:modules).")] /*!Use a [build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) to compile your main `.slint` file: */ #![doc = i_slint_core_macros::slint_doc_str!("In your Cargo.toml add a `build` assignment and use the [`slint-build`](slint:rust:slint_build/) crate in `build-dependencies`:")] /*! ```toml [package] ... build = "build.rs" edition = "2021" [dependencies] slint = "1.15" ... [build-dependencies] slint-build = "1.15" ``` Use the API of the slint-build crate in the `build.rs` file: ```rust,no_run fn main() { slint_build::compile("ui/hello.slint").unwrap(); } ``` Finally, use the [`include_modules!`] macro in your `main.rs`: ```ignore slint::include_modules!(); fn main() { HelloWorld::new().unwrap().run().unwrap(); } ``` Use our [Template Repository](https://github.com/slint-ui/slint-rust-template) to create a skeleton file hierarchy that uses this method: 1. Download and extract the [ZIP archive of the Rust Template](https://github.com/slint-ui/slint-rust-template/archive/refs/heads/main.zip). 2. Rename the extracted directory and change into it: ```bash mv slint-rust-template-main my-project cd my-project ``` ## Generated components Exported component from the macro or the main file that inherit `Window` or `Dialog` is mapped to a Rust structure. The components are generated and re-exported to the location of the [`include_modules!`] or [`slint!`] macro. It is represented as a struct with the same name as the component. For example, if you have ```slint,no-preview export component MyComponent inherits Window { /*...*/ } ``` in the .slint file, it will create a ```rust struct MyComponent { /*...*/ } ``` See also our [sample component](docs::generated_code::SampleComponent) for more information about the API of the generated struct. A component is instantiated using the [`fn new() -> Self`](docs::generated_code::SampleComponent::new) function. The following convenience functions are available through the [`ComponentHandle`] implementation: - [`fn clone_strong(&self) -> Self`](docs::generated_code::SampleComponent::clone_strong): creates a strongly referenced clone of the component instance. - [`fn as_weak(&self) -> Weak`](docs::generated_code::SampleComponent::as_weak): to create a [weak](Weak) reference to the component instance. - [`fn show(&self)`](docs::generated_code::SampleComponent::show): to show the window of the component. - [`fn hide(&self)`](docs::generated_code::SampleComponent::hide): to hide the window of the component. - [`fn run(&self)`](docs::generated_code::SampleComponent::run): a convenience function that first calls `show()`, followed by spinning the event loop, and `hide()` when returning from the event loop. - [`fn global>(&self) -> T`](docs::generated_code::SampleComponent::global): an accessor to the global singletons, For each top-level property - A setter [`fn set_(&self, value: )`](docs::generated_code::SampleComponent::set_counter) - A getter [`fn get_(&self) -> `](docs::generated_code::SampleComponent::get_counter) For each top-level callback - [`fn invoke_(&self)`](docs::generated_code::SampleComponent::invoke_hello): to invoke the callback - [`fn on_(&self, callback: impl Fn() + 'static)`](docs::generated_code::SampleComponent::on_hello): to set the callback handler. Note: All dashes (`-`) are replaced by underscores (`_`) in names of types or functions. After instantiating the component, call [`ComponentHandle::run()`] on show it on the screen and spin the event loop to react to input events. To show multiple components simultaneously, call [`ComponentHandle::show()`] on each instance. Call [`run_event_loop()`] when you're ready to enter the event loop. The generated component struct acts as a handle holding a strong reference (similar to an `Rc`). The `Clone` trait is not implemented. Instead you need to make explicit [`ComponentHandle::clone_strong`] and [`ComponentHandle::as_weak`] calls. A strong reference should not be captured by the closures given to a callback, as this would produce a reference loop and leak the component. Instead, the callback function should capture a weak component. ## Threading and Event-loop For platform-specific reasons, the event loop must run in the main thread, in most backends, and all the components must be created in the same thread as the thread the event loop is running or is going to run. You should perform the minimum amount of work in the main thread and delegate the actual logic to another thread to avoid blocking animations. Use the [`invoke_from_event_loop`] function to communicate from your worker thread to the UI thread. To run a function with a delay or with an interval use a [`Timer`]. To run an async function or a future, use [`spawn_local()`]. ## Exported Global singletons */ #![doc = i_slint_core_macros::slint_doc_str!("When you export a [global singleton](slint:globals) from the main file,")] /*! it is also generated with the exported name. Like the main component, the generated struct have inherent method to access the properties and callback: For each property - A setter: `fn set_(&self, value: )` - A getter: `fn get_(&self) -> ` For each callback - `fn invoke_(&self, ) -> ` to invoke the callback - `fn on_(&self, callback: impl Fn() + 'static)` to set the callback handler. The global can be accessed with the [`ComponentHandle::global()`] function, or with [`Global::get()`] See the [documentation of the `Global` trait](Global) for an example. **Note**: Global singletons are instantiated once per component. When declaring multiple components for `export` to Rust, each instance will have their own instance of associated globals singletons. */ #![warn(missing_docs)] #![deny(unsafe_code)] #![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")] #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::needless_doctest_main)] // We document how to write a main function extern crate alloc; #[cfg(not(feature = "compat-1-2"))] compile_error!( "The feature `compat-1-2` must be enabled to ensure \ forward compatibility with future version of this crate" ); pub use slint_macros::slint; pub use i_slint_backend_selector::api::*; pub use i_slint_core::api::*; #[doc(hidden)] #[deprecated(note = "Experimental type was made public by mistake")] pub use i_slint_core::component_factory::ComponentFactory; #[cfg(not(target_arch = "wasm32"))] pub use i_slint_core::graphics::{BorrowedOpenGLTextureBuilder, BorrowedOpenGLTextureOrigin}; pub use i_slint_core::items::{StandardListViewItem, TableColumn}; pub use i_slint_core::model::{ FilterModel, MapModel, Model, ModelExt, ModelNotify, ModelPeer, ModelRc, ModelTracker, ReverseModel, SortModel, VecModel, }; pub use i_slint_core::timers::{Timer, TimerMode}; pub use i_slint_core::translations::{SelectBundledTranslationError, select_bundled_translation}; pub mod private_unstable_api; /// Enters the main event loop. This is necessary in order to receive /// events from the windowing system for rendering to the screen /// and reacting to user input. /// This function will run until the last window is closed or until /// [`quit_event_loop()`] is called. /// /// See also [`run_event_loop_until_quit()`] to keep the event loop running until /// [`quit_event_loop()`] is called, even if all windows are closed. pub fn run_event_loop() -> Result<(), PlatformError> { i_slint_backend_selector::with_platform(|b| b.run_event_loop()) } /// Similar to [`run_event_loop()`], but this function enters the main event loop /// and continues to run even when the last window is closed, until /// [`quit_event_loop()`] is called. /// /// This is useful for system tray applications where the application needs to stay alive /// even if no windows are visible. pub fn run_event_loop_until_quit() -> Result<(), PlatformError> { i_slint_backend_selector::with_platform(|b| { #[allow(deprecated)] b.set_event_loop_quit_on_last_window_closed(false); b.run_event_loop() }) } /// Spawns a [`Future`] to execute in the Slint event loop. /// /// This function is intended to be invoked only from the main Slint thread that runs the event loop. /// /// For spawning a `Send` future from a different thread, this function should be called from a closure /// passed to [`invoke_from_event_loop()`]. /// /// This function is typically called from a UI callback. /// /// # Example /// /// ```rust,no_run /// slint::spawn_local(async move { /// // your async code goes here /// }).unwrap(); /// ``` /// /// # Compatibility with Tokio and other runtimes /// /// The runtime used to execute the future on the main thread is platform-dependent, /// for instance, it could be the winit event loop. Therefore, futures that assume a specific runtime /// may not work. This may be an issue if you call `.await` on a future created by another /// runtime, or pass the future directly to `spawn_local`. /// /// Futures from the [smol](https://docs.rs/smol/latest/smol/) runtime always hand off their work to /// separate I/O threads that run in parallel to the Slint event loop. /// /// The [Tokio](https://docs.rs/tokio/latest/tokio/index.html) runtime is subject to the following constraints: /// /// * Tokio futures require entering the context of a global Tokio runtime. /// * Tokio futures aren't guaranteed to hand off their work to separate threads and may therefore not complete, because /// the Slint runtime can't drive the Tokio runtime. /// * Tokio futures require regular yielding to the Tokio runtime for fairness, a constraint that also can't be met by Slint. /// * Tokio's [current-thread schedule](https://docs.rs/tokio/latest/tokio/runtime/index.html#current-thread-scheduler) /// cannot be used in Slint main thread, because Slint cannot yield to it. /// /// To address these constraints, use [async_compat](https://docs.rs/async-compat/latest/async_compat/index.html)'s [Compat::new()](https://docs.rs/async-compat/latest/async_compat/struct.Compat.html#method.new) /// to implicitly allocate a shared, multi-threaded Tokio runtime that will be used for Tokio futures. /// /// The following little example demonstrates the use of Tokio's [`TcpStream`](https://docs.rs/tokio/latest/tokio/net/struct.TcpStream.html) to /// read from a network socket. The entire future passed to `spawn_local()` is wrapped in `Compat::new()` to make it run: /// /// ```rust,no_run /// // A dummy TCP server that once reports "Hello World" /// # i_slint_backend_testing::init_integration_test_with_mock_time(); /// use std::io::Write; /// /// let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); /// let local_addr = listener.local_addr().unwrap(); /// let server = std::thread::spawn(move || { /// let mut stream = listener.incoming().next().unwrap().unwrap(); /// stream.write("Hello World".as_bytes()).unwrap(); /// }); /// /// let slint_future = async move { /// use tokio::io::AsyncReadExt; /// let mut stream = tokio::net::TcpStream::connect(local_addr).await.unwrap(); /// let mut data = Vec::new(); /// stream.read_to_end(&mut data).await.unwrap(); /// assert_eq!(data, "Hello World".as_bytes()); /// slint::quit_event_loop().unwrap(); /// }; /// /// // Wrap the future that includes Tokio futures in async_compat's `Compat` to ensure /// // presence of a Tokio run-time. /// slint::spawn_local(async_compat::Compat::new(slint_future)).unwrap(); /// /// slint::run_event_loop_until_quit().unwrap(); /// /// server.join().unwrap(); /// ``` /// /// The use of `#[tokio::main]` is **not recommended**. If it's necessary to use though, wrap the call to enter the Slint /// event loop in a call to [`tokio::task::block_in_place`](https://docs.rs/tokio/latest/tokio/task/fn.block_in_place.html): /// /// ```rust, no_run /// // Wrap the call to run_event_loop to ensure presence of a Tokio run-time. /// tokio::task::block_in_place(slint::run_event_loop).unwrap(); /// ``` #[cfg(target_has_atomic = "ptr")] pub fn spawn_local( fut: F, ) -> Result, EventLoopError> { i_slint_backend_selector::with_global_context(|ctx| ctx.spawn_local(fut)) .map_err(|_| EventLoopError::NoEventLoopProvider)? } #[i_slint_core_macros::slint_doc] /// Include the code generated with the slint-build crate from the build script. After calling `slint_build::compile` /// in your `build.rs` build script, the use of this macro includes the generated Rust code and makes the exported types /// available for you to instantiate. /// /// Check the documentation of the [`slint-build`](slint:rust:slint_build) crate for more information. #[macro_export] macro_rules! include_modules { () => { include!(env!("SLINT_INCLUDE_GENERATED")); }; } #[i_slint_core_macros::slint_doc] /// Initialize translations when using the `gettext` feature. /// /// Call this in your main function with the path where translations are located. /// This macro internally calls the [`bindtextdomain`](https://man7.org/linux/man-pages/man3/bindtextdomain.3.html) function from gettext. /// /// The first argument of the macro must be an expression that implements `Into`. /// It specifies the directory in which gettext should search for translations. /// /// Translations are expected to be found at `//LC_MESSAGES/.mo`, /// where `dirname` is the directory passed as an argument to this macro, /// `locale` is a locale name (e.g., `en`, `en_GB`, `fr`), and /// `crate` is the package name obtained from the `CARGO_PKG_NAME` environment variable. /// /// See also the [Translation documentation](slint:translations). /// /// ### Example /// ```rust /// fn main() { /// slint::init_translations!(concat!(env!("CARGO_MANIFEST_DIR"), "/translations/")); /// // ... /// } /// ``` /// /// For example, assuming this is in a crate called `example` and the default locale /// is configured to be French, it will load translations at runtime from /// `/path/to/example/translations/fr/LC_MESSAGES/example.mo`. /// /// Another example of loading translations relative to the executable: /// ```rust /// slint::init_translations!(std::env::current_exe().unwrap().parent().unwrap().join("translations")); /// ``` #[cfg(feature = "gettext")] #[macro_export] macro_rules! init_translations { ($dirname:expr) => { $crate::private_unstable_api::init_translations(env!("CARGO_PKG_NAME"), $dirname); }; } /// This module contains items that you need to use or implement if you want use Slint in an environment without /// one of the supplied platform backends such as qt or winit. /// /// The primary interface is the [`platform::Platform`] trait. Pass your implementation of it to Slint by calling /// [`platform::set_platform()`] early on in your application, before creating any Slint components. /// /// The [Slint on Microcontrollers](crate::docs::mcu) documentation has additional examples. pub mod platform { pub use i_slint_core::platform::*; /// This module contains the [`femtovg_renderer::FemtoVGRenderer`] and related types. /// /// It is only enabled when the `renderer-femtovg` Slint feature is enabled. #[cfg(all( not(target_os = "android"), any(feature = "renderer-femtovg", feature = "renderer-femtovg-wgpu") ))] pub mod femtovg_renderer { #[cfg(feature = "renderer-femtovg")] pub use i_slint_renderer_femtovg::FemtoVGOpenGLRenderer as FemtoVGRenderer; /// Use this type to render to a WGPU texture using FemtoVG. #[cfg(feature = "unstable-wgpu-28")] pub use i_slint_renderer_femtovg::FemtoVGWGPURenderer; #[cfg(feature = "renderer-femtovg")] pub use i_slint_renderer_femtovg::opengl::OpenGLInterface; } #[cfg(feature = "renderer-software")] /// This module contains the [`software_renderer::SoftwareRenderer`] and related types. /// /// It is only enabled when the `renderer-software` Slint feature is enabled. pub mod software_renderer { pub use i_slint_renderer_software::*; } } #[i_slint_core_macros::slint_doc] /// This module contains some of the enums and structs from the Slint language. /// /// See also the list of [global structs and enums](slint:StructType) pub mod language { pub use i_slint_core::items::ColorScheme; macro_rules! export_builtin_structs { ($( $(#[$attr:meta])* struct $Name:ident { @name = $NameTy:ident :: $NameVariant:ident, export { $( $(#[$pub_attr:meta])* $pub_field:ident : $pub_type:ty, )* } private { $( $(#[$pri_attr:meta])* $pri_field:ident : $pri_type:ty, )* } } )*) => { $( export_builtin_structs!(@export $NameTy $Name); )* }; (@export BuiltinPublicStruct $Name:ident) => { pub use i_slint_core::items::$Name; }; (@export BuiltinPrivateStruct $Name:ident) => {}; } i_slint_common::for_each_builtin_structs!(export_builtin_structs); } #[cfg(any( doc, all( target_os = "android", any(feature = "backend-android-activity-05", feature = "backend-android-activity-06") ) ))] pub mod android; /// Helper type that helps checking that the generated code is generated for the right version #[doc(hidden)] #[allow(non_camel_case_types)] pub struct VersionCheck_1_16_0; #[cfg(doctest)] mod compile_fail_tests; #[cfg(doc)] pub mod docs; #[cfg(feature = "unstable-wgpu-27")] pub mod wgpu_27 { //! WGPU 27.x specific types and re-exports. //! //! *Note*: This module is behind a feature flag and may be removed or changed in future minor releases, //! as new major WGPU releases become available. //! //! Use the types in this module in combination with other APIs to integrate external, WGPU-based rendering engines //! into a UI with Slint. //! //! First, ensure that WGPU is used for rendering with Slint by using [`slint::BackendSelector::require_wgpu_27()`](i_slint_backend_selector::api::BackendSelector::require_wgpu_27()). //! This function accepts a pre-configured WGPU setup or configuration hints such as required features or memory limits. //! //! For rendering, it's crucial that you're using the same [`wgpu::Device`] and [`wgpu::Queue`] for allocating textures or submitting commands as Slint. Obtain the same queue //! by either using [`WGPUConfiguration::Manual`] to make Slint use an existing WGPU configuration, or use [`slint::Window::set_rendering_notifier()`](i_slint_core::api::Window::set_rendering_notifier()) //! to let Slint invoke a callback that provides access device, queue, etc. in [`slint::GraphicsAPI::WGPU27`](i_slint_core::api::GraphicsAPI::WGPU27). //! //! To integrate rendering content into a scene shared with a Slint UI, use either [`slint::Window::set_rendering_notifier()`](i_slint_core::api::Window::set_rendering_notifier()) to render an underlay //! or overlay, or integrate externally produced [`wgpu::Texture`]s using [`slint::Image::try_from()`](i_slint_core::graphics::Image::try_from). //! //! The following example allocates a [`wgpu::Texture`] and, for the sake of simplicity in this documentation, fills with green as color, and then proceeds to set it as a `slint::Image` in the scene. //! //! `Cargo.toml`: //! ```toml //! slint = { version = "~1.16", features = ["unstable-wgpu-27"] } //! ``` //! //! `main.rs`: //!```rust,no_run //! //! use slint::wgpu_27::wgpu; //! use wgpu::util::DeviceExt; //! //!slint::slint!{ //! export component HelloWorld inherits Window { //! preferred-width: 320px; //! preferred-height: 300px; //! in-out property app-texture; //! VerticalLayout { //! Text { //! text: "hello world"; //! color: green; //! } //! Image { source: root.app-texture; } //! } //! } //!} //!fn main() -> Result<(), Box> { //! slint::BackendSelector::new() //! .require_wgpu_27(slint::wgpu_27::WGPUConfiguration::default()) //! .select()?; //! let app = HelloWorld::new()?; //! //! let app_weak = app.as_weak(); //! //! app.window().set_rendering_notifier(move |state, graphics_api| { //! let (Some(app), slint::RenderingState::RenderingSetup, slint::GraphicsAPI::WGPU27{ device, queue, ..}) = (app_weak.upgrade(), state, graphics_api) else { //! return; //! }; //! //! let mut pixels = slint::SharedPixelBuffer::::new(320, 200); //! pixels.make_mut_slice().fill(slint::Rgba8Pixel { //! r: 0, //! g: 255, //! b :0, //! a: 255, //! }); //! //! let texture = device.create_texture_with_data(queue, //! &wgpu::TextureDescriptor { //! label: None, //! size: wgpu::Extent3d { width: 320, height: 200, depth_or_array_layers: 1 }, //! mip_level_count: 1, //! sample_count: 1, //! dimension: wgpu::TextureDimension::D2, //! format: wgpu::TextureFormat::Rgba8Unorm, //! usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, //! view_formats: &[], //! }, //! wgpu::util::TextureDataOrder::default(), //! pixels.as_bytes(), //! ); //! //! let imported_image = slint::Image::try_from(texture).unwrap(); //! //! app.set_app_texture(imported_image); //! })?; //! //! app.run()?; //! //! Ok(()) //!} //!``` //! pub use i_slint_core::graphics::wgpu_27::api::*; } #[cfg(feature = "unstable-wgpu-28")] pub mod wgpu_28 { //! WGPU 28.x specific types and re-exports. //! //! *Note*: This module is behind a feature flag and may be removed or changed in future minor releases, //! as new major WGPU releases become available. //! //! Use the types in this module in combination with other APIs to integrate external, WGPU-based rendering engines //! into a UI with Slint. //! //! First, ensure that WGPU is used for rendering with Slint by using [`slint::BackendSelector::require_wgpu_28()`](i_slint_backend_selector::api::BackendSelector::require_wgpu_28()). //! This function accepts a pre-configured WGPU setup or configuration hints such as required features or memory limits. //! //! For rendering, it's crucial that you're using the same [`wgpu::Device`] and [`wgpu::Queue`] for allocating textures or submitting commands as Slint. Obtain the same queue //! by either using [`WGPUConfiguration::Manual`] to make Slint use an existing WGPU configuration, or use [`slint::Window::set_rendering_notifier()`](i_slint_core::api::Window::set_rendering_notifier()) //! to let Slint invoke a callback that provides access device, queue, etc. in [`slint::GraphicsAPI::WGPU28`](i_slint_core::api::GraphicsAPI::WGPU28). //! //! To integrate rendering content into a scene shared with a Slint UI, use either [`slint::Window::set_rendering_notifier()`](i_slint_core::api::Window::set_rendering_notifier()) to render an underlay //! or overlay, or integrate externally produced [`wgpu::Texture`]s using [`slint::Image::try_from()`](i_slint_core::graphics::Image::try_from). //! //! The following example allocates a [`wgpu::Texture`] and, for the sake of simplicity in this documentation, fills with green as color, and then proceeds to set it as a `slint::Image` in the scene. //! //! `Cargo.toml`: //! ```toml //! slint = { version = "~1.16", features = ["unstable-wgpu-28"] } //! ``` //! //! `main.rs`: //!```rust,no_run //! //! use slint::wgpu_28::wgpu; //! use wgpu::util::DeviceExt; //! //!slint::slint!{ //! export component HelloWorld inherits Window { //! preferred-width: 320px; //! preferred-height: 300px; //! in-out property app-texture; //! VerticalLayout { //! Text { //! text: "hello world"; //! color: green; //! } //! Image { source: root.app-texture; } //! } //! } //!} //!fn main() -> Result<(), Box> { //! slint::BackendSelector::new() //! .require_wgpu_28(slint::wgpu_28::WGPUConfiguration::default()) //! .select()?; //! let app = HelloWorld::new()?; //! //! let app_weak = app.as_weak(); //! //! app.window().set_rendering_notifier(move |state, graphics_api| { //! let (Some(app), slint::RenderingState::RenderingSetup, slint::GraphicsAPI::WGPU28{ device, queue, ..}) = (app_weak.upgrade(), state, graphics_api) else { //! return; //! }; //! //! let mut pixels = slint::SharedPixelBuffer::::new(320, 200); //! pixels.make_mut_slice().fill(slint::Rgba8Pixel { //! r: 0, //! g: 255, //! b :0, //! a: 255, //! }); //! //! let texture = device.create_texture_with_data(queue, //! &wgpu::TextureDescriptor { //! label: None, //! size: wgpu::Extent3d { width: 320, height: 200, depth_or_array_layers: 1 }, //! mip_level_count: 1, //! sample_count: 1, //! dimension: wgpu::TextureDimension::D2, //! format: wgpu::TextureFormat::Rgba8Unorm, //! usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, //! view_formats: &[], //! }, //! wgpu::util::TextureDataOrder::default(), //! pixels.as_bytes(), //! ); //! //! let imported_image = slint::Image::try_from(texture).unwrap(); //! //! app.set_app_texture(imported_image); //! })?; //! //! app.run()?; //! //! Ok(()) //!} //!``` //! pub use i_slint_core::graphics::wgpu_28::api::*; } #[cfg(feature = "unstable-winit-030")] pub mod winit_030 { //! Winit 0.30.x specific types and re-exports. //! //! *Note*: This module is behind a feature flag and may be removed or changed in future minor releases, //! as new major Winit releases become available. //! //! Use the types and traits in this module in combination with other APIs to access additional window properties, //! create custom windows, or hook into the winit event loop. //! //! For example, use the [`WinitWindowAccessor`] to obtain access to the underling [`winit::window::Window`]: //! //! `Cargo.toml`: //! ```toml //! slint = { version = "~1.16", features = ["unstable-winit-030"] } //! ``` //! //! `main.rs`: //! ```rust,no_run //! // Bring winit and accessor traits into scope. //! use slint::winit_030::{WinitWindowAccessor, winit}; //! //! slint::slint!{ //! import { VerticalBox, Button } from "std-widgets.slint"; //! export component HelloWorld inherits Window { //! callback clicked; //! VerticalBox { //! Text { //! text: "hello world"; //! color: green; //! } //! Button { //! text: "Click me"; //! clicked => { root.clicked(); } //! } //! } //! } //! } //! fn main() -> Result<(), Box> { //! // Make sure the winit backed is selected: //! slint::BackendSelector::new() //! .backend_name("winit".into()) //! .select()?; //! //! let app = HelloWorld::new()?; //! let app_weak = app.as_weak(); //! app.on_clicked(move || { //! // access the winit window //! let app = app_weak.unwrap(); //! app.window().with_winit_window(|winit_window: &winit::window::Window| { //! eprintln!("window id = {:#?}", winit_window.id()); //! }); //! }); //! app.run()?; //! Ok(()) //! } //! ``` //! See also [`BackendSelector::with_winit_event_loop_builder()`](crate::BackendSelector::with_winit_event_loop_builder()) //! and [`BackendSelector::with_winit_window_attributes_hook()`](crate::BackendSelector::with_winit_window_attributes_hook()). pub use i_slint_backend_winit::{ CustomApplicationHandler, EventLoopBuilder, EventResult, SlintEvent, WinitWindowAccessor, winit, }; #[deprecated(note = "Renamed to `EventResult`")] /// Deprecated alias to [`EventResult`] pub type WinitWindowEventResult = EventResult; } #[cfg(feature = "unstable-fontique-07")] pub mod fontique_07 { //! Fontique 0.7 specific types and re-exports. //! //! *Note*: This module is behind a feature flag and may be removed or changed in future minor releases, //! as new major Fontique releases become available. //! //! Use the types, functions, and re-exports in this module to register custom fonts at run-time for use //! by Slint's renderers. pub use i_slint_common::sharedfontique::fontique; #[i_slint_core_macros::slint_doc] /// Returns a clone of [`fontique::Collection`] that's used by Slint for text rendering. It's set up /// with shared storage, so fonts registered with the returned collection or additionally configured font /// fallbacks apply to the entire process. /// /// Note: The recommended way of including custom fonts is at compile time of Slint files. For details, /// see also the [Font Handling](slint:FontHandling) documentation. /// /// The example below sketches out the steps for registering a downloaded font to add additional glyph /// coverage for Japanese text: /// /// `Cargo.toml`: /// ```toml /// slint = { version = "~1.16", features = ["unstable-fontique-07"] } /// ``` /// /// `main.rs`: /// ```rust,no_run /// use slint::fontique_07::fontique; /// /// fn main() { /// // ... /// let downloaded_font: Vec = todo!("Download https://somewebsite.com/font.ttf"); /// let blob = fontique::Blob::new(std::sync::Arc::new(downloaded_font)); /// let mut collection = slint::fontique_07::shared_collection(); /// let fonts = collection.register_fonts(blob, None); /// collection /// .append_fallbacks(fontique::FallbackKey::new("Hira", None), fonts.iter().map(|x| x.0)); /// collection /// .append_fallbacks(fontique::FallbackKey::new("Kana", None), fonts.iter().map(|x| x.0)); /// collection /// .append_fallbacks(fontique::FallbackKey::new("Hani", None), fonts.iter().map(|x| x.0)); /// // ... /// } /// ``` pub fn shared_collection() -> fontique::Collection { i_slint_core::with_global_context( || panic!("slint platform not initialized"), |ctx| ctx.font_context().borrow().collection.clone(), ) .unwrap() } } ================================================ FILE: api/rs/slint/mcu.md ================================================ # Slint on Microcontrollers ![](https://slint.dev/blog/porting-slint-to-microcontrollers/rp-pico_and_screen.jpg) The following sections explain how to use Slint to develop a UI on a Microcontroller (MCU) in a bare metal environment. ## Prerequisites Writing an application in Rust that runs on a MCU requires several prerequisites: * Install a Rust toolchain to cross-compile to the target architecture. * Locate and select the correct Hardware Abstraction Layer (HAL) crates and drivers, and depend on them in your `Cargo.toml`. * Install tools for flashing and debugging your code on the device. We recommend reading the [Rust Embedded Book](https://docs.rust-embedded.org/book/), and the curated list of [Awesome Embedded Rust](https://github.com/rust-embedded/awesome-embedded-rust) for a wide range of crates, tools, and training materials. These resources should guide you through the initial setup. Many include a "hello world" example to get started with your device. Slint requires a global memory allocator in a bare metal environment with `#![no_std]`. The following sections assume that your setup is complete and you have a non-graphical skeleton Rust program running on your MCU. ## Changes to `Cargo.toml` Start by adding a dependency to the `slint` and the `slint-build` crates to your `Cargo.toml` using the `cargo` command: Start with the `slint` crate like this: ```sh cargo add slint@1.15 --no-default-features --features "compat-1-2 unsafe-single-threaded libm renderer-software" ``` The default features of the `slint` crate are tailored towards hosted environments and includes the "std" feature. In bare metal environments, you need to disable the default features. In the snippet above, three features are selected: * `compat-1-2`: We select this feature when disabling the default features. For a detailed explanation see our blog post ["Adding default cargo features without breaking Semantic Versioning"](https://slint.dev/blog/rust-adding-default-cargo-feature.html). * `unsafe-single-threaded`: Slint internally uses Rust's [`thread_local!`](https://doc.rust-lang.org/std/macro.thread_local.html) macro to store global data. This macro is only available in the Rust Standard Library (std), but not in bare metal environments. As a fallback, the `unsafe-single-threaded` feature changes Slint to use unsafe static for storage. This way, you guarantee to use Slint API only from a single thread, and not from interrupt handlers. * `libm`: We select this feature to enable the use of the [libm](https://crates.io/crates/libm) crate to provide traits and functions for floating point arithmetic. They're typically provided by the Rust Standard Library (std), but that's not available in bare metal environments. * `renderer-software`: We select this feature to use Slint's built-in software renderer. It might be necessary to enable the [Feature resolver version 2](https://doc.rust-lang.org/cargo/reference/features.html#feature-resolver-version-2) in your Cargo.toml if you notice that your dependencies are attempting to build with `std` support even when disabled. This is the default when using the Rust 2021 Edition, but not if you use a workspace. Then add the `slint-build` crate as a build dependency: ```sh cargo add --build slint-build@1.15 ``` For reference: These are the relevant parts of your `Cargo.toml` file, ready to use Slint: ```toml [package] ## ... ## Edition 2021 or later enables the feature resolver version 2. edition = "2021" [dependencies] ## ... your other dependencies [dependencies.slint] version = "1.15" default-features = false features = ["compat-1-2", "unsafe-single-threaded", "libm", "renderer-software"] [build-dependencies] slint-build = "1.15" ``` ## Changes to `build.rs` Next, write a build script to compile the `.slint` files to Rust code for embedding into the program binary, using the `slint-build` crate: ```rust,no_run fn main() { slint_build::compile_with_config( "ui/main.slint", slint_build::CompilerConfiguration::new() .embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer), ).unwrap(); } ``` Use the `slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer` configuration option to tell the Slint compiler to embed the images and fonts in the binary in a format that's suitable for the software based renderer we're going to use. ## Application Structure Typically, a graphical application in hosted environments has at least three different tasks: * Receives user input from operation system APIs. * Reacts to the input by performing application specific computations. * Renders an updated user interface and presents it on the screen using device-independent operating system APIs. The operating system provides an event loop to connect and schedule these tasks. Slint implements the task of receiving user input and forwarding it to the user interface layer, and rendering the user interface to the screen. In bare metal environments it's your responsibility to substitute and connect functionality that's otherwise provided by the operating system: * Select crates that allow you to initialize the chips that operate peripherals, such as a touch input or display controller. If there are no crates, you may have to to develop your own drivers. * Drive the event loop yourself by querying peripherals for input, forwarding that input into computational modules of your application and instructing Slint to render the user interface. In Slint, the two primary APIs you need to use to accomplish these tasks are the [`slint::platform::Platform`] trait and the [`slint::Window`] struct. In the following sections we're going to cover how to use them and how they integrate into your event loop. ### The `Platform` Trait The [`slint::platform::Platform`] trait defines the interface between Slint and platform APIs typically provided by operating and windowing systems. You need to provide a minimal implementation of this trait and call [`slint::platform::set_platform`] before constructing your Slint application. This minimal implementation needs to cover two functions: * `fn create_window_adapter(&self) -> Result, PlatformError>;`: Implement this function to return an implementation of the `WindowAdapter` trait that will be associated with the Slint components you create. We provide a convenience struct [`slint::platform::software_renderer::MinimalSoftwareWindow`] that implements this trait. * `fn duration_since_start(&self) -> Duration`: For animations in `.slint` design files to change properties correctly, Slint needs to know how much time has elapsed between two rendered frames. In a bare metal environment you need to provide a source of time. Often the HAL crate of your device provides a system timer API for this, which you can query in your implementation. You may override more functions of this trait, for example to handle debug output, to delegate the event loop, or to deliver events in multi-threaded environments. A typical minimal implementation of the [`Platform`] trait that uses the [`MinimalSoftwareWindow`] looks like this: ```rust,no_run #![no_std] extern crate alloc; use alloc::{rc::Rc, boxed::Box}; # mod hal { pub struct Timer(); impl Timer { pub fn get_time(&self) -> u64 { todo!() } } } use slint::platform::{Platform, software_renderer::MinimalSoftwareWindow}; # slint::slint!{ export MyUI := Window {} } /* slint::include_modules!(); # */ struct MyPlatform { window: Rc, // optional: some timer device from your device's HAL crate timer: hal::Timer, // ... maybe more devices } impl Platform for MyPlatform { fn create_window_adapter(&self) -> Result, slint::PlatformError> { // Since on MCUs, there can be only one window, just return a clone of self.window. // We'll also use the same window in the event loop. Ok(self.window.clone()) } fn duration_since_start(&self) -> core::time::Duration { core::time::Duration::from_micros(self.timer.get_time()) } // optional: You can put the event loop there, or in the main function, see later fn run_event_loop(&self) -> Result<(), slint::PlatformError> { todo!(); } } // #[hal::entry] fn main() { // Initialize the heap allocator, peripheral devices and other things. // ... // Initialize a window (we'll need it later). let window = MinimalSoftwareWindow::new(Default::default()); slint::platform::set_platform(Box::new(MyPlatform { window: window.clone(), timer: hal::Timer(/*...*/), //... })) .unwrap(); // Setup the UI. let ui = MyUI::new(); // ... setup callback and properties on `ui` ... // Make sure the window covers our entire screen. window.set_size(slint::PhysicalSize::new(320, 240)); // ... start event loop (see later) ... } ``` ### The Event Loop With a `Platform` in place, you can write the main event loop to drive all the different tasks. You can choose between two options: * You can implement [`slint::platform::Platform::run_event_loop`]: Use this if you want to start the event loop in a way similar to desktop platforms, using the [`run()`](slint::ComponentHandle::run) function of your component, or use [`slint::run_event_loop()`]. Both of these functions will call your implementation of [`slint::platform::Platform::run_event_loop`]. * Implement a `loop { ... }` directly in your main function: This is called a super loop architecture and common for programs running in bare metal environments on MCUs. It allows you to initialize you device peripherals and access them without the need to move them into your `Platform` implementation. A typical super loop with Slint combines the tasks of querying input drivers, application specific computations, rendering and possibly putting the device into a low-power sleep state. Below is an example: ```rust,no_run use slint::platform::software_renderer::MinimalSoftwareWindow; let window = MinimalSoftwareWindow::new(Default::default()); # fn check_for_touch_event() -> Option { todo!() } # mod hal { pub fn wfi() {} } //... loop { // Let Slint run the timer hooks and update animations. slint::platform::update_timers_and_animations(); // Check the touch screen or input device using your driver. if let Some(event) = check_for_touch_event(/*...*/) { // convert the event from the driver into a `slint::platform::WindowEvent` // and pass it to the window. window.try_dispatch_event(event).unwrap(); } // ... maybe some more application logic ... // Draw the scene if something needs to be drawn. window.draw_if_needed(|renderer| { // see next section about rendering. todo!() }); // Try to put the MCU to sleep if !window.has_active_animations() { if let Some(duration) = slint::platform::duration_until_next_timer_update() { // ... schedule a timer interrupt in `duration` ... } hal::wfi(); // Wait for interrupt } } ``` ### The Renderer In desktop and embedded environments, Slint typically uses operating system provided APIs to render the user interface using the GPU. In contrast, most MCUs don't have GPUs. Instead, software rendering is used where all rendering is done by software on the CPU. Slint provides a SoftwareRenderer for this task. In the earlier example, we've instantiated a [`slint::platform::software_renderer::MinimalSoftwareWindow`]. This struct implements the `slint::platform::WindowAdapter` trait and also holds an instance of a [`slint::platform::software_renderer::SoftwareRenderer`]. You access it through the callback parameter of the [`draw_if_needed()`](MinimalSoftwareWindow::draw_if_needed) function. Depending on the amount of RAM your MCU has, and the kind of screen attached, you can choose between two different ways of using the renderer: * Use the [`SoftwareRenderer::render()`] function if you have enough RAM to allocate one, or even two, copies of the entire screen (also known as frame buffer). * Use the [`SoftwareRenderer::render_by_line()`] function to render the entire user interface line by line and send each line of pixels to the screen, typically via the SPI. This requires allocating at least enough RAM to store one single line of pixels. With both methods Slint renders into a provided buffer, which is a slice of a type that implements the [`slint::platform::software_renderer::TargetPixel`] trait. For convenience, Slint provides an implementation for [`slint::Rgb8Pixel`] and [`slint::platform::software_renderer::Rgb565Pixel`]. #### Rendering Into a Buffer The following example uses double buffering and swaps between two buffers. This requires a graphics driver that takes the address of the currently displayed frame buffer, also known as front buffer. A dedicated chip is then responsible for reading from RAM and transferring the contents to the attached screen, without any interference of the CPU. Meanwhile, Slint renders into the second buffer, the back buffer. ```rust,no_run use slint::platform::software_renderer::Rgb565Pixel; # fn is_swap_pending()->bool {false} fn swap_buffers() {} // In this example, we have two buffer: one is currently displayed, and we are // rendering into the second one. Hence we use `RepaintBufferType::SwappedBuffers` let window = slint::platform::software_renderer::MinimalSoftwareWindow::new( slint::platform::software_renderer::RepaintBufferType::SwappedBuffers ); const DISPLAY_WIDTH: usize = 320; const DISPLAY_HEIGHT: usize = 240; let mut buffer1 = [Rgb565Pixel(0); DISPLAY_WIDTH * DISPLAY_HEIGHT]; let mut buffer2 = [Rgb565Pixel(0); DISPLAY_WIDTH * DISPLAY_HEIGHT]; // ... configure the screen driver to use buffer1 or buffer2 ... // ... rest of initialization ... let mut currently_displayed_buffer : &mut [_] = &mut buffer1; let mut work_buffer : &mut [_] = &mut buffer2; loop { // ... // Draw the scene if something needs to be drawn window.draw_if_needed(|renderer| { // The screen driver might be taking some time to do the swap. We need to wait until // work_buffer is ready to be written in while is_swap_pending() {} // Do the rendering! renderer.render(work_buffer, DISPLAY_WIDTH); // tell the screen driver to display the other buffer. swap_buffers(); // Swap the buffer references for our next iteration // (this just swap the reference, not the actual data) core::mem::swap::<&mut [_]>(&mut work_buffer, &mut currently_displayed_buffer); }); // ... } ``` #### Rendering Line by Line When rendering the user interface line by line, you need to implement the [`LineBufferProvider`] trait. It defines a bi-directional interface between Slint and your code to send lines to the screen: * The trait's associated `TargetPixel` type let's Slint know how to create and manipulate pixels. How exactly the pixels are represented in your device and how they are blended remains your implementation detail. * The trait's `process_line` function notifies you when a line can be rendered and provides a callback that you can invoke to fill a slice of pixels for the given line. The following example defines a `DisplayWrapper` struct: It connects screen driver that implements the [`embedded_graphics`](https://lib.rs/embedded-graphics) traits with Slint's `Rgb565Pixel` type to implement the `LineBufferProvider` trait. The pixels for one line are sent to the screen by calling the [DrawTarget::fill_contiguous](https://docs.rs/embedded-graphics/0.7.1/embedded_graphics/draw_target/trait.DrawTarget.html) function. ```rust,no_run use embedded_graphics_core::{prelude::*, primitives::Rectangle, pixelcolor::raw::RawU16}; # mod embedded_graphics_core { # pub mod prelude { # pub struct Point; impl Point { pub fn new(_:i32, _:i32) -> Self {todo!()} } # pub struct Size; impl Size { pub fn new(_:i32, _:i32) -> Self {todo!()} } # pub trait DrawTarget { type Color; fn fill_contiguous(&mut self, _: &super::primitives::Rectangle, _: impl IntoIterator) -> Result<(), ()> {Ok(())} } # } # pub mod primitives { pub struct Rectangle; impl Rectangle { pub fn new(_: super::prelude::Point, _: super::prelude::Size) -> Self { todo!() } } } # pub mod pixelcolor { # pub struct Rgb565; # pub mod raw { pub struct RawU16(); impl RawU16 { pub fn new(_:u16) -> Self {todo!()} } impl From for super::Rgb565 { fn from(_: RawU16) -> Self {todo!()} } } # } # } # mod hal { pub struct Display; impl Display { pub fn new()-> Self {todo!()} } } # impl DrawTarget for hal::Display{ type Color = embedded_graphics_core::pixelcolor::Rgb565; } struct DisplayWrapper<'a, T>{ display: &'a mut T, line_buffer: &'a mut [slint::platform::software_renderer::Rgb565Pixel], } impl> slint::platform::software_renderer::LineBufferProvider for DisplayWrapper<'_, T> { type TargetPixel = slint::platform::software_renderer::Rgb565Pixel; fn process_line( &mut self, line: usize, range: core::ops::Range, render_fn: impl FnOnce(&mut [Self::TargetPixel]), ) { // Render into the line render_fn(&mut self.line_buffer[range.clone()]); // Send the line to the screen using DrawTarget::fill_contiguous self.display.fill_contiguous( &Rectangle::new(Point::new(range.start as _, line as _), Size::new(range.len() as _, 1)), self.line_buffer[range.clone()].iter().map(|p| RawU16::new(p.0).into()) ).map_err(drop).unwrap(); } } // Note that we use `ReusedBuffer` as parameter for MinimalSoftwareWindow to indicate // that we just need to re-render what changed since the last frame. // What's shown on the screen buffer is not in our RAM, but actually within the display itself. // Only the changed part of the screen will be updated. let window = slint::platform::software_renderer::MinimalSoftwareWindow::new( slint::platform::software_renderer::RepaintBufferType::ReusedBuffer ); const DISPLAY_WIDTH: usize = 320; let mut line_buffer = [slint::platform::software_renderer::Rgb565Pixel(0); DISPLAY_WIDTH]; let mut display = hal::Display::new(/*...*/); // ... rest of initialization ... loop { // ... window.draw_if_needed(|renderer| { renderer.render_by_line(DisplayWrapper{ display: &mut display, line_buffer: &mut line_buffer }); }); // ... } ``` Note: In our experience, using the synchronous `DrawTarget::fill_contiguous` function is slow. If your device is capable of using DMA, you may be able to achieve better performance by using two line buffers: One buffer to render into with the CPU, while the other buffer is transferred to the screen using DMA asynchronously. ## Example Implementations The examples that come with Slint use a helper crate called `mcu-board-support`. It provides implementations of the `Platform` trait for some MCUs, along with support for touch input and system timers. You can find the crate in our Git repository at: If your MCU is among the supported boards, then you can use it by specifying it as a [dependency from our Git repository](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-dependencies-from-git-repositories) in your `Cargo.toml`. For an entire template, check out our [Slint Bare Metal Microcontroller Rust Template](https://github.com/slint-ui/slint-mcu-rust-template). We also have a version of our printer demo that we've adapted to small screens, the [MCU Printer Demo](https://github.com/slint-ui/slint/tree/master/demos/printerdemo_mcu). ================================================ FILE: api/rs/slint/private_unstable_api.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 //! Module containing the private api that is used by the generated code. //! //! This is internal API that shouldn't be used because compatibility is not //! guaranteed #![doc(hidden)] use core::pin::Pin; use re_exports::*; // Helper functions called from generated code to reduce code bloat from // extra copies of the original functions for each call site due to // the impl Fn() they are taking. pub trait StrongItemTreeRef: Sized { type Weak: Clone + 'static; fn to_weak(&self) -> Self::Weak; fn from_weak(weak: &Self::Weak) -> Option; } impl StrongItemTreeRef for VRc { type Weak = VWeak; fn to_weak(&self) -> Self::Weak { VRc::downgrade(self) } fn from_weak(weak: &Self::Weak) -> Option { weak.upgrade() } } impl StrongItemTreeRef for VRcMapped { type Weak = VWeakMapped; fn to_weak(&self) -> Self::Weak { VRcMapped::downgrade(self) } fn from_weak(weak: &Self::Weak) -> Option { weak.upgrade() } } impl StrongItemTreeRef for Pin> { type Weak = PinWeak; fn to_weak(&self) -> Self::Weak { PinWeak::downgrade(self.clone()) } fn from_weak(weak: &Self::Weak) -> Option { weak.upgrade() } } pub fn set_property_binding< T: Clone + Default + 'static, StrongRef: StrongItemTreeRef + 'static, >( property: Pin<&Property>, component_strong: &StrongRef, binding: fn(StrongRef) -> T, ) { let weak = component_strong.to_weak(); property.set_binding(move || { ::from_weak(&weak).map(binding).unwrap_or_default() }) } pub fn set_animated_property_binding< T: Clone + i_slint_core::properties::InterpolatedPropertyValue + 'static, StrongRef: StrongItemTreeRef + 'static, >( property: Pin<&Property>, component_strong: &StrongRef, binding: fn(StrongRef) -> T, compute_animation_details: fn( StrongRef, ) -> (PropertyAnimation, Option), ) { let weak_1 = component_strong.to_weak(); let weak_2 = weak_1.clone(); property.set_animated_binding( move || binding(::from_weak(&weak_1).unwrap()), move || { compute_animation_details(::from_weak(&weak_2).unwrap()) }, ) } pub fn set_property_state_binding( property: Pin<&Property>, component_strong: &StrongRef, binding: fn(StrongRef) -> i32, ) { let weak = component_strong.to_weak(); re_exports::set_state_binding(property, move || { binding(::from_weak(&weak).unwrap()) }) } pub fn set_callback_handler< Arg: ?Sized + 'static, Ret: Default + 'static, StrongRef: StrongItemTreeRef + 'static, >( callback: Pin<&Callback>, component_strong: &StrongRef, handler: fn(StrongRef, &Arg) -> Ret, ) { let weak = component_strong.to_weak(); callback.set_handler(move |arg| { handler(::from_weak(&weak).unwrap(), arg) }) } pub fn debug(s: SharedString) { #[cfg(feature = "log")] log::debug!("{s}"); #[cfg(not(feature = "log"))] i_slint_core::debug_log!("{s}"); } pub fn ensure_backend() -> Result<(), crate::PlatformError> { i_slint_backend_selector::with_platform(|_b| { // Nothing to do, just make sure a backend was created Ok(()) }) } /// Creates a new window to render components in. pub fn create_window_adapter() -> Result, crate::PlatformError> { i_slint_backend_selector::with_platform(|b| b.create_window_adapter()) } /// Wrapper around i_slint_core::translations::translate for the generated code pub fn translate( origin: SharedString, context: SharedString, domain: SharedString, args: Slice, n: i32, plural: SharedString, ) -> SharedString { i_slint_core::translations::translate(&origin, &context, &domain, args.as_slice(), n, &plural) } #[cfg(feature = "gettext")] pub fn init_translations(domain: &str, dirname: impl Into) { i_slint_core::translations::gettext_bindtextdomain(domain, dirname.into()).unwrap() } pub fn use_24_hour_format() -> bool { i_slint_core::date_time::use_24_hour_format() } /// internal re_exports used by the macro generated pub mod re_exports { pub use alloc::boxed::Box; pub use alloc::rc::{Rc, Weak}; pub use alloc::string::String; pub use alloc::{vec, vec::Vec}; pub use const_field_offset::{self, FieldOffsets, PinnedDrop}; pub use core::iter::FromIterator; pub use core::option::{Option, Option::*}; pub use core::result::{Result, Result::*}; pub use i_slint_core::styled_text::{StyledText, parse_markdown, string_to_styled_text}; // This one is empty when Qt is not available, which triggers a warning pub use euclid::approxeq::ApproxEq; #[allow(unused_imports)] pub use i_slint_backend_selector::native_widgets::*; pub use i_slint_core::accessibility::{ AccessibilityAction, AccessibleStringProperty, SupportedAccessibilityAction, }; pub use i_slint_core::animations::{EasingCurve, animation_tick, current_tick}; pub use i_slint_core::api::LogicalPosition; pub use i_slint_core::callbacks::Callback; pub use i_slint_core::context::SlintContext; pub use i_slint_core::date_time::*; pub use i_slint_core::detect_operating_system; pub use i_slint_core::graphics::*; pub use i_slint_core::input::{ FocusEvent, FocusReason, InputEventResult, KeyEvent, KeyEventResult, KeyboardModifiers, Keys, MouseEvent, key_codes::Key, make_keys, }; pub use i_slint_core::item_tree::{ IndexRange, ItemTree, ItemTreeRefPin, ItemTreeVTable, ItemTreeWeak, register_item_tree, unregister_item_tree, }; pub use i_slint_core::item_tree::{ ItemTreeNode, ItemVisitorRefMut, ItemVisitorVTable, ItemWeak, TraversalOrder, VisitChildrenResult, visit_item_tree, }; pub use i_slint_core::items::{Transform, *}; pub use i_slint_core::layout::*; pub use i_slint_core::lengths::{ LogicalLength, LogicalPoint, LogicalRect, logical_position_to_api, }; pub use i_slint_core::menus::{Menu, MenuFromItemTree, MenuVTable}; pub use i_slint_core::model::*; pub use i_slint_core::properties::{ ChangeTracker, Property, PropertyTracker, StateInfo, set_state_binding, }; pub use i_slint_core::slice::Slice; pub use i_slint_core::string::shared_string_from_number; pub use i_slint_core::string::shared_string_from_number_fixed; pub use i_slint_core::string::shared_string_from_number_precision; pub use i_slint_core::timers::{Timer, TimerMode}; pub use i_slint_core::translations::{ set_bundled_languages, translate_from_bundle, translate_from_bundle_with_plural, }; pub use i_slint_core::window::{ InputMethodRequest, WindowAdapter, WindowAdapterRc, WindowInner, }; pub use i_slint_core::{ Color, Coord, SharedString, SharedVector, format, string::ToSharedString, }; pub use i_slint_core::{ItemTreeVTable_static, MenuVTable_static}; pub use num_traits::float::Float; pub use num_traits::ops::euclid::Euclid; pub use once_cell::race::OnceBox; pub use once_cell::unsync::OnceCell; pub use pin_weak::rc::PinWeak; pub use unicode_segmentation::UnicodeSegmentation; pub use vtable::{self, *}; #[cfg(feature = "live-preview")] pub use slint_interpreter::live_preview; } ================================================ FILE: api/rs/slint/tests/invalid_rust_attr.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 @rust-attr("Doesn't Lex in rust \{"🦞"}") export struct Foo { int: int } ================================================ FILE: api/rs/slint/tests/partial_renderer.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use i_slint_renderer_skia::SkiaRenderer; use i_slint_renderer_skia::SkiaSharedContext; use i_slint_renderer_skia::skia_safe; use slint::platform::software_renderer::{ MinimalSoftwareWindow, PremultipliedRgbaColor, SoftwareRenderer, TargetPixel, }; use slint::platform::{PlatformError, WindowAdapter}; use slint::{Model, PhysicalPosition, PhysicalSize, SharedPixelBuffer}; use std::cell::{Cell, RefCell}; use std::rc::{Rc, Weak}; thread_local! { static WINDOW: Rc = MinimalSoftwareWindow::new(slint::platform::software_renderer::RepaintBufferType::ReusedBuffer); static SKIA_WINDOW: Rc = SkiaTestWindow::new(); static NEXT_WINDOW_CHOICE: Rc>>> = Rc::new(RefCell::new(None)); } struct TestPlatform; impl slint::platform::Platform for TestPlatform { fn create_window_adapter(&self) -> Result, PlatformError> { Ok(NEXT_WINDOW_CHOICE.with(|choice| { choice.borrow_mut().take().unwrap_or_else(|| WINDOW.with(|x| x.clone())) })) } } #[derive(Clone, Copy, Default)] struct TestPixel(bool); impl TargetPixel for TestPixel { fn blend(&mut self, _color: PremultipliedRgbaColor) { *self = Self(true); } fn from_rgb(_red: u8, _green: u8, _blue: u8) -> Self { Self(true) } } #[track_caller] fn do_test_render_region(renderer: &SoftwareRenderer, x: i32, y: i32, x2: i32, y2: i32) { let mut buffer = vec![TestPixel(false); 500 * 500]; let r = renderer.render(buffer.as_mut_slice(), 500); assert_eq!(r.bounding_box_size(), PhysicalSize { width: (x2 - x) as _, height: (y2 - y) as _ }); assert_eq!(r.bounding_box_origin(), PhysicalPosition { x, y }); let mut has_one_pixel = false; for py in 0..500 { for px in 0..500 { let in_bounding_box = (x..x2).contains(&(px as i32)) && (y..y2).contains(&(py as i32)); if !in_bounding_box { assert!( !buffer[py * 500 + px].0, "Something written outside of bounding box in {px},{py} - (x={x},y={y},x2={x2},y2={y2})" ) } else if buffer[py * 500 + px].0 { has_one_pixel = true; } } } assert!(has_one_pixel, "Nothing was rendered"); } struct SkiaTestWindow { window: slint::Window, renderer: SkiaRenderer, needs_redraw: Cell, size: Cell, render_buffer: Rc, } impl SkiaTestWindow { fn new() -> Rc { let render_buffer = Rc::new(SkiaTestSoftwareBuffer::default()); let renderer = SkiaRenderer::new_with_surface( &SkiaSharedContext::default(), Box::new(i_slint_renderer_skia::software_surface::SoftwareSurface::from( render_buffer.clone(), )), ); Rc::new_cyclic(|w: &Weak| Self { window: slint::Window::new(w.clone()), renderer, needs_redraw: Default::default(), size: Default::default(), render_buffer, }) } fn draw_if_needed(&self) -> bool { if self.needs_redraw.replace(false) { self.renderer.render().unwrap(); true } else { false } } fn last_dirty_region_bounding_box_size(&self) -> Option { self.render_buffer.last_dirty_region.borrow().as_ref().map(|r| { let size = r.bounding_rect().size; slint::LogicalSize::new(size.width as _, size.height as _) }) } fn last_dirty_region_bounding_box_origin(&self) -> Option { self.render_buffer.last_dirty_region.borrow().as_ref().map(|r| { let origin = r.bounding_rect().origin; slint::LogicalPosition::new(origin.x as _, origin.y as _) }) } } impl WindowAdapter for SkiaTestWindow { fn window(&self) -> &slint::Window { &self.window } fn size(&self) -> PhysicalSize { self.size.get() } fn renderer(&self) -> &dyn slint::platform::Renderer { &self.renderer } fn set_size(&self, size: slint::WindowSize) { self.size.set(size.to_physical(1.)); self.window .dispatch_event(slint::platform::WindowEvent::Resized { size: size.to_logical(1.) }) } fn request_redraw(&self) { self.needs_redraw.set(true); } } #[derive(Default)] struct SkiaTestSoftwareBuffer { pixels: RefCell>>, last_dirty_region: RefCell>, } impl i_slint_renderer_skia::software_surface::RenderBuffer for SkiaTestSoftwareBuffer { fn with_buffer( &self, _window: &slint::Window, size: PhysicalSize, render_callback: &mut dyn FnMut( std::num::NonZeroU32, std::num::NonZeroU32, i_slint_renderer_skia::skia_safe::ColorType, u8, &mut [u8], ) -> Result< Option, i_slint_core::platform::PlatformError, >, ) -> Result<(), i_slint_core::platform::PlatformError> { let Some((width, height)): Option<(std::num::NonZeroU32, std::num::NonZeroU32)> = size.width.try_into().ok().zip(size.height.try_into().ok()) else { // Nothing to render return Ok(()); }; let mut shared_pixel_buffer = self.pixels.borrow_mut().take(); if shared_pixel_buffer.as_ref().is_some_and(|existing_buffer| { existing_buffer.width() != width.get() || existing_buffer.height() != height.get() }) { shared_pixel_buffer = None; } let mut age = 1; let pixels = shared_pixel_buffer.get_or_insert_with(|| { age = 0; SharedPixelBuffer::new(width.get(), height.get()) }); let bytes = bytemuck::cast_slice_mut(pixels.make_mut_slice()); *self.last_dirty_region.borrow_mut() = render_callback(width, height, skia_safe::ColorType::RGBA8888, age, bytes)?; *self.pixels.borrow_mut() = shared_pixel_buffer; Ok(()) } } #[test] fn simple() { slint::slint! { export component Ui inherits Window { in property c: yellow; background: black; Rectangle { x: 1phx; y: 80phx; width: 15phx; height: 17phx; background: red; } Rectangle { x: 10phx; y: 19phx; Rectangle { x: 5phx; y: 80phx; width: 12phx; height: 13phx; background: c; } Rectangle { x: 50phx; y: 8phx; width: 15phx; height: 17phx; background: c; } } } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let ui = Ui::new().unwrap(); let window = WINDOW.with(|x| x.clone()); window.set_size(slint::PhysicalSize::new(180, 260)); ui.show().unwrap(); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_c(slint::Color::from_rgb_u8(45, 12, 13)); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 10 + 5, 19 + 8, 10 + 50 + 15, 19 + 80 + 13); })); ui.set_c(slint::Color::from_rgb_u8(45, 12, 13)); assert!(!window.draw_if_needed(|_| { unreachable!() })); } #[test] fn visibility() { slint::slint! { export component Ui inherits Window { in property c : true; in property another: false; background: black; Rectangle { x: 10phx; y: 19phx; Rectangle { x: 5phx; y: 80phx; width: 12phx; height: 13phx; background: red; visible: c; Rectangle { x: 3phx; y: 3phx; width: 3phx; height: 3phx; background: another ? blue: green; } } Rectangle { x: 50phx; y: 8phx; width: 15phx; height: 17phx; background: gray; visible: !c; Rectangle { x: 3phx; y: 3phx; width: 3phx; height: 3phx; background: another ? blue: green; } } } } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let ui = Ui::new().unwrap(); let window = WINDOW.with(|x| x.clone()); window.set_size(slint::PhysicalSize::new(180, 260)); ui.show().unwrap(); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_c(false); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 10 + 5, 19 + 8, 10 + 50 + 15, 19 + 80 + 13); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_another(true); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 10 + 50 + 3, 19 + 8 + 3, 10 + 50 + 6, 19 + 8 + 6); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_c(true); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 10 + 5, 19 + 8, 10 + 50 + 15, 19 + 80 + 13); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_another(false); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 10 + 5 + 3, 19 + 80 + 3, 10 + 5 + 6, 19 + 80 + 6); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); } #[test] fn if_condition() { slint::slint! { export component Ui inherits Window { in property c : true; background: black; if c: Rectangle { x: 45px; y: 45px; background: pink; width: 32px; height: 3px; } } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let ui = Ui::new().unwrap(); let window = WINDOW.with(|x| x.clone()); window.set_size(slint::PhysicalSize::new(180, 260)); ui.show().unwrap(); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_c(false); assert!(window.draw_if_needed(|renderer| { // Currently we redraw when a condition becomes false because we don't track the position otherwise do_test_render_region(renderer, 0, 0, 180, 260); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_c(true); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 45, 45, 45 + 32, 45 + 3); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); } #[test] fn list_view() { slint::slint! { // We can't rely on the style as they are all different so implement our own in very basic terms component ListView inherits Flickable { out property visible-width <=> self.width; out property visible-height <=> self.height; @children } export component Ui inherits Window { width: 300px; height: 300px; in property <[int]> model; ListView { x: 20px; y: 10px; width: 100px; height: 90px; for x in model: Rectangle { background: x == 1 ? red : blue; height: 10px; width: 25px; } } } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let ui = Ui::new().unwrap(); let window = WINDOW.with(|x| x.clone()); window.set_size(slint::PhysicalSize::new(300, 300)); ui.show().unwrap(); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 300, 300); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); let model = std::rc::Rc::new(slint::VecModel::from(vec![0])); ui.set_model(model.clone().into()); const LV_X: i32 = 20; const LV_Y: i32 = 10; assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, LV_X, LV_Y, LV_X + 25, LV_Y + 10); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); model.insert(0, 1); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, LV_X, LV_Y, LV_X + 25, LV_Y + 20); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); model.set_row_data(1, 1); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, LV_X, LV_Y + 10, LV_X + 25, LV_Y + 20); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); model.set_vec(vec![0, 0]); assert!(window.draw_if_needed(|renderer| { // Currently, when ItemTree are removed, we redraw the whole window. do_test_render_region(renderer, 0, 0, 300, 300); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); model.remove(1); assert!(window.draw_if_needed(|renderer| { // Currently, when ItemTree are removed, we redraw the whole window. do_test_render_region(renderer, 0, 0, 300, 300); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); } #[test] /// test for #6932 fn scale_factor() { slint::slint! { export component Ui inherits Window { } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let ui = Ui::new().unwrap(); let window = WINDOW.with(|x| x.clone()); window.set_size(slint::PhysicalSize::new(500, 500)); window.dispatch_event(slint::platform::WindowEvent::ScaleFactorChanged { scale_factor: 1.33 }); ui.show().unwrap(); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 500, 500); })); } #[test] fn rotated_image() { slint::slint! { export component Ui inherits Window { in property rotation <=> i.transform-rotation; in property x-pos <=> i.x; background: black; i := Image { x: 50px; y: 50px; width: 50px; height: 150px; source: @image-url("../../../logo/slint-logo-full-dark.png"); } } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let window = SKIA_WINDOW.with(|w| w.clone()); NEXT_WINDOW_CHOICE.with(|choice| { *choice.borrow_mut() = Some(window.clone()); }); let ui = Ui::new().unwrap(); window.set_size(slint::PhysicalSize::new(250, 250).into()); ui.show().unwrap(); assert!(window.draw_if_needed()); assert_eq!( window.last_dirty_region_bounding_box_size(), Some(slint::LogicalSize { width: 250., height: 250. }) ); assert_eq!( window.last_dirty_region_bounding_box_origin(), Some(slint::LogicalPosition { x: 0., y: 0. }) ); assert!(!window.draw_if_needed()); ui.set_x_pos(51.); assert!(window.draw_if_needed()); assert_eq!( window.last_dirty_region_bounding_box_size(), Some(slint::LogicalSize { width: 51., height: 150. }) ); assert_eq!( window.last_dirty_region_bounding_box_origin(), Some(slint::LogicalPosition { x: 50., y: 50. }) ); ui.set_rotation(90.); assert!(window.draw_if_needed()); assert_eq!( window.last_dirty_region_bounding_box_size(), Some(slint::LogicalSize { width: 150., height: 150. }) ); assert_eq!( window.last_dirty_region_bounding_box_origin(), Some(slint::LogicalPosition { x: 1., y: 50. }) ); } #[test] fn window_background() { slint::slint! { export component Ui inherits Window { in property c: yellow; background: c; } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let ui = Ui::new().unwrap(); let window = WINDOW.with(|x| x.clone()); window.set_size(slint::PhysicalSize::new(180, 260)); ui.show().unwrap(); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_c(slint::Color::from_rgb_u8(45, 12, 13)); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); ui.set_c(slint::Color::from_rgb_u8(45, 12, 13)); assert!(!window.draw_if_needed(|_| { unreachable!() })); } #[test] fn touch_area_doesnt_cause_redraw() { slint::slint! { export component Ui inherits Window { in property c: yellow; in property touch-area-1-x <=> ta1.x; in property touch-area-2-x <=> ta2.x; in property sole-pixel-color: red; background: black; ta1 := TouchArea { x: 10px; y: 0px; width: 20px; height: 40px; Rectangle { x: 1phx; y: 20phx; width: 15phx; height: 17phx; background: c; } } ta2 := TouchArea { x: 10px; y: 0px; width: 20px; height: 40px; } sole-pixel := Rectangle { x: 60px; y: 0px; width: 1px; height: 1px; background: sole-pixel-color; } } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let ui = Ui::new().unwrap(); let window = WINDOW.with(|x| x.clone()); window.set_size(slint::PhysicalSize::new(180, 260)); ui.show().unwrap(); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_c(slint::Color::from_rgb_u8(45, 12, 13)); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 10 + 1, 20, 10 + 1 + 15, 20 + 17); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_touch_area_1_x(20.); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 10 + 1, 20, 10 + 1 + 15 + 10, 20 + 17); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_touch_area_2_x(20.); ui.set_sole_pixel_color(slint::Color::from_rgb_u8(45, 12, 13)); // Moving the touch area should not cause it to be redrawn. assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 60, 0, 61, 1); })); } #[test] fn shadow_redraw_beyond_geometry() { slint::slint! { export component Ui inherits Window { in property x-pos: 10px; Rectangle { x: root.x-pos; y: 10px; width: 20px; height: 20px; drop-shadow-blur: 5px; drop-shadow-offset-x: 15px; drop-shadow-offset-y: 5px; drop-shadow-color: red; } } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let window = SKIA_WINDOW.with(|w| w.clone()); NEXT_WINDOW_CHOICE.with(|choice| { *choice.borrow_mut() = Some(window.clone()); }); let ui = Ui::new().unwrap(); window.set_size(slint::PhysicalSize::new(250, 250).into()); ui.show().unwrap(); assert!(window.draw_if_needed()); assert_eq!( window.last_dirty_region_bounding_box_size(), Some(slint::LogicalSize { width: 250., height: 250. }) ); assert_eq!( window.last_dirty_region_bounding_box_origin(), Some(slint::LogicalPosition { x: 0., y: 0. }) ); assert!(!window.draw_if_needed()); ui.set_x_pos(20.); assert!(window.draw_if_needed()); let shadow_width = /* rect width */ 20. + 2. * /* blur */ 5.; let move_delta = 10.; let shadow_height = /* rect height */ 20. + 2. * /*blur */ 5.; let old_shadow_x = /* rect x */ 10. + /* shadow offset */ 15. - /* blur */ 5.; let old_shadow_y = /* rect y */ 10. + /* shadow offset */ 5. - /* blur */ 5.; assert_eq!( window.last_dirty_region_bounding_box_size(), Some(slint::LogicalSize { width: shadow_width + move_delta, height: shadow_height }) ); assert_eq!( window.last_dirty_region_bounding_box_origin(), Some(slint::LogicalPosition { x: old_shadow_x, y: old_shadow_y }) ); } #[test] fn text_alignment() { slint::slint! { export component Ui inherits Window { in property c: green; Text { x: 10px; y: 10px; width: 200px; height: 50px; text: "Ok"; color: c; } } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let window = SKIA_WINDOW.with(|w| w.clone()); NEXT_WINDOW_CHOICE.with(|choice| { *choice.borrow_mut() = Some(window.clone()); }); let ui = Ui::new().unwrap(); window.set_size(slint::PhysicalSize::new(250, 250).into()); ui.show().unwrap(); assert!(window.draw_if_needed()); assert_eq!( window.last_dirty_region_bounding_box_size(), Some(slint::LogicalSize { width: 250., height: 250. }) ); assert_eq!( window.last_dirty_region_bounding_box_origin(), Some(slint::LogicalPosition { x: 0., y: 0. }) ); assert!(!window.draw_if_needed()); ui.set_c(slint::Color::from_rgb_u8(45, 12, 13)); assert!(window.draw_if_needed()); assert_eq!( window.last_dirty_region_bounding_box_size(), Some(slint::LogicalSize { width: 200., height: 50. }) ); assert_eq!( window.last_dirty_region_bounding_box_origin(), Some(slint::LogicalPosition { x: 10., y: 10. }) ); } #[test] fn nowrap_text_change_doesnt_change_height() { slint::slint! { export component Ui inherits Window { in property first-text: "First text"; out property first-label-width: first-label.width; out property first-label-height: first-label.height; background: black; VerticalLayout { first-label := Text { text: root.first-text; } Text { text: "Second text"; } } } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let ui = Ui::new().unwrap(); let window = WINDOW.with(|x| x.clone()); window.set_size(slint::PhysicalSize::new(180, 260)); ui.show().unwrap(); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_first_text("Hello World longer".into()); let expected_width = ui.get_first_label_width().ceil() as _; let expected_height = ui.get_first_label_height().ceil() as _; assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, expected_width, expected_height); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); } #[test] fn create_item_tree_during_rendering() { // This test has a `init` callback which will cause item tree to be changed during rendeiring, // between the compute dirty region and the actual rendering. slint::slint! { export component Ui inherits Window { in property cond1: false; property cond2: false; property cond3: false; in property foo: 5px; if cond3: Rectangle { x: 12px; y: foo; width: 10px; height: 10px; background: yellow; } if cond2: Rectangle { x: 10px; y: 10px; width: 10px; height: 10px; background: red; init => { cond3=true; } } if cond1: Rectangle { x: 10px; y: 15px; width: 10px; height: 10px; background: blue; init => { cond2=true; } } if cond2: Rectangle { x: 12px; y: 10px + foo; width: 10px; height: 10px; background: green; } } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let ui = Ui::new().unwrap(); let window = WINDOW.with(|x| x.clone()); window.set_size(slint::PhysicalSize::new(180, 260)); ui.show().unwrap(); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_cond1(true); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 10, 15, 22, 25); })); // FIXME: in this case, there is nothing done to trigger any redraw. Ideally this call shouldn't be necessary. assert!(!window.draw_if_needed(|_| ())); // So therefore force a redraw ui.set_foo(4.0); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 10, 4, 22, 25); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_foo(3.0); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 12, 3, 22, 24); })); } #[test] fn issue_9882_borrow_mut() { slint::slint! { export component App inherits Window { in-out property <[string]> strings: ["Home"]; VerticalLayout { VerticalLayout { for str in root.strings: Text { text: str; } } } } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let ui = App::new().unwrap(); let window = WINDOW.with(|x| x.clone()); window.set_size(slint::PhysicalSize::new(180, 260)); ui.show().unwrap(); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_strings(slint::ModelRc::new(slint::VecModel::from_iter([slint::SharedString::from( "Hello", )]))); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); ui.set_strings(slint::ModelRc::new(slint::VecModel::from_iter([slint::SharedString::from( "World", )]))); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); } #[test] /// Check that changing the position of a rectangle is properly tracked when RepaintBufferType::NewBuffer is used. fn position_tracking_without_partial_rendering() { slint::slint! { export component App inherits Window { width: 180px; height: 260px; in property cond: true; in property rect-x: 10px; Rectangle { background: red; width: 40px; height: 80px; x: rect-x; } Rectangle { width: 100px; height: 100px; x: 1000px; background: cond ? green : blue; } } } struct TestPlatform2(Rc); impl slint::platform::Platform for TestPlatform2 { fn create_window_adapter(&self) -> Result, PlatformError> { Ok(self.0.clone()) } } let window = MinimalSoftwareWindow::new( slint::platform::software_renderer::RepaintBufferType::NewBuffer, ); slint::platform::set_platform(Box::new(TestPlatform2(window.clone()))).ok(); let ui = App::new().unwrap(); window.set_size(slint::PhysicalSize::new(180, 260)); ui.show().unwrap(); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_cond(false); assert!(!window.draw_if_needed(|_| { unreachable!("the rectangle is outside the window so it should not be drawn") })); ui.set_rect_x(20.); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_rect_x(-500.); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); ui.set_rect_x(20.); assert!(window.draw_if_needed(|renderer| { do_test_render_region(renderer, 0, 0, 180, 260); })); assert!(!window.draw_if_needed(|_| { unreachable!() })); } ================================================ FILE: api/rs/slint/tests/popups.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use i_slint_renderer_skia::SkiaRenderer; use i_slint_renderer_skia::SkiaSharedContext; use i_slint_renderer_skia::skia_safe; use slint::platform::software_renderer::MinimalSoftwareWindow; use slint::platform::{PlatformError, WindowAdapter}; use slint::{PhysicalSize, SharedPixelBuffer}; use std::cell::{Cell, RefCell}; use std::rc::{Rc, Weak}; thread_local! { static WINDOW: Rc = MinimalSoftwareWindow::new(slint::platform::software_renderer::RepaintBufferType::ReusedBuffer); static SKIA_WINDOW: Rc = SkiaTestWindow::new(); static NEXT_WINDOW_CHOICE: Rc>>> = Rc::new(RefCell::new(None)); } struct TestPlatform; impl slint::platform::Platform for TestPlatform { fn create_window_adapter(&self) -> Result, PlatformError> { Ok(NEXT_WINDOW_CHOICE.with(|choice| { choice.borrow_mut().take().unwrap_or_else(|| WINDOW.with(|x| x.clone())) })) } } struct SkiaTestWindow { window: slint::Window, renderer: SkiaRenderer, needs_redraw: Cell, size: Cell, } impl SkiaTestWindow { fn new() -> Rc { let render_buffer = Rc::new(SkiaTestSoftwareBuffer::default()); let renderer = SkiaRenderer::new_with_surface( &SkiaSharedContext::default(), Box::new(i_slint_renderer_skia::software_surface::SoftwareSurface::from( render_buffer.clone(), )), ); Rc::new_cyclic(|w: &Weak| Self { window: slint::Window::new(w.clone()), renderer, needs_redraw: Default::default(), size: Default::default(), }) } fn draw_if_needed(&self) -> bool { if self.needs_redraw.replace(false) { self.renderer.render().unwrap(); true } else { false } } } impl WindowAdapter for SkiaTestWindow { fn window(&self) -> &slint::Window { &self.window } fn size(&self) -> PhysicalSize { self.size.get() } fn renderer(&self) -> &dyn slint::platform::Renderer { &self.renderer } fn set_size(&self, size: slint::WindowSize) { self.size.set(size.to_physical(1.)); self.window .dispatch_event(slint::platform::WindowEvent::Resized { size: size.to_logical(1.) }) } fn request_redraw(&self) { self.needs_redraw.set(true); } } #[derive(Default)] struct SkiaTestSoftwareBuffer { pixels: RefCell>>, last_dirty_region: RefCell>, } impl i_slint_renderer_skia::software_surface::RenderBuffer for SkiaTestSoftwareBuffer { fn with_buffer( &self, _window: &slint::Window, size: PhysicalSize, render_callback: &mut dyn FnMut( std::num::NonZeroU32, std::num::NonZeroU32, i_slint_renderer_skia::skia_safe::ColorType, u8, &mut [u8], ) -> Result< Option, i_slint_core::platform::PlatformError, >, ) -> Result<(), i_slint_core::platform::PlatformError> { let Some((width, height)): Option<(std::num::NonZeroU32, std::num::NonZeroU32)> = size.width.try_into().ok().zip(size.height.try_into().ok()) else { // Nothing to render return Ok(()); }; let mut shared_pixel_buffer = self.pixels.borrow_mut().take(); if shared_pixel_buffer.as_ref().is_some_and(|existing_buffer| { existing_buffer.width() != width.get() || existing_buffer.height() != height.get() }) { shared_pixel_buffer = None; } let mut age = 1; let pixels = shared_pixel_buffer.get_or_insert_with(|| { age = 0; SharedPixelBuffer::new(width.get(), height.get()) }); let bytes = bytemuck::cast_slice_mut(pixels.make_mut_slice()); *self.last_dirty_region.borrow_mut() = render_callback(width, height, skia_safe::ColorType::RGBA8888, age, bytes)?; *self.pixels.borrow_mut() = shared_pixel_buffer; Ok(()) } } #[test] fn interaction_with_dead_popup_impossible() { fn click(window: &Rc) { window.window().dispatch_event(slint::platform::WindowEvent::PointerPressed { position: slint::LogicalPosition { x: 50.0, y: 50.0 }, button: slint::platform::PointerEventButton::Left, }); window.draw_if_needed(); window.window().dispatch_event(slint::platform::WindowEvent::PointerReleased { position: slint::LogicalPosition { x: 50.0, y: 50.0 }, button: slint::platform::PointerEventButton::Left, }); window.draw_if_needed(); } slint::slint! { export component Ui inherits Window { property condition: true; out property result; out property under; out property open; width: 100px; height: 100px; TouchArea { clicked => { root.under += 1; debug("under" , root.under); } } if condition: TouchArea { clicked => { root.open += 1; debug("open" , root.open); popup.show(); } popup := PopupWindow { close-policy: close-on-click-outside; width: 100%; height: 100%; Rectangle { width: 100%; height: 100%; background: green; } TouchArea { clicked => { root.result += 1; debug("result", root.result); root.condition = false; debug("CONDITION CLEARED"); } } } } } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let window = SKIA_WINDOW.with(|w| w.clone()); NEXT_WINDOW_CHOICE.with(|choice| { *choice.borrow_mut() = Some(window.clone()); }); let ui = Ui::new().unwrap(); window.set_size(slint::PhysicalSize::new(100, 100).into()); assert_eq!(ui.get_result(), 0); assert_eq!(ui.get_under(), 0); assert_eq!(ui.get_open(), 0); click(&window); assert_eq!(ui.get_result(), 0); assert_eq!(ui.get_under(), 0); assert_eq!(ui.get_open(), 1); click(&window); assert_eq!(ui.get_result(), 1); assert_eq!(ui.get_under(), 0); assert_eq!(ui.get_open(), 1); click(&window); assert_eq!(ui.get_result(), 1); assert_eq!(ui.get_under(), 1); assert_eq!(ui.get_open(), 1); click(&window); assert_eq!(ui.get_result(), 1); assert_eq!(ui.get_under(), 2); assert_eq!(ui.get_open(), 1); } #[test] fn interaction_with_dead_popup_panics() { fn click(window: &Rc) { window.window().dispatch_event(slint::platform::WindowEvent::PointerPressed { position: slint::LogicalPosition { x: 50.0, y: 50.0 }, button: slint::platform::PointerEventButton::Left, }); window.draw_if_needed(); window.window().dispatch_event(slint::platform::WindowEvent::PointerReleased { position: slint::LogicalPosition { x: 50.0, y: 50.0 }, button: slint::platform::PointerEventButton::Left, }); window.draw_if_needed(); } slint::slint! { export component Ui inherits Window { in-out property condition: true; width: 100px; height: 100px; t := Timer { running: false; interval: 1ms; triggered => { root.condition = false; self.running = false; } } if condition: TouchArea { clicked => { popup.show(); t.start(); } popup := PopupWindow { width: 100%; height: 100%; Rectangle { width: 100%; height: 100%; background: green; } TouchArea { clicked => { popup.close(); } } } } } } slint::platform::set_platform(Box::new(TestPlatform)).ok(); let window = SKIA_WINDOW.with(|w| w.clone()); NEXT_WINDOW_CHOICE.with(|choice| { *choice.borrow_mut() = Some(window.clone()); }); let ui = Ui::new().unwrap(); window.set_size(slint::PhysicalSize::new(100, 100).into()); assert!(ui.get_condition()); click(&window); assert!(ui.get_condition()); ui.set_condition(false); click(&window); assert!(!ui.get_condition()); } ================================================ FILE: api/rs/slint/tests/show_strongref.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use ::slint::slint; #[test] fn show_maintains_strong_reference() { i_slint_backend_testing::init_integration_test_with_mock_time(); slint!(export component TestWindow inherits Window { callback root-clicked(); TouchArea { clicked => { root.root-clicked(); } } }); let window = TestWindow::new().unwrap(); let window_weak = window.as_weak(); let window_weak_2 = window_weak.clone(); slint::invoke_from_event_loop(move || { window_weak_2.upgrade().unwrap().hide().unwrap(); slint::quit_event_loop().unwrap(); }) .unwrap(); window.show().unwrap(); drop(window); slint::run_event_loop().unwrap(); assert!(window_weak.upgrade().is_none()); } ================================================ FILE: api/rs/slint/tests/simple_macro.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use ::slint::slint; #[test] fn simple_window() { i_slint_backend_testing::init_no_event_loop(); slint!(export component X inherits Window{}); X::new().unwrap(); } #[test] fn empty_stuff() { slint!(); slint!(export struct Hei { abcd: bool }); slint!(export global G { }); } #[test] fn test_serialize_deserialize_struct() { i_slint_backend_testing::init_no_event_loop(); slint! { @rust-attr(derive(serde::Serialize, serde::Deserialize)) export enum TestEnum { hello, world, xxx } @rust-attr(derive(serde::Serialize, serde::Deserialize)) export struct TestStruct { enum: TestEnum, foo: int, } export component Test inherits Window { } } let data = TestStruct { foo: 1, r#enum: TestEnum::World }; let serialized = serde_json::to_string(&data).unwrap(); let deserialized: TestStruct = serde_json::from_str(&serialized).unwrap(); assert_eq!(data, deserialized); } #[test] fn test_multiple_rust_attrs() { i_slint_backend_testing::init_no_event_loop(); slint! { @rust-attr(derive(serde::Serialize, serde::Deserialize)) @rust-attr(serde(rename_all = "camelCase")) export struct MultiAttr { field-foo: int, field-bar: float, } export component TestAttrs inherits Window { } } let data = MultiAttr { field_foo: 1, field_bar: 1.0 }; let value = serde_json::to_value(&data).unwrap(); assert_eq!( value, serde_json::json!({ "fieldFoo": 1, "fieldBar": 1.0 }) ); } ================================================ FILE: api/rs/slint/tests/spawn_local.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 /// Code from https://doc.rust-lang.org/std/task/trait.Wake.html#examples mod executor { use std::future::Future; use std::sync::Arc; use std::task::{Context, Poll, Wake}; use std::thread::{self, Thread}; /// A waker that wakes up the current thread when called. struct ThreadWaker(Thread); impl Wake for ThreadWaker { fn wake(self: Arc) { self.0.unpark(); } } /// Run a future to completion on the current thread. pub fn block_on(fut: impl Future) -> T { // Pin the future so it can be polled. let mut fut = Box::pin(fut); // Create a new context to be passed to the future. let t = thread::current(); let waker = Arc::new(ThreadWaker(t)).into(); let mut cx = Context::from_waker(&waker); // Run the future to completion. loop { match fut.as_mut().poll(&mut cx) { Poll::Ready(res) => return res, Poll::Pending => thread::park(), } } } } #[test] fn main() { i_slint_backend_testing::init_integration_test_with_mock_time(); // test_spawn_local_from_thread std::thread::spawn(|| { assert_eq!( slint::spawn_local(async { panic!("the future shouldn't be run since we're in a thread") }) .map(drop), Err(slint::EventLoopError::NoEventLoopProvider) ); }) .join() .unwrap(); slint::invoke_from_event_loop(|| { let handle = slint::spawn_local(async { String::from("Hello") }).unwrap(); slint::spawn_local(async move { panic!("Aborted task") }).unwrap().abort(); let handle2 = slint::spawn_local(async move { handle.await + ", World" }).unwrap(); std::thread::spawn(move || { let x = executor::block_on(handle2); assert_eq!(x, "Hello, World"); }); }) .unwrap(); // test_is_finished slint::invoke_from_event_loop(|| { let handle_one = slint::spawn_local(async { "Hello, World!" }).unwrap(); assert!(!handle_one.is_finished()); let handle_two = slint::spawn_local(async move { assert!(handle_one.is_finished()); }) .unwrap(); std::thread::spawn(move || { let _ = executor::block_on(handle_two); }); slint::quit_event_loop().unwrap(); }) .unwrap(); slint::run_event_loop().unwrap(); } #[test] fn with_context() { use i_slint_core::SlintContext; let ctx = SlintContext::new(Box::new(i_slint_backend_testing::TestingBackend::new( i_slint_backend_testing::TestingBackendOptions { mock_time: true, threading: true }, ))); let handle = ctx.spawn_local(async { String::from("Hello") }).unwrap(); ctx.spawn_local(async move { panic!("Aborted task") }).unwrap().abort(); let handle2 = ctx.spawn_local(async move { handle.await + ", World" }).unwrap(); let proxy = ctx.event_loop_proxy().unwrap(); std::thread::spawn(move || { let x = executor::block_on(handle2); assert_eq!(x, "Hello, World"); proxy.quit_event_loop().unwrap(); }); ctx.run_event_loop().unwrap() } ================================================ FILE: api/rs/slint/tests/text_layout_cache.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use slint::PhysicalSize; use slint::platform::software_renderer::{ MinimalSoftwareWindow, PremultipliedRgbaColor, RepaintBufferType, SoftwareRenderer, TargetPixel, }; use slint::platform::{PlatformError, WindowAdapter}; use std::rc::Rc; thread_local! { static WINDOW: Rc = MinimalSoftwareWindow::new(RepaintBufferType::ReusedBuffer); } struct TestPlatform; impl slint::platform::Platform for TestPlatform { fn create_window_adapter(&self) -> Result, PlatformError> { Ok(WINDOW.with(|x| x.clone())) } } #[allow(dead_code)] #[derive(Clone, Copy, Default)] struct TestPixel(bool); impl TargetPixel for TestPixel { fn blend(&mut self, _color: PremultipliedRgbaColor) { *self = Self(true); } fn from_rgb(_red: u8, _green: u8, _blue: u8) -> Self { Self(true) } } const WIDTH: usize = 200; const HEIGHT: usize = 100; fn setup() -> Rc { slint::platform::set_platform(Box::new(TestPlatform)).ok(); let window = WINDOW.with(|x| x.clone()); window.set_size(PhysicalSize::new(WIDTH as u32, HEIGHT as u32)); window } fn render_and_get_miss_count(renderer: &SoftwareRenderer) -> u64 { renderer.text_layout_cache().reset_cache_miss_count(); let mut buf = vec![TestPixel(false); WIDTH * HEIGHT]; renderer.render(buf.as_mut_slice(), WIDTH); renderer.text_layout_cache().cache_miss_count() } #[test] fn cache_hit_avoids_reshaping() { let window = setup(); slint::slint! { export component TestComponent inherits Window { Text { text: "Hello World"; } } } let ui = TestComponent::new().unwrap(); ui.show().unwrap(); let mut miss_count = 0u64; // First render: should shape at least once assert!(window.draw_if_needed(|renderer| { miss_count = render_and_get_miss_count(renderer); })); assert!(miss_count > 0, "Expected at least one cache miss on first render"); // Second render without changes: should hit cache window.request_redraw(); assert!(window.draw_if_needed(|renderer| { miss_count = render_and_get_miss_count(renderer); })); assert_eq!(miss_count, 0, "Expected zero cache misses on re-render without changes"); } #[test] fn text_change_invalidates_cache() { let window = setup(); slint::slint! { export component TestComponent inherits Window { in property label: "Hello"; Text { text: label; } } } let ui = TestComponent::new().unwrap(); ui.show().unwrap(); // First render window.draw_if_needed(|renderer| { render_and_get_miss_count(renderer); }); // Change text ui.set_label("Goodbye".into()); let mut miss_count = 0u64; assert!(window.draw_if_needed(|renderer| { miss_count = render_and_get_miss_count(renderer); })); assert!(miss_count > 0, "Expected cache miss after text change"); } #[test] fn font_size_change_invalidates_cache() { let window = setup(); slint::slint! { export component TestComponent inherits Window { in property size: 16px; Text { text: "Hello"; font-size: size; } } } let ui = TestComponent::new().unwrap(); ui.show().unwrap(); // First render window.draw_if_needed(|renderer| { render_and_get_miss_count(renderer); }); // Change font-size ui.set_size(24.0); let mut miss_count = 0u64; assert!(window.draw_if_needed(|renderer| { miss_count = render_and_get_miss_count(renderer); })); assert!(miss_count > 0, "Expected cache miss after font-size change"); } #[test] fn font_weight_change_invalidates_cache() { let window = setup(); slint::slint! { export component TestComponent inherits Window { in property weight: 400; Text { text: "Hello"; font-weight: weight; } } } let ui = TestComponent::new().unwrap(); ui.show().unwrap(); // First render window.draw_if_needed(|renderer| { render_and_get_miss_count(renderer); }); // Change font-weight ui.set_weight(700); let mut miss_count = 0u64; assert!(window.draw_if_needed(|renderer| { miss_count = render_and_get_miss_count(renderer); })); assert!(miss_count > 0, "Expected cache miss after font-weight change"); } #[test] fn wrap_change_invalidates_cache() { let window = setup(); slint::slint! { export component TestComponent inherits Window { in property use-no-wrap: false; Text { text: "Hello World this is a long text"; wrap: use-no-wrap ? no-wrap : word-wrap; } } } let ui = TestComponent::new().unwrap(); ui.show().unwrap(); // First render (word-wrap) window.draw_if_needed(|renderer| { render_and_get_miss_count(renderer); }); // Change wrap to no-wrap ui.set_use_no_wrap(true); let mut miss_count = 0u64; assert!(window.draw_if_needed(|renderer| { miss_count = render_and_get_miss_count(renderer); })); assert!(miss_count > 0, "Expected cache miss after wrap change"); } #[test] fn alignment_change_does_not_reshape() { let window = setup(); slint::slint! { export component TestComponent inherits Window { in property use-center-align: false; Text { text: "Hello World"; horizontal-alignment: use-center-align ? TextHorizontalAlignment.center : TextHorizontalAlignment.left; } } } let ui = TestComponent::new().unwrap(); ui.show().unwrap(); // First render (left-aligned) window.draw_if_needed(|renderer| { render_and_get_miss_count(renderer); }); // Change alignment to center ui.set_use_center_align(true); let mut miss_count = 0u64; assert!(window.draw_if_needed(|renderer| { miss_count = render_and_get_miss_count(renderer); })); assert_eq!(miss_count, 0, "Alignment change should not cause reshaping"); } #[test] fn overflow_change_does_not_reshape() { let window = setup(); slint::slint! { export component TestComponent inherits Window { in property use-elide: false; Text { text: "Hello World"; overflow: use-elide ? TextOverflow.elide : TextOverflow.clip; } } } let ui = TestComponent::new().unwrap(); ui.show().unwrap(); // First render (clip) window.draw_if_needed(|renderer| { render_and_get_miss_count(renderer); }); // Change overflow to elide ui.set_use_elide(true); let mut miss_count = 0u64; assert!(window.draw_if_needed(|renderer| { miss_count = render_and_get_miss_count(renderer); })); assert_eq!(miss_count, 0, "Overflow change should not cause reshaping"); } #[test] fn color_change_does_not_reshape() { let window = setup(); slint::slint! { export component TestComponent inherits Window { in property text-color: black; Text { text: "Hello World"; color: text-color; } } } let ui = TestComponent::new().unwrap(); ui.show().unwrap(); // First render window.draw_if_needed(|renderer| { render_and_get_miss_count(renderer); }); // Change color ui.set_text_color(slint::Color::from_rgb_u8(255, 0, 0)); let mut miss_count = 0u64; assert!(window.draw_if_needed(|renderer| { miss_count = render_and_get_miss_count(renderer); })); assert_eq!(miss_count, 0, "Color change should not cause reshaping"); } ================================================ FILE: api/rs/slint/tests/tokio.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #[test] fn tokio_poll_with_compat() { i_slint_backend_testing::init_integration_test_with_mock_time(); use std::io::Write; let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = listener.local_addr().unwrap(); let server = std::thread::spawn(move || { let mut stream = listener.incoming().next().unwrap().unwrap(); stream.write("Hello World".as_bytes()).unwrap(); }); let slint_future = async move { for _ in 0..1000 { tokio::task::consume_budget().await; } use tokio::io::AsyncReadExt; let mut stream = tokio::net::TcpStream::connect(local_addr).await.unwrap(); let mut data = Vec::new(); stream.read_to_end(&mut data).await.unwrap(); assert_eq!(data, "Hello World".as_bytes()); slint::quit_event_loop().unwrap(); }; slint::spawn_local(async_compat::Compat::new(slint_future)).unwrap(); slint::run_event_loop_until_quit().unwrap(); server.join().unwrap(); } ================================================ FILE: api/rs/slint/tests/tokio_block_in_place.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn tokio_poll_with_block_in_place() { i_slint_backend_testing::init_integration_test_with_mock_time(); use std::io::Write; let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = listener.local_addr().unwrap(); let server = std::thread::spawn(move || { let mut stream = listener.incoming().next().unwrap().unwrap(); stream.write("Hello World".as_bytes()).unwrap(); }); let slint_future = async move { for _ in 0..1000 { tokio::task::consume_budget().await; } use tokio::io::AsyncReadExt; let mut stream = tokio::net::TcpStream::connect(local_addr).await.unwrap(); let mut data = Vec::new(); stream.read_to_end(&mut data).await.unwrap(); assert_eq!(data, "Hello World".as_bytes()); slint::quit_event_loop().unwrap(); }; slint::spawn_local(slint_future).unwrap(); tokio::task::block_in_place(slint::run_event_loop_until_quit).unwrap(); server.join().unwrap(); } ================================================ FILE: api/rs/slint/type-mappings.md ================================================ # Type Mappings The types used for properties in `.slint` design markup each translate to specific types in Rust. The follow table summarizes the entire mapping: | `.slint` Type | Rust Type | Note | | --- | --- | --- | | `angle` | `f32` | The angle in degrees | | `array` | [`ModelRc`] | Arrays are represented as models, so that their contents can change dynamically. | | `bool` | `bool` | | | `brush` | [`Brush`] | | | `color` | [`Color`] | | | `duration` | `i64` | At run-time, durations are always represented as signed 64-bit integers with millisecond precision. | | `float` | `f32` | | | `image` | [`Image`] | | | `int` | `i32` | | | `length` | `f32` | At run-time, logical lengths are automatically translated to physical pixels using the device pixel ratio. | | `physical_length` | `f32` | The unit are physical pixels. | | `Point` | [`LogicalPosition`] | A struct with `x` and `y` fields, representing logical coordinates. | | `relative-font-size` | `f32` | Relative font size factor that is multiplied with the `Window.default-font-size` and can be converted to a `length`. | | `string` | [`SharedString`] | A reference-counted string type that can be easily converted to a str reference. | | anonymous object | anonymous tuple | The fields are in alphabetical order. | | enumeration | `enum` of the same name | The values are converted to CamelCase | | structure | `struct` of the same name | | For user defined structures in the .slint, an extra struct is generated. For example, if the `.slint` contains ```slint,ignore export struct MyStruct { foo: int, bar: string, names: [string], } ``` The following struct would be generated: ```rust #[derive(Default, Clone, Debug, PartialEq)] struct MyStruct { foo: i32, bar: slint::SharedString, names: slint::ModelRc, } ``` The `.slint` file allows you to utilize Rust attributes and features for defining structures using the `@rust-attr()` directive. This enables you to customize the generated code by applying additional traits, derivations, or annotations. Consider the following structure defined in the `.slint` file with Rust attributes: ```slint,ignore @rust-attr(derive(serde::Serialize, serde::Deserialize)) struct MyStruct { foo: int, } ``` Based on this structure, the following Rust code would be generated: ```rust #[derive(serde::Serialize, serde::Deserialize)] #[derive(Default, Clone, Debug, PartialEq)] struct MyStruct { foo: i32, } ``` Also you can use multiple `@rust-attr()` directives. For example: ```slint,ignore @rust-attr(derive(serde::Serialize, serde::Deserialize)) @rust-attr(serde(rename_all = "camelCase")) struct MyStruct { foo-bar: int, } ``` ================================================ FILE: api/wasm-interpreter/Cargo.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 [package] name = "slint-wasm-interpreter" description = "Slint wasm glue" authors.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true version.workspace = true publish = false [lib] crate-type = ["cdylib"] [dependencies] slint-interpreter = { workspace = true, features = ["std", "backend-winit", "renderer-femtovg", "compat-1-2", "internal"] } i-slint-backend-winit = { workspace = true } i-slint-core = { workspace = true } send_wrapper = { workspace = true } vtable = { workspace = true } console_error_panic_hook = { version = "0.1.6", optional = true } js-sys = "0.3.44" wasm-bindgen-futures = { version = "0.4.18" } wasm-bindgen = { version = "0.2.66" } web-sys = { workspace = true, features = ["Request", "RequestInit", "RequestMode", "Response", "Window"] } #[dev-dependencies] #wasm-bindgen-test = "0.3.13" ================================================ FILE: api/wasm-interpreter/src/lib.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 //! This wasm library can be loaded from JS to load and display the content of .slint files #![cfg(target_arch = "wasm32")] use std::cell::RefCell; use std::path::Path; use std::rc::Rc; use wasm_bindgen::prelude::*; use slint_interpreter::ComponentHandle; #[wasm_bindgen] #[allow(dead_code)] pub struct CompilationResult { component: Option, diagnostics: js_sys::Array, error_string: String, } #[wasm_bindgen] impl CompilationResult { #[wasm_bindgen(getter)] pub fn component(&self) -> Option { self.component.clone() } #[wasm_bindgen(getter)] pub fn diagnostics(&self) -> js_sys::Array { self.diagnostics.clone() } #[wasm_bindgen(getter)] pub fn error_string(&self) -> String { self.error_string.clone() } } #[wasm_bindgen(typescript_custom_section)] const CALLBACK_FUNCTION_SECTION: &'static str = r#" type ImportCallbackFunction = (url: string) => Promise; type CurrentElementInformationCallbackFunction = (url: string, start_line: number, start_column: number, end_line: number, end_column: number) => void; "#; #[wasm_bindgen] extern "C" { #[wasm_bindgen(typescript_type = "ImportCallbackFunction")] pub type ImportCallbackFunction; #[wasm_bindgen(typescript_type = "CurrentElementInformationCallbackFunction")] pub type CurrentElementInformationCallbackFunction; #[wasm_bindgen(typescript_type = "Promise")] pub type InstancePromise; } /// Compile the content of a string. /// /// Returns a promise to a compiled component which can be run with ".run()" #[wasm_bindgen] pub async fn compile_from_string( source: String, base_url: String, optional_import_callback: Option, ) -> Result { compile_from_string_with_style(source, base_url, String::new(), optional_import_callback).await } /// Same as [`compile_from_string`], but also takes a style parameter #[wasm_bindgen] pub async fn compile_from_string_with_style( source: String, base_url: String, style: String, optional_import_callback: Option, ) -> Result { #[allow(deprecated)] let mut compiler = slint_interpreter::ComponentCompiler::default(); if !style.is_empty() { compiler.set_style(style) } if let Some(load_callback) = optional_import_callback { let open_import_callback = move |file_name: &Path| -> core::pin::Pin< Box>>>, > { Box::pin({ let load_callback = js_sys::Function::from(load_callback.clone()); let file_name: String = file_name.to_string_lossy().into(); async move { let result = load_callback.call1(&JsValue::UNDEFINED, &file_name.into()); let promise: js_sys::Promise = result.unwrap().into(); let future = wasm_bindgen_futures::JsFuture::from(promise); match future.await { Ok(js_ok) => Some(Ok(js_ok.as_string().unwrap_or_default())), Err(js_err) => Some(Err(std::io::Error::new( std::io::ErrorKind::Other, js_err.as_string().unwrap_or_default(), ))), } } }) }; compiler.set_file_loader(open_import_callback); } let c = compiler.build_from_source(source, base_url.into()).await; let line_key = JsValue::from_str("lineNumber"); let column_key = JsValue::from_str("columnNumber"); let message_key = JsValue::from_str("message"); let file_key = JsValue::from_str("fileName"); let level_key = JsValue::from_str("level"); let mut error_as_string = String::new(); let array = js_sys::Array::new(); for d in compiler.diagnostics().into_iter() { let filename = d.source_file().as_ref().map_or(String::new(), |sf| sf.to_string_lossy().into()); let filename_js = JsValue::from_str(&filename); let (line, column) = d.line_column(); if d.level() == slint_interpreter::DiagnosticLevel::Error { if !error_as_string.is_empty() { error_as_string.push_str("\n"); } use std::fmt::Write; write!(&mut error_as_string, "{}:{}:{}", filename, line, d).unwrap(); } let error_obj = js_sys::Object::new(); js_sys::Reflect::set(&error_obj, &message_key, &JsValue::from_str(&d.message()))?; js_sys::Reflect::set(&error_obj, &line_key, &JsValue::from_f64(line as f64))?; js_sys::Reflect::set(&error_obj, &column_key, &JsValue::from_f64(column as f64))?; js_sys::Reflect::set(&error_obj, &file_key, &filename_js)?; js_sys::Reflect::set(&error_obj, &level_key, &JsValue::from_f64(d.level() as i8 as f64))?; array.push(&error_obj); } Ok(CompilationResult { component: c.map(|c| WrappedCompiledComp(c)), diagnostics: array, error_string: error_as_string, }) } #[wasm_bindgen] #[derive(Clone)] pub struct WrappedCompiledComp(slint_interpreter::ComponentDefinition); #[wasm_bindgen] impl WrappedCompiledComp { /// Run this compiled component in a canvas. /// The HTML must contains a element with the given `canvas_id` /// where the result is gonna be rendered #[wasm_bindgen] pub fn run(&self, canvas_id: String) { NEXT_CANVAS_ID.with(|next_id| { *next_id.borrow_mut() = Some(canvas_id); }); let component = self.0.create().unwrap(); component.show().unwrap(); // Merely spawns the event loop, but does not block. slint_interpreter::run_event_loop().unwrap(); } /// Creates this compiled component in a canvas, wrapped in a promise. /// The HTML must contains a element with the given `canvas_id` /// where the result is gonna be rendered. /// You need to call `show()` on the returned instance for rendering. /// /// Note that the promise will only be resolved after calling `slint.run_event_loop()`. #[wasm_bindgen] pub fn create(&self, canvas_id: String) -> Result { Ok(JsValue::from(js_sys::Promise::new(&mut |resolve, reject| { let comp = send_wrapper::SendWrapper::new(self.0.clone()); let canvas_id = canvas_id.clone(); let resolve = send_wrapper::SendWrapper::new(resolve); if let Err(e) = slint_interpreter::invoke_from_event_loop(move || { NEXT_CANVAS_ID.with(|next_id| { *next_id.borrow_mut() = Some(canvas_id); }); let instance = WrappedInstance(comp.take().create().unwrap()); resolve.take().call1(&JsValue::UNDEFINED, &JsValue::from(instance)).unwrap_throw(); }) { reject .call1( &JsValue::UNDEFINED, &JsValue::from(format!( "internal error: Failed to queue closure for event loop invocation: {e}" )), ) .unwrap_throw(); } })) .unchecked_into::()) } /// Creates this compiled component in the canvas of the provided instance, wrapped in a promise. /// For this to work, the provided instance needs to be visible (show() must've been /// called) and the event loop must be running (`slint.run_event_loop()`). After this /// call the provided instance is not rendered anymore and can be discarded. /// /// Note that the promise will only be resolved after calling `slint.run_event_loop()`. #[wasm_bindgen] pub fn create_with_existing_window( &self, instance: WrappedInstance, ) -> Result { Ok(JsValue::from(js_sys::Promise::new(&mut |resolve, reject| { let params = send_wrapper::SendWrapper::new(( self.0.clone(), instance.0.clone_strong(), resolve, )); if let Err(e) = slint_interpreter::invoke_from_event_loop(move || { let (comp, instance, resolve) = params.take(); let instance = WrappedInstance(comp.create_with_existing_window(instance.window()).unwrap()); resolve.call1(&JsValue::UNDEFINED, &JsValue::from(instance)).unwrap_throw(); }) { reject .call1( &JsValue::UNDEFINED, &JsValue::from(format!( "internal error: Failed to queue closure for event loop invocation: {e}" )), ) .unwrap_throw(); } })) .unchecked_into::()) } } #[wasm_bindgen] pub struct WrappedInstance(slint_interpreter::ComponentInstance); impl Clone for WrappedInstance { fn clone(&self) -> Self { Self(self.0.clone_strong()) } } #[wasm_bindgen] impl WrappedInstance { /// Marks this instance for rendering and input handling. /// /// Note that the promise will only be resolved after calling `slint.run_event_loop()`. #[wasm_bindgen] pub fn show(&self) -> Result { self.invoke_from_event_loop_wrapped_in_promise(|instance| instance.show()) } /// Hides this instance and prevents further updates of the canvas element. /// /// Note that the promise will only be resolved after calling `slint.run_event_loop()`. #[wasm_bindgen] pub fn hide(&self) -> Result { self.invoke_from_event_loop_wrapped_in_promise(|instance| instance.hide()) } fn invoke_from_event_loop_wrapped_in_promise( &self, callback: impl FnOnce( &slint_interpreter::ComponentInstance, ) -> Result<(), slint_interpreter::PlatformError> + 'static, ) -> Result { let callback = std::cell::RefCell::new(Some(callback)); Ok(js_sys::Promise::new(&mut |resolve, reject| { let inst_weak = self.0.as_weak(); if let Err(e) = slint_interpreter::invoke_from_event_loop({ let params = send_wrapper::SendWrapper::new(( resolve, reject.clone(), callback.take().unwrap(), )); move || { let (resolve, reject, callback) = params.take(); match inst_weak.upgrade() { Some(instance) => match callback(&instance) { Ok(()) => { resolve.call0(&JsValue::UNDEFINED).unwrap_throw(); } Err(e) => { reject .call1( &JsValue::UNDEFINED, &JsValue::from(format!( "Invocation on ComponentInstance from within event loop failed: {e}" )), ) .unwrap_throw(); } }, None => { reject .call1( &JsValue::UNDEFINED, &JsValue::from(format!( "Invocation on ComponentInstance failed because instance was deleted too soon" )), ) .unwrap_throw(); } } } }) { reject .call1( &JsValue::UNDEFINED, &JsValue::from(format!( "internal error: Failed to queue closure for event loop invocation: {e}" )), ) .unwrap_throw(); } })) } } /// Register DOM event handlers on all instance and set up the event loop for that. /// You can call this function only once. It will throw an exception but that is safe /// to ignore. #[wasm_bindgen] pub fn run_event_loop() -> Result<(), JsValue> { // Merely spawns the event loop, but does not block. slint_interpreter::run_event_loop().map_err(|e| -> JsValue { format!("{e}").into() })?; Ok(()) } thread_local!( static NEXT_CANVAS_ID: Rc>> = Default::default(); ); #[wasm_bindgen(start)] pub fn init() -> Result<(), JsValue> { #[cfg(feature = "console_error_panic_hook")] console_error_panic_hook::set_once(); let backend = i_slint_backend_winit::Backend::builder() .with_spawn_event_loop(true) .with_window_attributes_hook(|mut attrs| { NEXT_CANVAS_ID.with(|next_id| { if let Some(canvas_id) = next_id.borrow_mut().take() { use i_slint_backend_winit::winit::platform::web::WindowAttributesExtWebSys; use wasm_bindgen::JsCast; let html_canvas = web_sys::window() .expect("wasm-interpreter: Could not retrieve DOM window") .document() .expect("wasm-interpreter: Could not retrieve DOM document") .get_element_by_id(&canvas_id) .unwrap_or_else(|| { panic!( "wasm-interpreter: Could not retrieve existing HTML Canvas element '{}'", canvas_id ) }) .dyn_into::() .unwrap_or_else(|_| { panic!( "winit backend: Specified DOM element '{}' is not a HTML Canvas", canvas_id ) }); attrs = attrs .with_canvas(Some(html_canvas)) // Don't activate the window by default, as that will cause the page to scroll, // ignoring any existing anchors. .with_active(false); attrs } else { attrs } }) }) .build() .unwrap(); i_slint_core::platform::set_platform(Box::new(backend)) .map_err(|e| -> JsValue { format!("{e}").into() })?; Ok(()) } ================================================ FILE: biome.json ================================================ { "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", "formatter": { "enabled": true, "formatWithErrors": false, "indentStyle": "space", "indentWidth": 4, "lineEnding": "lf", "attributePosition": "auto", "includes": [ "**", "!**/.vscode/**", "!**/*.json", "!**/dist/**", "!**/build/**", "!**/!biome.json", "!**/editors/vscode/out/**" ] }, "assist": { "actions": { "source": { "organizeImports": "off" } } }, "linter": { "enabled": true, "includes": ["**", "!**/dist/**"], "rules": { "recommended": false, "complexity": { "noBannedTypes": "warn", "noUselessConstructor": "error" }, "style": { "useBlockStatements": "warn", "noUselessElse": "off", "useConst": "error", "useImportType": "error", "useNodejsImportProtocol": "error" }, "nursery": { "noFloatingPromises": "error" }, "suspicious": { "noDoubleEquals": "error", "noRedundantUseStrict": "warn", "noImplicitAnyLet": "error", "useAwait": "error" } } }, "javascript": { "formatter": { "jsxQuoteStyle": "double", "quoteProperties": "asNeeded", "semicolons": "always", "bracketSpacing": true, "bracketSameLine": false, "quoteStyle": "double", "attributePosition": "auto" } } } ================================================ FILE: cmake/FindOpenGLES2.cmake ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 include(CheckCXXSourceCompiles) find_path(OPENGLES2_INCLUDE_DIR NAMES "GLES2/gl2.h" "OpenGLES/ES2/gl.h" DOC "The path where the OpenGL ES 2.0 headers are located") find_library(OPENGLES2_LIBRARY NAMES GLESv2 OpenGLES) # Sometimes EGL linkage is required, so look it up and always use if available. find_package(OpenGL COMPONENTS EGL) # See if we can compile some example code with what we've found. set(saved_libraries "${CMAKE_REQUIRED_LIBRARIES}") set(saved_includes "${CMAKE_REQUIRED_INCLUDES}") if (OPENGLES2_LIBRARY) list(APPEND CMAKE_REQUIRED_INCLUDES "${OPENGLES2_INCLUDE_DIR}") list(APPEND CMAKE_REQUIRED_LIBRARIES "${OPENGLES2_LIBRARY}") endif() if(OPENGL_egl_LIBRARY) list(APPEND CMAKE_REQUIRED_INCLUDES "${OPENGL_EGL_INCLUDE_DIRS}") list(APPEND CMAKE_REQUIRED_LIBRARIES "${OPENGL_egl_LIBRARY}") endif() check_cxx_source_compiles(" #include #include int main(int argc, char *argv[]) { glClear(GL_STENCIL_BUFFER_BIT); glUseProgram(0); }" HAVE_OPENGLES2) set(CMAKE_REQUIRED_INCLUDES "${saved_includes}") set(CMAKE_REQUIRED_LIBRARIES "${saved_libraries}") # Standard CMake package dance set(package_args OPENGLES2_INCLUDE_DIR OPENGLES2_LIBRARY HAVE_OPENGLES2) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(OpenGLES2 DEFAULT_MSG ${package_args}) mark_as_advanced(${package_args}) # Create a convenience target for linkage if (OPENGLES2_FOUND AND NOT TARGET OpenGLES2::OpenGLES2) add_library(OpenGLES2::OpenGLES2 UNKNOWN IMPORTED) set_property(TARGET OpenGLES2::OpenGLES2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${OPENGLES2_INCLUDE_DIR}") set_property(TARGET OpenGLES2::OpenGLES2 PROPERTY IMPORTED_LOCATION "${OPENGLES2_LIBRARY}") if (TARGET OpenGL::EGL) target_link_libraries(OpenGLES2::OpenGLES2 INTERFACE OpenGL::EGL) endif() endif() ================================================ FILE: cmake/FindOpenGLES3.cmake ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 include(CheckCXXSourceCompiles) find_path(OPENGLES3_INCLUDE_DIR NAMES "GLES3/gl3.h" "OpenGLES/ES3/gl.h" DOC "The path where the OpenGL ES 3.0 headers are located") # GLESv3 entry points are in v2 lib find_library(OPENGLES3_LIBRARY NAMES GLESv2 OpenGLES) # Sometimes EGL linkage is required, so look it up and always use if available. find_package(OpenGL COMPONENTS EGL) # See if we can compile some example code with what we've found. set(saved_libraries "${CMAKE_REQUIRED_LIBRARIES}") set(saved_includes "${CMAKE_REQUIRED_INCLUDES}") if (OPENGLES3_LIBRARY AND OPENGLES3_INCLUDE_DIR) list(APPEND CMAKE_REQUIRED_INCLUDES "${OPENGLES3_INCLUDE_DIR}") list(APPEND CMAKE_REQUIRED_LIBRARIES "${OPENGLES3_LIBRARY}") endif() if(OPENGL_egl_LIBRARY) list(APPEND CMAKE_REQUIRED_INCLUDES "${OPENGL_EGL_INCLUDE_DIRS}") list(APPEND CMAKE_REQUIRED_LIBRARIES "${OPENGL_egl_LIBRARY}") endif() check_cxx_source_compiles(" #include #include int main(int argc, char *argv[]) { glClear(GL_STENCIL_BUFFER_BIT); glUseProgram(0); }" HAVE_OPENGLES3) set(CMAKE_REQUIRED_INCLUDES "${saved_includes}") set(CMAKE_REQUIRED_LIBRARIES "${saved_libraries}") # Standard CMake package dance set(package_args OPENGLES3_INCLUDE_DIR OPENGLES3_LIBRARY HAVE_OPENGLES3) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(OpenGLES3 DEFAULT_MSG ${package_args}) mark_as_advanced(${package_args}) # Create a convenience target for linkage if (OPENGLES3_FOUND AND NOT TARGET OpenGLES3::OpenGLES3) add_library(OpenGLES3::OpenGLES3 UNKNOWN IMPORTED) set_property(TARGET OpenGLES3::OpenGLES3 PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${OPENGLES3_INCLUDE_DIR}") set_property(TARGET OpenGLES3::OpenGLES3 PROPERTY IMPORTED_LOCATION "${OPENGLES3_LIBRARY}") if (TARGET OpenGL::EGL) target_link_libraries(OpenGLES3::OpenGLES3 INTERFACE OpenGL::EGL) endif() endif() ================================================ FILE: cspell.json ================================================ { "version": "0.2", "language": "en", "languageSettings": [ { "languageId": "rust", "words": [ "ARGB", "astrojs", "bbox", "bindgen", "builtins", "canonicalize", // std::path::Path::canonicalize "codemap", "concat", "Consts", "evenodd", "FBOs", // plural of FBO (frame buffer object) "femtovg", "fontdb", "fullscreen", "glutin", "glutin's", "maxx", "maxy", "minx", "miny", "peekable", "Realloc", "refcell", "Rgba", "rtti", "Smol", "smolstr", "subspan", "uninit", "unmap", "unsync", "vsync" ], "ignoreRegExpList": [ "/#\\[cfg\\b.+\\]/", "/#\\[repr\\b.+\\]/", // Exclude crate paths "/(\\b|::)[a-z0-9_]+::/", "/::[a-z0-9_]+(\\b|::)/", // Qt things: "/\\bQt_[a-zA-Z0-9_]+/" ] }, { "languageId": "restructuredtext", "words": [ "genindex", "modindex", "toctree" ], "ignoreRegExpList": [ "/:.+:/" ] }, { "languageId": [ "markdown", "md", "mdx" ], "words": [ "Espressif", "linebuffer", "mylibrary", "otherlibrary", "Lmylibrary", "progressindicator", "standardbutton", "standardlistview", "standardtableview", "rustup", "setdefault", "Sonoma", "SUBDIR", "Torizon", "Yocto", "relpath", "classslint", "vectormodel", "structslint", "namespaceslint", "compilerconfiguration", "tschüß", "TSCHÜSS", "ὈΔΥΣΣΕΎΣ", "ὀδυσσεύς", "commonmark", "strikethroughs" ], } ], "words": [ "aarch", "accesskit", "antialiasing", "armv", "astrojs", "Bezier", "Bézier", "cbindgen", "cdylib", "cmake", "colspan", "combobox", "cupertino", "datastructures", "dealloc", "distros", "Femto", "femtovg", "flexboxlayout", "flickable", "focusable", "frameless", "fullscreen", "gles", "Goffart", "gradians", "grayscale", "gridlayout", "groupbox", "Hausmann", "Helvetica", "imagefilter", "inout", "knip", "layouting", "linebreak", "lineedit", "listview", "LINUXFB", "lvalue", "microcontroller", "microcontrollers", "muda", "MSVC", "napi", "Oooops", "ogoffart", "opengl", "opengles", "pixmap", "prestart", "printerdemo", "pythonw", "riscv", "rowspan", "rustc", "rustdoc", "rustdocs", "rustflags", "rvalue", "SANDBOXING", "scrollview", "SDK", "seti", "sixtyfps", "skia", "slint", "slintdoc", "slintdocs", "slintpad", "spdx", "spinbox", "streetsidesoftware", "struct", "structs", "tableview", "tabwidget", "testcase", "textedit", "tmpobj", "toolchain", "toolkit", "touchpad", "trackpad", "trackpads", "tronical", "typeloader", "uefi", "uncompiled", "unerase", "unmapping", "untracked", "viewbox", "vtable", "vulkan", "wasm", "webassembly", "windowrc", "winit", "xtask", "xtensa", "Toradex", "Vivante", "espflash", "pico", "wgpu", "WGPU", "Xcodegen", "Xcodeproj", ], "ignorePaths": [ "api/cpp/docs/conf.py", "Cargo.toml", "CHANGELOG.md", "cspell.json", "LICENSES", "examples/*/lang/**", ".github/workflows/*.yaml", "**/package.json" ], "ignoreRegExpList": [ // Color encodings in all languages: "/(\\b|0x|#|_)?[argbARGB][argbARGB][argbARGB]+(\\b|_|Color)/" ], "overrides": [ { "filename": "**/*.rst", "languageId": "restructuredtext" } ] } ================================================ FILE: demos/CMakeLists.txt ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: MIT cmake_minimum_required(VERSION 3.21) project(SlintDemos LANGUAGES CXX) list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../cmake") if (NOT TARGET Slint::Slint) find_package(Slint REQUIRED) include(FetchContent) endif() if (TARGET Slint::slint-compiler) add_subdirectory(printerdemo/cpp/) endif() if (SLINT_FEATURE_INTERPRETER) add_subdirectory(printerdemo/cpp_interpreted/) endif() ================================================ FILE: demos/README.md ================================================ # Demos These demos showcase different complex use-cases for building UIs with Slint. | Thumbnail | Description | Demo | | --- | --- | --- | | [Printer UI ![Printer Demo image](https://github.com/user-attachments/assets/34627f84-affd-46a6-9c52-1f623d33a507)](./printerdemo) | A fictional user interface for the touch screen of a printer.
[Project...](./printerdemo) | [Wasm Demo](https://slint.dev/snapshots/master/demos/printerdemo/) | | [Energy Meter![Energy meter demo image](https://github.com/user-attachments/assets/abfe03e3-ded6-4ddc-82b7-8303ee45515c "Energy meter demo image")](./energy-monitor/) | A fictional user interface of a device that monitors energy consumption in a building.
[Project...](./energy-monitor) | [Wasm Demo](https://slint.dev/snapshots/master/demos/energy-monitor/) | | [Home Automation![Home Automation demo image](https://github.com/user-attachments/assets/607e07a5-2e79-4045-9fe4-3da2493ba187 "Home Automation demo image")](./home-automation/) | A fictional user interface of a device that automates the control of a home.
[Project...](./home-automation) | [Wasm Demo](https://slint.dev/snapshots/master/demos/home-automation/) | | [Weather![Weather demo image](./weather-demo/docs/img/desktop-preview.png "7 GUI's demo image")](./weather-demo/) | A simple, cross-platform (Desktop, Android, Wasm) weather application using real weather data from the [OpenWeather](https://openweathermap.org/) API.
[Project...](./weather-demo/) | [Wasm Demo](https://slint.dev/snapshots/master/demos/weather-demo/) | | [Usecases ![Usecases Demo image](https://github.com/user-attachments/assets/72dd3e98-36b8-41b6-9d6e-6eb6053ace43)](./usecases) | Different example use cases in one app.
[Project...](./usecases) | [Wasm Demo](https://slint.dev/snapshots/master/demos/usecases/) | --- ### Running the Rust Demos You can run the examples either by going into folder or into the rust sub-folder and use `cargo run`, for example: ```sh cd demos/printerdemo/rust cargo run --release ``` or you can run them from anywhere in the Cargo workspace by name: ```sh cargo run --release --bin printerdemo ``` --- ### Wasm builds In order to make the wasm build of the example, you first need to edit the Cargo.toml files to uncomment the line starting with `#wasm#` (or use the `sed` line bellow) You can then use wasm-pack (which you may need to obtain with `cargo install wasm-pack`). This will generate the wasm in the `./pkg` directory, which the `index.html` file will open. Since wasm files cannot be served from `file://` URL, you need to open a wab server to serve the content ```sh cd demos/printerdemo/rust sed -i "s/^#wasm# //" Cargo.toml wasm-pack build --release --target web python3 -m http.server ``` --- ### Running the C++ Examples * **When compiling Slint from sources:** If you follow the [C++ build instructions](/docs/building.md#c-build), this will build the C++ examples as well by default * **From [installed binary packages](/api/cpp/README.md#binary-packages):** Simply run cmake in one of the example directory containing a CMakeLists.txt ```sh mkdir build && cd build cmake -GNinja -DCMAKE_PREFIX_PATH="" .. cmake --build . ``` --- ### Running the Node Examples You can run the examples by going into the node sub-folder and use [`pnpm`](https://pnpm.io), for example: ```sh cd demos/printerdemo/node pnpm install pnpm start ``` ================================================ FILE: demos/energy-monitor/Cargo.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: MIT [package] name = "energy-monitor" version = "1.16.0" authors = ["Slint Developers "] edition.workspace = true build = "build.rs" publish = false license = "MIT" [lib] crate-type = ["cdylib", "lib"] [dependencies] slint = { path = "../../api/rs/slint", default-features = false, features = ["compat-1-2"] } mcu-board-support = { path = "../../examples/mcu-board-support", optional = true } chrono = { version = "0.4.34", optional = true, default-features = false, features = ["clock", "std", "wasmbind"] } weer_api = { version = "0.1", optional = true } tokio = { version = "1.25", optional = true, features = ["full"] } futures = { version = "0.3.26", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = { version = "0.2" } web-sys = { version = "0.3", features = ["console"] } console_error_panic_hook = "0.1.5" [build-dependencies] slint-build = { path = "../../api/rs/build" } [features] default = ["slint/default", "network", "chrono", "slint/backend-android-activity-06"] simulator = ["mcu-board-support", "slint/renderer-software", "slint/backend-winit", "slint/std"] network = ["dep:weer_api", "tokio", "futures"] ================================================ FILE: demos/energy-monitor/README.md ================================================ # Energy Monitor Demo ![Energy Monitor Demo Screenshot](https://slint.dev/resources/energy-monitor-screenshot.png "Energy Monitor") This is a demonstration of the Slint toolkit. This demo can be executed on various platforms. ## Displaying Real-Time Weather Data To showcase real-time weather data, you will need an application key from https://www.weatherapi.com/. You can inject the API key by setting the `WEATHER_API` environment variable. The geographical location for the weather data can be set using the `WEATHER_LAT` and `WEATHER_LONG` variables. By default, the location is set to Berlin. ## Platform Compatibility ### Desktop (Windows/Mac/Linux) or Embedded Linux You can run the demo on a desktop or embedded Linux environment with the following command: ```sh cargo run -p energy-monitor ``` ### Microcontrollers (MCU) Refer to the [MCU backend Readme](../../examples/mcu-board-support) for instructions on how to run the demo on smaller devices like the Raspberry Pi Pico. To run the MCU-like code on desktop, use the `--features=simulator` ```sh cargo run -p energy-monitor --no-default-features --features=simulator --release ``` ### Android First, [set up your Android environment](https://slint.dev/snapshots/master/docs/rust/slint/android/#building-and-deploying). Then, you can run the demo on an Android device with the following command: ```sh cargo apk run -p energy-monitor --target aarch64-linux-android --lib ``` ### Web ```sh cargo install wasm-pack cd demos/energy-monitor/ wasm-pack build --release --target web --no-default-features --features slint/default,chrono python3 -m http.server ``` ### Building and running on iOS This demo can be cross-compiled to iOS to run on iPhones, iPads, and the respective simulators. #### Prerequisites - A computer running macOS. - An up-to-date installation of [Xcode](https://developer.apple.com/xcode/). - [Xcodegen](https://github.com/yonaskolb/XcodeGen?tab=readme-ov-file#installing) - [Rust](https://rustup.rs). Add the target and simulator toolchains using `rustup target add aarch64-apple-ios` and `rustup target add aarch64-apple-ios-sim` #### Building 1. Run `xcodegen -s ios-project.yml` to generate an XCode project file (`.xcodeproj`). 2. Open XCode and open the generated `.xcodeproj` in it. 3. Run, deploy, and debug the demo from within Xcode. ================================================ FILE: demos/energy-monitor/build.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT #[cfg(not(feature = "mcu-board-support"))] fn main() { slint_build::compile("ui/desktop_window.slint").unwrap(); } #[cfg(feature = "mcu-board-support")] fn main() { let config = slint_build::CompilerConfiguration::new() .embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer); slint_build::compile_with_config("ui/mcu_window.slint", config).unwrap(); slint_build::print_rustc_flags().unwrap(); } ================================================ FILE: demos/energy-monitor/index.html ================================================ Slint Energy Monitor Demo (Web Assembly version)

Energy Monitor Demo

This is the Slint Energy Monitor Demo compiled to WebAssembly.

Loading...
================================================ FILE: demos/energy-monitor/ios-project.yml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: MIT name: Energy Monitor options: bundleIdPrefix: dev.slint.demos settings: ENABLE_USER_SCRIPT_SANDBOXING: NO targets: Energy Monitor: type: application platform: iOS deploymentTarget: "12.0" info: path: Info.plist properties: UILaunchScreen: - ImageRespectSafeAreaInsets: false sources: [] postCompileScripts: - script: | ../../scripts/build_for_ios_with_cargo.bash energy-monitor outputFileLists: $TARGET_BUILD_DIR/$EXECUTABLE_PATH ================================================ FILE: demos/energy-monitor/src/controllers/header.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT use crate::ui::*; use chrono::prelude::*; use slint::*; pub fn setup(window: &MainWindow) -> Timer { let update_timer = Timer::default(); update_timer.start(slint::TimerMode::Repeated, std::time::Duration::from_millis(300), { let weak_window = window.as_weak(); move || { update(&weak_window.unwrap().global::()); } }); update_timer } fn update(header_adapter: &HeaderAdapter) { let now = Local::now(); header_adapter.set_date(slint::format!("{}", now.format("%A %e %B %Y"))); header_adapter.set_time(slint::format!("{}", now.format("%I:%M"))); header_adapter.set_time_suffix(slint::format!("{}", now.format("%p"))); } ================================================ FILE: demos/energy-monitor/src/controllers/weather.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT use crate::ui::*; use chrono::prelude::*; use core::cmp::Ordering; use futures::future; use slint::*; use std::rc::Rc; use weer_api::*; use std::thread; const WEATHER_API_KEY: &str = "WEATHER_API"; const WEATHER_LAT_KEY: &str = "WEATHER_LAT"; const WEATHER_LONG_KEY: &str = "WEATHER_LONG"; const LAT_BERLIN: f32 = 52.520008; const LONG_BERLIN: f32 = 13.404954; const FORECAST_DAYS: i64 = 3; pub fn setup(window: &MainWindow) -> thread::JoinHandle<()> { let window_weak = window.as_weak(); thread::spawn(move || { tokio::runtime::Runtime::new().unwrap().block_on(weather_worker_loop(window_weak)) }) } async fn weather_worker_loop(window_weak: Weak) { let api_key = api_key(); if api_key.is_empty() { return; } let lat = lat(); let long = long(); let now = Local::now(); let mut forecast_days = Vec::new(); let client = Client::new(&api_key, true); let mut forecast_list = future::join_all((0..FORECAST_DAYS).map(|i| { let client = client.clone(); async move { current_forecast( client.clone(), lat, long, now + chrono::TimeDelta::try_days(i).unwrap_or_default(), ) .await } })) .await; for i in 0..forecast_list.len() { if let Some((date, forecast)) = forecast_list.remove(0) { if i == 1 { display_current( window_weak.clone(), forecast.current, SharedString::from(now.format("%e %B %Y").to_string()), ); } { let forecast = forecast.forecast; let mut day = forecast.forecast_day; // the api provides only one day in the forecast therefore an iteration is necessary to get all. if !day.is_empty() { forecast_days.push((day.remove(0), date.format("%A").to_string())); } } } } if !forecast_days.is_empty() { display_forecast(window_weak.clone(), forecast_days); } } async fn current_forecast( client: Client, lat: f32, long: f32, date: DateTime, ) -> Option<(DateTime, Forecast)> { if let Ok(forecast) = client.forecast().query(Query::Coords(lat, long)).dt(date).call() { return Some((date, forecast)); } None } fn display_current(window_weak: Weak, current: Current, current_date: SharedString) { window_weak .upgrade_in_event_loop(move |window| { window .global::() .set_current_temperature(SharedString::from(current.temp_c.to_string())); window.global::().set_current_day(current_date); window.global::().set_current_weather_description(SharedString::from( current.condition.text.to_string(), )); window .global::() .set_current_temperature_icon(get_icon(&window, ¤t.condition)); }) .unwrap(); } fn display_forecast(window_weak: Weak, forecast: Vec<(ForecastDay, String)>) { window_weak .upgrade_in_event_loop(move |window| { let forecast_model = VecModel::default(); let max_temp = forecast .iter() .max_by(|lhs, rhs| { if lhs.0.day.temp_c().max() > rhs.0.day.temp_c().max() { Ordering::Greater } else { Ordering::Less } }) .map(|d| d.0.day.temp_c().max()) .unwrap_or_default(); let min_temp = forecast .iter() .min_by(|lhs, rhs| { if lhs.0.day.temp_c().min() > rhs.0.day.temp_c().min() { Ordering::Greater } else { Ordering::Less } }) .map(|d| d.0.day.temp_c().min()) .unwrap_or_default(); for (forecast_day, day) in forecast { let model = BarTileModel { title: SharedString::from(&day.as_str()[0..3]), max: forecast_day.day.temp_c().max().round() as i32, min: forecast_day.day.temp_c().min().round() as i32, absolute_max: max_temp.round() as i32, absolute_min: min_temp.round() as i32, unit: SharedString::from("°"), icon: get_icon(&window, &forecast_day.day.condition), }; forecast_model.push(model); } window.global::().set_week_model(Rc::new(forecast_model).into()); }) .unwrap(); } fn get_icon(window: &MainWindow, condition: &Condition) -> Image { // code mapping can be found at https://www.weatherapi.com/docs/conditions.json match condition.code { 1003 => window.global::().get_cloudy(), 1006 => window.global::().get_cloud(), _ => window.global::().get_sunny(), } } fn api_key() -> String { if let Some(lat) = option_env!("WEATHER_API") { return lat.to_string(); } #[cfg(not(feature = "mcu-board-support"))] if let Some(lat) = std::env::var_os(WEATHER_API_KEY) && let Some(lat) = lat.to_str() { return lat.to_string(); } String::default() } fn lat() -> f32 { if let Some(lat) = option_env!("WEATHER_LAT") { return lat.parse().unwrap_or_default(); } #[cfg(not(feature = "mcu-board-support"))] if let Some(lat) = std::env::var_os(WEATHER_LAT_KEY) && let Some(lat) = lat.to_str() { return lat.parse().unwrap_or_default(); } LAT_BERLIN } fn long() -> f32 { if let Some(lat) = option_env!("WEATHER_LONG") { return lat.parse().unwrap_or_default(); } #[cfg(not(feature = "mcu-board-support"))] if let Some(lat) = std::env::var_os(WEATHER_LONG_KEY) && let Some(lat) = lat.to_str() { return lat.parse().unwrap_or_default(); } LONG_BERLIN } ================================================ FILE: demos/energy-monitor/src/lib.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT #![cfg_attr(feature = "mcu-board-support", no_std)] #[cfg(feature = "mcu-board-support")] extern crate alloc; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; pub mod ui { slint::include_modules!(); } use slint::*; use ui::*; #[cfg(not(feature = "mcu-board-support"))] mod controllers { #[cfg(feature = "chrono")] pub mod header; #[cfg(feature = "network")] pub mod weather; } #[cfg(not(feature = "mcu-board-support"))] use controllers::*; #[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))] pub fn main() { // This provides better error messages in debug mode. // It's disabled in release mode so it doesn't bloat up the file size. #[cfg(all(debug_assertions, target_arch = "wasm32"))] console_error_panic_hook::set_once(); let window = MainWindow::new().unwrap(); // let _ to keep the timer alive. #[cfg(all(not(feature = "mcu-board-support"), feature = "chrono"))] let _timer = header::setup(&window); #[cfg(all(not(feature = "mcu-board-support"), feature = "network"))] let weather_join = weather::setup(&window); let _kiosk_mode_timer = kiosk_timer(&window); window.run().unwrap(); #[cfg(all(not(feature = "mcu-board-support"), feature = "network"))] weather_join.join().unwrap(); } fn kiosk_timer(window: &MainWindow) -> Timer { let kiosk_mode_timer = Timer::default(); kiosk_mode_timer.start(TimerMode::Repeated, core::time::Duration::from_secs(4), { let window_weak = window.as_weak(); move || { if !SettingsAdapter::get(&window_weak.unwrap()).get_kiosk_mode_checked() { return; } let current_page = MenuOverviewAdapter::get(&window_weak.unwrap()).get_current_page(); let count = MenuOverviewAdapter::get(&window_weak.unwrap()).get_count(); if current_page >= count - 1 { MenuOverviewAdapter::get(&window_weak.unwrap()).set_current_page(0); } else { MenuOverviewAdapter::get(&window_weak.unwrap()).set_current_page(current_page + 1); } } }); kiosk_mode_timer } #[cfg(target_os = "android")] #[unsafe(no_mangle)] fn android_main(app: slint::android::AndroidApp) { slint::android::init(app).unwrap(); main(); } ================================================ FILE: demos/energy-monitor/src/main.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT #![no_std] #![cfg_attr(all(feature = "mcu-board-support", not(feature = "simulator")), no_main)] #[cfg(feature = "mcu-board-support")] #[allow(unused_imports)] use mcu_board_support::prelude::*; #[cfg(not(feature = "mcu-board-support"))] pub fn main() { energy_monitor::main(); } #[cfg(feature = "mcu-board-support")] #[mcu_board_support::entry] fn main() -> ! { mcu_board_support::init(); energy_monitor::main(); panic!("The MCU demo should not quit") } ================================================ FILE: demos/energy-monitor/ui/big_main.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { TabWidget, TabItem, BarTileModel } from "widgets/widgets.slint"; import { Dashboard, WeatherAdapter } from "pages/pages.slint"; import { Header, HeaderAdapter } from "blocks/blocks.slint"; export component BigMain{ VerticalLayout { Header {} i-tab-widget := TabWidget { tabs: [ { text: "Dashboard" }, { text: "Energy Flow" }, { text: "Weather" }, { text: "Statistics" }, { text: "Settings" } ]; Dashboard { index: 0; current-index: i-tab-widget.selected-tab; } } } } ================================================ FILE: demos/energy-monitor/ui/blocks/blocks.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Header, HeaderAdapter } from "header.slint"; export { Header, HeaderAdapter } import { MobileHeader } from "mobile_header.slint"; export { MobileHeader } ================================================ FILE: demos/energy-monitor/ui/blocks/header.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; import { Images } from "../images.slint"; export global HeaderAdapter { in property date: "Sunday 8th, January 2023"; in property time: "5:20"; in property time-suffix: "PM"; in property logo: Images.slint-logo; } export component Header { in property date <=> HeaderAdapter.date; in property time <=> HeaderAdapter.time; in property time-suffix <=> HeaderAdapter.time-suffix; in property logo <=> HeaderAdapter.logo; min-height: 50px; vertical-stretch: 0; HorizontalLayout { padding: 20px; spacing: 5px; Rectangle { horizontal-stretch: 1; Text { x: 0px; color: Theme.palette.white; text: date; font-size: Theme.typo.header-item.size; font-weight: Theme.typo.header-item.weight; } } Rectangle { horizontal-stretch: 1; Image { height: 30px; source: logo; } } Rectangle { horizontal-stretch: 1; Text { x: i-time-suffix.x - 5px - self.width; horizontal-alignment: right; color: Theme.palette.white; text: time; font-size: Theme.typo.header-item.size; font-weight: Theme.typo.header-item.weight; } i-time-suffix := Text { x: parent.width - self.width; horizontal-stretch: 0; color: Theme.palette.shark-gray; text: time-suffix; font-size: Theme.typo.header-item.size; font-weight: Theme.typo.header-item.weight; } } } } ================================================ FILE: demos/energy-monitor/ui/blocks/kiosk_overlay.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { SettingsAdapter } from "../pages/menu_page/settings.slint"; import { Theme } from "../theme.slint"; export component KioskOverlay { preferred-width: 100%; preferred-height: 100%; TouchArea { clicked => { SettingsAdapter.kiosk-mode-checked = false; } } Rectangle { x: i-text.x - 1px; y: i-text.y - 1px; width: i-text.width + 2px; height: i-text.height + 2px; background: Theme.palette.pure-black; opacity: 0.5; } i-text := Text { x: parent.width - self.width - 5px; y: 5px; font-size: Theme.typo.description.size; font-weight: Theme.typo.description.weight; text: "Kiosk"; color: Theme.palette.lemon-green; opacity: 0.5; } } ================================================ FILE: demos/energy-monitor/ui/blocks/mobile_header.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; export component MobileHeader { min-height: 30px; vertical-stretch: 0; Rectangle { background: Theme.palette.pure-black; HorizontalLayout { padding-left: 8px; padding-right: 8px; @children } } } ================================================ FILE: demos/energy-monitor/ui/components/menu_background.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; export component MenuBackground inherits Rectangle { border-radius: 4px; background: Theme.palette.ebony-radial-gradient; clip: true; } ================================================ FILE: demos/energy-monitor/ui/components/state_layer.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; export component StateLayer inherits TouchArea { in property border-radius <=> i-container.border-radius; width: 100%; height: 100%; states [ pressed when root.pressed : { i-container.opacity: 0.12; } ] i-container := Rectangle { width: 100%; height: 100%; background: Theme.palette.pure-black; opacity: 0.0; animate background { duration: Theme.durations.medium; } } } ================================================ FILE: demos/energy-monitor/ui/desktop_window.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme, ScreenSize } from "theme.slint"; import { SmallMain } from "small_main.slint"; import { BigMain } from "big_main.slint"; import { MidMain } from "mid_main.slint"; import { MidMain } from "mid_main.slint"; import { MobileMain } from "mobile_main.slint"; import { BarTileModel } from "widgets/widgets.slint"; import { Images } from "images.slint"; import { Theme } from "theme.slint"; import { HeaderAdapter } from "blocks/blocks.slint"; import { Navigation, MenuButton, Menu, Value } from "widgets/widgets.slint"; import { BalanceAdapter, OverviewAdapter, UsageAdapter, WeatherAdapter, MenuPageAdapter, MenuOverviewAdapter, SettingsAdapter, } from "pages/pages.slint"; import { KioskOverlay } from "blocks/kiosk_overlay.slint"; export { OverviewAdapter, UsageAdapter, Value, WeatherAdapter, MenuPageAdapter, MenuOverviewAdapter, SettingsAdapter, BarTileModel, Images, HeaderAdapter } export component MainWindow inherits Window { private property big-break-point: 1366px; private property mid-break-point: 799px; private property mobile-break-point: 444px; private property screen-size: root.get-screen-size(); title: "EnergyMNG Demo"; min-width: 320px; min-height: 240px; background: Theme.palette.pure-black; preferred-width: 800px; preferred-height: 480px; /* The design is not finished yet if root.screen-size == ScreenSize.Desktop : BigMain { preferred-width: 100%; preferred-height: 100%; } */ if root.screen-size == ScreenSize.Mobile: HorizontalLayout { padding-left: root.safe-area-insets.left; padding-top: root.safe-area-insets.top; padding-right: root.safe-area-insets.right; padding-bottom: root.safe-area-insets.bottom; MobileMain { preferred-width: 100%; preferred-height: 100%; } } if root.screen-size == ScreenSize.EmbeddedMedium: MidMain { preferred-width: 100%; preferred-height: 100%; } if root.screen-size == ScreenSize.EmbeddedSmall: SmallMain { preferred-width: 100%; preferred-height: 100%; } if SettingsAdapter.kiosk-mode-checked: KioskOverlay { } pure function get-screen-size() -> ScreenSize { if (root.width <= root.mobile-break-point && root.width < root.height) { return ScreenSize.Mobile; } if (root.width < root.mid-break-point) { return ScreenSize.EmbeddedSmall; } return ScreenSize.EmbeddedMedium; } } ================================================ FILE: demos/energy-monitor/ui/images.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT export global Images { in property arrow-left: @image-url("assets/arrow-left.svg"); in property arrow-right: @image-url("assets/arrow-right.svg"); in property check: @image-url("assets/check.svg"); in property sunny: @image-url("assets/sunny.svg"); in property cloud: @image-url("assets/cloud.svg"); in property cloudy: @image-url("assets/cloudy.svg"); in property settings: @image-url("assets/settings.svg"); in property information: @image-url("assets/information.svg"); in property slint-logo: @image-url("../../../logo/slint-logo-simple-dark.svg"); in property spyrosoft-logo: @image-url("assets/spyrosoft-logo.svg"); in property dashboard: @image-url("assets/dashboard.svg"); } ================================================ FILE: demos/energy-monitor/ui/mcu_window.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "theme.slint"; import { SmallMain } from "small_main.slint"; import { BarTileModel } from "widgets/widgets.slint"; import { Images } from "images.slint"; import { Theme } from "theme.slint"; import { HeaderAdapter } from "blocks/blocks.slint"; import { Navigation, MenuButton, Menu, Value } from "widgets/widgets.slint"; import { BalanceAdapter, OverviewAdapter, UsageAdapter, WeatherAdapter, MenuPageAdapter, MenuOverviewAdapter, SettingsAdapter } from "pages/pages.slint"; export { OverviewAdapter, UsageAdapter, Value, WeatherAdapter, MenuPageAdapter, MenuOverviewAdapter, SettingsAdapter, BarTileModel, Images, HeaderAdapter } export component MainWindow inherits Window { title: "EnergyMNG Demo"; min-width: 320px; min-height: 240px; background: Theme.palette.pure-black; SmallMain { preferred-width: 100%; preferred-height: 100%; } } ================================================ FILE: demos/energy-monitor/ui/mid_main.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Header, HeaderAdapter } from "blocks/blocks.slint"; import { Images } from "images.slint"; import { Theme } from "theme.slint"; import { Menu, PageScrollView, PageContainer } from "widgets/widgets.slint"; import { MenuPage } from "pages/pages.slint"; import { Balance, Overview, Usage, UsageAdapter, Weather, About } from "pages/pages.slint"; export component MidMain { Rectangle { background: Theme.palette.background-gradient; VerticalLayout { padding-bottom: 50px; Header {} i-page-scroll-view := PageScrollView { vertical-stretch: 1; page-count: 5; PageContainer { clicked => { i-page-scroll-view.toggle-selection(self.index, self.x); } index: 0; selected <=> i-page-scroll-view.selection; selected-width: i-page-scroll-view.selected-width; selected-height: i-page-scroll-view.selected-height; selected-h-offset: i-page-scroll-view.selected-h-offset; Overview {} } PageContainer { clicked => { i-page-scroll-view.toggle-selection(self.index, self.x); } index: 1; selected <=> i-page-scroll-view.selection; selected-width: i-page-scroll-view.selected-width; selected-height: i-page-scroll-view.selected-height; selected-h-offset: i-page-scroll-view.selected-h-offset; Usage {} } PageContainer { clicked => { i-page-scroll-view.toggle-selection(self.index, self.x); } index: 2; selected <=> i-page-scroll-view.selection; selected-width: i-page-scroll-view.selected-width; selected-height: i-page-scroll-view.selected-height; selected-h-offset: i-page-scroll-view.selected-h-offset; Balance {} } PageContainer { clicked => { i-page-scroll-view.toggle-selection(self.index, self.x); } index: 3; selected <=> i-page-scroll-view.selection; selected-width: i-page-scroll-view.selected-width; selected-height: i-page-scroll-view.selected-height; selected-h-offset: i-page-scroll-view.selected-h-offset; Weather {} } PageContainer { clicked => { i-page-scroll-view.toggle-selection(self.index, self.x); } index: 4; selected <=> i-page-scroll-view.selection; selected-width: i-page-scroll-view.selected-width; selected-height: i-page-scroll-view.selected-height; selected-h-offset: i-page-scroll-view.selected-h-offset; About {} } } } i-menu := Menu { stays-open: true; preferred-width: 100%; preferred-height: 100%; start-y: 35px; end-y: 75px; menu-width: root.width / 3; menu-height: root.height - 75px; opened => { i-menu-page.current-index = 0; } i-menu-page := MenuPage { close => { i-menu.hide(); } preferred-width: 100%; preferred-height: 100%; } } } } ================================================ FILE: demos/energy-monitor/ui/mobile_main.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "theme.slint"; import { Navigation, MenuButton, MobileMenu, Value, IconButton } from "widgets/widgets.slint"; import { Balance, Overview, Usage, UsageAdapter, Weather, MenuPage, MenuOverviewAdapter, About } from "pages/pages.slint"; import { Images } from "images.slint"; import { TabWidget, TabItem } from "widgets/widgets.slint"; import { DashboardMobile, Weather, About } from "pages/pages.slint"; import { MobileHeader } from "blocks/blocks.slint"; import { MenuBackground } from "components/menu_background.slint"; export component MobileMain { tab-widget := TabWidget { y: header.height + 16px; width: 100%; height: parent.height - header.height - 16px; tabs: [ { text: "Dashboard", icon: Images.dashboard }, { text: "Weather", icon: Images.sunny }, { text: "About", icon: Images.information }, ]; DashboardMobile { index: 0; current-index: tab-widget.selected-tab; } Weather { index: 1; current-index: tab-widget.selected-tab; } About { index: 2; current-index: tab-widget.selected-tab; } } menu := MobileMenu { end-y: settings-button.y + settings-button.height; menu-x: settings-button.x + settings-button.width - self.menu-width; width: 100%; height: 100%; MenuPage { width: 100%; height: 100%; } } header := MobileHeader { width: 100%; y: 0px; HorizontalLayout { alignment: start; } HorizontalLayout { alignment: center; Image { horizontal-stretch: 1; y: (parent.height - self.height) / 2; height: 24px; source: Images.slint-logo; } } settings-button := IconButton { y: (parent.height - self.height) / 2; icon: Images.settings; clicked => { if (!menu.open) { menu.open-menu(); return; } menu.hide(); } } } } ================================================ FILE: demos/energy-monitor/ui/pages/about.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; import { Images } from "../images.slint"; import { Page } from "page.slint"; import { IconButton } from "../widgets/widgets.slint"; import { GroupBox } from "../widgets/group_box.slint"; export component About inherits Page { GroupBox { VerticalLayout { vertical-stretch: 1; spacing: Theme.spaces.medium; alignment: start; Text { vertical-stretch: 0; horizontal-alignment: center; text: "Developed by"; color: Theme.palette.slint-blue-100; font-size: Theme.typo.header-item.size; font-weight: Theme.typo.header-item.weight; } Image { preferred-height: 55px; source: Images.slint-logo; } // spacer Rectangle { vertical-stretch: 1; } Text { vertical-stretch: 0; horizontal-alignment: center; text: "Designed by"; color: Theme.palette.slint-blue-100; font-size: Theme.typo.header-item.size; font-weight: Theme.typo.header-item.weight; } Image { preferred-height: 42px; source: Images.spyrosoft-logo; } } } } ================================================ FILE: demos/energy-monitor/ui/pages/balance.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Page } from "page.slint"; import { GroupBox, BalanceChart } from "../widgets/widgets.slint"; export global BalanceAdapter { in property title: "Balance"; in property <[string]> x-axis-model: [ "12:00", "", "", "16:00", "", "", "20:00" ]; in property <[int]> y-axis-model: [ 1.0, 0, -2, -4, -6 ]; in property <[float]> model: [ 0.2, 0.5, -1.7, -2.0, -4.0, -5.0, -5.5, -6.0, -6.2, -6.4, -4.5, -3.0, 0.25, 0.5 ]; in property min: -7.8; in property max: 2; in property y-unit: "K"; } export component Balance inherits Page { in property <[string]> x-axis-model <=> BalanceAdapter.x-axis-model; in property <[int]> y-axis-model <=> BalanceAdapter.y-axis-model; in property <[float]> model <=> BalanceAdapter.model; in property min <=> BalanceAdapter.min; in property max <=> BalanceAdapter.max; in property y-unit <=> BalanceAdapter.y-unit; in property title <=> BalanceAdapter.title; GroupBox { title: root.title; BalanceChart { x-axis-model: root.x-axis-model; y-axis-model: root.y-axis-model; model: root.model; min: root.min; max: root.max; y-unit: root.y-unit; active: root.active; } } } ================================================ FILE: demos/energy-monitor/ui/pages/dashboard.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Page } from "page.slint"; import { OverviewAdapter } from "overview.slint"; import { UsageAdapter } from "usage.slint"; import { WeatherAdapter } from "weather.slint"; import { BalanceAdapter } from "balance.slint"; import { GroupBox, Value, ValueDisplay, BarChart, BarTileModel, Tile, BarTiles, BalanceChart } from "../widgets/widgets.slint"; export component ValueTile { in property title <=> i-group-box.title; in property <[Value]> model <=> i-value-display.model; in property alternative-colors <=> i-value-display.alternative-colors; in property active; i-group-box := GroupBox { preferred-width: 100%; preferred-height: 100%; i-value-display := ValueDisplay { active: root.active; } } } export component BarChartTile { in property title <=> i-group-box.title; in property <[Value]> value-model <=> i-value-display.model; in property <[float]> model <=> i-bar-chart.model; in property min <=> i-bar-chart.min; in property max <=> i-bar-chart.max; in property active; height: i-group-box.min-height; i-group-box := GroupBox { preferred-width: 100%; min-height: 124px; i-value-display := ValueDisplay { width: 100%; alternative-colors: true; active: root.active; vertical: true; } i-bar-chart := BarChart { horizontal-stretch: 1; } } } export component WeatherTile { in property title <=> i-group-box.title; in property current-temperature-icon <=> i-tile.icon; in property current-temperature <=> i-tile.value; in property current-day <=> i-tile.text; in property current-weather-description <=> i-tile.sub-text; in property <[BarTileModel]> week-model <=> i-bar-tiles.model; i-group-box := GroupBox { spacing: 1px; i-tile := Tile {} i-bar-tiles := BarTiles {} } } export component BalanceTile { in property <[string]> x-axis-model; in property <[int]> y-axis-model; in property <[float]> model; in property min; in property max; in property y-unit; in property title; GroupBox { title: root.title; BalanceChart { x-axis-model: root.x-axis-model; y-axis-model: root.y-axis-model; model: root.model; min: root.min; max: root.max; y-unit: root.y-unit; } } } export component Dashboard inherits Page { GridLayout { padding-left: 20px; padding-right: 20px; padding-top: 20px; padding-bottom: 60px; spacing: 20px; Row { ValueTile { title: OverviewAdapter.production-title; model: OverviewAdapter.production-model; active: root.active; } ValueTile { title: OverviewAdapter.self-consumption-title; model: OverviewAdapter.self-consumption-model; alternative-colors: true; active: root.active; } } Row { BalanceTile { x-axis-model <=> BalanceAdapter.x-axis-model; y-axis-model <=> BalanceAdapter.y-axis-model; model <=> BalanceAdapter.model; min <=> BalanceAdapter.min; max <=> BalanceAdapter.max; y-unit <=> BalanceAdapter.y-unit; title <=> BalanceAdapter.title; } WeatherTile { title <=> WeatherAdapter.title; current-temperature-icon <=> WeatherAdapter.current-temperature-icon; current-temperature <=> WeatherAdapter.current-temperature; current-day <=> WeatherAdapter.current-day; current-weather-description <=> WeatherAdapter.current-weather-description; week-model <=> WeatherAdapter.week-model; } } Row { BarChartTile { colspan: 2; title: UsageAdapter.title; model: UsageAdapter.model; min: UsageAdapter.min; max: UsageAdapter.max; value-model: UsageAdapter.overview-model; active: root.active; } } } } ================================================ FILE: demos/energy-monitor/ui/pages/dashboard_mobile.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Page } from "page.slint"; import { Usage } from "usage.slint"; import { OverviewAdapter } from "overview.slint"; import { BalanceAdapter } from "balance.slint"; import { UsageAdapter } from "usage.slint"; import { UsageAdapter } from "usage.slint"; import { ScrollView, GroupBox, BalanceChart, BarChart, ValueDisplay} from "../widgets/widgets.slint"; import { ValueTile, BalanceTile, BarChartTile } from "dashboard.slint"; export component DashboardMobile inherits Page { ScrollView { VerticalLayout { alignment: start; spacing: 16px; ValueTile { title: OverviewAdapter.production-title; model: OverviewAdapter.production-model; active: root.active; } ValueTile { title: OverviewAdapter.self-consumption-title; model: OverviewAdapter.self-consumption-model; alternative-colors: true; active: root.active; } GroupBox { title: UsageAdapter.title; VerticalLayout { ValueDisplay { model: UsageAdapter.overview-model; transparent-background: true; alternative-colors: true; active: root.active; } BarChart { model: UsageAdapter.model; min: UsageAdapter.min; max: UsageAdapter.max; active: root.active; } } } GroupBox { title: BalanceAdapter.title; BalanceChart { x-axis-model: BalanceAdapter.x-axis-model; y-axis-model: BalanceAdapter.y-axis-model; model: BalanceAdapter.model; min: BalanceAdapter.min; max: BalanceAdapter.max; y-unit: BalanceAdapter.y-unit; active: root.active; min-height: 200px; } } } } } ================================================ FILE: demos/energy-monitor/ui/pages/menu_page/menu_overview.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../../theme.slint"; import { Images } from "../../images.slint"; import { Page } from "../page.slint"; import { IconButton, ListView } from "../../widgets/widgets.slint"; export global MenuOverviewAdapter { in property <[StandardListViewItem]> model: [ { text: "Production & Self-consumption"}, { text: "Usage"}, { text: "Balance"}, { text: "Weather"}, { text: "About"}, ]; in-out property current-page; out property count: model.length; } ================================================ FILE: demos/energy-monitor/ui/pages/menu_page/menu_page.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { MenuOverviewAdapter } from "menu_overview.slint"; import { Settings, SettingsAdapter } from "settings.slint"; import { Theme } from "../../theme.slint"; export { MenuOverviewAdapter, SettingsAdapter } export global MenuPageAdapter { in property <[StandardListViewItem]> model: [ { text: "Production & Self-consumption"}, { text: "Usage"}, { text: "Balance"}, { text: "Weather"}, ]; in-out property selected-index; } export component MenuPage { in-out property current-index; callback page-changed(/* index */ int); callback close; private property show-settings; function back() { current-index = 0; } Rectangle { x: -parent.width * current-index; width: 2 * parent.width; clip: true; animate x { duration: Theme.durations.fast; } if(current-index == 0) : Settings { close => { root.close(); } width: root.width; } } } ================================================ FILE: demos/energy-monitor/ui/pages/menu_page/settings.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../../theme.slint"; import { Images } from "../../images.slint"; import { Page } from "../page.slint"; import { IconButton, CheckBox, RadioButton, Switch, ScrollView, Item, ItemGroupBox } from "../../widgets/widgets.slint"; export global SettingsAdapter { // functions in-out property function-one-checked: true; in-out property kiosk-mode-checked; in-out property function-three-checked; // radio options in-out property radio-option-one-checked: true; in-out property radio-option-two-checked: false; // check options in-out property check-option-one-checked: true; in-out property check-option-two-checked; in-out property check-option-three-checked; callback check-radio-option(int /* index */); check-radio-option(index) => { if(index == 0) { radio-option-one-checked = true; radio-option-two-checked = false; return; } radio-option-one-checked = false; radio-option-two-checked = true; } } export component Settings inherits Page { // functions in-out property function-one-checked <=> SettingsAdapter.function-one-checked; in-out property kiosk-mode-checked <=> SettingsAdapter.kiosk-mode-checked; in-out property function-three-checked <=> SettingsAdapter.function-three-checked; // radio options in-out property radio-option-one-checked <=> SettingsAdapter.radio-option-one-checked; in-out property radio-option-two-checked <=> SettingsAdapter.radio-option-two-checked; // check options in-out property check-option-one-checked <=> SettingsAdapter.check-option-one-checked; in-out property check-option-two-checked <=> SettingsAdapter.check-option-two-checked; in-out property check-option-three-checked <=> SettingsAdapter.check-option-three-checked; callback close(); horizontal-padding: 0; VerticalLayout { padding-left: Theme.spaces.medium; padding-right: Theme.spaces.medium; padding-top: Theme.spaces.medium; padding-bottom: Theme.spaces.large; spacing: Theme.spaces.medium; // spacer ScrollView { vertical-stretch: 1; VerticalLayout { alignment: start; spacing: Theme.spaces.medium; Item { clicked => { i-function-one-check-box.clicked(); } text: "Function One"; i-function-one-check-box := CheckBox { checked <=> root.function-one-checked; } } ItemGroupBox { title: "Subtitle"; Item { clicked => { i-radio-option-one.clicked(); } text: "Option One"; has-separator: true; i-radio-option-one := RadioButton { clicked => { SettingsAdapter.check-radio-option(0); } checked: root.radio-option-one-checked; } } Item { clicked => { i-radio-option-two.clicked(); } text: "Option Two"; i-radio-option-two := RadioButton { clicked => { SettingsAdapter.check-radio-option(1); } checked: root.radio-option-two-checked; } } } ItemGroupBox { title: "Subtitle"; Item { clicked => { i-check-option-one.clicked(); } text: "Option One"; has-separator: true; i-check-option-one := CheckBox { checked <=> root.check-option-one-checked; } } Item { clicked => { i-check-option-two.clicked(); } text: "Option Two"; has-separator: true; i-check-option-two := CheckBox { checked <=> root.check-option-two-checked; } } Item { clicked => { i-check-option-three.clicked(); } text: "Option Three"; i-check-option-three := CheckBox { checked <=> root.check-option-three-checked; } } } Item { clicked => { i-kiosk-mode.clicked(); } text: "Kiosk mode"; i-kiosk-mode := Switch { changed => { close(); } checked <=> root.kiosk-mode-checked; } } Item { clicked => { i-function-three.clicked(); } text: "Function Three"; i-function-three := Switch { checked <=> root.function-three-checked; } } } } } } ================================================ FILE: demos/energy-monitor/ui/pages/overview.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Page } from "page.slint"; import { GroupBox, ValueDisplay, Value } from "../widgets/widgets.slint"; export global OverviewAdapter { in property production-title: "Production"; in property self-consumption-title: "Self-consumption"; in property <[Value]> production-model: [ { title: "Daily", value: 12.56, unit: "kWh", }, { title: "Weekly", value: 90.28, unit: "kWh", } ]; in property <[Value]> self-consumption-model: [ { title: "Weekly", value: 54.08, unit: "kWh", }, { title: "Monthly", value: 320.18, unit: "kWh", } ]; } export component Overview inherits Page { in property production-title <=> OverviewAdapter.production-title; in property self-consumption-title <=> OverviewAdapter.self-consumption-title; in property <[Value]> production-model <=> OverviewAdapter.production-model; in property <[Value]> self-consumption-model <=> OverviewAdapter.self-consumption-model; width: 100%; height: 100%; VerticalLayout { spacing: 12px; i-production-group := GroupBox { title: root.production-title; ValueDisplay { active: root.active; model: root.production-model; } } i-self-consumption-group := GroupBox { title: root.self-consumption-title; ValueDisplay { active: root.active; alternative-colors: true; model: root.self-consumption-model; } } } } ================================================ FILE: demos/energy-monitor/ui/pages/page.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; export component Page inherits Rectangle { in property index; in property current-index; in property horizontal-padding: 14px; out property active: index == current-index; x: (index - current-index) * self.width; width: 100%; height: 100%; animate x { duration: Theme.durations.fast; } GridLayout { padding-left: root.horizontal-padding; padding-right: root.horizontal-padding; @children } } ================================================ FILE: demos/energy-monitor/ui/pages/pages.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Balance, BalanceAdapter } from "balance.slint"; import { Overview, OverviewAdapter } from "overview.slint"; import { Usage, UsageAdapter } from "usage.slint"; import { Weather, WeatherAdapter } from "weather.slint"; import { Page } from "page.slint"; import { Dashboard } from "dashboard.slint"; import { DashboardMobile } from "dashboard_mobile.slint"; import { MenuPage, MenuPageAdapter, MenuOverviewAdapter, SettingsAdapter } from "menu_page/menu_page.slint"; import { About } from "about.slint"; export { Balance, BalanceAdapter } export { Overview, OverviewAdapter } export { Usage, UsageAdapter } export { Weather, WeatherAdapter } export { Page } export { Dashboard } export { DashboardMobile } export { MenuPage, MenuPageAdapter, MenuOverviewAdapter, SettingsAdapter } export { About } ================================================ FILE: demos/energy-monitor/ui/pages/usage.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Page } from "page.slint"; import { GroupBox, Value , ValueDisplay, BarChart } from "../widgets/widgets.slint"; export global UsageAdapter { in property title: "Usage"; in property <[Value]> overview-model: [ { value: 16.41, title: "Daily", unit: "kWh", }, { value: 15.23, title: "Weekly", unit: "kWh", } ]; in property <[float]> model: [ 10.0, 9.0, 11.0, 12.0, 8.0, 14.0, 9.0, 16.0, 18.0, 12.0, 11.0, 14.0, 12.0, 16.0 ]; in property min: 0.0; in property max: 24.0; } export component Usage inherits Page { in property title <=> UsageAdapter.title; in property <[Value]> overview-model <=> UsageAdapter.overview-model; in property <[float]> model <=> UsageAdapter.model; in property min <=> UsageAdapter.min; in property max <=> UsageAdapter.max; GroupBox { title: root.title; Rectangle { BarChart { preferred-width: 100%; preferred-height: 100%; model: root.model; min: root.min; max: root.max; active: root.active; } VerticalLayout { alignment: start; ValueDisplay { model: overview-model; transparent-background: true; alternative-colors: true; active: root.active; } } } } } ================================================ FILE: demos/energy-monitor/ui/pages/weather.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Page } from "page.slint"; import { GroupBox, Tile, BarTiles, BarTileModel } from "../widgets/widgets.slint"; import { Images } from "../images.slint"; export global WeatherAdapter { in property title: "Weather"; in property current-temperature-icon: Images.cloud; in property current-temperature: "22°"; in property current-day: "May 6th 2023"; in property current-weather-description: "Very cloudy"; in property <[BarTileModel]> week-model: [ { title: "Thu", icon: Images.cloudy, max: 21, min: 18, absolute-max: 21, absolute-min: 15, unit: "°" }, { title: "Fri", icon: Images.cloudy, max: 20, min: 17, absolute-max: 21, absolute-min: 15, unit: "°" }, { title: "Sat", icon: Images.cloudy, max: 18, min: 15, absolute-max: 21, absolute-min: 15, unit: "°" } ]; } export component Weather inherits Page { in property title <=> WeatherAdapter.title; in property current-temperature-icon <=> WeatherAdapter.current-temperature-icon; in property current-temperature <=> WeatherAdapter.current-temperature; in property current-day <=> WeatherAdapter.current-day; in property current-weather-description <=> WeatherAdapter.current-weather-description; in property <[BarTileModel]> week-model <=> WeatherAdapter.week-model; GroupBox { title: root.title; spacing: 1px; Tile { value: current-temperature; text: current-day; sub-text: current-weather-description; icon: current-temperature-icon; } BarTiles { model: week-model; active: root.active; } // stretches the empty element if(week-model.length == 0) : Rectangle {} } } ================================================ FILE: demos/energy-monitor/ui/small_main.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "theme.slint"; import { Navigation, MenuButton, Menu, Value } from "widgets/widgets.slint"; import { Balance, Overview, Usage, UsageAdapter, Weather, MenuPage, MenuOverviewAdapter, About } from "pages/pages.slint"; export component SmallMain { i-navigation := Navigation { pagination-clicked => { i-menu.open-menu(); i-navigation.hide(); } clicked => { // if the navigation is clicked and the arrow displayed a open menu button should be hide. i-menu.hide-button(); } current-index <=> MenuOverviewAdapter.current-page; page-count: MenuOverviewAdapter.count; // check current-index to generate only displayed items if(i-navigation.current-index <= 1 && !i-menu.open) : Overview { index: 0; current-index: i-navigation.current-index; } if(i-navigation.current-index >= 0 && i-navigation.current-index <= 2 && !i-menu.open) : Usage { index: 1; current-index: i-navigation.current-index; } if(i-navigation.current-index >= 1 && i-navigation.current-index <= 3 && !i-menu.open) : Balance { index: 2; current-index: i-navigation.current-index; } if(i-navigation.current-index >= 2 && i-navigation.current-index <= 4 && !i-menu.open) : Weather { index: 3; current-index: i-navigation.current-index; } if(i-navigation.current-index >= 3 && i-navigation.current-index <= 5 && !i-menu.open) : About { index: 4; current-index: i-navigation.current-index; } } i-menu := Menu { preferred-width: 100%; preferred-height: 100%; start-y: 25px; end-y: 22px; menu-width: parent.width - 8px; menu-height: parent.height - 14px; if(i-menu.open) : MenuPage { page-changed => { i-menu.hide(); } close => { i-menu.hide(); } preferred-width: 100%; preferred-height: 100%; } } } ================================================ FILE: demos/energy-monitor/ui/theme.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT export struct Palette { // primary slint-blue: brush, pure-black: brush, dark-deep-blue: brush, shark-gray: brush, lemon-green: brush, lemon-green-op10: brush, white: brush, dimmer: brush, // slint blue slint-blue-50: brush, slint-blue-100: brush, slint-blue-200: brush, slint-blue-300: brush, slint-blue-400: brush, slint-blue-500: brush, slint-blue-600: brush, slint-blue-700: brush, slint-blue-800: brush, slint-blue-900: brush, // lime green lime-green-50: brush, lime-green-100: brush, lime-green-200: brush, lime-green-300: brush, lime-green-400: brush, lime-green-500: brush, lime-green-600: brush, lime-green-700: brush, lime-green-800: brush, lime-green-900: brush, // gradients lemon-green-gradient: brush, lemon-green-light-gradient: brush, lemon-green-radial-gradient: brush, slint-blue-gradient: brush, heliotrope-gradient: brush, dark-left-gradient: brush, dark-right-gradient: brush, ebony-radial-gradient: brush, bar-gradient: brush, alternative-bar-gradient: brush, alternative-light-bar-gradient: brush, inverted-bar-gradient: brush, inverted-alternative-bar-gradient: brush, bar-background-gradient: brush, tab-gradient: brush, background-gradient: brush, } export struct Spaces { small: length, medium: length, large: length, } export struct Durations { fast: duration, medium: duration, slow: duration, } struct Font { size: length, weight: float, } export struct Typo { label: Font, label-light: Font, header: Font, header-item: Font, header-item-light: Font, value: Font, description: Font, description-light: Font, value-big: Font, } export global Theme { in property palette: { // primary slint-blue: #0025FF, pure-black: #000000, dark-deep-blue: #040708, shark-gray: #2C2F36, lemon-green: #DEFB3A, lemon-green-op10: #defb3a1a, white: #FFFFFF, dimmer: #0000007b, // slint blue slint-blue-50: #EEE6FF, slint-blue-100: #D0C3FF, slint-blue-200: #AF9AFF, slint-blue-300: #896FFF, slint-blue-400: #654EFF, slint-blue-500: #2F2AFF, slint-blue-600: #0025FF, slint-blue-700: #001FF7, slint-blue-800: #0019F2, slint-blue-900: #000AEF, // lime green lime-green-50: #FBFFE6, lime-green-100: #F4FDC0, lime-green-200: #EBFC93, lime-green-300: #E2FA63, lime-green-400: #DEFB3A, lime-green-500: #D6F800, lime-green-600: #CBE600, lime-green-700: #BBCF00, lime-green-800: #ACB700, lime-green-900: #D9D9D9, // gradients lemon-green-gradient: @linear-gradient(135deg, #defb3a75 0%, #defb3a00 100%), lemon-green-light-gradient: @linear-gradient(135deg, #DEFB3A 0%, #defb3a33 100%), lemon-green-radial-gradient: @radial-gradient(circle, #DEFB3A20 0%, #DEFB3A00 100%), slint-blue-gradient: @linear-gradient(135deg, #6c4bff76 0%, #6C4BFF00 100%), heliotrope-gradient: @linear-gradient(180deg, #896fff 0%, #39316B 100%), dark-left-gradient: @linear-gradient(90deg, #040708 0%, #04070875 50%, #04070800 100%), dark-right-gradient: @linear-gradient(90deg, #04070800 0%, #04070875 50%, #040708 100%), ebony-radial-gradient: @radial-gradient(circle, #100F23 0%, #1F1946 100%), bar-gradient: @linear-gradient(180deg, #6C4BFF 0%, #6c4bff00 100%), alternative-bar-gradient: @linear-gradient(180deg, #CBE600 0%, #CBE60000 100%), alternative-light-bar-gradient: @linear-gradient(180deg, #EBFC93 0%, #EBFC9300 100%), inverted-bar-gradient: @linear-gradient(180deg, #6c4bff00 0%, #6C4BFF 100%), inverted-alternative-bar-gradient: @linear-gradient(180deg, #CBE60000 0%, #CBE600 100%), bar-background-gradient: @linear-gradient(180deg, #896FFF 0%, #896FFF00 100%), tab-gradient: @linear-gradient(180deg, #0026ff21 0%, #0026ff01 100%), background-gradient: @radial-gradient(circle, #0026ff40 50%, #0025FF00 100%), }; in property spaces: { small: 5px, medium: 10px, large: 20px, }; in property durations: { fast: 125ms, medium: 200ms, slow: 500ms, }; in property typo: { label-light: { size: 12px, weight: 400 }, label: { size: 12px, weight: 500 }, header: { size: 16px, weight: 600 }, header-item: { size: 18px, weight: 400 }, header-item-light: { size: 18px, weight: 200 }, value: { size: 26px, weight: 500 }, description: { size: 14px, weight: 400 }, description-light: { size: 14px, weight: 200 }, value-big: { size: 40px, weight: 200 }, }; } export enum ScreenSize { EmbeddedSmall, EmbeddedMedium, Mobile, Desktop } ================================================ FILE: demos/energy-monitor/ui/widgets/balance_chart.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; import { ChartAxis, AxisLabel, AxisValue } from "chart_axis.slint"; import { ChartPattern, BarBackground } from "chart_pattern.slint"; component UpBar { VerticalLayout { Rectangle { height: 0.6 * root.height; background: Theme.palette.alternative-bar-gradient; } Rectangle { height: 0.4 * root.height; background: Theme.palette.alternative-light-bar-gradient; } } } component DownBar { VerticalLayout { Rectangle { height: 0.37 * root.height; background: Theme.palette.inverted-bar-gradient; } Rectangle { height: 0.26 * root.height; background: Theme.palette.inverted-alternative-bar-gradient; } Rectangle { height: 0.37 * root.height; background: Theme.palette.inverted-alternative-bar-gradient; } } } export component BalanceChart { in property min; in property max; in property <[string]> x-axis-model; in property <[int]> y-axis-model; in property y-unit; in property <[float]> model; in property active; private property zero: root.height * (1 - (0. - min) / (max - min)); i-zero-pattern := ChartPattern { preferred-width: 100%; preferred-height: 100%; y: 0px; count: x-axis-model.length; height: root.zero; cache-rendering-hint: true; } ChartPattern { preferred-width: 100%; preferred-height: 100%; y: i-zero-pattern.height; count: x-axis-model.length; height: root.height - i_zero_pattern.height; cache-rendering-hint: true; } ChartAxis { preferred-width: 100%; preferred-height: 100%; x-model: x-axis-model; y-model: y-axis-model; vertical-stretch: 1; y-min: min; y-max: max; y-unit: root.y-unit; } Rectangle { cache-rendering-hint: true; HorizontalLayout { padding-left: 6px; padding-right: 6px; spacing: 10px; for value[index] in model : Rectangle { private property display-value; if(value > 0.0) : UpBar { width: 100%; y: zero - self.height; height: parent.height * (display-value / (root.max - root.min)); } if(value < 0.0) : DownBar { y: zero; width: 100%; height: parent.height * (-1 * value / (root.max - root.min)); } states [ active when active : { display-value: value; in { animate display-value { duration: Theme.durations.slow; easing: ease-in-out; } } } ] } } } } ================================================ FILE: demos/energy-monitor/ui/widgets/bar_chart.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; import { ChartPattern } from "chart_pattern.slint"; component Bar { in property bar-height; horizontal-stretch: 1; Rectangle { border-radius: 2px; y: parent.height - self.height; height: bar-height; clip: true; Rectangle { height: root.height; y: parent.height - self.height; background: Theme.palette.bar-gradient; } } } export component BarBackground inherits Rectangle { border-radius: 2px; background: Theme.palette.bar-background-gradient; opacity: 0.25; } export component BarChart { in property <[float]> model; in property min; in property max; in property active; cache-rendering-hint: true; ChartPattern { count: model.length / 2; } layout := HorizontalLayout { spacing: 1px; for value in model : Bar { private property display-value; min-height: 120px; preferred-height: 100%; bar-height: parent.height * (display-value - root.min) / (root.max - root.min); states [ active when active : { display-value: value; in { animate display-value { duration: Theme.durations.slow; easing: ease-in-out; } } } ] } } } ================================================ FILE: demos/energy-monitor/ui/widgets/bar_tiles.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; import { BarBackground } from "bar_chart.slint"; export struct BarTileModel { title: string, icon: image, max: int, min: int, absolute-min: int, absolute-max: int, unit: string, } component ValueLabel { in property text; in property unit; HorizontalLayout { Text { color: Theme.palette.white; vertical-stretch: 0; horizontal-alignment: right; text: root.text; font-size: Theme.typo.description-light.size; font-weight: Theme.typo.description-light.weight; } Text { color: Theme.palette.white; vertical-stretch: 0; horizontal-alignment: left; text: "°"; font-size: Theme.typo.description-light.size; font-weight: Theme.typo.description-light.weight; } } } component BarTile { in property title <=> i-title.text; in property icon <=> i-icon.source; in property max; in property min; in property unit; in property absolute-min; in property absolute-max; HorizontalLayout { alignment: center; VerticalLayout { spacing: 7px; i-title := Text { color: Theme.palette.white; vertical-stretch: 0; horizontal-alignment: center; font-size: Theme.typo.description.size; font-weight: Theme.typo.description.weight; } i-icon := Image { height: 20px; vertical-stretch: 0; } ValueLabel { text: floor(max); unit: unit; } Rectangle { private property range: root.absolute-max - root.absolute-min; private property max-y: self.height * (root.max - root.absolute-min) / range; private property min-y: self.height * (root.min - root.absolute-min) / range; vertical-stretch: 1; HorizontalLayout { alignment: center; y: parent.height - max-y; height: max-y - min-y; Rectangle { min_width: 12px; border-radius: 6px; background: Theme.palette.lemon-green-light-gradient; } } } ValueLabel { text: floor(min); unit: unit; } } } } export component BarTiles { in property <[BarTileModel]> model; in property active; horizontal-stretch: 1; vertical-stretch: 1; BarBackground {} HorizontalLayout { padding-right: 18px; padding-left: 18px; padding-top: 11px; padding-bottom: 11px; for tile in model : BarTile { private property display-max: tile.max; horizontal-stretch: 1; title: tile.title; icon: tile.icon; min: tile.min; absolute-min: tile.absolute-min; absolute-max: tile.absolute-max; unit: tile.unit; states [ active when active : { max: display-max; in { animate max { duration: Theme.durations.slow; easing: cubic-bezier(0, 0, 0, 1); } } } ] } } } ================================================ FILE: demos/energy-monitor/ui/widgets/chart_axis.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; export component AxisLabel inherits Text { font-size: Theme.typo.label.size; font-weight: Theme.typo.label.weight; color: Theme.palette.white; horizontal-alignment: center; } export struct AxisValue { value: int, unit: string } export component ChartAxis { in property <[string]> x-model; in property <[int]> y-model; in property y-min; in property y-max; in property y-unit; private property y-zero: root.height * (1 - (0 - y-min) / (y-max - y-min)); VerticalLayout { horizontal-stretch: 1; alignment: end; HorizontalLayout { spacing: 1px; for text in x-model : Rectangle { if(text != "") : AxisLabel { text: text; y: parent.height - self.height - 3px; } } } } HorizontalLayout { alignment: end; Rectangle { background: green; for value in y-model : AxisLabel { y: (value >= 0 ? parent.height * (1 - (value - y-min) / (y-max - y-min)) : y-zero + parent.height * (-1 * value / (y-max - y-min))) - self.height / 2; text: "\{value}\{y-unit}"; } } } } ================================================ FILE: demos/energy-monitor/ui/widgets/chart_pattern.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; export component BarBackground inherits Rectangle { border-radius: 2px; background: Theme.palette.bar-background-gradient; opacity: 0.25; } export component ChartPattern { in property count; HorizontalLayout { spacing: 1px; for _ in count : BarBackground {} } } ================================================ FILE: demos/energy-monitor/ui/widgets/check_box.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; import { Images } from "../images.slint"; export component CheckBox { callback clicked <=> i-touch-area.clicked; in-out property checked; min-height: 18px; width: self.height; states [ checked when checked : { i-container.border-width: 0; i-container.background: Theme.palette.lemon-green; i-check-icon.opacity: 1.0; } ] i-container := Rectangle { border-color: Theme.palette.slint-blue-300; border-width: 2px; border-radius: 2px; animate background { duration: Theme.durations.fast; } i-check-icon := Image { opacity: 0.0; colorize: Theme.palette.pure-black; source: Images.check; animate opacity { duration: Theme.durations.fast; } } } i-touch-area := TouchArea { clicked => { checked = !checked; } } } ================================================ FILE: demos/energy-monitor/ui/widgets/float_button.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; import { StateLayer } from "../components/state_layer.slint"; export component FloatButton { in property icon <=> i-icon.source; in property enabled <=> i-state-layer.enabled; callback clicked <=> i-state-layer.clicked; min_width: 50px; min_height: 50px; i-container := Rectangle { background: Theme.palette.heliotrope-gradient; border-radius: max(self.width, self.height) / 2; i-icon := Image { colorize: Theme.palette.white; } } i-state-layer := StateLayer { border-radius: i-container.border-radius; } } ================================================ FILE: demos/energy-monitor/ui/widgets/group_box.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; export component GroupBox { in property title <=> i-title.text; in property spacing; layout := VerticalLayout { spacing: Theme.spaces.medium; i-title := Text { horizontal-alignment: left; color: Theme.palette.white; font-size: Theme.typo.header.size; font-weight: Theme.typo.header.weight; } i-layout := HorizontalLayout { spacing: root.spacing; @children } } } ================================================ FILE: demos/energy-monitor/ui/widgets/icon_button.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; export component IconButton { in property icon <=> i-icon.source; in property enabled <=> i-touch-area.enabled; callback clicked <=> i-touch-area.clicked; vertical-stretch: 0; horizontal-stretch: 0; min-width: 24px; min-height: 24px; states [ disabled when !root.enabled : { opacity: 0.25; } pressed when i-touch-area.pressed : { i-icon.colorize: Theme.palette.lemon-green; } ] GridLayout { padding: 4px; i-icon := Image { colorize: Theme.palette.slint-blue-300; animate colorize { duration: Theme.durations.medium; } } } i-touch-area := TouchArea {} } ================================================ FILE: demos/energy-monitor/ui/widgets/item.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { StateLayer } from "../components/state_layer.slint"; import { ScrollView } from "scroll_view.slint"; import { Theme } from "../theme.slint"; import { StateLayer } from "../components/state_layer.slint"; export component Item { in property text <=> i-text.text; in property has-separator; callback clicked <=> i-touch-area.clicked; min-height: 40px; i-container := Rectangle { background: Theme.palette.dark-deep-blue; border-radius: 4px; } i-touch-area := TouchArea {} HorizontalLayout { padding-left: Theme.spaces.medium; padding-top: Theme.spaces.medium; padding-bottom: Theme.spaces.medium; padding-right: Theme.spaces.medium; spacing: Theme.spaces.medium; i-text := Text { horizontal-stretch: 1; color: Theme.palette.white; font-size: Theme.typo.description.size; font-weight: Theme.typo.description.weight; vertical-alignment: center; } @children } if (has-separator) : Rectangle { width: parent.width - 2 * Theme.spaces.medium; height: 1px; x: Theme.spaces.medium; y: parent.height - self.height; background: Theme.palette.slint-blue-300; opacity: 0.25; } } export component ItemGroupBox { in property title <=> i-title.text; VerticalLayout { HorizontalLayout { padding: Theme.spaces.medium; i-title := Text { color: Theme.palette.white; font-size: Theme.typo.header.size; font-weight: Theme.typo.header.weight; } } Rectangle { background: Theme.palette.dark-deep-blue; border-radius: 4px; VerticalLayout { @children } } } } ================================================ FILE: demos/energy-monitor/ui/widgets/list_view.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { StateLayer } from "../components/state_layer.slint"; import { ScrollView } from "scroll_view.slint"; import { Theme } from "../theme.slint"; import { Images } from "../images.slint"; import { StateLayer } from "../components/state_layer.slint"; component ListViewItem { in property text <=> i-text.text; in property selected; callback clicked <=> i-state-layer.clicked; min-height: 40px; states [ selected when selected : { i-container.border-color: Theme.palette.lemon-green; i-icon.visible: true; } ] i-container := Rectangle { background: Theme.palette.dark-deep-blue; border-radius: 4px; border-width: 1px; border-color: Theme.palette.slint-blue-400; } HorizontalLayout { padding-left: Theme.spaces.medium; padding-top: Theme.spaces.medium; padding-bottom: Theme.spaces.medium; padding-right: Theme.spaces.medium; spacing: Theme.spaces.medium; accessible-role: list-item; accessible-item-selectable: true; accessible-item-selected: root.selected; i-text := Text { horizontal-stretch: 1; color: Theme.palette.white; font-size: Theme.typo.description.size; font-weight: Theme.typo.description.weight; vertical-alignment: center; } i-icon := Image { horizontal-stretch: 0; visible: false; source: Images.check; colorize: Theme.palette.lemon-green; } } i-state-layer := StateLayer { width: i-container.width; height: i-container.height; border-radius: i-container.border-radius; } } export component ListView { in property <[StandardListViewItem]> model; in-out property selected-index; callback selection-changed(/* index */ int); function select(index: int) { selected-index = index; selection-changed(index); } i-scroll-view := ScrollView { i-blub := VerticalLayout { alignment: start; spacing: Theme.spaces.medium; for item[index] in model : ListViewItem { clicked => { select(index); } private property offset: i-scroll-view.viewport-y + index * (self.height + parent.spacing); text: item.text; selected: index == selected-index; animate opacity { duration: Theme.durations.fast; } } } } } ================================================ FILE: demos/energy-monitor/ui/widgets/menu.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; import { MenuButton } from "menu_button.slint"; import { MenuBackground } from "../components/menu_background.slint"; export component Menu { in-out property menu-button-visible; in property start-y; in property end-y; in property stays-open; in property menu-width <=> i-menu-container.width; in property menu-height <=> i-menu-container.height; out property open; callback opened(); callback closed(); public function hide-button() { menu-button-visible = false; } public function open-menu() { open = true; } public function hide() { menu-button-visible = false; open = false; closed(); } private property container-visibility; states [ open when root.open : { container-visibility: 1.0; i-menu-container.y: end-y; in { animate i-menu-container.y { duration: Theme.durations.medium; } } out { animate container-visibility, i-menu-container.y { duration: Theme.durations.medium; } } } ] if (open) : Rectangle { background: Theme.palette.pure-black; opacity: 0.5; TouchArea { clicked => { hide(); } } } i-menu-container := Rectangle { x: (parent.width - self.width) / 2; y: parent.height - start-y; width: root.width / 3; height: root.height - 75px; i-container := MenuBackground { visible: container-visibility == 1.0; // avoid click-through TouchArea {} @children } if(menu-button-visible || container-visibility == 1.0 || stays-open) : HorizontalLayout { y: -i-menu-button.height / 2; alignment: center; VerticalLayout { alignment: start; i-menu-button := MenuButton { clicked => { if(open) { hide(); } else { open-menu(); } } } } } } } export component MobileMenu { out property open; in property end-y; in property menu-x; out property menu-width: 200px; if (root.open) : Rectangle { background: Theme.palette.pure-black; opacity: 0.5; TouchArea { clicked => { hide(); } } } public function open-menu() { root.open = true; } public function hide() { root.open = false; } Rectangle { clip: true; menu := Rectangle { x: root.menu-x; y: -self.height; width: root.menu-width; height: root.height / 2; visible: visibility > 0.0; private property visibility; MenuBackground { // avoid click-through TouchArea {} @children } states [ open when root.open : { menu.y: end-y; visibility: 1.0; out { animate visibility { duration: Theme.durations.medium; } } } ] animate y { duration: Theme.durations.fast; } } } } ================================================ FILE: demos/energy-monitor/ui/widgets/menu_button.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; import { StateLayer } from "../components/state_layer.slint"; export component MenuButton { callback clicked <=> i-state-layer.clicked; min-width: 100px; min-height: 32px; opacity: root.visible ? 1.0 : 0.0; animate opacity { duration: Theme.durations.fast; } i-container := Rectangle { background: Theme.palette.heliotrope-gradient; border-radius: root.height / 2; Rectangle { width: 55px; height: 5px; background: Theme.palette.white; border-radius: 3px; } } i-state-layer := StateLayer { border-radius: i-container.border-radius; } } ================================================ FILE: demos/energy-monitor/ui/widgets/navigation.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Pagination } from "pagination.slint"; import { FloatButton } from "float_button.slint"; import { Theme } from "../theme.slint"; import { Images } from "../images.slint"; export component Navigation { in-out property current-index <=> i-pagination.selected-index; in property page-count <=> i-pagination.count; callback pagination-clicked <=> i-pagination.clicked; callback clicked; public function hide() { show-navigation = false; } private property show-navigation; function toggle-show-navigation() { show-navigation = !self.show-navigation; } function increment() { current-index = max(current-index - 1, 0); } function decrement() { current-index = min(current-index + 1, page-count - 1); } preferred-width: 100%; preferred-height: 100%; VerticalLayout { padding-top: Theme.spaces.small; Rectangle { clip: true; TouchArea { clicked => { toggle-show-navigation(); root.clicked(); } } @children } i-pagination := Pagination {} } if (show-navigation) : HorizontalLayout { Rectangle { visible: current-index > 0; opacity: self.visible ? 1 : 0; min_width: 129px; background: Theme.palette.dark-left-gradient; animate opacity { duration: Theme.durations.fast; } VerticalLayout { alignment: center; HorizontalLayout { padding-left: Theme.spaces.large; alignment: start; FloatButton { icon: Images.arrow-left; clicked => { root.increment(); } } } } } Rectangle {} Rectangle { visible: current-index < page-count - 1; opacity: self.visible ? 1 : 0; min_width: 129px; background: Theme.palette.dark-right-gradient; animate opacity { duration: Theme.durations.fast; } VerticalLayout { alignment: center; HorizontalLayout { padding-right: Theme.spaces.large; alignment: end; FloatButton { icon: Images.arrow-right; clicked => { root.decrement(); } } } } } } } ================================================ FILE: demos/energy-monitor/ui/widgets/page_scroll_view.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { FloatButton } from "float_button.slint"; import { Images } from "../images.slint"; import { Theme } from "../theme.slint"; component ScrollBar { private property ref-width: self.width - 4px; in-out property page-size; in-out property value; in-out property maximum; in property enabled <=> i-ta.enabled; min-height: 14px; Rectangle { border-width: 1px; border-radius: 6px; border-color: Theme.palette.slint-blue-300; i-indicator := Rectangle { x: 2px + (root.ref-width - i-indicator.width) * (-root.value / root.maximum); height: parent.height - 4px; background: Theme.palette.slint-blue-300; width: max(32px, ref-width * root.page-size / (root.maximum + root.page-size)); border-radius: parent.border-radius - 2px; } } i-ta := TouchArea { property pressed-value; width: parent.width; height: parent.height; pointer-event(event) => { if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) { self.pressed-value = -root.value; } } moved => { if (self.enabled && self.pressed) { root.value = -max(0px, min(root.maximum, self.pressed-value + ( (self.mouse-x - self.pressed-x) * (root.maximum / (root.width - i-indicator.width)) ))); } } } } export component PageContainer { callback clicked <=> i-touch-area.clicked; in property transparent-background: true; out property mouse-x: i-touch-area.mouse-x; in property selected; in property index; in property selected-height; in property selected-width; in property selected-h-offset; min-width: 320px; min-height: 240px; cache-rendering-hint: true; i-rect := Rectangle { height: parent.height; animate opacity { duration: Theme.durations.medium; } i-touch-area := TouchArea {} Rectangle { border-radius: 6px; background: Theme.palette.dark-deep-blue; opacity: root.transparent-background ? 0.5 : 1.0; } Rectangle { y: 10px; height: i-rect.height - 20px; @children } } states [ selected when selected == index : { min-width: selected-width; i-rect.height: selected-height; i-rect.y: (self.height - selected-height) /2 + selected-h-offset; out { animate i-rect.y, i-rect.height { duration: Theme.durations.medium; } } in { animate i-rect.y, i-rect.height { duration: Theme.durations.medium; } } cache-rendering-hint: false; } shrink when selected != -1 : { horizontal-stretch: 0; i-rect.opacity: 0.0; } ] animate min-width { duration: Theme.durations.medium; } } export component PageScrollView { in property page-count: 1; out property selection: -1; out property selected-height : root.height - i-layout.spacing; out property selected-width: i-flickable.width - i-layout.spacing * 2; out property selected-h-offset: (root.height - i-flick-container.height)/2; private property item-width: i-flickable.viewport-width / page-count; private property saved-item-width: item-width; public function toggle-selection(idx: int, x: length) { if (selection >= 0) { i-flickable.viewport-x = min(0px, max(i-flickable.width - saved-item-width * page-count , -x + i-flickable.width / 2 - saved-item-width / 2)); selection = -1; } else { saved-item-width = item-width; selection = idx; i-flickable.viewport-x = -x + i-layout.spacing; } } function scroll-left() { i-flickable.viewport-x = min(i-flickable.viewport-x + item-width, 0); } function scroll-right() { i-flickable.viewport-x = max(i-flickable.viewport-x - item-width, i-flickable.width - i-flickable.viewport-width); } VerticalLayout { i-flick-container := Rectangle { z: 1; i-flickable := Flickable { y: 0; height: selection == -1 ? parent.height : selected-height ; animate viewport-x { duration: Theme.durations.medium; } viewport-height: i-flickable.height; i-layout := HorizontalLayout { y: (i-flick-container.height - self.height)/2; height: i-layout.preferred-height; width: i-layout.preferred-width; padding: 20px; spacing: 20px; @children } interactive: selection == -1; } } HorizontalLayout { vertical-stretch: 0; spacing: 25px; padding-left: 25px; padding-right: 25px; opacity: selection != -1 ? 0 : 1; animate opacity { duration: Theme.durations.medium; } FloatButton { visible: i-flickable.viewport-x < 0; horizontal-stretch: 0; icon: Images.arrow-left; enabled: selection == -1; clicked => { scroll-left(); } } VerticalLayout { alignment: center; horizontal-stretch: 1; ScrollBar { maximum: i-flickable.viewport-width - i-flickable.width; page-size: i-flickable.width; value <=> i-flickable.viewport-x; enabled: selection == -1; } } FloatButton { visible: i-flickable.viewport-x > i-flickable.width - i-flickable.viewport-width; horizontal-stretch: 0; icon: Images.arrow-right; enabled: selection == -1; clicked => { scroll-right(); } } } } } ================================================ FILE: demos/energy-monitor/ui/widgets/pagination.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; component Bubble inherits Rectangle { in property selected; states [ selected when selected : { background: Theme.palette.white; } ] min_width: 5px; min_height: 5px; border-radius: max(self.width, self.height) / 2; background: Theme.palette.slint-blue; i-touch-area := TouchArea {} } export component Pagination { in property count; in property selected_index; callback clicked <=> i-touch-area.clicked; min-height: 26px; vertical-stretch: 0; VerticalLayout { alignment: center; HorizontalLayout { alignment: center; spacing: Theme.spaces.small; for index in count : Bubble { selected: index == selected-index; } } } i-touch-area := TouchArea {} } ================================================ FILE: demos/energy-monitor/ui/widgets/radio_button.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; export component RadioButton { in property checked; callback clicked <=> i-touch-area.clicked; states [ checked when checked : { i-container.border-color: Theme.palette.lemon-green; i-indicator.background: Theme.palette.lemon-green; } ] min-height: 18px; width: self.height; i-container := Rectangle { border-color: Theme.palette.slint-blue-300; border-width: 2px; border-radius: self.height / 2; animate border-color { duration: Theme.durations.fast; } i-indicator := Rectangle { height: parent.height - 4 * parent.border-width; width: self.height; border-radius: self.height / 2; animate background { duration: Theme.durations.fast; } } } i-touch-area := TouchArea {} } ================================================ FILE: demos/energy-monitor/ui/widgets/scroll_view.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; import { StateLayer } from "../components/state_layer.slint"; component ScrollBar inherits Rectangle { in-out property horizontal; in-out property maximum; in-out property page-size; // this is always negative and bigger than -maximum in-out property value; background: Theme.palette.pure-black; border-width: 1px; i-handle := Rectangle { width: !root.horizontal ? parent.width : root.maximum <= 0phx ? 0phx : parent.width * (root.page-size / (root.maximum + root.page-size)); height: root.horizontal ? parent.height : root.maximum <= 0phx ? 0phx : parent.height * (root.page-size / (root.maximum + root.page-size)); border-radius: 3px; background: Theme.palette.slint-blue-400; x: !root.horizontal ? 0phx : (root.width - i-handle.width) * (-root.value / root.maximum); y: root.horizontal ? 0phx : (root.height - i-handle.height) * (-root.value / root.maximum); } i-touch-area := StateLayer { private property pressed-value; pointer-event(event) => { if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) { self.pressed-value = -root.value; } } moved => { if (self.enabled && self.pressed) { root.value = -max(0px, min(root.maximum, self.pressed-value + ( root.horizontal ? (i-touch-area.mouse-x - i-touch-area.pressed-x) * (root.maximum / (root.width - i-handle.width)) : (i-touch-area.mouse-y - i-touch-area.pressed-y) * (root.maximum / (root.height - i-handle.height)) ))); } } width: parent.width; height: parent.height; } } export component ScrollView { in property enabled: true; out property visible-width <=> i-flickable.width; out property visible-height <=> i-flickable.height; // FIXME: remove. This property is currently set by the ListView and is used by the native style to draw the scrollbar differently when it has focus in-out property has-focus; in-out property viewport-width <=> i-flickable.viewport-width; in-out property viewport-height <=> i-flickable.viewport-height; in-out property viewport-x <=> i-flickable.viewport-x; in-out property viewport-y <=> i-flickable.viewport-y; min-height: 50px; min-width: 50px; horizontal-stretch: 1; vertical-stretch: 1; i-flickable := Flickable { x: 2px; y: 2px; viewport-y <=> i-ver-bar.value; viewport-x <=> i-hor-bar.value; width: parent.width - i-ver-bar.width - Theme.spaces.medium; height: parent.height - i-hor-bar.height - Theme.spaces.medium; @children } i-ver-bar := ScrollBar { visible: viewport-height > visible-height; width: 5px; x: i-flickable.width + i-flickable.x + Theme.spaces.medium; y: i-flickable.y; height: i-flickable.height; horizontal: false; maximum: i-flickable.viewport-height - i-flickable.height; page-size: i-flickable.height; } i-hor-bar := ScrollBar { visible: viewport-width > visible-width; height: 5px; y: i-flickable.height + i-flickable.y; x: i-flickable.x; width: i-flickable.width; horizontal: true; maximum: i-flickable.viewport-width - i-flickable.width; page-size: i-flickable.width; } Rectangle { visible: viewport-y < 0; x: 2px; y: 2px; width: i-flickable.width; height: 38px; background: @linear-gradient(180deg, #D9D9D9 0%, #D9D9D900 100%); opacity: 0.1; } Rectangle { visible: viewport-height > visible-height && viewport-y > visible-height - viewport-height; x: 2px; y: i-flickable.y + i-flickable.height - self.height; width: i-flickable.width; height: 38px; background: @linear-gradient(180deg, #D9D9D900 0%, #D9D9D9 100%); opacity: 0.1; } } ================================================ FILE: demos/energy-monitor/ui/widgets/switch.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; export component Switch { in-out property checked; callback changed(/* checked */ bool); callback clicked <=> i-touch-area.clicked; function toggle-checked() { checked = !checked; changed(checked); } min-height: 18px; width: 2 * self.height; states [ checked when checked : { i-container.background: Theme.palette.lemon-green-op10; i-container.border-color: Theme.palette.lemon-green; i-indicator.background: Theme.palette.lemon-green; i-indicator.x: i-container.width - i-indicator.width - 2 * i-container.border-width; } ] i-container := Rectangle { border-color: Theme.palette.slint-blue-300; border-width: 2px; border-radius: self.height / 2; animate border-color { duration: Theme.durations.fast; } animate background { duration: Theme.durations.fast; } i-indicator := Rectangle { x: 2 * parent.border-width; height: parent.height - 4 * parent.border-width; width: self.height; border-radius: self.height / 2; background: Theme.palette.slint-blue-300; animate background { duration: Theme.durations.fast; } animate x { duration: Theme.durations.fast; } } } i-touch-area := TouchArea { clicked => { toggle-checked(); } } } ================================================ FILE: demos/energy-monitor/ui/widgets/tab_widget.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; component Tab { in property title; in property selected; in property icon; callback clicked <=> i-touch-area.clicked; private property angle: Math.atan(self.height / 2 / self.width); private property has-icon: root.icon.width > 0 && root.icon.height > 0; preferred-width: 200px; states [ selected when selected : { i-initiator.width: root.width; i-title.color: Theme.palette.lemon-green; i-gradient.opacity: 1.0; } ] GridLayout { padding: root.has-icon ? 7px : 25px; VerticalLayout { spacing: root.has-icon ? 7px : 0; if (root.has-icon) : Image { x: (parent.width - self.width) / 2; width: 24px; height: 24px; source: root.icon; colorize: i-title.color; } i-title := Text { text: title; horizontal-alignment: center; vertical-alignment: center; font-size: root.has-icon ? Theme.typo.description-light.size : Theme.typo.header-item-light.size; font-weight: root.has-icon ? Theme.typo.header-item-light.weight : Theme.typo.description-light.weight; color: Theme.palette.white; animate color { duration: Theme.durations.medium; } } } } i-gradient := Rectangle { opacity: 0; visible: !root.has-icon; Rectangle { y: 0; width: 50%; height: 50%; x: 0; background: @linear-gradient(angle, rgba(222, 251, 58, 0) , rgba(222, 251, 58, 0.2)); } Rectangle { y: 0; width: 50%; height: 50%; x: self.width; background: @linear-gradient(-angle, rgba(222, 251, 58, 0) , rgba(222, 251, 58, 0.2)); } animate opacity { duration: Theme.durations.medium; } } i-initiator := Rectangle { width: 0; y: 0; height: 1px; background: Theme.palette.lemon-green; visible: selected; animate width { duration: Theme.durations.medium; } } i-touch-area := TouchArea {} } export struct TabItem { text: string, icon: image } export component TabWidget { in property <[TabItem]> tabs; in-out property selected-tab; vertical-stretch: 1; VerticalLayout { Rectangle { vertical-stretch: 1; @children } Rectangle { vertical-stretch: 0; background: Theme.palette.tab-gradient; HorizontalLayout { alignment: center; min-height: 80px; vertical-stretch: 0; for tab[index] in tabs : Tab { title: tab.text; icon: tab.icon; selected: index == selected-tab; clicked => { selected-tab = index; } } } } } } ================================================ FILE: demos/energy-monitor/ui/widgets/tile.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; import { BarBackground } from "bar_chart.slint"; export component Tile { in property icon <=> i-icon.source; in property value <=> i-value.text; in property text <=> i-text.text; in property sub-text <=> i-sub-text.text; horizontal-stretch: 0; vertical-stretch: 1; BarBackground {} VerticalLayout { padding-left: 20px; padding-right: 20px; padding-top: 10px; padding-bottom: 10px; spacing: 11px; alignment: center; i-icon := Image { x: (parent.width - self.width) / 2; min-width: 52px; height: 43px; colorize: Theme.palette.lemon-green; image-fit: contain; } i-value := Text { horizontal-alignment: center; color: Theme.palette.white; font-size: Theme.typo.value-big.size; font-weight: Theme.typo.value-big.weight; } VerticalLayout { i-text := Text { horizontal-alignment: center; color: Theme.palette.white; font-size: Theme.typo.description.size; font-weight: Theme.typo.description.weight; } i-sub-text := Text { horizontal-alignment: center; color: Theme.palette.lemon-green; font-size: Theme.typo.description-light.size; font-weight: Theme.typo.description-light.weight; } } } } ================================================ FILE: demos/energy-monitor/ui/widgets/value_display.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { Theme } from "../theme.slint"; component ValueDelegate { in property active; in property title <=> i-title.text; in property unit <=> i-unit.text; in property value; in property alternative-colors; private property display-value; states [ active when active : { display-value: value; in { animate display-value { duration: Theme.durations.slow; } } } ] HorizontalLayout { spacing: 15px; Rectangle { min_width: 1px; background: alternative-colors ? Theme.palette.slint-blue-300 : Theme.palette.lemon-green; horizontal-stretch: 0; } VerticalLayout { alignment: center; horizontal-stretch: 1; i-title := Text { color: alternative-colors ? Theme.palette.slint-blue-300 : Theme.palette.lemon-green; font-size: Theme.typo.label.size; font-weight: Theme.typo.label.weight; } HorizontalLayout { alignment: start; spacing: 5px; Text { color: Theme.palette.white; text: round(display-value * 100) / 100; font-size: Theme.typo.value.size; font-weight: Theme.typo.value.weight; vertical-alignment: center; } i-unit := Text { y: Theme.spaces.small; vertical-alignment: center; color: alternative-colors ? Theme.palette.slint-blue-300 : Theme.palette.lemon-green; font-size: Theme.typo.label-light.size; font-weight: Theme.typo.label-light.weight; } } } } } export struct Value { title: string, value: float, unit: string, } export component ValueDisplay { in property alternative-colors; in property <[Value]> model; in property active; in property transparent-background; in property vertical; min-height: 70px; i-container := Rectangle { visible: !transparent-background; width: 100%; height: 100%; border-radius: 4px; background: alternative-colors ? Theme.palette.slint-blue-gradient : Theme.palette.lemon-green-gradient; } if(model.length > 0 && !vertical) : HorizontalLayout { x: 15px; width: parent.width - 30px; height: 100%; padding-top: 12px; padding-bottom: 12px; for value in root.model : ValueDelegate { width: parent.width / model.length; horizontal-stretch: 1; alternative-colors: root.alternative-colors; title: value.title; value: value.value; unit: value.unit; active: root.active; } } if(model.length > 0 && vertical) : VerticalLayout { for value in root.model : ValueDelegate { vertical-stretch: 1; alternative-colors: root.alternative-colors; title: value.title; value: value.value; unit: value.unit; active: root.active; } } } ================================================ FILE: demos/energy-monitor/ui/widgets/widgets.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { BalanceChart } from "balance_chart.slint"; import { BarChart } from "bar_chart.slint"; import { FloatButton } from "float_button.slint"; import { GroupBox } from "group_box.slint"; import { MenuButton } from "menu_button.slint"; import { Menu, MobileMenu } from "menu.slint"; import { Navigation } from "navigation.slint"; import { Pagination } from "pagination.slint"; import { ValueDisplay, Value } from "value_display.slint"; import { Tile } from "tile.slint"; import { BarTiles, BarTileModel } from "bar_tiles.slint"; import { TabWidget, TabItem } from "tab_widget.slint"; import { IconButton } from "icon_button.slint"; import { ScrollView } from "scroll_view.slint"; import { ListView } from "list_view.slint"; import { Item, ItemGroupBox } from "item.slint"; import { CheckBox } from "check_box.slint"; import { RadioButton } from "radio_button.slint"; import { Switch } from "switch.slint"; import { PageScrollView, PageContainer } from "page_scroll_view.slint"; export { BalanceChart } export { BarChart } export { FloatButton } export { GroupBox } export { Navigation } export { Pagination } export { MenuButton } export { Menu, MobileMenu } export { ValueDisplay, Value } export { Tile } export { BarTiles, BarTileModel } export { TabWidget, TabItem } export { IconButton } export { ScrollView } export { ListView } export { Item, ItemGroupBox } export { CheckBox } export { RadioButton } export { Switch } export { PageScrollView, PageContainer } ================================================ FILE: demos/home-automation/README.md ================================================ ### `Home Automation Demo` A fictional Home Automation User Interface. | `.slint` Design | Rust Source | C++ Source | Node Source | Online wasm Preview | Open in SlintPad | | --- | --- | --- | --- | --- | --- | | [`ui.slint`](./ui/demo.slint) | [`main.rs`](./rust/main.rs) | | [`main.js`](./node/main.js) | [Online simulation](https://slint.dev/snapshots/master/demos/home-automation/) | [Preview in Online Code Editor](https://slint.dev/snapshots/master/editor?load_url=https://raw.githubusercontent.com/slint-ui/slint/master/demos/home-automation/ui/demo.slint) | ![Screenshot of the Home Automation Demo](https://github.com/user-attachments/assets/607e07a5-2e79-4045-9fe4-3da2493ba187 "Home Automation Demo") ## Building and running on iOS This demo can be cross-compiled to iOS to run on iPhones, iPads, and the respective simulators. ### Prerequisites - A computer running macOS. - An up-to-date installation of [Xcode](https://developer.apple.com/xcode/). - [Xcodegen](https://github.com/yonaskolb/XcodeGen?tab=readme-ov-file#installing) - [Rust](https://rustup.rs). Add the target and simulator toolchains using `rustup target add aarch64-apple-ios` and `rustup target add aarch64-apple-ios-sim` ### Building 1. Run `xcodegen -s ios-project.yml` to generate an XCode project file (`.xcodeproj`). 2. Open XCode and open the generated `.xcodeproj` in it. 3. Run, deploy, and debug the demo from within Xcode. ================================================ FILE: demos/home-automation/node/biome.json ================================================ { "root": true, "extends": ["../../../biome.json"], "files": { "includes": [ "**", "!**/.vscode/**", "!**/webviews/**", "!**/.vscode/**", "!**/build/**", "!**/out/**" ] }, "linter": { "rules": { "style": { "noParameterAssign": "error", "useAsConstAssertion": "error", "useDefaultParameterLast": "error", "useEnumInitializers": "error", "useSelfClosingElements": "error", "useSingleVarDeclarator": "error", "noUnusedTemplateLiteral": "error", "useNumberNamespace": "error", "noInferrableTypes": "error", "noUselessElse": "error" } } } } ================================================ FILE: demos/home-automation/node/main.js ================================================ #!/usr/bin/env node // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import * as slint from "slint-ui"; const ui = slint.loadFile("../ui/demo.slint"); const window = new ui.AppWindow(); const api = window.Api; const date = api.current_date; const time = api.current_time; const timer = setInterval(() => { const now = new Date(); date.year = now.getFullYear(); date.month = now.getMonth() + 1; date.day = now.getDate(); api.current_date = date; time.hour = now.getHours(); time.minute = now.getMinutes(); time.second = now.getSeconds(); api.current_time = time; }, 1000); await window.run(); clearInterval(timer); ================================================ FILE: demos/home-automation/node/package.json ================================================ { "name": "home_automation", "version": "1.16.0", "main": "main.js", "type": "module", "dependencies": { "@biomejs/biome": "catalog:", "slint-ui": "workspace:*" }, "scripts": { "start": "node .", "prestart": "cd ../../../api/node/ && pnpm run build && pnpm compile", "check": "biome check", "format": "biome format", "format:fix": "biome format --write", "lint": "biome lint", "lint:fix": "biome lint --fix" } } ================================================ FILE: demos/home-automation/rust/Cargo.toml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: MIT [package] name = "home-automation" version = "1.16.0" authors = ["Slint Developers "] edition.workspace = true publish = false license = "MIT" [[bin]] path = "main.rs" name = "home-automation" [lib] crate-type = ["lib", "cdylib"] path = "lib.rs" name = "home_automation_lib" [dependencies] slint = { path = "../../../api/rs/slint", features = ["backend-android-activity-06"] } chrono = "0.4" [features] sw-renderer = [] [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = { version = "0.2" } web-sys = { version = "0.3", features = ["console"] } console_error_panic_hook = "0.1.5" ================================================ FILE: demos/home-automation/rust/index.html ================================================
Loading...
================================================ FILE: demos/home-automation/rust/ios-project.yml ================================================ # Copyright © SixtyFPS GmbH # SPDX-License-Identifier: MIT name: Home Automation options: bundleIdPrefix: dev.slint.demos settings: ENABLE_USER_SCRIPT_SANDBOXING: NO targets: Home Automation: type: application platform: iOS deploymentTarget: "12.0" info: path: Info.plist properties: UILaunchScreen: - ImageRespectSafeAreaInsets: false sources: [] postCompileScripts: - script: | ../../../scripts/build_for_ios_with_cargo.bash home-automation outputFileLists: $TARGET_BUILD_DIR/$EXECUTABLE_PATH ================================================ FILE: demos/home-automation/rust/lib.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT use chrono::{Datelike, Local, Timelike}; use slint::{Timer, TimerMode}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; #[cfg(feature = "sw-renderer")] slint::slint! { export { Api, AppWindow } from "../ui/demo-sw-renderer.slint"; } #[cfg(not(feature = "sw-renderer"))] slint::slint! { export { Api, AppWindow } from "../ui/demo.slint"; } #[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))] pub fn main() { // This provides better error messages in debug mode. // It's disabled in release mode so it doesn't bloat up the file size. #[cfg(all(debug_assertions, target_arch = "wasm32"))] console_error_panic_hook::set_once(); let app = AppWindow::new().expect("AppWindow::new() failed"); let app_weak = app.as_weak(); let timer = Timer::default(); timer.start(TimerMode::Repeated, std::time::Duration::from_millis(1000), move || { if let Some(app) = app_weak.upgrade() { let api = app.global::(); let now = Local::now(); let date = Date { year: now.year(), month: now.month() as i32, day: now.day() as i32 }; api.set_current_date(date); let time = Time { hour: now.hour() as i32, minute: now.minute() as i32, second: now.second() as i32, }; api.set_current_time(time); } }); app.run().expect("AppWindow::run() failed"); } #[cfg(target_os = "android")] #[unsafe(no_mangle)] fn android_main(android_app: slint::android::AndroidApp) { slint::android::init(android_app).unwrap(); main(); } ================================================ FILE: demos/home-automation/rust/main.rs ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT fn main() { home_automation_lib::main(); } ================================================ FILE: demos/home-automation/ui/api.slint ================================================ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { AppState, Orientation } from "appState.slint"; import { Date, Time } from "std-widgets.slint"; export enum WeatherCondition { sunny, sunny-rainy, sunny-cloudy, cloudy, rainy, } export struct WeatherData { condition: WeatherCondition, day: string, temperature: float, } export global Api { in property indoor-temperature: 22.0; in property outdoor-temperature: 24.0; in property price-of-electricity: 13.0; in property current-electricity-use: 152.9; out property orientation <=> AppState.orientation; in-out property graphics-accelerator-available <=> AppState.graphics-accelerator-available; in-out property current-date: { year: 2025, month: 3, day: 18, }; in-out property