├── .clang-format ├── .github └── workflows │ └── build.yaml ├── .gitignore ├── .vscode ├── c_cpp_properties.json ├── launch.json ├── settings.json └── tasks.json ├── .zed └── settings.json ├── ARCHITECTURE.md ├── LICENSE ├── README.md ├── assets ├── 04_paper-C-grain.webp ├── Grenze-Light.ttf ├── Grenze-Regular.ttf ├── Grenze-SemiBold.ttf ├── Grenze-Thin.ttf ├── NotoSans[wght].ttf ├── SFX │ ├── button_down.wav │ ├── button_up.wav │ ├── cable_click.wav │ ├── cable_end.wav │ ├── cable_loop.wav │ ├── cable_start.wav │ ├── canvas_drop.wav │ ├── canvas_pick.wav │ ├── key_down.wav │ ├── key_up.wav │ ├── macro_start.wav │ ├── macro_stop.wav │ ├── mouse_down.wav │ ├── mouse_up.wav │ ├── next.wav │ ├── toolbar_pick.wav │ ├── toolbar_select_01.wav │ ├── toolbar_select_02.wav │ ├── toolbar_select_03.wav │ └── trash.wav ├── anchor_warp_rt.sksl ├── assembler_stars_rt.sksl ├── automat_state.json ├── bubble_menu_rt.sksl ├── cable-weave-color.webp ├── cable-weave-normal.webp ├── cable_rt_sksl ├── card-reverse.webp ├── carpet.webp ├── connector_case_rt.sksl ├── connector_insert_rt.sksl ├── connector_rubber_rt.sksl ├── eng.traineddata ├── flag-cf.webp ├── flag-of.webp ├── flag.webp ├── flip-flop-color.webp ├── glitch_rt.sksl ├── heavy_data.ttf ├── helsinki.ttf ├── leaf.webp ├── macro-recorder-front-color.webp ├── mouse-base.webp ├── mouse-lmb-mask.webp ├── mouse-rmb-mask.webp ├── pointing-hand-color.webp ├── pressing-hand-color.webp ├── reg-ax.webp ├── reg-bx.webp ├── reg-cx.webp ├── reg-di.webp ├── reg-dx.webp ├── reg-read.webp ├── reg-si.webp ├── reg-write.webp ├── rose-0.webp ├── rose-1.webp ├── rose-2.webp ├── rose-3.webp ├── rose-4.webp ├── rose-5.webp ├── rose-6.webp ├── rosewood-color.webp ├── sharingan-color.svg ├── skybox.webp ├── slkscr.ttf ├── stalk.png ├── tray.webp └── venus.webp ├── docs ├── 411172__silverillusionist__liquid-drink.mp3 ├── Audiowide.woff2 ├── CNAME ├── Caveat.woff2 ├── HomemadeApple.woff2 ├── PixelifySans.woff2 ├── arches.png ├── arrow.webp ├── assets │ ├── 2024-01-24 09-54-03.webm │ ├── Makefile │ ├── favicon-1024x1024.png │ ├── favicon-512x512.png │ ├── output.webm │ └── 電磁祭囃子 in NEO TOKYO 🏮 [A0VYsiMtrNE].m4a ├── bg.js ├── cable_playground.html ├── cable_playground.js ├── cup-down.flac ├── cup-up.flac ├── cups-1.5x.webp ├── cups-1x.webp ├── cups-2x.webp ├── design │ ├── Assembler.md │ ├── Background Events.md │ ├── Deleting Objects.md │ ├── Multithreading.md │ ├── Release Process.md │ ├── Serialization.md │ ├── Signal-Based Machine Code Execution.md │ ├── Single binary.md │ └── customizing-automat.webp ├── eject.flac ├── favicon-16x16.png ├── favicon-32x32.png ├── floppy-blue-2x.webp ├── floppy-down.flac ├── floppy-insert.flac ├── floppy-up.flac ├── index.html ├── napkin-down.flac ├── napkin-up.flac ├── napkin.webp ├── noise.webp ├── notebook-bg-2x.webp ├── notebook-cover-2x.webp ├── notebook-down.flac ├── notebook-flip.flac ├── notebook-up.flac ├── page-1-2x.webp ├── page-2-2x.webp ├── page-3-2x.webp ├── page-4-2x.webp ├── player-down.flac ├── player-up.flac ├── privacy_policy.html ├── vhs-1x.webp ├── vhs-2x.webp ├── vhs-3x.webp ├── vhs-down.flac ├── vhs-insert.flac ├── vhs-up.flac └── video_player-2x.webp ├── pyrightconfig.json ├── run.bat ├── run.py ├── run_py ├── __main__.py ├── args.py ├── autotools.py ├── build.py ├── cc_embed.py ├── clang.py ├── cmake.py ├── debian_deps.py ├── extension_helper.py ├── fs_utils.py ├── git.py ├── graph_printer.py ├── inotify.py ├── make.py ├── monitor_new_pids.py ├── ninja.py ├── src.py ├── windows.py └── windows_deps.py ├── source_images ├── Arrow │ └── Direction.xcf └── Mouse │ ├── 01 Civitai Base.jpeg │ ├── 02 Slight Variation Fooocus.png │ ├── 03 Inpaint Cable Button.png │ ├── 04 Inpaint Rest.png │ ├── 05 Clipdrop Transparency.png │ └── 06 - GIMP.xcf ├── src ├── action.hh ├── algebra.cc ├── algebra.hh ├── animation.cc ├── animation.hh ├── arcline.cc ├── arcline.hh ├── arcline_test.cc ├── argument.cc ├── argument.hh ├── arr.hh ├── audio.cc ├── audio.hh ├── automat.cc ├── automat.hh ├── backtrace.cc ├── backtrace.hh ├── base.cc ├── base.hh ├── blockingconcurrentqueue.hh ├── cavalier_contours.py ├── channel.hh ├── channel_test.cc ├── color.cc ├── color.hh ├── complex_test.cc ├── concurrentqueue.hh ├── connection.cc ├── connection.hh ├── connector_optical.cc ├── connector_optical.hh ├── control_flow.hh ├── dec64.cc ├── dec64.hh ├── dec64_test.cc ├── demo_static_vulkan_crash.cc ├── deserializer.cc ├── deserializer.hh ├── dev_actions.py ├── drag_action.cc ├── drag_action.hh ├── drawable.cc ├── drawable.hh ├── drawable_rtti.cc ├── drawable_rtti.hh ├── drawing.cc ├── drawing.hh ├── embedded.hh ├── embedded.py ├── error.cc ├── error.hh ├── fasttrigo.py ├── fn.hh ├── font.cc ├── font.hh ├── format.cc ├── format.hh ├── global_resources.cc ├── global_resources.hh ├── gtest.cc ├── gtest.hh ├── gtest.py ├── gui_button.cc ├── gui_button.hh ├── gui_connection_widget.cc ├── gui_connection_widget.hh ├── gui_constants.hh ├── gui_shape_widget.cc ├── gui_shape_widget.hh ├── gui_small_buffer_widget.cc ├── gui_small_buffer_widget.hh ├── gui_text.cc ├── gui_text.hh ├── hex.cc ├── hex.hh ├── hid.cc ├── hid.hh ├── hidapi.c ├── hidapi.h ├── hidapi_cfgmgr32.h ├── hidapi_descriptor_reconstruct.c ├── hidapi_descriptor_reconstruct.h ├── hidapi_hidclass.h ├── hidapi_hidpi.h ├── hidapi_hidsdi.h ├── hidapi_winapi.h ├── int.hh ├── key.cc ├── key.hh ├── key_button.cc ├── key_button.hh ├── keyboard.cc ├── keyboard.hh ├── knob.cc ├── knob.hh ├── leptonica.py ├── library.cc ├── library.hh ├── library_alert.cc ├── library_alert.hh ├── library_assembler.cc ├── library_assembler.hh ├── library_flip_flop.cc ├── library_flip_flop.hh ├── library_hotkey.cc ├── library_hotkey.hh ├── library_increment.cc ├── library_increment.hh ├── library_instruction.cc ├── library_instruction.hh ├── library_instruction_library.cc ├── library_instruction_library.hh ├── library_key_presser.cc ├── library_key_presser.hh ├── library_macro_recorder.cc ├── library_macro_recorder.hh ├── library_mouse_click.cc ├── library_mouse_click.hh ├── library_number.cc ├── library_number.hh ├── library_test.cc ├── library_timeline.cc ├── library_timeline.hh ├── library_timer.cc ├── library_timer.hh ├── library_toolbar.cc ├── library_toolbar.hh ├── library_window.cc ├── library_window.hh ├── lightweightsemaphore.hh ├── linux_hidapi.c ├── list_test.cc ├── llvm.py ├── llvm_asm.cc ├── llvm_asm.hh ├── llvm_asm.py ├── llvm_asm_test.cc ├── llvm_test.cc ├── loading_animation.cc ├── loading_animation.hh ├── location.cc ├── location.hh ├── log.cc ├── log.hh ├── log_skia.cc ├── log_skia.hh ├── machine_code.cc ├── machine_code.hh ├── manifest.rc ├── manifest.xml ├── math.cc ├── math.hh ├── math_constants.hh ├── menu.cc ├── menu.hh ├── number_text_field.cc ├── number_text_field.hh ├── object.cc ├── object.hh ├── on_off.hh ├── optional.hh ├── path.cc ├── path.hh ├── persistence.cc ├── persistence.hh ├── pipewire.py ├── pointer.cc ├── pointer.hh ├── prototypes.cc ├── prototypes.hh ├── ptr.hh ├── ptr_test.cc ├── random.cc ├── random.hh ├── rapidjson.py ├── release.py ├── release.service ├── release.timer ├── renderer.cc ├── renderer.hh ├── renderer_test.cc ├── root_widget.cc ├── root_widget.hh ├── run_button.cc ├── run_button.hh ├── seven_gui_test.cc ├── shared_or_weak.hh ├── sincos.cc ├── sincos.hh ├── sincos_test.cc ├── skia.py ├── span.hh ├── status.cc ├── status.hh ├── str.cc ├── str.hh ├── string_multimap.hh ├── svg.cc ├── svg.hh ├── tasks.cc ├── tasks.hh ├── template.hh ├── term.hh ├── tesseract.py ├── test_base.hh ├── text_field.cc ├── text_field.hh ├── textures.cc ├── textures.hh ├── theme_xp.cc ├── theme_xp.hh ├── thread_name.cc ├── thread_name.hh ├── threading_prototype.cc ├── time.cc ├── time.hh ├── timer_thread.cc ├── timer_thread.hh ├── touchpad.cc ├── touchpad.hh ├── tree_algorithms.hh ├── treemath.cc ├── treemath.hh ├── units.hh ├── vec.hh ├── virtual_fs.cc ├── virtual_fs.hh ├── vk.cc ├── vk.hh ├── vk_bootstrap.py ├── vulkan.py ├── wave1d.cc ├── wave1d.hh ├── widget.cc ├── widget.hh ├── win32.cc ├── win32.hh ├── win32_window.cc ├── win32_window.hh ├── win_hidapi.c ├── win_key.cc ├── win_key.hh ├── window.cc ├── window.hh ├── x11.cc ├── x11.hh ├── xcb.cc ├── xcb.hh ├── xcb.py ├── xcb_window.cc ├── xcb_window.hh └── xkbcommon.py ├── tests ├── event_bench.json ├── event_bench.py ├── event_bench_log.json ├── perf_bench.json └── perf_bench.py └── third_party ├── FastTrigo ├── LICENSE ├── README.md ├── fasttrigo.cpp └── fasttrigo.h └── cavalier_contours ├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── c_api_include └── cavaliercontours.h ├── examples ├── CMakeLists.txt ├── basicpolylinefunctions.cpp ├── polylinecombine.cpp ├── polylineoffset.cpp ├── polylineoffsetislands.cpp └── windingnumber.cpp ├── include └── cavc │ ├── internal │ ├── common.hpp │ └── diagnostics.hpp │ ├── intrcircle2circle2.hpp │ ├── intrlineseg2circle2.hpp │ ├── intrlineseg2lineseg2.hpp │ ├── mathutils.hpp │ ├── plinesegment.hpp │ ├── polyline.hpp │ ├── polylinecombine.hpp │ ├── polylineintersects.hpp │ ├── polylineoffset.hpp │ ├── polylineoffsetislands.hpp │ ├── staticspatialindex.hpp │ ├── vector.hpp │ └── vector2.hpp ├── src └── cavaliercontours.cpp └── tests ├── CMakeLists.txt ├── benchmarks ├── CMakeLists.txt ├── areabenchmarks.cpp ├── benchmarkprofiles.h ├── clipper.cmake ├── clipperbenchmarks.cpp ├── combinebenchmarks.cpp ├── extentsbenchmarks.cpp ├── offsetbenchmarks.cpp ├── pathlengthbenchmarks.cpp ├── spatialindexbenchmarks.cpp └── windingnumberbenchmarks.cpp ├── polylinefactory ├── CMakeLists.txt ├── include │ └── polylinefactory.hpp └── src │ └── polylinefactory.cpp └── tests ├── CMakeLists.txt ├── TEST_cavc_combine_plines.cpp ├── TEST_cavc_parallel_offset.cpp ├── TEST_cavc_pline.cpp ├── TEST_cavc_pline_function.cpp ├── TEST_sample.cpp ├── TEST_staticspatialindex.cpp ├── c_api_test_helpers.hpp └── testhelpers.hpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | ColumnLimit: 100 4 | --- 5 | Language: Cpp 6 | # Force pointers to the type for C++. 7 | DerivePointerAlignment: false 8 | PointerAlignment: Left 9 | --- 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | __pycache__ 35 | .cache 36 | *.pyc 37 | 38 | *.log 39 | *.pcap 40 | *.pdb 41 | 42 | *~ 43 | 44 | build 45 | third_party/* 46 | !third_party/FastTrigo 47 | !third_party/cavalier_contours 48 | 49 | tmp 50 | 51 | .gdb_history 52 | 53 | tests/dhclient 54 | tests/dhclient.pid 55 | 56 | source_images 57 | 58 | compile_commands.json 59 | 60 | www/assets 61 | www/builds 62 | 63 | perf.data 64 | perf.data.old 65 | palette.map 66 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "compilerPath": "/usr/lib/llvm/18/bin/clang++", 10 | "cStandard": "gnu23", 11 | "cppStandard": "gnu++23", 12 | "compileCommands": "${workspaceFolder}/compile_commands.json" 13 | } 14 | ], 15 | "version": 4 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.analysis.extraPaths": [ 3 | "./run_py" 4 | ], 5 | "cmake.configureOnOpen": false, 6 | "editor.formatOnSave": true 7 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "link current file", 8 | "type": "shell", 9 | "command": "python run.py 'link ${fileBasenameNoExtension}'", 10 | "presentation": { 11 | "reveal": "silent" 12 | } 13 | }, 14 | { 15 | "label": "link current file debug", 16 | "type": "shell", 17 | "command": "python run.py 'link debug_${fileBasenameNoExtension}'", 18 | "presentation": { 19 | "reveal": "silent" 20 | } 21 | }, 22 | { 23 | "label": "link debug_automat", 24 | "type": "shell", 25 | "command": "python run.py 'link debug_automat'", 26 | "presentation": { 27 | "reveal": "silent", 28 | "close": true 29 | } 30 | }, 31 | { 32 | "label": "link automat", 33 | "type": "shell", 34 | "command": "python run.py 'link automat'", 35 | "presentation": { 36 | "reveal": "silent", 37 | "close": true 38 | } 39 | }, 40 | ], 41 | } -------------------------------------------------------------------------------- /.zed/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "tab_size": 2, 3 | "lsp": { 4 | "pyright": { 5 | "settings": { 6 | "python.analysis": { 7 | "diagnosticMode": "workspace", 8 | "typeCheckingMode": "strict", 9 | "extraPaths": ["./src", "./run_py"] 10 | } 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Automat Authors 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /assets/04_paper-C-grain.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/04_paper-C-grain.webp -------------------------------------------------------------------------------- /assets/Grenze-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/Grenze-Light.ttf -------------------------------------------------------------------------------- /assets/Grenze-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/Grenze-Regular.ttf -------------------------------------------------------------------------------- /assets/Grenze-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/Grenze-SemiBold.ttf -------------------------------------------------------------------------------- /assets/Grenze-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/Grenze-Thin.ttf -------------------------------------------------------------------------------- /assets/NotoSans[wght].ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/NotoSans[wght].ttf -------------------------------------------------------------------------------- /assets/SFX/button_down.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/button_down.wav -------------------------------------------------------------------------------- /assets/SFX/button_up.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/button_up.wav -------------------------------------------------------------------------------- /assets/SFX/cable_click.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/cable_click.wav -------------------------------------------------------------------------------- /assets/SFX/cable_end.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/cable_end.wav -------------------------------------------------------------------------------- /assets/SFX/cable_loop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/cable_loop.wav -------------------------------------------------------------------------------- /assets/SFX/cable_start.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/cable_start.wav -------------------------------------------------------------------------------- /assets/SFX/canvas_drop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/canvas_drop.wav -------------------------------------------------------------------------------- /assets/SFX/canvas_pick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/canvas_pick.wav -------------------------------------------------------------------------------- /assets/SFX/key_down.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/key_down.wav -------------------------------------------------------------------------------- /assets/SFX/key_up.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/key_up.wav -------------------------------------------------------------------------------- /assets/SFX/macro_start.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/macro_start.wav -------------------------------------------------------------------------------- /assets/SFX/macro_stop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/macro_stop.wav -------------------------------------------------------------------------------- /assets/SFX/mouse_down.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/mouse_down.wav -------------------------------------------------------------------------------- /assets/SFX/mouse_up.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/mouse_up.wav -------------------------------------------------------------------------------- /assets/SFX/next.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/next.wav -------------------------------------------------------------------------------- /assets/SFX/toolbar_pick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/toolbar_pick.wav -------------------------------------------------------------------------------- /assets/SFX/toolbar_select_01.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/toolbar_select_01.wav -------------------------------------------------------------------------------- /assets/SFX/toolbar_select_02.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/toolbar_select_02.wav -------------------------------------------------------------------------------- /assets/SFX/toolbar_select_03.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/toolbar_select_03.wav -------------------------------------------------------------------------------- /assets/SFX/trash.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/SFX/trash.wav -------------------------------------------------------------------------------- /assets/automat_state.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "window": { 4 | "maximized": { 5 | "horizontally": false, 6 | "vertically": false 7 | }, 8 | "output_device_x": 0.002333, 9 | "output_device_y": 0.010759, 10 | "width": 0.231259, 11 | "height": 0.145444, 12 | "camera": { 13 | "x": -0.007981, 14 | "y": 0.002341, 15 | "zoom": 0.999998 16 | } 17 | }, 18 | "root": { 19 | "name": "Root machine", 20 | "locations": {} 21 | } 22 | } -------------------------------------------------------------------------------- /assets/cable-weave-color.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/cable-weave-color.webp -------------------------------------------------------------------------------- /assets/cable-weave-normal.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/cable-weave-normal.webp -------------------------------------------------------------------------------- /assets/cable_rt_sksl: -------------------------------------------------------------------------------- 1 | // kind=shader 2 | 3 | const float PI = 3.1415926535897932384626433832795; 4 | 5 | uniform shader cable_color; 6 | uniform shader cable_normal; 7 | 8 | float3x3 transpose3x3(in float3x3 inMatrix) { 9 | float3 i0 = inMatrix[0]; 10 | float3 i1 = inMatrix[1]; 11 | float3 i2 = inMatrix[2]; 12 | 13 | float3x3 outMatrix = float3x3( 14 | float3(i0.x, i1.x, i2.x), 15 | float3(i0.y, i1.y, i2.y), 16 | float3(i0.z, i1.z, i2.z) 17 | ); 18 | 19 | return outMatrix; 20 | } 21 | 22 | float4 main(vec2 uv) { 23 | vec3 lightDir = normalize(vec3(0, 1, 1)); // normalized vector pointing from current fragment towards the light 24 | float h = sqrt(1 - uv.x * uv.x ); 25 | float angle = acos(uv.x); 26 | 27 | vec2 uv_x = vec2(dFdy(uv.x), dFdx(uv.x)); 28 | float cable_width = 2 / length(uv_x); 29 | 30 | vec3 T = vec3(normalize(uv_x), 0); 31 | vec3 N = normalize(vec3(uv.x * T.y, -uv.x * T.x, h)); 32 | vec3 B = cross(T, N); 33 | float3x3 TBN = float3x3(T, B, N); 34 | float3x3 TBN_inv = transpose3x3(TBN); 35 | 36 | vec2 texCoord = vec2(-angle / PI, uv.y / cable_width / 2) * 512; 37 | 38 | vec3 normalTanSpace = normalize(cable_normal.eval(texCoord).yxz * 2 - 1 + vec3(0, 0, 0.5)); // already in tangent space 39 | normalTanSpace.x = -normalTanSpace.x; 40 | vec3 lightDirTanSpace = normalize(TBN_inv * lightDir); 41 | vec3 viewDirTanSpace = normalize(TBN_inv * vec3(0, 0, 1)); 42 | 43 | vec3 normal = normalize(TBN * normalTanSpace); 44 | 45 | vec4 color; 46 | color.rgba = cable_color.eval(texCoord).rgba; 47 | float light = min(1, 0.2 + max(dot(normalTanSpace, lightDirTanSpace), 0)); 48 | color.rgb = light * color.rgb; 49 | 50 | color.rgb += pow(length(normal.xy), 8) * vec3(0.9, 0.9, 0.9) * 0.5; // rim lighting 51 | 52 | color.rgb += pow(max(dot(reflect(-lightDirTanSpace, normalTanSpace), viewDirTanSpace), 0), 10) * vec3(0.4, 0.4, 0.35); 53 | 54 | // color.r = uv.x * 0.5 + 0.5; 55 | // color.g = uv.y * 10; 56 | // color.b = 0; 57 | // color.a = 1; 58 | return color; 59 | } -------------------------------------------------------------------------------- /assets/card-reverse.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/card-reverse.webp -------------------------------------------------------------------------------- /assets/carpet.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/carpet.webp -------------------------------------------------------------------------------- /assets/connector_insert_rt.sksl: -------------------------------------------------------------------------------- 1 | // kind=shader 2 | 3 | const float kCaseSideRadius = 0.08; 4 | // NOTE: fix this once Skia supports array initializers here 5 | const vec3 kCaseBorderDarkColor = vec3(0x38, 0x36, 0x33) / 255; // subtle dark contour 6 | const vec3 kCaseBorderReflectionColor = vec3(0xdd, 0xdb, 0xd6) / 255; // canvas reflection 7 | const vec3 kCaseSideDarkColor = vec3(0x54, 0x51, 0x4e) / 255; // darker metal between reflections 8 | const vec3 kCaseFrontColor = vec3(0x85, 0x83, 0x80) / 255; // front color 9 | const float kBorderDarkWidth = 0.3; 10 | const float kCaseSideDarkH = 0.7; 11 | const float kCaseFrontH = 1; 12 | const vec3 kTopLightColor = vec3(0x32, 0x34, 0x39) / 255 - kCaseFrontColor; 13 | const float kBevelRadius = kBorderDarkWidth * kCaseSideRadius; 14 | 15 | vec4 main(vec2 uv) { 16 | float2 h = sin(min((0.5 - abs(0.5 - uv)) / kCaseSideRadius, 1) * 3.14159265358979323846 / 2); 17 | float bevel = 1 - length(1 - sin(min((0.5 - abs(0.5 - uv)) / kBevelRadius, 1) * 3.14159265358979323846 / 2)); 18 | vec4 color; 19 | if (h.x < kCaseSideDarkH) { 20 | color.rgb = mix(kCaseBorderReflectionColor, kCaseSideDarkColor, (h.x - kBorderDarkWidth) / (kCaseSideDarkH - kBorderDarkWidth)); 21 | } else { 22 | color.rgb = mix(kCaseSideDarkColor, kCaseFrontColor, (h.x - kCaseSideDarkH) / (kCaseFrontH - kCaseSideDarkH)); 23 | } 24 | if (bevel < 1) { 25 | vec3 edge_color = kCaseBorderDarkColor; 26 | if (uv.y > 0.5) { 27 | edge_color = mix(edge_color, vec3(0.4), clamp((h.x - kCaseSideDarkH) / (kCaseFrontH - kCaseSideDarkH), 0, 1)); 28 | } 29 | color.rgb = mix(edge_color, color.rgb, bevel); 30 | } 31 | color.a = 1; 32 | // Make the corners transparent 33 | vec2 grad_y = dFdy(uv); 34 | float plug_width_pixels = 1/length(grad_y); 35 | float radius_pixels = kBevelRadius * plug_width_pixels; 36 | color.rgba *= clamp(bevel * max(radius_pixels / 2, 1), 0, 1); 37 | return color; 38 | } 39 | -------------------------------------------------------------------------------- /assets/eng.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/eng.traineddata -------------------------------------------------------------------------------- /assets/flag-cf.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/flag-cf.webp -------------------------------------------------------------------------------- /assets/flag-of.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/flag-of.webp -------------------------------------------------------------------------------- /assets/flag.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/flag.webp -------------------------------------------------------------------------------- /assets/flip-flop-color.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/flip-flop-color.webp -------------------------------------------------------------------------------- /assets/glitch_rt.sksl: -------------------------------------------------------------------------------- 1 | // kind=shader 2 | 3 | uniform shader surface; 4 | uniform float2 surfaceResolution; // size of the surface, in pixels 5 | uniform mat3 surfaceTransform; 6 | uniform float time; 7 | 8 | float hash(float n) { 9 | return fract(sin(n + time) * 43758.5453); 10 | } 11 | 12 | vec4 main(float2 localCoord) { 13 | 14 | float2 uv = (surfaceTransform * vec3(localCoord, 1)).xy; 15 | // return vec4(uv.x, uv.y, 0, 1); 16 | bool oob = false; 17 | float x_overshot = 0; 18 | float y_overshot = 0; 19 | if (uv.x > 1 || uv.x < 0) { 20 | oob = true; 21 | x_overshot = min(max(0, abs(uv.x - 0.5) - 0.5) / 2, 0.05); 22 | uv.x += hash(floor(uv.x * surfaceResolution.x / 10) / surfaceResolution.x) / 10; 23 | } 24 | if (uv.y > 1 || uv.y < 0) { 25 | oob = true; 26 | y_overshot = min(max(0, abs(uv.y - 0.5) - 0.5) / 2, 0.05); 27 | uv.y += hash(floor(uv.y * surfaceResolution.y / 10) / surfaceResolution.y) / 10; 28 | } 29 | if (oob) { 30 | uv.y += sin(uv.x * 4 + 2 * time * 2 * 3.14) * x_overshot; 31 | uv.x += sin(uv.y * 4 + 2 * time * 2 * 3.14) * y_overshot; 32 | } 33 | uv.y = 1 - uv.y; 34 | vec4 ret = surface.eval(uv * surfaceResolution); 35 | if (oob) { 36 | ret *= 0.5; 37 | ret *= hash(dot(uv, uv)); 38 | } 39 | // Debug output: show similarity to some anchors 40 | // ret.r = similarity[0]; 41 | // ret.g = similarity[1]; 42 | return ret; 43 | } 44 | -------------------------------------------------------------------------------- /assets/heavy_data.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/heavy_data.ttf -------------------------------------------------------------------------------- /assets/helsinki.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/helsinki.ttf -------------------------------------------------------------------------------- /assets/leaf.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/leaf.webp -------------------------------------------------------------------------------- /assets/macro-recorder-front-color.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/macro-recorder-front-color.webp -------------------------------------------------------------------------------- /assets/mouse-base.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/mouse-base.webp -------------------------------------------------------------------------------- /assets/mouse-lmb-mask.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/mouse-lmb-mask.webp -------------------------------------------------------------------------------- /assets/mouse-rmb-mask.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/mouse-rmb-mask.webp -------------------------------------------------------------------------------- /assets/pointing-hand-color.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/pointing-hand-color.webp -------------------------------------------------------------------------------- /assets/pressing-hand-color.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/pressing-hand-color.webp -------------------------------------------------------------------------------- /assets/reg-ax.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/reg-ax.webp -------------------------------------------------------------------------------- /assets/reg-bx.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/reg-bx.webp -------------------------------------------------------------------------------- /assets/reg-cx.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/reg-cx.webp -------------------------------------------------------------------------------- /assets/reg-di.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/reg-di.webp -------------------------------------------------------------------------------- /assets/reg-dx.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/reg-dx.webp -------------------------------------------------------------------------------- /assets/reg-read.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/reg-read.webp -------------------------------------------------------------------------------- /assets/reg-si.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/reg-si.webp -------------------------------------------------------------------------------- /assets/reg-write.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/reg-write.webp -------------------------------------------------------------------------------- /assets/rose-0.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/rose-0.webp -------------------------------------------------------------------------------- /assets/rose-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/rose-1.webp -------------------------------------------------------------------------------- /assets/rose-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/rose-2.webp -------------------------------------------------------------------------------- /assets/rose-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/rose-3.webp -------------------------------------------------------------------------------- /assets/rose-4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/rose-4.webp -------------------------------------------------------------------------------- /assets/rose-5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/rose-5.webp -------------------------------------------------------------------------------- /assets/rose-6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/rose-6.webp -------------------------------------------------------------------------------- /assets/rosewood-color.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/rosewood-color.webp -------------------------------------------------------------------------------- /assets/skybox.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/skybox.webp -------------------------------------------------------------------------------- /assets/slkscr.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/slkscr.ttf -------------------------------------------------------------------------------- /assets/stalk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/stalk.png -------------------------------------------------------------------------------- /assets/tray.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/tray.webp -------------------------------------------------------------------------------- /assets/venus.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/assets/venus.webp -------------------------------------------------------------------------------- /docs/411172__silverillusionist__liquid-drink.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/411172__silverillusionist__liquid-drink.mp3 -------------------------------------------------------------------------------- /docs/Audiowide.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/Audiowide.woff2 -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | automat.org -------------------------------------------------------------------------------- /docs/Caveat.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/Caveat.woff2 -------------------------------------------------------------------------------- /docs/HomemadeApple.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/HomemadeApple.woff2 -------------------------------------------------------------------------------- /docs/PixelifySans.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/PixelifySans.woff2 -------------------------------------------------------------------------------- /docs/arches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/arches.png -------------------------------------------------------------------------------- /docs/arrow.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/arrow.webp -------------------------------------------------------------------------------- /docs/assets/2024-01-24 09-54-03.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/assets/2024-01-24 09-54-03.webm -------------------------------------------------------------------------------- /docs/assets/Makefile: -------------------------------------------------------------------------------- 1 | output.webm: 2 | ffmpeg -i '2024-01-24 09-54-03.webm' -stream_loop -1 -i '電磁祭囃子 in NEO TOKYO 🏮 [A0VYsiMtrNE].m4a' -c:v copy -t 98s 'output.webm' 3 | -------------------------------------------------------------------------------- /docs/assets/favicon-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/assets/favicon-1024x1024.png -------------------------------------------------------------------------------- /docs/assets/favicon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/assets/favicon-512x512.png -------------------------------------------------------------------------------- /docs/assets/output.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/assets/output.webm -------------------------------------------------------------------------------- /docs/assets/電磁祭囃子 in NEO TOKYO 🏮 [A0VYsiMtrNE].m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/assets/電磁祭囃子 in NEO TOKYO 🏮 [A0VYsiMtrNE].m4a -------------------------------------------------------------------------------- /docs/cable_playground.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Automat Cable Playground 7 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/cup-down.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/cup-down.flac -------------------------------------------------------------------------------- /docs/cup-up.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/cup-up.flac -------------------------------------------------------------------------------- /docs/cups-1.5x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/cups-1.5x.webp -------------------------------------------------------------------------------- /docs/cups-1x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/cups-1x.webp -------------------------------------------------------------------------------- /docs/cups-2x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/cups-2x.webp -------------------------------------------------------------------------------- /docs/design/Deleting Objects.md: -------------------------------------------------------------------------------- 1 | # Object deletion in Automat 2 | 3 | A designated corner is going to be used as a drag target for deleting objects (Vlojure approach) 4 | Optionally also drag a trash icon onto the menu object to delete it 5 | Alternatively a dock object with edit mode or buttons to move & delete objects 6 | Alternatively drop objects outside of the canvas to delete them :P 7 | 8 | - excluding the solution with context menu 9 | - excluding the solution with "delete" button on every object 10 | 11 | 1. Pinning objects to the screen (they don't move with the camera) 12 | 2. Trash object that deletes all objects that are dragged over it 13 | 3. Trash grabber that can be grabbed to delete objects 14 | 15 | 4. A new "dock" object that holds the prototypes 16 | - buttons to delete and move prototypes around 17 | - "edit" mode when the dock can be altered (iOS approach) 18 | -------------------------------------------------------------------------------- /docs/design/Serialization.md: -------------------------------------------------------------------------------- 1 | # Why don't Automat objects have UUIDs? 2 | 3 | This is because mutable objects are fuzzy (their boundaries can change, their identity is often defined by their context) and numerous. Tracking object identity involves a lot of work and creates many problems for pretty little gain. Instead of UUIDs, Automat objects are identified by their address in memory. When serialized, they're assigned short unique identifiers meant to identify them only within the context of their parent machine. Objects can also have names assigned by the user - they are very different and worth accurate tracking. -------------------------------------------------------------------------------- /docs/design/customizing-automat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/design/customizing-automat.webp -------------------------------------------------------------------------------- /docs/eject.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/eject.flac -------------------------------------------------------------------------------- /docs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/favicon-16x16.png -------------------------------------------------------------------------------- /docs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/favicon-32x32.png -------------------------------------------------------------------------------- /docs/floppy-blue-2x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/floppy-blue-2x.webp -------------------------------------------------------------------------------- /docs/floppy-down.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/floppy-down.flac -------------------------------------------------------------------------------- /docs/floppy-insert.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/floppy-insert.flac -------------------------------------------------------------------------------- /docs/floppy-up.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/floppy-up.flac -------------------------------------------------------------------------------- /docs/napkin-down.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/napkin-down.flac -------------------------------------------------------------------------------- /docs/napkin-up.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/napkin-up.flac -------------------------------------------------------------------------------- /docs/napkin.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/napkin.webp -------------------------------------------------------------------------------- /docs/noise.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/noise.webp -------------------------------------------------------------------------------- /docs/notebook-bg-2x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/notebook-bg-2x.webp -------------------------------------------------------------------------------- /docs/notebook-cover-2x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/notebook-cover-2x.webp -------------------------------------------------------------------------------- /docs/notebook-down.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/notebook-down.flac -------------------------------------------------------------------------------- /docs/notebook-flip.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/notebook-flip.flac -------------------------------------------------------------------------------- /docs/notebook-up.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/notebook-up.flac -------------------------------------------------------------------------------- /docs/page-1-2x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/page-1-2x.webp -------------------------------------------------------------------------------- /docs/page-2-2x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/page-2-2x.webp -------------------------------------------------------------------------------- /docs/page-3-2x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/page-3-2x.webp -------------------------------------------------------------------------------- /docs/page-4-2x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/page-4-2x.webp -------------------------------------------------------------------------------- /docs/player-down.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/player-down.flac -------------------------------------------------------------------------------- /docs/player-up.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/player-up.flac -------------------------------------------------------------------------------- /docs/vhs-1x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/vhs-1x.webp -------------------------------------------------------------------------------- /docs/vhs-2x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/vhs-2x.webp -------------------------------------------------------------------------------- /docs/vhs-3x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/vhs-3x.webp -------------------------------------------------------------------------------- /docs/vhs-down.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/vhs-down.flac -------------------------------------------------------------------------------- /docs/vhs-insert.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/vhs-insert.flac -------------------------------------------------------------------------------- /docs/vhs-up.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/vhs-up.flac -------------------------------------------------------------------------------- /docs/video_player-2x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/docs/video_player-2x.webp -------------------------------------------------------------------------------- /pyrightconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src/**/*.py", 4 | "run_py/**/*.py" 5 | ], 6 | } -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 3 | # SPDX-License-Identifier: MIT 4 | 5 | # This file needs a .py suffix in order to run under Windows. 6 | 7 | import sys, os 8 | from subprocess import run 9 | from pathlib import Path 10 | 11 | base_dir = Path(__file__).parent 12 | exit_code = 0 13 | try: 14 | completed_process = run(['python', str(base_dir / 'run_py')] + sys.argv[1:], cwd=base_dir) 15 | exit_code = completed_process.returncode 16 | except KeyboardInterrupt: 17 | pass 18 | except Exception as e: 19 | print(e) 20 | 21 | # On Windows, if launched with double-click, keep 22 | # the window open until the user presses ENTER. 23 | # 24 | # Note: Temporarily disabled because it's not working with VSCode tasks. 25 | # 26 | # if os.name == 'nt' and 'PROMPT' not in os.environ: 27 | # input('Press ENTER to exit...') 28 | 29 | sys.exit(exit_code) 30 | -------------------------------------------------------------------------------- /run_py/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 3 | # SPDX-License-Identifier: MIT 4 | 5 | '''Run Automat.''' 6 | 7 | from args import args 8 | from sys import platform, exit 9 | 10 | if platform == 'win32': 11 | import windows_deps 12 | windows_deps.check_and_install() 13 | 14 | import build 15 | import subprocess 16 | 17 | import debian_deps 18 | debian_deps.check_and_install() 19 | 20 | 21 | recipe = build.recipe() 22 | 23 | 24 | if args.verbose: 25 | import fs_utils 26 | import graph_printer 27 | graph_printer.print_graph(recipe, fs_utils.build_dir / 'graph.html') 28 | print('Build graph written to build/graph.html') 29 | 30 | if args.fresh: 31 | print('Cleaning old build results:') 32 | recipe.clean() 33 | 34 | active_recipe = None 35 | 36 | while True: 37 | recipe.set_target(args.target) 38 | if args.live: 39 | watcher = subprocess.Popen( 40 | ['python', 'run_py/inotify.py', 'src/', 'assets/'], stdout=subprocess.DEVNULL) 41 | else: 42 | watcher = None 43 | 44 | try: 45 | ok = recipe.execute(watcher) 46 | except KeyboardInterrupt: 47 | exit(2) 48 | if ok: 49 | if active_recipe: 50 | active_recipe.interrupt() 51 | active_recipe = recipe 52 | if watcher: 53 | try: 54 | print('Watching src/ for changes...') 55 | watcher.wait() 56 | except KeyboardInterrupt: 57 | watcher.kill() 58 | break 59 | else: 60 | break 61 | # Reload the recipe because dependencies may have changed 62 | recipe = build.recipe() 63 | if not ok: 64 | exit(1) 65 | -------------------------------------------------------------------------------- /run_py/args.py: -------------------------------------------------------------------------------- 1 | '''Module with all of the command line flags used by the build script.''' 2 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 3 | # SPDX-License-Identifier: MIT 4 | 5 | # TODO: allow different python modules to add their own args - instead of a centralized location. This should allow for better Hyperdeck / Automat reusability. 6 | 7 | import __main__ 8 | import argparse 9 | import sys 10 | 11 | sys.argv[0] = 'run' 12 | 13 | parser = argparse.ArgumentParser( 14 | description=__main__.__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) 15 | parser.add_argument('--fresh', action='store_true') 16 | parser.add_argument('--live', action='store_true') 17 | parser.add_argument('--verbose', action='store_true') 18 | parser.add_argument('target') 19 | parser.add_argument('-x', action='append', 20 | help='argument passed to the target', dest='extra_args', default=[]) 21 | args = parser.parse_args() 22 | -------------------------------------------------------------------------------- /run_py/cc_embed.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | # SPDX-License-Identifier: MIT 3 | import string 4 | 5 | byte_to_c_string_table = {c: chr(c) for c in range(32, 127)} 6 | byte_to_c_string_table[0x22] = '\\"' 7 | byte_to_c_string_table[0x5c] = '\\\\' 8 | byte_to_c_string_table[0x07] = '\\a' 9 | byte_to_c_string_table[0x08] = '\\b' 10 | byte_to_c_string_table[0x0c] = '\\f' 11 | byte_to_c_string_table[0x0a] = '\\n' 12 | byte_to_c_string_table[0x0d] = '\\r' 13 | byte_to_c_string_table[0x09] = '\\t' 14 | byte_to_c_string_table[0x0b] = '\\v' 15 | 16 | digit_bytes = set(ord(c) for c in string.digits) 17 | 18 | 19 | def byte_to_c_string(b, next_b=None): 20 | if b in byte_to_c_string_table: 21 | return byte_to_c_string_table[b] 22 | elif next_b in digit_bytes: 23 | return '\\' + format(b, '03o') 24 | else: 25 | return '\\' + format(b, 'o') 26 | 27 | 28 | def pairwise(iterable): 29 | it = iter(iterable) 30 | a = next(it) 31 | for b in it: 32 | yield a, b 33 | a = b 34 | 35 | 36 | def bytes_to_c_string(bytes): 37 | # x is a dummy byte 38 | return '"' + ''.join(byte_to_c_string(b, next_b) for b, next_b in pairwise(bytes + b'x')) + '"' 39 | -------------------------------------------------------------------------------- /run_py/clang.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | # SPDX-License-Identifier: MIT 3 | import subprocess 4 | import shutil 5 | from glob import glob 6 | import re 7 | 8 | def natural_sort(l): 9 | convert = lambda text: int(text) if text.isdigit() else text.lower() 10 | alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] 11 | return sorted(l, key=alphanum_key) 12 | 13 | if shutil.which('clang'): 14 | executable = 'clang++' 15 | executable_c = 'clang' 16 | else: 17 | clang_binaries = glob('/usr/lib/llvm/*/bin/clang') 18 | if clang_binaries: 19 | clang_binaries = natural_sort(clang_binaries) 20 | executable = clang_binaries[-1] + '++' 21 | executable_c = clang_binaries[-1] 22 | else: 23 | raise FileNotFoundError('Couldn\'t find `clang` program. Searched $PATH and `/usr/lib/llvm/*/bin/`.') 24 | 25 | def query_default_defines() -> set[str]: 26 | result = subprocess.run([executable, '-dM', '-E', '-'], 27 | stdin=subprocess.DEVNULL, stdout=subprocess.PIPE) 28 | return set([line.split()[1] for line in result.stdout.decode().splitlines()]) 29 | 30 | 31 | default_defines = query_default_defines() 32 | -------------------------------------------------------------------------------- /run_py/cmake.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | # SPDX-License-Identifier: MIT 3 | import build 4 | import ninja 5 | 6 | build.fast.CMAKE_BUILD_TYPE = 'RelWithDebInfo' 7 | build.release.CMAKE_BUILD_TYPE = 'Release' 8 | build.debug.CMAKE_BUILD_TYPE = 'Debug' 9 | 10 | build.fast.CMAKE_MSVC_RUNTIME_LIBRARY = 'MultiThreaded' 11 | build.release.CMAKE_MSVC_RUNTIME_LIBRARY = 'MultiThreaded' 12 | build.debug.CMAKE_MSVC_RUNTIME_LIBRARY = 'MultiThreadedDebug' 13 | 14 | def CMakeArgs(build_type: build.BuildType, extra_defines: dict[str, str] = {}): 15 | CMAKE_BUILD_TYPE = build_type.CMAKE_BUILD_TYPE 16 | CMAKE_MSVC_RUNTIME_LIBRARY = build_type.CMAKE_MSVC_RUNTIME_LIBRARY 17 | CMAKE_MAKE_PROGRAM = str(ninja.BIN) 18 | CMAKE_INSTALL_LIBDIR = 'lib64' 19 | 20 | cmake_args = ['cmake', '-G', 'Ninja', f'-D{CMAKE_BUILD_TYPE=}', f'-D{CMAKE_MAKE_PROGRAM=}', 21 | f'-DCMAKE_C_COMPILER={build.compiler_c}', f'-DCMAKE_CXX_COMPILER={build.compiler}', 22 | f'-D{CMAKE_MSVC_RUNTIME_LIBRARY=}', f'-D{CMAKE_INSTALL_LIBDIR=}'] 23 | 24 | cmake_args += ['-DCMAKE_INSTALL_PREFIX=' + str(build_type.PREFIX())] 25 | 26 | for name, value in extra_defines.items(): 27 | cmake_args += ['-D' + name + '=' + value] 28 | 29 | return cmake_args 30 | -------------------------------------------------------------------------------- /run_py/debian_deps.py: -------------------------------------------------------------------------------- 1 | '''This module ensures that all of the necessary Debian dependencies are installed.''' 2 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 3 | # SPDX-License-Identifier: MIT 4 | 5 | import os 6 | from subprocess import run 7 | from sys import platform 8 | 9 | path_to_package = { 10 | "/usr/include/zlib.h": "zlib1g-dev", 11 | "/usr/include/openssl/ssl.h": "libssl-dev", 12 | "/usr/include/gmock": "libgmock-dev", 13 | "/usr/bin/meson": "meson", 14 | "/usr/include/X11/Xlib.h": "libx11-dev", 15 | "/usr/include/X11/extensions/Xrandr.h": "libxrandr-dev", 16 | "/usr/include/X11/extensions/Xinerama.h": "libxinerama-dev", 17 | "/usr/include/X11/Xcursor/Xcursor.h": "libxcursor-dev", 18 | "/usr/include/X11/extensions/XInput2.h": "libxi-dev", 19 | "/usr/include/GL/gl.h": "libgl-dev", 20 | "/usr/include/fontconfig/fontconfig.h": "libfontconfig-dev", 21 | } 22 | 23 | 24 | def check_and_install(): 25 | if platform != 'linux': 26 | return 27 | missing_packages = set() 28 | for path, package in path_to_package.items(): 29 | if not os.path.exists(path): 30 | missing_packages.add(package) 31 | if missing_packages: 32 | print("Some packages are missing from your system. Will try to install them automatically:\n") 33 | print(" ", ', '.join(missing_packages)) 34 | print("In case of errors with clang or libc++ installation - add the repositories from https://apt.llvm.org/ and re-run this script.\n") 35 | print("Press enter to continue or Ctrl+C to cancel.") 36 | try: 37 | input() 38 | except EOFError: 39 | print("Got EOF - batch job detected. Continuing with the installation.") 40 | command = ["apt", "-y", "install"] + list(missing_packages) 41 | if os.geteuid() != 0: # non-root users need `sudo` to install stuff 42 | command = ["sudo"] + command 43 | print(" ".join(command) + "\n") 44 | run(command, check=True) 45 | -------------------------------------------------------------------------------- /run_py/fs_utils.py: -------------------------------------------------------------------------------- 1 | '''Utilities for operating on filesystem.''' 2 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 3 | # SPDX-License-Identifier: MIT 4 | 5 | from pathlib import Path 6 | from sys import platform 7 | 8 | project_root = Path(__file__).resolve().parents[1] 9 | project_name = Path(project_root).name.lower() 10 | build_dir = project_root / 'build' 11 | src_dir = project_root / 'src' 12 | generated_dir = project_root / 'build' / 'generated' 13 | third_party_dir = project_root / 'third_party' 14 | run_py_dir = project_root / 'run_py' 15 | 16 | binary_extension = '.exe' if platform == 'win32' else '' -------------------------------------------------------------------------------- /run_py/git.py: -------------------------------------------------------------------------------- 1 | '''Utilities for working with git repositories.''' 2 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 3 | # SPDX-License-Identifier: MIT 4 | 5 | from functools import partial 6 | import make 7 | 8 | def clone(url, out_directory, tag): 9 | '''Return an asynchronous 'git clone' wrapper.''' 10 | return partial(make.Popen, [ 11 | 'git', 12 | '-c', 'advice.detachedHead=false', 13 | '-c', 'core.autocrlf=false', 14 | 'clone', 15 | '--depth', '1', 16 | '--branch', tag, 17 | url, out_directory]) 18 | -------------------------------------------------------------------------------- /run_py/graph_printer.py: -------------------------------------------------------------------------------- 1 | import fs_utils 2 | from pathlib import Path 3 | 4 | def short_path(path_str): 5 | path = Path(path_str) 6 | if path.is_relative_to(fs_utils.project_root): 7 | return str(path.relative_to(fs_utils.project_root)) 8 | else: 9 | return path_str 10 | 11 | def print_graph(recipe, html_path): 12 | fs_utils.build_dir.mkdir(parents=True, exist_ok=True) 13 | file_to_step = {} 14 | for step in recipe.steps: 15 | for output in step.outputs: 16 | file_to_step[output] = step 17 | with open(html_path, 'w') as f: 18 | f.write('\n') 19 | for step in recipe.steps: 20 | f.write(f'

{step.shortcut}

\n') 21 | f.write(f'

{step.desc}

\n') 22 | f.write('

Inputs: ') 23 | for inp in sorted(str(x) for x in step.inputs): 24 | inp_short = short_path(inp) 25 | if inp in file_to_step: 26 | input_step = file_to_step[inp] 27 | f.write(f'{inp_short}') 28 | else: 29 | f.write(inp_short) 30 | f.write(', ') 31 | f.write('

\n') 32 | f.write('

Outputs: ') 33 | for out in sorted(str(x) for x in step.outputs): 34 | f.write(short_path(out)) 35 | f.write(', ') 36 | f.write('

\n') 37 | f.write('') 38 | -------------------------------------------------------------------------------- /run_py/ninja.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | # SPDX-License-Identifier: MIT 3 | '''Rules for downloading the Ninja build system''' 4 | 5 | # TODO: move this to src/ and remove the empty hook_recipe function 6 | 7 | import shutil 8 | from pathlib import Path 9 | 10 | BIN_STR = shutil.which('ninja') 11 | 12 | if BIN_STR: 13 | BIN = Path(BIN_STR) 14 | def hook_recipe(recipe): 15 | pass 16 | else: 17 | print('Ninja not found - adding recipe to download it.') 18 | 19 | import build, sys, fs_utils 20 | if sys.platform == 'win32': 21 | ZIP_NAME = 'ninja-win.zip' 22 | elif sys.platform == 'darwin': 23 | ZIP_NAME = 'ninja-mac.zip' 24 | elif sys.platform == 'linux': 25 | ZIP_NAME = 'ninja-linux.zip' 26 | else: 27 | raise NotImplementedError(f'TODO: platform {sys.platform} is missing in ninja.py') 28 | 29 | DOWNLOAD_URL = 'https://github.com/ninja-build/ninja/releases/latest/download/' + ZIP_NAME 30 | ZIP_PATH = fs_utils.build_dir / ZIP_NAME 31 | BIN = build.base.PREFIX() / 'bin' / 'ninja' 32 | 33 | def download(url, out_path): 34 | import urllib.request, shutil 35 | with urllib.request.urlopen(url) as response: 36 | with open(out_path, 'wb') as out_file: 37 | shutil.copyfileobj(response, out_file) 38 | 39 | def download_step(url, out_path): 40 | import functools 41 | return functools.partial(download, url, out_path) 42 | 43 | def unzip(zip_path, out_dir): 44 | import zipfile 45 | with zipfile.ZipFile(zip_path, 'r') as zip_ref: 46 | zip_ref.extractall(out_dir) 47 | 48 | def unzip_step(zip_path, out_dir): 49 | import functools 50 | return functools.partial(unzip, zip_path, out_dir) 51 | 52 | def hook_recipe(recipe): 53 | recipe.add_step( 54 | download_step(DOWNLOAD_URL, ZIP_PATH), 55 | outputs=[ZIP_PATH], 56 | inputs=[], 57 | desc='Downloading Ninja', 58 | shortcut='download ninja') 59 | 60 | recipe.add_step( 61 | unzip_step(ZIP_PATH, BIN.parent), 62 | outputs=[BIN], 63 | inputs=[ZIP_PATH], 64 | desc='Unzipping Ninja', 65 | shortcut='unzip ninja') 66 | -------------------------------------------------------------------------------- /run_py/windows.py: -------------------------------------------------------------------------------- 1 | ''' Utilities for working with Windows. ''' 2 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 3 | # SPDX-License-Identifier: MIT 4 | 5 | import sys 6 | 7 | if sys.platform != 'win32': 8 | raise Exception('This module is only for Windows') 9 | 10 | import ctypes 11 | 12 | user32 = ctypes.windll.user32 13 | GetWindowThreadProcessId = user32.GetWindowThreadProcessId 14 | EnumWindows = user32.EnumWindows 15 | EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)) 16 | GetWindowText = user32.GetWindowTextW 17 | GetWindowTextLength = user32.GetWindowTextLengthW 18 | GetParent = user32.GetParent 19 | IsWindowVisible = user32.IsWindowVisible 20 | 21 | 22 | def is_main(hwnd): 23 | return GetParent(hwnd) == 0 24 | 25 | 26 | def is_visible(hwnd): 27 | return IsWindowVisible(hwnd) 28 | 29 | 30 | def get_pid(hwnd): 31 | pid = ctypes.c_ulong() 32 | GetWindowThreadProcessId(hwnd, ctypes.byref(pid)) 33 | return pid.value 34 | 35 | 36 | '''Sends a WM_CLOSE message to the main window of the process with the given PID. 37 | 38 | Returns True if the window was found and WM_CLOSE sent, False otherwise.''' 39 | def close_window(pid): 40 | hwnds = [] 41 | def foreach_window(hwnd, lParam): 42 | if not is_main(hwnd) or not is_visible(hwnd): 43 | return True 44 | # length = GetWindowTextLength(hwnd) 45 | # buff = ctypes.create_unicode_buffer(length + 1) 46 | # GetWindowText(hwnd, buff, length + 1) 47 | # hwnd_title = buff.value 48 | hwnd_pid = get_pid(hwnd) 49 | if pid == hwnd_pid: 50 | hwnds.append(hwnd) 51 | return True 52 | EnumWindows(EnumWindowsProc(foreach_window), 0) 53 | if len(hwnds) != 1: 54 | return False 55 | hwnd = hwnds[0] 56 | user32.PostMessageW(hwnd, 0x0010, 0, 0) 57 | return True 58 | 59 | -------------------------------------------------------------------------------- /source_images/Arrow/Direction.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/source_images/Arrow/Direction.xcf -------------------------------------------------------------------------------- /source_images/Mouse/01 Civitai Base.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/source_images/Mouse/01 Civitai Base.jpeg -------------------------------------------------------------------------------- /source_images/Mouse/02 Slight Variation Fooocus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/source_images/Mouse/02 Slight Variation Fooocus.png -------------------------------------------------------------------------------- /source_images/Mouse/03 Inpaint Cable Button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/source_images/Mouse/03 Inpaint Cable Button.png -------------------------------------------------------------------------------- /source_images/Mouse/04 Inpaint Rest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/source_images/Mouse/04 Inpaint Rest.png -------------------------------------------------------------------------------- /source_images/Mouse/05 Clipdrop Transparency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/source_images/Mouse/05 Clipdrop Transparency.png -------------------------------------------------------------------------------- /source_images/Mouse/06 - GIMP.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafik/automat/1c946f2373fdb8ae8abac172353b92187d7d0bfa/source_images/Mouse/06 - GIMP.xcf -------------------------------------------------------------------------------- /src/action.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | namespace automat { 6 | 7 | namespace gui { 8 | struct Pointer; 9 | struct Widget; 10 | } // namespace gui 11 | 12 | // Action represents an action/gesture that user can perform by pressing a key / button / touching 13 | // the screen and then moving the pointer around before releasing it. 14 | // 15 | // Actions are the main mechanism for user to interact with the UI. 16 | struct Action { 17 | gui::Pointer& pointer; 18 | 19 | // Each action must be bound to a pointer. A reference to the pointer is stored internally to keep 20 | // track of its position. 21 | Action(gui::Pointer& pointer) : pointer(pointer) {} 22 | 23 | // Action is destroyed when the pointer is released. This typically corresponds to the button 24 | // release or key up. 25 | virtual ~Action() = default; 26 | 27 | // Update is called when the pointer moves (although spurious calls are also possible). This 28 | // function may be called hundreds of times per second. 29 | virtual void Update() = 0; 30 | 31 | virtual gui::Widget* Widget() { return nullptr; } 32 | }; 33 | 34 | struct EmptyAction : Action { 35 | EmptyAction(gui::Pointer& pointer) : Action(pointer) {} 36 | void Update() override {} 37 | }; 38 | 39 | } // namespace automat 40 | -------------------------------------------------------------------------------- /src/arr.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include 4 | 5 | namespace automat { 6 | 7 | template 8 | using Arr = std::array; 9 | 10 | } // namespace automat -------------------------------------------------------------------------------- /src/audio.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include "virtual_fs.hh" 6 | 7 | namespace automat::audio { 8 | 9 | using Sound = fs::VFile; 10 | 11 | #ifdef __linux__ 12 | void Init(int* argc, char*** argv); 13 | #else 14 | void Init(); 15 | #endif 16 | 17 | void Stop(); 18 | 19 | void Play(Sound&); 20 | 21 | struct Effect { 22 | virtual ~Effect() {} 23 | }; 24 | 25 | std::unique_ptr MakeBeginLoopEndEffect(Sound& begin, Sound& loop, Sound& end); 26 | 27 | } // namespace automat::audio -------------------------------------------------------------------------------- /src/automat.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | 7 | #include "base.hh" 8 | 9 | // High-level automat code. 10 | 11 | namespace automat { 12 | 13 | extern std::stop_source stop_source; 14 | extern Ptr root_location; 15 | extern Ptr root_machine; 16 | extern std::jthread automat_thread; 17 | extern std::atomic_bool automat_thread_finished; 18 | 19 | extern std::thread::id main_thread_id; 20 | 21 | void StopAutomat(Status&); 22 | 23 | void EnqueueTask(Task* task); 24 | void RunOnAutomatThread(std::function); 25 | void RunOnAutomatThreadSynchronous(std::function); 26 | void AssertAutomatThread(); 27 | 28 | void RunLoop(const int max_iterations = -1); 29 | 30 | } // namespace automat -------------------------------------------------------------------------------- /src/backtrace.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | bool PrintBacktrace(); 6 | void EnableBacktraceOnSIGSEGV(); 7 | -------------------------------------------------------------------------------- /src/cavalier_contours.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright 2025 Automat Authors 2 | # SPDX-License-Identifier: MIT 3 | import build 4 | from extension_helper import ExtensionHelper 5 | 6 | hook = ExtensionHelper('cavalier_contours', globals()) 7 | hook.SkipConfigure() 8 | hook.InstallWhenIncluded(r'cavc/.+\.hpp') 9 | hook.AddCompileArg('-I' + str(hook.checkout_dir / 'include')) 10 | -------------------------------------------------------------------------------- /src/channel_test.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include "channel.hh" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | using namespace automat; 11 | 12 | TEST(ChannelTest, SendForce) { 13 | channel c; 14 | c.send_force(std::make_unique(1)); 15 | EXPECT_EQ(*c.recv(), 1); 16 | } 17 | 18 | TEST(ChannelTest, ManySenders) { 19 | channel c; 20 | for (int i = 0; i < 100; ++i) { 21 | std::thread([&c, i] { c.send(std::make_unique(i)); }).detach(); 22 | } 23 | std::vector received; 24 | while (received.size() < 100) { 25 | received.push_back(*c.recv()); 26 | } 27 | std::sort(received.begin(), received.end()); 28 | for (int i = 0; i < 100; ++i) { 29 | EXPECT_EQ(received[i], i); 30 | } 31 | } 32 | 33 | TEST(ChannelTest, RecvBeforeSend) { 34 | channel c; 35 | std::thread([&c] { 36 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 37 | c.send(std::make_unique(1)); 38 | }).detach(); 39 | EXPECT_EQ(*c.recv(), 1); 40 | } 41 | -------------------------------------------------------------------------------- /src/complex_test.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include 4 | 5 | #include "base.hh" 6 | #include "library.hh" 7 | #include "test_base.hh" 8 | 9 | using namespace automat; 10 | 11 | struct ComplexTest : TestBase { 12 | Location& label = machine.Create(); 13 | Location& complex = machine.Create(); 14 | Location& field = machine.Create(); 15 | 16 | ComplexTest() { 17 | label.SetText("X"); 18 | field.ConnectTo(label, "label"); 19 | field.ConnectTo(complex, "complex"); 20 | field.Put(Create()); 21 | } 22 | }; 23 | 24 | TEST_F(ComplexTest, FollowField) { 25 | ASSERT_EQ(complex.As()->objects.size(), 1); 26 | EXPECT_EQ(complex.As()->objects.begin()->second.get(), field.Follow()); 27 | } 28 | 29 | TEST_F(ComplexTest, TakeField) { 30 | ASSERT_EQ(complex.As()->objects.size(), 1); 31 | 32 | auto taken = field.Take(); 33 | EXPECT_NE(taken.get(), nullptr); 34 | EXPECT_EQ(complex.As()->objects.size(), 0); 35 | } 36 | 37 | TEST_F(ComplexTest, CloneWithField) { 38 | field.Follow()->SetText(field, "Hello, world!"); 39 | 40 | Location& clone = machine.Create(*complex.object, "Clone"); 41 | EXPECT_NE(&complex, &clone); 42 | auto& [clone_label, clone_member] = *clone.As()->objects.begin(); 43 | EXPECT_NE(field.Follow(), clone_member.get()); 44 | EXPECT_EQ(clone_label, "X"); 45 | EXPECT_EQ(clone_member->GetText(), "Hello, world!"); 46 | } 47 | -------------------------------------------------------------------------------- /src/connection.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include "connection.hh" 4 | 5 | #include "gui_connection_widget.hh" 6 | #include "location.hh" 7 | #include "root_widget.hh" 8 | 9 | using namespace automat::gui; 10 | 11 | namespace automat { 12 | Connection::~Connection() { 13 | auto [begin, end] = from.outgoing.equal_range(this); 14 | for (auto it = begin; it != end; ++it) { 15 | if (*it == this) { 16 | from.outgoing.erase(it); 17 | break; 18 | } 19 | } 20 | auto [begin2, end2] = to.incoming.equal_range(this); 21 | for (auto it = begin2; it != end2; ++it) { 22 | if (*it == this) { 23 | to.incoming.erase(it); 24 | break; 25 | } 26 | } 27 | if (root_widget) { 28 | for (int i = 0; i < root_widget->connection_widgets.size(); ++i) { 29 | auto& widget = *root_widget->connection_widgets[i]; 30 | if (&widget.from == &from && &widget.arg == &argument) { 31 | widget.WakeAnimation(); 32 | } 33 | } 34 | } 35 | if (from.object) { 36 | from.object->ConnectionRemoved(from, *this); 37 | } 38 | } 39 | 40 | Connection::Connection(Argument& arg, Location& from, Location& to, 41 | PointerBehavior pointer_behavior) 42 | : argument(arg), from(from), to(to), pointer_behavior(pointer_behavior) {} 43 | } // namespace automat -------------------------------------------------------------------------------- /src/connection.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace automat { 9 | 10 | struct Location; 11 | struct Argument; 12 | 13 | struct Connection { 14 | enum PointerBehavior { kFollowPointers, kTerminateHere }; 15 | Argument& argument; 16 | Location &from, &to; 17 | PointerBehavior pointer_behavior; 18 | Connection(Argument&, Location& from, Location& to, PointerBehavior); 19 | ~Connection(); 20 | }; 21 | 22 | struct ConnectionHasher { 23 | using is_transparent = std::true_type; 24 | size_t operator()(const Connection* c) const { 25 | return std::hash{}(&c->argument); 26 | } 27 | size_t operator()(const Argument* a) const { return std::hash{}(a); } 28 | }; 29 | 30 | struct ConnectionEqual { 31 | using is_transparent = std::true_type; 32 | bool operator()(const Connection* a, const Connection* b) const { 33 | return &a->argument == &b->argument; 34 | } 35 | bool operator()(const Connection* a, const Argument* b) const { return &a->argument == b; } 36 | bool operator()(const Argument* a, const Connection* b) const { return a == &b->argument; } 37 | }; 38 | 39 | } // namespace automat -------------------------------------------------------------------------------- /src/control_flow.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | namespace automat { 6 | 7 | // Mechanism for controlling various search loops. 8 | enum class ControlFlow : char { 9 | VisitChildren = 0, // Continue the search including children of the current element. 10 | SkipChildren = 1, // Continue the search but don't visit the children of the current element. 11 | StopSearching = 2, // Stop the search immediately. 12 | }; 13 | 14 | } // namespace automat -------------------------------------------------------------------------------- /src/demo_static_vulkan_crash.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma maf main 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | int main() { 13 | void* library = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_GLOBAL); 14 | printf("Loaded libvulkan.so.1\n"); 15 | PFN_vkGetInstanceProcAddr ptr_vkGetInstanceProcAddr = 16 | reinterpret_cast(dlsym(library, "vkGetInstanceProcAddr")); 17 | 18 | PFN_vkCreateInstance fp_vkCreateInstance = reinterpret_cast( 19 | ptr_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance")); 20 | 21 | VkApplicationInfo applicationInfo = { 22 | .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, 23 | .pApplicationName = "Hello world app", 24 | .applicationVersion = 0, 25 | .pEngineName = "awesomeengine", 26 | .engineVersion = 0, 27 | .apiVersion = VK_API_VERSION_1_0, 28 | }; 29 | 30 | VkInstanceCreateInfo createInfo = { 31 | .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 32 | .flags = 0, 33 | .pApplicationInfo = &applicationInfo, 34 | .enabledLayerCount = 0, 35 | .ppEnabledLayerNames = nullptr, 36 | .enabledExtensionCount = 0, 37 | .ppEnabledExtensionNames = nullptr, 38 | }; 39 | 40 | VkInstance instance; 41 | printf("Creating instance...\n"); 42 | if (fp_vkCreateInstance(&createInfo, NULL, &instance) != VK_SUCCESS) { 43 | printf("Failed to create instance.\n"); 44 | return EXIT_FAILURE; 45 | } 46 | printf("Instance created\n"); 47 | PFN_vkDestroyInstance fp_vkDestroyInstance = reinterpret_cast( 48 | ptr_vkGetInstanceProcAddr(instance, "vkDestroyInstance")); 49 | fp_vkDestroyInstance(instance, NULL); 50 | 51 | printf("Demo failed to crash (yay!)\n"); 52 | 53 | return EXIT_SUCCESS; 54 | } 55 | -------------------------------------------------------------------------------- /src/dev_actions.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | # SPDX-License-Identifier: MIT 3 | import make 4 | from pathlib import Path 5 | import subprocess 6 | import fs_utils 7 | import shutil 8 | 9 | def optimize_sfx(): 10 | # go through every .wav file in assets and run 11 | # ffmpeg -i in.mov -map_metadata -1 -c:v copy -c:a copy -fflags +bitexact -flags:v +bitexact -flags:a +bitexact out.mov 12 | assets = fs_utils.project_root / 'assets' 13 | for wav_path in assets.glob('**/*.wav'): 14 | if wav_path.stem.endswith('_temp'): 15 | continue 16 | print(wav_path) 17 | temp_copy = wav_path.with_stem(wav_path.stem + '_temp') 18 | args = ['ffmpeg', '-i', str(wav_path), '-map_metadata', '-1', '-c:v', 'copy', '-c:a', 'copy', '-fflags', '+bitexact', '-flags:v', '+bitexact', '-flags:a', '+bitexact', str(temp_copy)] 19 | print('Running:', *args) 20 | subprocess.run(args, check=True) 21 | shutil.move(temp_copy, wav_path) 22 | 23 | 24 | def hook_recipe(recipe: make.Recipe): 25 | recipe.add_step(optimize_sfx, [], []) 26 | -------------------------------------------------------------------------------- /src/drag_action.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "action.hh" 10 | #include "object.hh" 11 | #include "time.hh" 12 | 13 | namespace automat { 14 | 15 | struct Location; 16 | 17 | namespace gui { 18 | 19 | // Interface for widgets that can receive locations being dropped on them. 20 | struct DropTarget { 21 | virtual bool CanDrop(Location&) const = 0; 22 | virtual void SnapPosition(Vec2& position, float& scale, Location&, 23 | Vec2* fixed_point = nullptr) = 0; 24 | 25 | // When a location is being dragged around, its still owned by its original Machine. Only when 26 | // this method is called, the location may be re-parented into the new drop target. 27 | // The drop target is responsible for re-parenting the location! 28 | virtual void DropLocation(Ptr&&) = 0; 29 | }; 30 | } // namespace gui 31 | 32 | struct DragLocationAction; 33 | 34 | struct DragLocationWidget : gui::Widget { 35 | DragLocationAction& action; 36 | DragLocationWidget(DragLocationAction& action) : action(action) {} 37 | SkPath Shape() const override; 38 | void FillChildren(Vec>& children) override; 39 | Optional TextureBounds() const override { return std::nullopt; } 40 | }; 41 | 42 | struct DragLocationAction : Action { 43 | Vec2 contact_point; // in the coordinate space of the dragged Object 44 | Vec2 last_position; // root machine coordinates 45 | Vec2 current_position; // root machine coordinates 46 | Vec2 last_snapped_position; // root machine coordinates 47 | time::SteadyPoint last_update; 48 | Vec> locations; 49 | Ptr widget; 50 | 51 | DragLocationAction(gui::Pointer&, Ptr&&, Vec2 contact_point); 52 | DragLocationAction(gui::Pointer&, Vec>&&, Vec2 contact_point); 53 | ~DragLocationAction() override; 54 | 55 | void Update() override; 56 | gui::Widget* Widget() override { return widget.get(); } 57 | }; 58 | 59 | bool IsDragged(const Location& location); 60 | 61 | } // namespace automat -------------------------------------------------------------------------------- /src/drawable.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include "drawable.hh" 4 | 5 | namespace automat { 6 | 7 | TextDrawable::TextDrawable(StrView text, float letter_size, gui::Font& font) 8 | : text(text), letter_size(letter_size), font(font) { 9 | width = font.MeasureText(text); 10 | } 11 | 12 | void TextDrawable::onDraw(SkCanvas* canvas) { 13 | canvas->translate(-width / 2, -letter_size / 2); 14 | font.DrawText(*canvas, text, paint); 15 | } 16 | 17 | } // namespace automat -------------------------------------------------------------------------------- /src/drawable.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "font.hh" 10 | #include "str.hh" 11 | 12 | namespace automat { 13 | 14 | // A minimal alternative to SkDrawable, for internal use in Automat. 15 | // Can be used to represent arbitrary object that can be drawn on a canvas. 16 | struct Drawable { 17 | virtual ~Drawable() = default; 18 | virtual void onDraw(SkCanvas*) = 0; 19 | }; 20 | 21 | // A base class for Drawables that may be drawn with arbitrary SkPaint. 22 | struct PaintDrawable : Drawable { 23 | SkPaint paint; 24 | }; 25 | 26 | struct TextDrawable : PaintDrawable { 27 | Str text; 28 | float width; 29 | float letter_size; 30 | gui::Font& font; 31 | TextDrawable(StrView text, float letter_size, gui::Font& font); 32 | 33 | void onDraw(SkCanvas* canvas) override; 34 | }; 35 | 36 | struct DrawableSkPath : PaintDrawable { 37 | SkPath path; 38 | DrawableSkPath(SkPath path) : path(std::move(path)) {} 39 | void onDraw(SkCanvas* c) override { c->drawPath(path, paint); } 40 | }; 41 | 42 | } // namespace automat -------------------------------------------------------------------------------- /src/drawable_rtti.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include "drawable_rtti.hh" 4 | 5 | #pragma maf add compile argument "-fno-rtti" 6 | 7 | namespace automat { 8 | 9 | struct SkDrawableNoRTTI : SkDrawable { 10 | std::unique_ptr drawable; 11 | SkDrawableNoRTTI(std::unique_ptr drawable) : drawable(std::move(drawable)) {} 12 | SkRect onGetBounds() override { return drawable->onGetBounds(); } 13 | void onDraw(SkCanvas* c) override { drawable->onDraw(c); } 14 | const char* getTypeName() const override { return drawable->getTypeName(); } 15 | void flatten(SkWriteBuffer& buffer) const override { drawable->flatten(buffer); } 16 | }; 17 | 18 | SkDrawableRTTI& SkDrawableRTTI::Unwrap(SkDrawable& sk) { 19 | return *static_cast(sk).drawable; 20 | } 21 | 22 | sk_sp SkDrawableRTTI::Wrap(std::unique_ptr drawable) { 23 | return sk_sp(new SkDrawableNoRTTI(std::move(drawable))); 24 | } 25 | 26 | } // namespace automat -------------------------------------------------------------------------------- /src/drawable_rtti.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace automat { 10 | 11 | struct SkDrawableRTTI; 12 | 13 | template 14 | concept SkDrawableRTTI_Subclass = std::derived_from; 15 | 16 | // For efficiency and Objective-C compatibility, Skia avoids C++ RTTI. Because of that it's not 17 | // possible to derive from Skia base classes in Automat. This class provides a workaround for that. 18 | // It can be used as a base class to provide an SkDrawable-compatible interface which then can be 19 | // wrapped in a custom SkDrawable adapter compiled with `-fno-rtti`. 20 | struct SkDrawableRTTI { 21 | SkDrawableRTTI() = default; 22 | virtual ~SkDrawableRTTI() = default; 23 | 24 | virtual SkRect onGetBounds() = 0; 25 | virtual void onDraw(SkCanvas*) = 0; 26 | virtual const char* getTypeName() const = 0; 27 | virtual void flatten(SkWriteBuffer&) const = 0; 28 | 29 | // Create an instance of T (derived from SkDrawableRTTI), wrap it in a sk_sp and 30 | // return it. Optionally store a pointer to the typed instance in typed_ptr. Once 31 | // sk_sp is destroyed, the *typed_ptr becomes invalid. 32 | template 33 | static sk_sp Make(T** typed_ptr = nullptr, Args&&... args) { 34 | T* ptr = new T(std::forward(args)...); 35 | if (typed_ptr) { 36 | *typed_ptr = ptr; 37 | } 38 | return Wrap(std::unique_ptr(ptr)); 39 | } 40 | 41 | static SkDrawableRTTI& Unwrap(SkDrawable&); 42 | 43 | private: 44 | static sk_sp Wrap(std::unique_ptr drawable); 45 | }; 46 | 47 | } // namespace automat -------------------------------------------------------------------------------- /src/drawing.cc: -------------------------------------------------------------------------------- 1 | #include "drawing.hh" 2 | 3 | #include 4 | 5 | #include "sincos.hh" 6 | 7 | namespace automat { 8 | 9 | void SetRRectShader(SkPaint& paint, const RRect& rrect, SkColor top, SkColor middle, 10 | SkColor bottom) { 11 | // Get the center point of the rounded rectangle 12 | SkPoint center = rrect.Center(); 13 | 14 | // Define color stops for a sweep gradient 15 | // We'll use strategic positions to create the transitions between colors 16 | constexpr int count = 8; 17 | SkColor colors[count] = { 18 | middle, // right top 19 | top, // top right 20 | top, // top left 21 | middle, // left top 22 | middle, // left bottom 23 | bottom, // bottom left 24 | bottom, // bottom right 25 | middle, // right bottom 26 | }; 27 | 28 | auto Angle = [](Vec2 v) -> float { return SinCos::FromVec2(v).ToRadiansPositive() / M_PI / 2; }; 29 | 30 | // Position stops at strategic angles (in 0-1 range where 1.0 = 360°, 0 = stright right) 31 | float positions[count] = { 32 | Angle(rrect.LineEndRightUpper()), Angle(rrect.LineEndUpperRight()), 33 | Angle(rrect.LineEndUpperLeft()), Angle(rrect.LineEndLeftUpper()), 34 | Angle(rrect.LineEndLeftLower()), Angle(rrect.LineEndLowerLeft()), 35 | Angle(rrect.LineEndLowerRight()), Angle(rrect.LineEndRightLower()), 36 | }; 37 | 38 | paint.setShader(SkGradientShader::MakeSweep(center.x(), center.y(), colors, positions, count)); 39 | } 40 | 41 | } // namespace automat -------------------------------------------------------------------------------- /src/drawing.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2025 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | 7 | #include "math.hh" 8 | 9 | // Utilities for drawing things on the screen. 10 | 11 | namespace automat { 12 | 13 | // Configure the paint to draw a smooth gradient that shades the given rrect from top to bottom. 14 | // 15 | // This should be used for borders - the inner color of the SkPaint will draw artifacts. 16 | // 17 | // TODO: switch this from a simple conic gradient into a proper rrect-based shader. 18 | void SetRRectShader(SkPaint& paint, const RRect& rrect, SkColor top, SkColor middle, 19 | SkColor bottom); 20 | 21 | } // namespace automat -------------------------------------------------------------------------------- /src/embedded.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include "../build/generated/embedded.hh" // IWYU pragma: export 6 | -------------------------------------------------------------------------------- /src/error.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include "error.hh" 4 | 5 | #include "object.hh" 6 | 7 | namespace automat { 8 | 9 | Error::~Error() {} 10 | 11 | Error::Error(std::string_view text, std::source_location source_location) 12 | : text(text), source(nullptr), saved_object(nullptr), source_location(source_location) {} 13 | } // namespace automat -------------------------------------------------------------------------------- /src/error.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include "ptr.hh" 9 | 10 | namespace automat { 11 | 12 | struct Location; 13 | struct Object; 14 | 15 | /* 16 | The goal of Errors is to explain to the user what went wrong and help with 17 | recovery. 18 | 19 | Errors can be placed in Locations (alongside Objects). Each location can hold up 20 | to one Error. 21 | 22 | While present, Errors pause the execution at their locations. Each object is 23 | responsible for checking the error at its location and taking it into account 24 | when executing itself. 25 | 26 | Errors keep track of their source (object? location?) which is usually the same 27 | as their location. Some objects can trigger errors at remote locations to pause 28 | them. 29 | 30 | Errors can be cleaned by the user or by their source. The source of the error 31 | should clean it automatically - but sometimes it can be executed explicitly to 32 | recheck conditions & clean the error. Errors caused by failing preconditions 33 | clear themselves automatically when an object is executed. 34 | 35 | Errors can also save objects that would otherwise be deleted. The objects are 36 | held in the Error instance and can be accessed by the user. 37 | 38 | In the UI the errors are visualized as spiders sitting on the error locations. 39 | Source of the error is indicated by a spider web. Saved objects are cocoons. 40 | 41 | When an error is added to an object it causes a notification to be sent to all 42 | `error_observers` of the object. The observers may fix the error or notify the 43 | user somehow. The parent Machine is an implicit error observer and propagates 44 | the error upwards. Top-level Machines print their errors to the console. 45 | */ 46 | struct Error { 47 | std::string text; 48 | Location* source; 49 | Ptr saved_object; 50 | std::source_location source_location; 51 | 52 | Error(std::string_view text, 53 | std::source_location source_location = std::source_location::current()); 54 | 55 | ~Error(); 56 | }; 57 | 58 | inline std::ostream& operator<<(std::ostream& os, const Error& e) { return os << e.text; } 59 | 60 | } // namespace automat 61 | -------------------------------------------------------------------------------- /src/fasttrigo.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | # SPDX-License-Identifier: MIT 3 | from functools import partial 4 | from subprocess import Popen 5 | from sys import platform 6 | 7 | import cmake 8 | import fs_utils 9 | import build 10 | 11 | FASTTRIGO_ROOT = fs_utils.third_party_dir / 'FastTrigo' 12 | FASTTRIGO_INCLUDE = FASTTRIGO_ROOT 13 | 14 | build.base.compile_args += ['-I', FASTTRIGO_INCLUDE] 15 | 16 | def get_object_path(build_type : build.BuildType): 17 | return fs_utils.build_dir / 'FastTrigo' / build_type.name / 'fasttrigo.o' 18 | 19 | def hook_recipe(recipe): 20 | for build_type in build.types: 21 | object_path = get_object_path(build_type) 22 | object_path.parent.mkdir(parents=True, exist_ok=True) 23 | outputs = [str(object_path)] 24 | recipe.add_step( 25 | partial(Popen, [build.compiler] + build_type.CXXFLAGS() + 26 | ['-c', str(FASTTRIGO_ROOT / 'fasttrigo.cpp'), '-o'] + outputs), 27 | outputs=outputs, 28 | inputs=[FASTTRIGO_ROOT / 'fasttrigo.cpp'], 29 | desc=f'Building FastTrigo {build_type}'.strip(), 30 | shortcut=f'build fasttrigo {build_type}'.strip()) 31 | 32 | fasttrigo_bins = set() 33 | 34 | def hook_plan(srcs, objs, bins, recipe): 35 | fasttrigo_objs = set() 36 | for obj in objs: 37 | if 'fasttrigo.h' in obj.source.system_includes: 38 | fasttrigo_objs.add(obj) 39 | 40 | for bin in bins: 41 | if fasttrigo_objs.intersection(bin.objects): 42 | bin.link_args.append(str(get_object_path(bin.build_type))) 43 | fasttrigo_bins.add(bin) 44 | 45 | def hook_final(srcs, objs, bins, recipe): 46 | for step in recipe.steps: 47 | needs_fasttrigo = False 48 | build_type = None 49 | for bin in fasttrigo_bins: 50 | if str(bin.path) in step.outputs: 51 | needs_fasttrigo = True 52 | build_type = bin.build_type 53 | break 54 | 55 | if needs_fasttrigo: 56 | step.inputs.add(str(get_object_path(build_type))) 57 | -------------------------------------------------------------------------------- /src/fn.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | 7 | // Shortcut for std::function 8 | namespace automat { 9 | 10 | template 11 | using Fn = std::function; 12 | 13 | template 14 | struct FnIs { 15 | const T* bare_ptr; 16 | FnIs(T* bare_ptr) : bare_ptr(bare_ptr) {} 17 | 18 | bool operator()(const Fn& fn) const { 19 | T* const* fn_ptr_ptr = fn.template target(); 20 | return (fn_ptr_ptr != nullptr) && (*fn_ptr_ptr == bare_ptr); 21 | } 22 | }; 23 | 24 | } // namespace automat -------------------------------------------------------------------------------- /src/font.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "virtual_fs.hh" 13 | 14 | namespace automat::gui { 15 | 16 | struct Font { 17 | SkFont sk_font; 18 | float font_scale; 19 | float line_thickness; 20 | float letter_height; 21 | float ascent; // distance to reserve above baseline, typically negative 22 | float descent; // distance to reserve below baseline, typically positive 23 | 24 | static sk_sp LoadTypeface(fs::VFile& ttf_file); 25 | static sk_sp GetNotoSans(); 26 | static sk_sp GetGrenzeThin(); 27 | static sk_sp GetGrenzeLight(); 28 | static sk_sp GetGrenzeRegular(); 29 | static sk_sp GetGrenzeSemiBold(); 30 | static sk_sp GetSilkscreen(); 31 | static sk_sp GetHeavyData(); 32 | static sk_sp GetHelsinki(); 33 | static sk_sp MakeWeightVariation(sk_sp base, float weight); 34 | static std::unique_ptr MakeV2(sk_sp typeface, float letter_size); 35 | 36 | // TODO: If this causes performance issues, cache text shaping / SkTextBlob 37 | // results somehow 38 | void DrawText(SkCanvas& canvas, std::string_view text, const SkPaint& paint); 39 | float MeasureText(std::string_view text); 40 | float PositionFromIndex(std::string_view text, int index); 41 | int IndexFromPosition(std::string_view text, float x); 42 | // TODO: If this causes performance issues, use ICU directly rather than going 43 | // through SkShaper 44 | int PrevIndex(std::string_view text, int index); 45 | int NextIndex(std::string_view text, int index); 46 | }; 47 | 48 | sk_sp GetFontMgr(); 49 | Font& GetFont(); 50 | 51 | } // namespace automat::gui -------------------------------------------------------------------------------- /src/format.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include "str.hh" 6 | 7 | #if !__has_builtin(__builtin_dump_struct) 8 | #include 9 | #endif 10 | 11 | namespace automat { 12 | 13 | // TODO: replace this with std::format when it's available 14 | Str f(const char* fmt, ...); 15 | 16 | // Prefix each line with `spaces` spaces. 17 | std::string IndentString(std::string in, int spaces = 2); 18 | 19 | std::string Slugify(std::string in); 20 | 21 | #pragma clang diagnostic push 22 | #pragma clang diagnostic ignored "-Wformat-security" 23 | constexpr void constexpr_sprintf(std::string& out, const char* format, auto... args) { 24 | int n = snprintf(nullptr, 0, format, args...) + 1; 25 | char buf[n]; 26 | snprintf(buf, n, format, args...); 27 | out += buf; 28 | } 29 | #pragma clang diagnostic pop 30 | 31 | // Convert a platform-specific type name (obtained from type_info.name()) into a short class name. 32 | std::string_view CleanTypeName(std::string_view mangled); 33 | 34 | template 35 | std::string dump_struct(const T& t) { 36 | std::string s; 37 | #if __has_builtin(__builtin_dump_struct) 38 | __builtin_dump_struct(&t, constexpr_sprintf, s); 39 | #else 40 | #if __cpp_rtti 41 | s += typeid(T).name(); 42 | s += ' '; 43 | #endif 44 | for (int i = 0; i < sizeof(T); ++i) { 45 | constexpr_sprintf(s, "%02x ", ((unsigned char*)&t)[i]); 46 | } 47 | #endif 48 | return s; 49 | } 50 | 51 | } // namespace automat -------------------------------------------------------------------------------- /src/global_resources.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "global_resources.hh" 5 | 6 | #include "path.hh" 7 | 8 | namespace automat::resources { 9 | 10 | std::vector>> mesh_specifications; 11 | std::vector>> sk_ref_cnt_objects; 12 | 13 | template <> 14 | sk_sp& Hold(sk_sp spec) { 15 | mesh_specifications.emplace_back(std::make_unique>(spec)); 16 | return *mesh_specifications.back(); 17 | } 18 | 19 | template <> 20 | sk_sp& Hold(sk_sp shader) { 21 | sk_ref_cnt_objects.emplace_back(std::make_unique>(shader)); 22 | return *reinterpret_cast*>(sk_ref_cnt_objects.back().get()); 23 | } 24 | 25 | template <> 26 | sk_sp& Hold(sk_sp shader) { 27 | sk_ref_cnt_objects.emplace_back(std::make_unique>(shader)); 28 | return *reinterpret_cast*>(sk_ref_cnt_objects.back().get()); 29 | } 30 | 31 | void Release() { 32 | mesh_specifications.clear(); 33 | sk_ref_cnt_objects.clear(); 34 | } 35 | 36 | sk_sp CompileShader(fs::VFile sksl_file, Status& status) { 37 | auto fs = SkString(sksl_file.content); 38 | SkRuntimeEffect::Options options; 39 | auto name = Path(sksl_file.path).Stem(); 40 | options.fName = name; 41 | auto result = SkRuntimeEffect::MakeForShader(fs, options); 42 | if (!result.errorText.isEmpty()) { 43 | AppendErrorMessage(status) += result.errorText.c_str(); 44 | return nullptr; 45 | } 46 | return Hold(std::move(result.effect)); // creates a copy of sk_sp (it's fine) 47 | } 48 | } // namespace automat::resources 49 | -------------------------------------------------------------------------------- /src/global_resources.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "status.hh" 10 | #include "virtual_fs.hh" 11 | 12 | namespace automat::resources { 13 | 14 | // Make a copy of the given sk_sp and return a reference to it. 15 | // 16 | // The sk_sp will be released when `Release()` is called. 17 | // 18 | // This can be used to cache an expensive-to-compute resource. 19 | template 20 | sk_sp& Hold(sk_sp); 21 | 22 | template <> 23 | sk_sp& Hold(sk_sp); 24 | 25 | template <> 26 | sk_sp& Hold(sk_sp); 27 | 28 | template <> 29 | sk_sp& Hold(sk_sp); 30 | 31 | sk_sp CompileShader(fs::VFile sksl_file, Status& status); 32 | 33 | void Release(); 34 | 35 | } // namespace automat::resources 36 | -------------------------------------------------------------------------------- /src/gtest.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include "gtest.hh" 4 | 5 | int main(int argc, char** argv) { 6 | testing::InitGoogleTest(&argc, argv); 7 | return RUN_ALL_TESTS(); 8 | } -------------------------------------------------------------------------------- /src/gtest.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include // IWYU pragma: export 4 | #include // IWYU pragma: export 5 | 6 | #pragma maf add link argument "-lgmock" 7 | #pragma maf add link argument "-lgtest" 8 | #pragma maf add run argument "--gtest_color=yes" 9 | #pragma maf main -------------------------------------------------------------------------------- /src/gtest.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | # SPDX-License-Identifier: MIT 3 | import build 4 | from extension_helper import ExtensionHelper 5 | 6 | hook = ExtensionHelper('googletest', globals()) 7 | hook.FetchFromURL('https://github.com/google/googletest/releases/download/v1.16.0/googletest-1.16.0.tar.gz') 8 | hook.ConfigureOption('CMAKE_CXX_STANDARD', '20') 9 | hook.ConfigureWithCMake('{PREFIX}/include/gtest/gtest.h') 10 | hook.InstallWhenIncluded(r'(gmock/gmock.h|gtest/gtest.h)') 11 | 12 | # Shortcut recipe for running all tests (default build type) 13 | # def hook_final(srcs, objs, bins, recipe): 14 | # tests = [ 15 | # bin for bin in bins if '-lgtest' in bin.link_args and bin.build_type == build.fast and bin.path.stem != 'gtest'] 16 | 17 | # def run_tests(): 18 | # for test in tests: 19 | # print(f'Running {test.path}') 20 | # run([str(test.path)] + test.run_args, check=True) 21 | 22 | # recipe.add_step(run_tests, outputs=[], inputs=tests, 23 | # desc='Running tests', shortcut='tests') 24 | -------------------------------------------------------------------------------- /src/gui_constants.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include "units.hh" 6 | 7 | namespace automat::gui { 8 | 9 | static constexpr float kMinimalTouchableSize = 8_mm; 10 | static constexpr float kBorderWidth = 1_mm / 2; 11 | static constexpr float kMargin = 1_mm; 12 | static constexpr float kLetterSize = 3_mm; 13 | static constexpr float kLetterSizeMM = kLetterSize * 1000; 14 | 15 | } // namespace automat::gui -------------------------------------------------------------------------------- /src/gui_shape_widget.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include "gui_shape_widget.hh" 4 | 5 | #include "svg.hh" 6 | 7 | namespace automat::gui { 8 | 9 | ShapeWidget::ShapeWidget(SkPath path) : path(path) {} 10 | 11 | SkPath ShapeWidget::Shape() const { return path; } 12 | 13 | void ShapeWidget::Draw(SkCanvas& canvas) const { canvas.drawPath(path, paint); } 14 | 15 | Ptr MakeShapeWidget(const char* svg_path, SkColor fill_color, const SkMatrix* transform) { 16 | SkPath path = PathFromSVG(svg_path); 17 | if (transform) { 18 | path.transform(*transform); 19 | } 20 | auto ret = MakePtr(path); 21 | ret->paint.setAntiAlias(true); 22 | ret->paint.setColor(fill_color); 23 | return ret; 24 | } 25 | 26 | } // namespace automat::gui -------------------------------------------------------------------------------- /src/gui_shape_widget.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include "widget.hh" 9 | 10 | namespace automat::gui { 11 | 12 | struct ShapeWidget : Widget, PaintMixin { 13 | SkPath path; 14 | 15 | ShapeWidget(SkPath path); 16 | SkPath Shape() const override; 17 | void Draw(SkCanvas&) const override; 18 | bool CenteredAtZero() const override { return true; } 19 | }; 20 | 21 | Ptr MakeShapeWidget(const char* svg_path, SkColor fill_color, 22 | const SkMatrix* transform = nullptr); 23 | 24 | } // namespace automat::gui -------------------------------------------------------------------------------- /src/gui_text.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include "gui_text.hh" 4 | 5 | #include "font.hh" 6 | #include "gui_constants.hh" 7 | 8 | using namespace std; 9 | 10 | namespace automat::gui { 11 | 12 | Text::Text(string_view text) : text(text) {}; 13 | 14 | SkPath Text::Shape() const { 15 | float w = GetFont().MeasureText(text); 16 | return SkPath::Rect(SkRect::MakeWH(w, kLetterSize)); 17 | } 18 | 19 | void Text::Draw(SkCanvas& canvas) const { GetFont().DrawText(canvas, text, paint); } 20 | 21 | Optional Text::TextureBounds() const { return nullopt; } 22 | } // namespace automat::gui -------------------------------------------------------------------------------- /src/gui_text.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include "widget.hh" 6 | 7 | namespace automat::gui { 8 | 9 | struct Text : Widget, PaintMixin { 10 | std::string text; 11 | Text(std::string_view text = ""); 12 | SkPath Shape() const override; 13 | void Draw(SkCanvas&) const override; 14 | StrView Name() const override { return "Text"; } 15 | Optional TextureBounds() const override; 16 | }; 17 | 18 | } // namespace automat::gui -------------------------------------------------------------------------------- /src/hex.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include "arr.hh" 6 | #include "span.hh" 7 | #include "str.hh" 8 | #include "vec.hh" 9 | 10 | namespace automat { 11 | 12 | void HexToBytesUnchecked(StrView hex, char* out_bytes); 13 | 14 | Str BytesToHex(Span<> bytes); 15 | 16 | inline Str BytesToHex(const char* bytes, size_t len) { 17 | return BytesToHex(Span<>{const_cast(bytes), len}); 18 | } 19 | 20 | constexpr U8 HexToU8(char c) { 21 | if (c >= '0' && c <= '9') return c - '0'; 22 | if (c >= 'a' && c <= 'f') return c - 'a' + 10; 23 | if (c >= 'A' && c <= 'F') return c - 'A' + 10; 24 | return 0; 25 | } 26 | 27 | constexpr U8 HexToU8(const char c[2]) { return (HexToU8(c[0]) << 4) | HexToU8(c[1]); } 28 | 29 | template 30 | inline Str ValToHex(const T& val) { 31 | return BytesToHex(Span<>((char*)(&val), sizeof(T))); 32 | } 33 | 34 | inline automat::Vec<> operator""_HexVec(const char* str, size_t len) { 35 | automat::Vec buf; 36 | buf.resize(len / 2); 37 | HexToBytesUnchecked({str, len}, buf.data()); 38 | return buf; 39 | } 40 | 41 | template 42 | constexpr Arr HexArr(const char (&str)[N]) { 43 | Arr arr; 44 | HexToBytesUnchecked(StrView(str, N - 1), arr.data()); 45 | return arr; 46 | } 47 | 48 | // Print the given bytes as a hex dump. 49 | // 50 | // Each printed live covers 16 bytes. 51 | // Left side has hex offsets, then 16-column hex string with spaces between 52 | // every 4 bytes and on the right side - ASCII (or '.'). 53 | Str HexDump(StrView bytes); 54 | Str HexDump(Span<> bytes); 55 | 56 | } // namespace automat -------------------------------------------------------------------------------- /src/hidapi.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #if defined(_WIN32) 4 | 5 | #pragma comment(lib, "hid") 6 | 7 | // clang-format off 8 | #include 9 | #include 10 | // clang-format on 11 | 12 | #include "win_hidapi.c" 13 | 14 | #elif defined(__linux__) 15 | 16 | #include "linux_hidapi.c" 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/hidapi_hidclass.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | libusb/hidapi Team 6 | 7 | Copyright 2022, All Rights Reserved. 8 | 9 | At the discretion of the user of this library, 10 | this software may be licensed under the terms of the 11 | GNU General Public License v3, a BSD-Style license, or the 12 | original HIDAPI license as outlined in the LICENSE.txt, 13 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 14 | files located at the root of the source distribution. 15 | These files may also be found in the public source 16 | code repository located at: 17 | https://github.com/libusb/hidapi . 18 | ********************************************************/ 19 | 20 | #ifndef HIDAPI_HIDCLASS_H 21 | #define HIDAPI_HIDCLASS_H 22 | 23 | #ifdef HIDAPI_USE_DDK 24 | 25 | #include 26 | 27 | #else 28 | 29 | /* This part of the header mimics hidclass.h, 30 | but only what is used by HIDAPI */ 31 | 32 | #define HID_OUT_CTL_CODE(id) \ 33 | CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS) 34 | #define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100) 35 | #define IOCTL_HID_GET_INPUT_REPORT HID_OUT_CTL_CODE(104) 36 | 37 | #endif 38 | 39 | #endif /* HIDAPI_HIDCLASS_H */ 40 | -------------------------------------------------------------------------------- /src/hidapi_hidpi.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | libusb/hidapi Team 6 | 7 | Copyright 2022, All Rights Reserved. 8 | 9 | At the discretion of the user of this library, 10 | this software may be licensed under the terms of the 11 | GNU General Public License v3, a BSD-Style license, or the 12 | original HIDAPI license as outlined in the LICENSE.txt, 13 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 14 | files located at the root of the source distribution. 15 | These files may also be found in the public source 16 | code repository located at: 17 | https://github.com/libusb/hidapi . 18 | ********************************************************/ 19 | 20 | #ifndef HIDAPI_HIDPI_H 21 | #define HIDAPI_HIDPI_H 22 | 23 | #ifdef HIDAPI_USE_DDK 24 | 25 | #include 26 | 27 | #else 28 | 29 | /* This part of the header mimics hidpi.h, 30 | but only what is used by HIDAPI */ 31 | 32 | typedef enum _HIDP_REPORT_TYPE { HidP_Input, HidP_Output, HidP_Feature } HIDP_REPORT_TYPE; 33 | 34 | typedef struct _HIDP_PREPARSED_DATA* PHIDP_PREPARSED_DATA; 35 | 36 | typedef struct _HIDP_CAPS { 37 | USAGE Usage; 38 | USAGE UsagePage; 39 | USHORT InputReportByteLength; 40 | USHORT OutputReportByteLength; 41 | USHORT FeatureReportByteLength; 42 | USHORT Reserved[17]; 43 | 44 | USHORT NumberLinkCollectionNodes; 45 | 46 | USHORT NumberInputButtonCaps; 47 | USHORT NumberInputValueCaps; 48 | USHORT NumberInputDataIndices; 49 | 50 | USHORT NumberOutputButtonCaps; 51 | USHORT NumberOutputValueCaps; 52 | USHORT NumberOutputDataIndices; 53 | 54 | USHORT NumberFeatureButtonCaps; 55 | USHORT NumberFeatureValueCaps; 56 | USHORT NumberFeatureDataIndices; 57 | } HIDP_CAPS, *PHIDP_CAPS; 58 | 59 | #define HIDP_STATUS_SUCCESS 0x00110000 60 | #define HIDP_STATUS_INVALID_PREPARSED_DATA 0xc0110001 61 | 62 | typedef NTSTATUS(__stdcall* HidP_GetCaps_)(PHIDP_PREPARSED_DATA preparsed_data, PHIDP_CAPS caps); 63 | 64 | #endif 65 | 66 | #endif /* HIDAPI_HIDPI_H */ 67 | -------------------------------------------------------------------------------- /src/int.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace automat { 10 | 11 | using I8 = signed char; 12 | using I16 = signed short; 13 | using I32 = signed int; 14 | using I64 = signed long long; 15 | using I128 = __int128; // _BitInt(128); 16 | 17 | static_assert(sizeof(I64) == 8); 18 | 19 | using U8 = unsigned char; 20 | using U16 = unsigned short; 21 | 22 | struct U24 { 23 | unsigned char data[3]; 24 | constexpr U24(unsigned int x) 25 | : data{(unsigned char)(x & 0xff), (unsigned char)((x >> 8) & 0xff), 26 | (unsigned char)((x >> 16) & 0xff)} {} 27 | constexpr operator unsigned int() const { 28 | return (unsigned int)(data[0]) | ((unsigned int)(data[1]) << 8) | 29 | ((unsigned int)(data[2]) << 16); 30 | } 31 | } __attribute__((__packed__)); 32 | 33 | // using U24 = unsigned _BitInt(24); // this has size of 4! 34 | 35 | static_assert(sizeof(U24) == 3); 36 | 37 | using U32 = unsigned int; 38 | using U64 = unsigned long; 39 | using U128 = unsigned __int128; // _BitInt(128); 40 | 41 | using Size = size_t; 42 | using SSize = I64; 43 | 44 | } // namespace automat 45 | 46 | namespace std { 47 | 48 | template <> 49 | struct is_integral { 50 | static constexpr bool value = true; 51 | }; 52 | 53 | } // namespace std -------------------------------------------------------------------------------- /src/key.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | 7 | #include "str.hh" 8 | 9 | namespace automat::gui { 10 | 11 | enum class AnsiKey : uint8_t { 12 | Unknown, 13 | Escape, 14 | F1, 15 | F2, 16 | F3, 17 | F4, 18 | F5, 19 | F6, 20 | F7, 21 | F8, 22 | F9, 23 | F10, 24 | F11, 25 | F12, 26 | PrintScreen, 27 | ScrollLock, 28 | Pause, 29 | Insert, 30 | Delete, 31 | Home, 32 | End, 33 | PageUp, 34 | PageDown, 35 | Up, 36 | Down, 37 | Left, 38 | Right, 39 | NumLock, 40 | NumpadDivide, 41 | NumpadMultiply, 42 | NumpadMinus, 43 | NumpadPlus, 44 | NumpadEnter, 45 | NumpadPeriod, 46 | Numpad0, 47 | Numpad1, 48 | Numpad2, 49 | Numpad3, 50 | Numpad4, 51 | Numpad5, 52 | Numpad6, 53 | Numpad7, 54 | Numpad8, 55 | Numpad9, 56 | Grave, 57 | Digit1, 58 | Digit2, 59 | Digit3, 60 | Digit4, 61 | Digit5, 62 | Digit6, 63 | Digit7, 64 | Digit8, 65 | Digit9, 66 | Digit0, 67 | Minus, 68 | Equals, 69 | Backspace, 70 | Tab, 71 | Q, 72 | W, 73 | E, 74 | R, 75 | T, 76 | Y, 77 | U, 78 | I, 79 | O, 80 | P, 81 | BracketLeft, 82 | BracketRight, 83 | Backslash, 84 | CapsLock, 85 | A, 86 | S, 87 | D, 88 | F, 89 | G, 90 | H, 91 | J, 92 | K, 93 | L, 94 | Semicolon, 95 | Apostrophe, 96 | Enter, 97 | ShiftLeft, 98 | Z, 99 | X, 100 | C, 101 | V, 102 | B, 103 | N, 104 | M, 105 | Comma, 106 | Period, 107 | Slash, 108 | ShiftRight, 109 | ControlLeft, 110 | SuperLeft, 111 | AltLeft, 112 | Space, 113 | AltRight, 114 | SuperRight, 115 | Application, 116 | ControlRight, 117 | Count 118 | }; 119 | 120 | StrView ToStr(AnsiKey) noexcept; 121 | AnsiKey AnsiKeyFromStr(StrView) noexcept; 122 | 123 | struct Key { 124 | bool ctrl; 125 | bool alt; 126 | bool shift; 127 | bool windows; 128 | AnsiKey physical; 129 | AnsiKey logical; 130 | std::string text; 131 | }; 132 | 133 | } // namespace automat::gui -------------------------------------------------------------------------------- /src/key_button.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | 7 | #include "color.hh" 8 | #include "font.hh" 9 | #include "gui_button.hh" 10 | #include "gui_constants.hh" 11 | 12 | namespace automat::library { 13 | 14 | using namespace automat::gui; 15 | 16 | static constexpr float kKeyHeight = gui::kMinimalTouchableSize; 17 | static constexpr float kBaseKeyWidth = kKeyHeight; 18 | 19 | static constexpr float kKeyLetterSize = 2.4_mm; 20 | 21 | // Margins around the key face 22 | static constexpr float kKeyTopSide = 0.5_mm; 23 | static constexpr float kKeySide = 1_mm; 24 | static constexpr float kKeyBottomSide = 1.5_mm; 25 | 26 | static constexpr float kKeyFaceRadius = 1_mm; 27 | static constexpr float kKeyBaseRadius = kKeyFaceRadius; 28 | 29 | gui::Font& KeyFont(); 30 | 31 | // static constexpr float kKeySpareHeight = kKeyHeight - kKeyLetterSize; 32 | // static constexpr float kKeyFaceHeight = kKeyHeight - kKeyTopSide - kKeyBottomSide; 33 | 34 | struct KeyButton : gui::Button { 35 | float width; 36 | std::function activate; 37 | SkColor fg; 38 | KeyButton(Ptr child, SkColor color, float width); 39 | void Activate(gui::Pointer&) override; 40 | SkRRect RRect() const override; 41 | SkColor ForegroundColor() const override { return fg; } 42 | void DrawButtonFace(SkCanvas&, SkColor bg, SkColor fg) const override; 43 | StrView Name() const override { return "KeyButton"; } 44 | void SetLabel(StrView new_label); 45 | }; 46 | 47 | Ptr MakeKeyLabelWidget(StrView label); 48 | 49 | static constexpr SkColor kKeyEnabledColor = "#f3a75b"_color; 50 | static constexpr SkColor kKeyDisabledColor = "#f4efea"_color; 51 | static constexpr SkColor kKeyGrabbingColor = "#f15555"_color; 52 | 53 | static SkColor KeyColor(bool enabled) { return enabled ? kKeyEnabledColor : kKeyDisabledColor; } 54 | 55 | } // namespace automat::library -------------------------------------------------------------------------------- /src/knob.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "math.hh" 6 | #include "sincos.hh" 7 | #include "units.hh" 8 | 9 | namespace automat { 10 | 11 | static constexpr bool kDebugKnob = false; 12 | 13 | // Turns a gesture into a continuous value. 14 | // 15 | // Allows scrolling and turning in any direction. 16 | struct Knob { 17 | // Better don't use this. 18 | std::deque history; 19 | 20 | // Use these to parametrize the knob. 21 | SinCos unit_angle = SinCos::FromDegrees(45); 22 | float unit_distance = 5_mm; 23 | 24 | // Use this to read out the current value of the knob. 25 | float value = 0; 26 | 27 | // Use this to read out the current direction of the values. 28 | // 29 | // Initially the values increase towards the right. 30 | SinCos tangent = SinCos::FromDegrees(0); 31 | 32 | // Use this to read out the current curvature of the values. 33 | // 34 | // Initially the values are placed in a straight line. 35 | float radius = std::numeric_limits::infinity(); 36 | Vec2 center = {0, 0}; 37 | 38 | void Update(Vec2 position); 39 | }; 40 | 41 | } // namespace automat -------------------------------------------------------------------------------- /src/leptonica.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright 2025 Automat Authors 2 | # SPDX-License-Identifier: MIT 3 | 4 | import sys 5 | import build 6 | import extension_helper 7 | 8 | hook = extension_helper.ExtensionHelper('Leptonica', globals()) 9 | 10 | def leptonica_output(build_type : build.BuildType): 11 | if sys.platform == 'linux': 12 | return build_type.PREFIX() / 'lib' / 'libleptonica.a' 13 | elif sys.platform == 'win32': 14 | return build_type.PREFIX() / 'lib' / 'leptonica.lib' 15 | 16 | hook.FetchFromURL('https://github.com/DanBloomberg/leptonica/releases/download/1.85.0/leptonica-1.85.0.tar.gz') 17 | hook.ConfigureOptions(SW_BUILD='OFF', 18 | ENABLE_ZLIB='OFF', 19 | ENABLE_PNG='OFF', 20 | ENABLE_GIF='OFF', 21 | ENABLE_JPEG='OFF', 22 | ENABLE_TIFF='OFF', 23 | ENABLE_WEBP='OFF', 24 | ENABLE_OPENJPEG='OFF', 25 | CMAKE_C_FLAGS='-DNO_CONSOLE_IO=1') 26 | hook.ConfigureWithCMake(leptonica_output) 27 | hook.AddLinkArg('-lleptonica') 28 | if sys.platform == 'win32': 29 | hook.AddLinkArg(lambda t: ['-lmsvcrt' + ('d' if t == build.debug else '')]) 30 | hook.InstallWhenIncluded(r'^leptonica/.*\.h$') 31 | -------------------------------------------------------------------------------- /src/library_alert.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include "library_alert.hh" 4 | 5 | #include "base.hh" 6 | 7 | #ifdef _WIN32 8 | #include 9 | 10 | #include "root_widget.hh" 11 | #include "win32_window.hh" 12 | 13 | #endif 14 | 15 | namespace automat { 16 | 17 | Argument Alert::message_arg("message", Argument::kRequiresObject); 18 | 19 | void Alert::OnRun(Location& here) { 20 | auto message = message_arg.GetObject(here); 21 | if (message.ok) { 22 | string text = message.object->GetText(); 23 | if (test_interceptor) { 24 | test_interceptor->push_back(text); 25 | } else { 26 | #ifdef _WIN32 27 | auto& win32_window = dynamic_cast(*gui::root_widget->window); 28 | MessageBox(win32_window.hwnd, text.data(), "Alert", MB_OK); 29 | #else // not Windows 30 | LOG << text; 31 | #endif 32 | } 33 | } 34 | } 35 | 36 | } // namespace automat -------------------------------------------------------------------------------- /src/library_alert.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include "base.hh" 6 | #include "object.hh" 7 | 8 | namespace automat { 9 | 10 | struct Alert : Object, Runnable { 11 | static Argument message_arg; 12 | std::unique_ptr> test_interceptor; 13 | string_view Name() const override { return "Alert"; } 14 | Ptr Clone() const override { return MakePtr(); } 15 | void Args(std::function cb) override { cb(message_arg); } 16 | void OnRun(Location& here) override; 17 | }; 18 | 19 | } // namespace automat -------------------------------------------------------------------------------- /src/library_flip_flop.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include "animation.hh" 6 | #include "base.hh" 7 | #include "gui_button.hh" 8 | 9 | namespace automat::library { 10 | 11 | struct FlipFlop; 12 | 13 | struct YingYangIcon : gui::Widget, gui::PaintMixin { 14 | YingYangIcon() {} 15 | void Draw(SkCanvas&) const override; 16 | SkPath Shape() const override; 17 | bool CenteredAtZero() const override { return true; } 18 | }; 19 | 20 | struct FlipFlopButton : gui::ToggleButton { 21 | FlipFlop* flip_flop; 22 | 23 | FlipFlopButton(); 24 | bool Filled() const override; 25 | }; 26 | 27 | struct FlipFlop : LiveObject, Object::FallbackWidget, Runnable { 28 | Ptr button; 29 | 30 | bool current_state = false; 31 | struct AnimationState { 32 | float light = 0; 33 | }; 34 | AnimationState animation_state; 35 | 36 | FlipFlop(); 37 | string_view Name() const override; 38 | Ptr Clone() const override; 39 | animation::Phase Tick(time::Timer& timer) override; 40 | void Draw(SkCanvas&) const override; 41 | SkPath Shape() const override; 42 | void Args(std::function cb) override; 43 | 44 | void SetKey(gui::AnsiKey); 45 | 46 | void FillChildren(Vec>& children) override; 47 | 48 | void OnRun(Location& here) override; 49 | void SerializeState(Serializer& writer, const char* key) const override; 50 | void DeserializeState(Location& l, Deserializer& d) override; 51 | }; 52 | 53 | } // namespace automat::library -------------------------------------------------------------------------------- /src/library_hotkey.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | 7 | #include "base.hh" 8 | #include "key_button.hh" 9 | #include "keyboard.hh" 10 | #include "on_off.hh" 11 | 12 | namespace automat::library { 13 | 14 | struct HotKey : LiveObject, Object::FallbackWidget, OnOff, gui::CaretOwner, gui::KeyGrabber { 15 | gui::AnsiKey key = gui::AnsiKey::F11; 16 | bool ctrl = true; 17 | bool alt = false; 18 | bool shift = false; 19 | bool windows = false; 20 | 21 | Ptr power_button; 22 | Ptr ctrl_button; 23 | Ptr alt_button; 24 | Ptr shift_button; 25 | Ptr windows_button; 26 | Ptr shortcut_button; 27 | 28 | // This is used to select the main hotkey 29 | gui::Caret* hotkey_selector = nullptr; 30 | 31 | // This is used to get hotkey events 32 | gui::KeyGrab* hotkey = nullptr; 33 | 34 | HotKey(); 35 | string_view Name() const override; 36 | Ptr Clone() const override; 37 | animation::Phase Tick(time::Timer&) override; 38 | void Draw(SkCanvas&) const override; 39 | SkPath Shape() const override; 40 | void Args(std::function cb) override; 41 | 42 | bool IsOn() const override; 43 | void On() override; 44 | void Off() override; 45 | 46 | void ReleaseCaret(gui::Caret&) override; 47 | void ReleaseKeyGrab(gui::KeyGrab&) override; 48 | 49 | void KeyDown(gui::Caret&, gui::Key) override; 50 | 51 | void KeyGrabberKeyDown(gui::KeyGrab&) override; 52 | void KeyGrabberKeyUp(gui::KeyGrab&) override; 53 | 54 | void FillChildren(Vec>& children) override; 55 | 56 | void SerializeState(Serializer& writer, const char* key) const override; 57 | void DeserializeState(Location& l, Deserializer& d) override; 58 | }; 59 | 60 | } // namespace automat::library -------------------------------------------------------------------------------- /src/library_increment.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include "base.hh" 6 | 7 | namespace automat::library { 8 | 9 | struct Increment : Object, gui::Widget, Runnable { 10 | static Argument target_arg; 11 | string_view Name() const override; 12 | Ptr Clone() const override; 13 | void Args(std::function cb) override { cb(target_arg); } 14 | void OnRun(Location& h) override; 15 | void Draw(SkCanvas&) const override; 16 | SkPath Shape() const override; 17 | }; 18 | 19 | } // namespace automat::library -------------------------------------------------------------------------------- /src/library_key_presser.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include "base.hh" 6 | #include "key_button.hh" 7 | 8 | namespace automat::library { 9 | 10 | struct KeyPresser; 11 | 12 | struct KeyPresserButton : KeyButton { 13 | KeyPresser* key_presser; 14 | KeyPresserButton(KeyPresser* key_presser, Ptr parent, SkColor color, float width) 15 | : key_presser(key_presser), KeyButton(parent, color, width) {} 16 | using KeyButton::KeyButton; 17 | float PressRatio() const override; 18 | }; 19 | 20 | struct KeyPresser : Object, Object::FallbackWidget, gui::CaretOwner, Runnable, LongRunning { 21 | gui::AnsiKey key = gui::AnsiKey::F; 22 | 23 | mutable Ptr shortcut_button; 24 | 25 | // This is used to select the pressed key 26 | gui::Caret* key_selector = nullptr; 27 | bool key_pressed = false; 28 | 29 | KeyPresser(gui::AnsiKey); 30 | KeyPresser(); 31 | ~KeyPresser() override; 32 | string_view Name() const override; 33 | Ptr Clone() const override; 34 | animation::Phase Tick(time::Timer&) override; 35 | void Draw(SkCanvas&) const override; 36 | SkPath Shape() const override; 37 | void ConnectionPositions(Vec& out_positions) const override; 38 | std::unique_ptr FindAction(gui::Pointer& p, gui::ActionTrigger btn) override; 39 | 40 | void KeyDown(gui::Caret&, gui::Key) override; 41 | void ReleaseCaret(gui::Caret&) override; 42 | 43 | void SetKey(gui::AnsiKey); 44 | 45 | void FillChildren(Vec>& children) override; 46 | bool AllowChildPointerEvents(Widget& child) const override { return false; } 47 | 48 | void OnRun(Location& here) override; 49 | void Cancel() override; 50 | 51 | void SerializeState(Serializer& writer, const char* key) const override; 52 | void DeserializeState(Location& l, Deserializer& d) override; 53 | }; 54 | 55 | } // namespace automat::library -------------------------------------------------------------------------------- /src/library_mouse_click.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include "base.hh" 6 | 7 | namespace automat::library { 8 | 9 | struct MouseClick : Object, Object::FallbackWidget, Runnable { 10 | gui::PointerButton button; 11 | bool down; 12 | MouseClick(gui::PointerButton, bool down); 13 | string_view Name() const override; 14 | Ptr Clone() const override; 15 | void Draw(SkCanvas&) const override; 16 | SkPath Shape() const override; 17 | void Args(std::function cb) override; 18 | void OnRun(Location&) override; 19 | audio::Sound& NextSound() override; 20 | }; 21 | 22 | } // namespace automat::library -------------------------------------------------------------------------------- /src/library_number.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include "base.hh" 6 | #include "gui_button.hh" 7 | #include "number_text_field.hh" 8 | 9 | namespace automat::library { 10 | 11 | struct Number; 12 | 13 | struct NumberButton : gui::Button { 14 | std::function activate; 15 | NumberButton(Ptr child); 16 | NumberButton(std::string text); 17 | void Activate(gui::Pointer&) override; 18 | StrView Name() const override { return "NumberButton"; } 19 | SkColor BackgroundColor() const override; 20 | }; 21 | 22 | struct Number : Object, Object::FallbackWidget { 23 | double value; 24 | Ptr digits[10]; 25 | Ptr dot; 26 | Ptr backspace; 27 | Ptr text_field; 28 | Number(double x = 0); 29 | string_view Name() const override; 30 | Ptr Clone() const override; 31 | string GetText() const override; 32 | void SetText(Location& error_context, string_view text) override; 33 | void Draw(SkCanvas&) const override; 34 | SkPath Shape() const override; 35 | void FillChildren(Vec>& children) override; 36 | 37 | void SerializeState(Serializer& writer, const char* key) const override; 38 | void DeserializeState(Location& l, Deserializer& d) override; 39 | }; 40 | 41 | } // namespace automat::library -------------------------------------------------------------------------------- /src/library_test.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include "library.hh" 4 | 5 | #include 6 | 7 | #include "base.hh" 8 | #include "tasks.hh" 9 | #include "test_base.hh" 10 | 11 | using namespace automat; 12 | 13 | struct StartsWithTestTest : TestBase { 14 | Location& starts = machine.Create("starts"); 15 | Location& with = machine.Create("with"); 16 | 17 | Location& test = machine.Create(); 18 | StartsWithTestTest() { 19 | test.ConnectTo(starts, "starts"); 20 | test.ConnectTo(with, "with"); 21 | RunLoop(); 22 | } 23 | }; 24 | 25 | TEST_F(StartsWithTestTest, StartsWithTrue) { 26 | starts.SetText("Hello, world!"); 27 | with.SetText("Hello"); 28 | RunLoop(); 29 | EXPECT_EQ("true", test.GetText()); 30 | } 31 | 32 | TEST_F(StartsWithTestTest, StartsWithFalse) { 33 | starts.SetText("Hello, world!"); 34 | with.SetText("world"); 35 | RunLoop(); 36 | EXPECT_EQ("false", test.GetText()); 37 | } 38 | -------------------------------------------------------------------------------- /src/library_window.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2025 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | 7 | #include "base.hh" 8 | #include "str.hh" 9 | 10 | #ifdef __linux__ 11 | #include 12 | #include 13 | #include 14 | #endif 15 | 16 | namespace automat::library { 17 | 18 | struct Window : public Object, Runnable { 19 | std::mutex mutex; 20 | Str title = ""; 21 | 22 | #ifdef __linux__ 23 | xcb_window_t xcb_window = XCB_WINDOW_NONE; 24 | 25 | struct XSHMCapture { 26 | xcb_shm_seg_t shmseg = -1; 27 | int shmid = -1; 28 | std::span data; 29 | int width = 0; 30 | int height = 0; 31 | 32 | XSHMCapture(); 33 | ~XSHMCapture(); 34 | void Capture(xcb_window_t xcb_window); 35 | }; 36 | 37 | std::optional capture; 38 | #endif 39 | 40 | tesseract::TessBaseAPI tesseract; 41 | 42 | float x_min_ratio = 0.25f; 43 | float x_max_ratio = 0.75f; 44 | float y_min_ratio = 0.25f; 45 | float y_max_ratio = 0.75f; 46 | 47 | Window(); 48 | 49 | std::string_view Name() const override; 50 | Ptr Clone() const override; 51 | Ptr MakeWidget() override; 52 | 53 | // Run OCR on the currently captured window 54 | std::string RunOCR(); 55 | 56 | void Args(std::function cb) override; 57 | void OnRun(Location& here) override; 58 | 59 | // Called after deserialization. Makes the window object attach its native handle to the window 60 | // with the current title. 61 | void AttachToTitle(); 62 | 63 | void SerializeState(Serializer& writer, const char* key) const override; 64 | void DeserializeState(Location& l, Deserializer& d) override; 65 | }; 66 | 67 | } // namespace automat::library -------------------------------------------------------------------------------- /src/list_test.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include 4 | 5 | #include 6 | 7 | #include "base.hh" 8 | #include "library.hh" 9 | #include "tasks.hh" 10 | #include "test_base.hh" 11 | 12 | using namespace automat; 13 | 14 | struct ListTest : TestBase { 15 | Location& list = machine.Create(); 16 | 17 | ListTest() { 18 | auto l = list.As(); 19 | for (int i = 0; i < 10; ++i) { 20 | std::unique_ptr obj = Create(); 21 | dynamic_cast(obj.get())->i = i; 22 | l->objects.emplace_back(std::move(obj)); 23 | } 24 | } 25 | }; 26 | 27 | TEST_F(ListTest, Filter) { 28 | Location& filter = machine.Create(); 29 | filter.ConnectTo(list, "list"); 30 | Location& test = machine.Create(); 31 | filter.ConnectTo(test, "test"); 32 | Location& treshold = machine.Create(); 33 | treshold.As()->i = 5; 34 | test.ConnectTo(treshold, "than"); 35 | Location& element = machine.Create(); 36 | test.ConnectTo(element, "less"); 37 | element.ConnectTo(filter, "of"); 38 | filter.ConnectTo(element, "element"); 39 | RunLoop(); 40 | EXPECT_EQ(5, filter.As()->objects.size()); 41 | } 42 | -------------------------------------------------------------------------------- /src/llvm_asm.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2025 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include "llvm_asm.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | using namespace std; 15 | using namespace llvm; 16 | 17 | namespace automat { 18 | 19 | constexpr static char kTripleStr[] = "x86_64-pc-linux-gnu"; 20 | const Triple kTriple = Triple(kTripleStr); 21 | 22 | LLVM_Assembler& LLVM_Assembler::Get() { 23 | static unique_ptr x86_64_assembler = []() { 24 | LLVMInitializeX86TargetInfo(); 25 | LLVMInitializeX86Target(); 26 | LLVMInitializeX86TargetMC(); 27 | LLVMInitializeX86AsmPrinter(); 28 | 29 | auto a = make_unique(); 30 | 31 | string err; 32 | a->target = TargetRegistry::lookupTarget(kTripleStr, err); 33 | assert(a->target); 34 | 35 | TargetOptions target_options; 36 | a->target_machine = std::unique_ptr( 37 | a->target->createTargetMachine(kTripleStr, "generic", "", target_options, nullopt)); 38 | a->mc_asm_info = a->target_machine->getMCAsmInfo(); 39 | a->mc_instr_info = a->target_machine->getMCInstrInfo(); 40 | a->mc_reg_info = a->target_machine->getMCRegisterInfo(); 41 | a->mc_subtarget_info = a->target_machine->getMCSubtargetInfo(); 42 | a->mc_context.emplace(kTriple, a->mc_asm_info, a->mc_reg_info, a->mc_subtarget_info); 43 | 44 | a->mc_code_emitter.reset(a->target->createMCCodeEmitter(*a->mc_instr_info, *a->mc_context)); 45 | a->mc_inst_printer.reset(a->target->createMCInstPrinter(kTriple, 1 /*Intel*/, *a->mc_asm_info, 46 | *a->mc_instr_info, *a->mc_reg_info)); 47 | 48 | return a; 49 | }(); 50 | return *x86_64_assembler; 51 | } 52 | 53 | } // namespace automat -------------------------------------------------------------------------------- /src/llvm_asm.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2025 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace automat { 11 | 12 | struct LLVM_Assembler { 13 | const llvm::Target* target; 14 | std::unique_ptr target_machine; 15 | const llvm::MCAsmInfo* mc_asm_info; 16 | const llvm::MCInstrInfo* mc_instr_info; 17 | const llvm::MCRegisterInfo* mc_reg_info; 18 | const llvm::MCSubtargetInfo* mc_subtarget_info; 19 | std::optional mc_context; 20 | std::unique_ptr mc_code_emitter; 21 | std::unique_ptr mc_inst_printer; 22 | 23 | static LLVM_Assembler& Get(); 24 | }; 25 | 26 | } // namespace automat -------------------------------------------------------------------------------- /src/log_skia.cc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #include "log_skia.hh" 4 | 5 | #include "format.hh" 6 | 7 | namespace automat { 8 | 9 | const Logger& operator<<(const Logger& logger, SkMatrix& m) { 10 | std::string out = ""; 11 | for (int i = 0; i < 3; ++i) { 12 | out += "\n"; 13 | for (int j = 0; j < 3; ++j) { 14 | int index = i * 3 + j; 15 | out += f("%.4f", m.get(index)); 16 | if (j < 2) { 17 | out += ", "; 18 | } 19 | } 20 | } 21 | logger << out; 22 | return logger; 23 | } 24 | 25 | } // namespace automat -------------------------------------------------------------------------------- /src/log_skia.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | 7 | #include "log.hh" 8 | 9 | namespace automat { 10 | 11 | const Logger& operator<<(const Logger&, SkMatrix&); 12 | 13 | } // namespace automat -------------------------------------------------------------------------------- /src/manifest.rc: -------------------------------------------------------------------------------- 1 | 1 24 "manifest.xml" 2 | -------------------------------------------------------------------------------- /src/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | UTF-8 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/math_constants.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2024 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include // IWYU pragma: keep 6 | 7 | constexpr float kMetersPerInch = 0.0254f; 8 | constexpr float kPi = M_PI; 9 | constexpr float kSqrt1_2 = M_SQRT1_2; 10 | constexpr float kLog2e = M_LOG2E; -------------------------------------------------------------------------------- /src/menu.hh: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright 2025 Automat Authors 2 | // SPDX-License-Identifier: MIT 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include "action.hh" 9 | #include "ptr.hh" 10 | #include "vec.hh" 11 | 12 | namespace automat { 13 | 14 | // Option represents a potential action. It's the core of the menu system. 15 | struct Option { 16 | Ptr icon; 17 | Option(Ptr&& icon); 18 | Option(Str name); 19 | virtual ~Option() = default; 20 | virtual std::unique_ptr