├── .clang-format ├── .clippy.toml ├── .editorconfig ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE-GPL-3.0 ├── README.md ├── Xargo.toml ├── build-mac.sh ├── ci ├── README.md ├── common.yml ├── jobs │ ├── cargo-build.yml │ ├── cargo-check.yml │ ├── cargo-clippy.yml │ ├── cargo-doc.yml │ ├── cargo-test.yml │ ├── publish.yml │ └── rustfmt.yml ├── pipelines-ci.yml ├── pipelines-nightly.yml └── steps │ ├── install-rust.yml │ └── set-rustflags.yml ├── docs └── images │ └── banner.svg ├── harmony ├── Cargo.toml └── src │ ├── lib.rs │ └── miniserde.rs ├── res ├── mkmacosbundle │ ├── Cargo.toml │ ├── Info.plist │ └── main.rs └── windres │ ├── Cargo.toml │ ├── build.rs │ ├── lib.rs │ ├── stella2.manifest │ └── stella2.rc ├── rust-toolchain ├── shell.nix ├── stella2 ├── Cargo.toml ├── meta │ ├── Cargo.toml │ ├── build.rs │ ├── lib.rs │ ├── lib.tcwdl │ └── view │ │ ├── channellist.tcwdl │ │ ├── dpiscalewatcher.tcwdl │ │ ├── logview.tcwdl │ │ ├── prefwnd.tcwdl │ │ ├── radiolist.tcwdl │ │ ├── splitutils.tcwdl │ │ ├── tabbar.tcwdl │ │ └── toolbar.tcwdl └── src │ ├── config.rs │ ├── config │ ├── cmdline.rs │ ├── lock.rs │ ├── profile.rs │ └── viewpersistence.rs │ ├── crashhandler.rs │ ├── main.rs │ ├── model.rs │ ├── stylesheet.rs │ ├── view.rs │ └── view │ ├── channellist.rs │ ├── dpiscalewatcher.rs │ ├── global.rs │ ├── logview.rs │ ├── prefwnd.rs │ ├── radiolist.rs │ ├── splitutils.rs │ ├── tabbar.rs │ └── toolbar.rs ├── stella2_assets ├── Cargo.toml └── src │ ├── close.svg │ ├── lib.rs │ ├── list_group_open.svg │ ├── pref │ ├── tab_about.svg │ ├── tab_accounts.svg │ ├── tab_advanced.svg │ ├── tab_connection.svg │ └── tab_general.svg │ ├── search.svg │ └── toolbar │ ├── menu.svg │ ├── sidebar_hide.svg │ ├── sidebar_show.svg │ └── user_outline.svg ├── stvg ├── LICENSE-MIT ├── io │ ├── Cargo.toml │ ├── src │ │ ├── dec.rs │ │ ├── enc.rs │ │ └── lib.rs │ └── tests │ │ └── test.rs ├── macro │ ├── Cargo.toml │ ├── impl │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── src │ │ └── lib.rs └── tests │ ├── LICENSE.md │ └── tiger.svgz ├── support ├── alt_fp │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── README.md │ ├── README.tpl │ ├── benches │ │ └── castf32.rs │ └── src │ │ ├── cast.rs │ │ ├── cmp.rs │ │ ├── fma.rs │ │ ├── lib.rs │ │ └── simd.rs ├── array_intrusive_list │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── as_any │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── atom2 │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── winrt_comptr.rs │ └── tests │ │ └── atom.rs ├── boxed_slice_tools │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cggeom │ ├── Cargo.toml │ └── src │ │ ├── average.rs │ │ ├── boxes.rs │ │ ├── elementwise.rs │ │ ├── lib.rs │ │ └── twodim.rs ├── demotools │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── icon_baker │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── src │ │ ├── icns.rs │ │ ├── ico.rs │ │ ├── lib.rs │ │ ├── resample.rs │ │ └── test.rs │ └── tests │ │ └── deref.svg ├── iterpool │ ├── Cargo.toml │ ├── benches │ │ └── pool.rs │ └── src │ │ ├── intrusive_list.rs │ │ └── lib.rs ├── leakypool │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── token.rs ├── minisort │ ├── Cargo.toml │ ├── benches │ │ └── bench.rs │ └── src │ │ ├── auto.rs │ │ ├── cstdlib.rs │ │ ├── insertion.rs │ │ └── lib.rs ├── nativedispatch │ ├── Cargo.toml │ ├── benches │ │ └── bench.rs │ └── src │ │ ├── dispatch.rs │ │ ├── glib.rs │ │ ├── lib.rs │ │ ├── windows.rs │ │ └── windows │ │ └── utils.rs ├── neo_linked_list │ ├── Cargo.toml │ └── src │ │ ├── cell.rs │ │ ├── lib.rs │ │ ├── linked_list.rs │ │ └── linked_list │ │ └── tests.rs ├── rope │ ├── Cargo.toml │ ├── benches │ │ └── rope.rs │ └── src │ │ ├── iter.rs │ │ ├── lib.rs │ │ ├── misc.rs │ │ ├── offset.rs │ │ ├── ops.rs │ │ └── sel.rs ├── sorted_diff │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── subscriber_list │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── unicount │ ├── Cargo.toml │ ├── benches │ │ └── bench.rs │ └── src │ │ └── lib.rs └── utf16count │ ├── Cargo.toml │ ├── benches │ └── bench.rs │ └── src │ └── lib.rs └── tcw3 ├── Cargo.toml ├── LICENSE-MIT ├── assets ├── checkbox_light.svg ├── checkbox_light_act.svg ├── checkbox_light_checked.svg ├── checkbox_light_checked_act.svg ├── radio_light.svg ├── radio_light_act.svg ├── radio_light_checked.svg ├── radio_light_checked_act.svg ├── slider_knob.svg └── slider_knob_act.svg ├── designer ├── Cargo.toml ├── src │ ├── bin │ │ └── tcw3codegen.rs │ ├── codegen.rs │ ├── codegen │ │ ├── diag.rs │ │ ├── implgen.rs │ │ ├── implgen │ │ │ ├── accessorgen.rs │ │ │ ├── analysis.rs │ │ │ ├── bitsetgen.rs │ │ │ ├── buildergen.rs │ │ │ ├── docgen.rs │ │ │ ├── dropgen.rs │ │ │ ├── evalgen.rs │ │ │ ├── initgen.rs │ │ │ ├── iterutils.rs │ │ │ └── weakrefgen.rs │ │ ├── metagen.rs │ │ ├── parser.rs │ │ ├── parser │ │ │ └── visit_mut.rs │ │ ├── prelude.txt │ │ ├── resolve.rs │ │ └── sem.rs │ ├── lib.md │ ├── lib.rs │ ├── metadata.rs │ └── metadata │ │ └── visit_mut.rs ├── tests │ ├── bad.rs │ └── bad │ │ ├── comp_path_super.tcwdl │ │ ├── comp_path_unknown.tcwdl │ │ ├── const_definite.tcwdl │ │ ├── const_indefinite.tcwdl │ │ ├── const_uninitable.tcwdl │ │ ├── const_watch.tcwdl │ │ ├── input_circular.tcwdl │ │ ├── input_circular2.tcwdl │ │ ├── input_circular_objinit.tcwdl │ │ ├── input_circular_this.tcwdl │ │ ├── input_field_not_comp.tcwdl │ │ ├── input_field_not_comp2.tcwdl │ │ ├── input_field_not_comp3.tcwdl │ │ ├── input_field_unknown.tcwdl │ │ ├── input_inline_unsyntactic.tcwdl │ │ ├── objinit_comp_unknown.tcwdl │ │ ├── objinit_explicit_type.tcwdl │ │ ├── objinit_field_dupe.tcwdl │ │ ├── objinit_field_short_badinput.tcwdl │ │ ├── objinit_field_unknown.tcwdl │ │ ├── objinit_field_wrong_ty.tcwdl │ │ ├── objinit_settable.tcwdl │ │ ├── objinit_subexpr.tcwdl │ │ ├── prop_uninitable.tcwdl │ │ ├── prop_unsettable.tcwdl │ │ ├── use_dupe.tcwdl │ │ ├── use_self.tcwdl │ │ ├── use_super.tcwdl │ │ ├── use_unknown.tcwdl │ │ └── watch_nonnullary.tcwdl └── tests_impl │ ├── Cargo.toml │ ├── build.rs │ ├── commit │ ├── remotetrigger.rs │ └── remotetrigger.tcwdl │ ├── field │ ├── accessors.rs │ ├── accessors.tcwdl │ ├── bug_type_deduction.rs │ ├── bug_type_deduction.tcwdl │ ├── lifetime_elision.rs │ ├── lifetime_elision.tcwdl │ ├── prop.rs │ └── prop.tcwdl │ ├── func │ ├── inline.rs │ └── inline.tcwdl │ ├── interop │ ├── builder_simple.rs │ └── builder_simple.tcwdl │ ├── lib.rs │ ├── lib.tcwdl │ ├── misc │ ├── exprpath.rs │ ├── exprpath.tcwdl │ ├── genericresolve.rs │ ├── genericresolve.tcwdl │ ├── primitives.rs │ ├── primitives.tcwdl │ ├── weakref.rs │ └── weakref.tcwdl │ └── objinit │ ├── alias.rs │ ├── alias.tcwdl │ ├── shorthand.rs │ └── shorthand.tcwdl ├── designer_runtime ├── Cargo.toml └── src │ └── lib.rs ├── examples ├── tcw3_color.rs ├── tcw3_layout.rs ├── tcw3_stvg.rs ├── tcw3_table.rs ├── tcw3_text.rs └── tcw3_widgets.rs ├── images ├── Cargo.toml └── src │ ├── bitmap.rs │ ├── canvas.rs │ ├── figures.rs │ ├── img.rs │ └── lib.rs ├── meta ├── Cargo.toml ├── build.rs ├── lib.rs ├── lib.tcwdl ├── theming │ ├── manager.tcwdl │ └── view.tcwdl ├── uicore.tcwdl └── views │ ├── button.tcwdl │ ├── checkbox.tcwdl │ ├── entry.tcwdl │ ├── label.tcwdl │ ├── slider.tcwdl │ ├── spacer.tcwdl │ ├── split.tcwdl │ └── table.tcwdl ├── pal ├── Cargo.toml ├── build.rs ├── examples │ ├── tcw3_pal.rs │ └── tcw3_stress.rs ├── macro │ ├── Cargo.toml │ └── src │ │ ├── accel.rs │ │ ├── accel │ │ ├── gtk.rs │ │ ├── macos.rs │ │ ├── testing.rs │ │ └── windows.rs │ │ ├── keycode.rs │ │ └── lib.rs ├── src │ ├── canvas.rs │ ├── cells.rs │ ├── cells │ │ └── init.rs │ ├── futuresext.rs │ ├── gtk.rs │ ├── gtk │ │ ├── comp.rs │ │ ├── textinput.rs │ │ ├── timer.rs │ │ ├── window.h │ │ ├── window.rs │ │ └── wndwidget.c │ ├── iface.rs │ ├── lib.rs │ ├── macos.rs │ ├── macos │ │ ├── TCWBridge.h │ │ ├── TCWGestureHandlerView.h │ │ ├── TCWGestureHandlerView.m │ │ ├── TCWWindowController.h │ │ ├── TCWWindowController.m │ │ ├── TCWWindowView.h │ │ ├── TCWWindowView.m │ │ ├── Timers.m │ │ ├── bitmap.rs │ │ ├── drawutils.rs │ │ ├── layer.rs │ │ ├── text.rs │ │ ├── timer.rs │ │ ├── utils.rs │ │ └── window.rs │ ├── swrast.rs │ ├── swrast │ │ ├── binner.rs │ │ ├── binrast.rs │ │ ├── layers.rs │ │ ├── rast.rs │ │ └── utils.rs │ ├── testing.rs │ ├── testing │ │ ├── eventloop.rs │ │ ├── logging.rs │ │ ├── screen.rs │ │ ├── textinput.rs │ │ ├── tictxlistenershim.rs │ │ ├── uniqpool.rs │ │ ├── wmapi.rs │ │ └── wndlistenershim.rs │ ├── testing_dis.rs │ ├── timerqueue.rs │ ├── unix │ │ ├── bitmap.rs │ │ └── text.rs │ ├── windows.rs │ └── windows │ │ ├── acceltable.rs │ │ ├── bitmap.rs │ │ ├── bitmap │ │ └── text.rs │ │ ├── codecvt.rs │ │ ├── comp.cpp │ │ ├── comp.rs │ │ ├── comp │ │ └── effects.rs │ │ ├── drawutils.rs │ │ ├── eventloop.rs │ │ ├── frameclock.rs │ │ ├── surface.rs │ │ ├── text.rs │ │ ├── textinput.rs │ │ ├── textinput │ │ ├── textstore.rs │ │ └── tsf.rs │ │ ├── utils.rs │ │ ├── winapiext.rs │ │ └── window.rs └── tests │ ├── common │ └── mod.rs │ ├── futures.rs │ ├── terminate_with_pending_invoke.rs │ ├── testing.rs │ ├── text.rs │ └── timer.rs ├── src ├── lib.md ├── lib.rs ├── ui │ ├── editing │ │ └── history.rs │ ├── layouts │ │ ├── abs.rs │ │ ├── empty.rs │ │ ├── fill.rs │ │ └── table.rs │ ├── mixins │ │ ├── button.rs │ │ ├── canvas.rs │ │ └── scrollwheel.rs │ ├── mod.rs │ ├── scrolling │ │ ├── lineset.rs │ │ ├── lineset │ │ │ ├── debug.rs │ │ │ └── multiset.rs │ │ ├── piecewise.rs │ │ └── tableremap.rs │ ├── theming │ │ ├── manager.rs │ │ ├── style.rs │ │ ├── style │ │ │ └── prop_macros.rs │ │ ├── stylesheet.rs │ │ ├── view.rs │ │ └── widget.rs │ ├── types.rs │ └── views │ │ ├── button.rs │ │ ├── checkbox.rs │ │ ├── entry.rs │ │ ├── entry │ │ ├── history.rs │ │ └── tests.rs │ │ ├── label.rs │ │ ├── scrollbar.rs │ │ ├── slider.rs │ │ ├── slider │ │ └── wrap.rs │ │ ├── spacer.rs │ │ ├── split.rs │ │ ├── table.rs │ │ └── table │ │ ├── edit.rs │ │ ├── fixedpoint.rs │ │ ├── listener.rs │ │ ├── scrollable.rs │ │ ├── scrollbar.rs │ │ ├── scrollwheel.rs │ │ └── update.rs ├── uicore │ ├── images.rs │ ├── invocation.rs │ ├── keybd.rs │ ├── layer.rs │ ├── layout.rs │ ├── mod.rs │ ├── mount.rs │ ├── mouse.rs │ ├── taborder.rs │ └── window.rs └── utils │ └── resetiter.rs ├── stvg ├── Cargo.toml └── src │ └── lib.rs ├── testing ├── Cargo.toml ├── macros │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── src │ └── lib.rs └── tests ├── LICENSE.md ├── horse.svgz ├── view_actions.rs ├── view_focus_events.rs ├── view_key_events.rs ├── view_layout.rs ├── view_mouse_events.rs ├── view_tab.rs └── window.rs /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | IndentWidth: 4 4 | ... 5 | -------------------------------------------------------------------------------- /.clippy.toml: -------------------------------------------------------------------------------- 1 | type-complexity-threshold = 500 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 4 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.yml] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.toml] 17 | indent_style = space 18 | indent_size = 4 19 | 20 | [*.rs] 21 | indent_style = space 22 | indent_size = 4 23 | 24 | [*.{m,h}] 25 | indent_style = space 26 | indent_size = 4 27 | 28 | [*.{xml,manifest,plist}] 29 | indent_style = space 30 | indent_size = 2 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /publish 4 | **/target/criterion 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "stella2", 4 | "stella2/meta", 5 | "stella2_assets", 6 | "tcw3", 7 | "tcw3/designer", 8 | "tcw3/designer_runtime", 9 | "tcw3/designer/tests_impl", 10 | "tcw3/testing", 11 | "tcw3/pal", 12 | "tcw3/pal/macro", 13 | "tcw3/stvg", 14 | "tcw3/images", 15 | "tcw3/meta", 16 | "harmony", 17 | "stvg/io", 18 | "stvg/macro", 19 | "stvg/macro/impl", 20 | "support/alt_fp", 21 | "support/array_intrusive_list", 22 | "support/as_any", 23 | "support/atom2", 24 | "support/boxed_slice_tools", 25 | "support/cggeom", 26 | "support/demotools", 27 | "support/iterpool", 28 | "support/leakypool", 29 | "support/minisort", 30 | "support/nativedispatch", 31 | "support/neo_linked_list", 32 | "support/rope", 33 | "support/subscriber_list", 34 | "support/sorted_diff", 35 | "support/unicount", 36 | "support/utf16count", 37 | "res/mkmacosbundle", 38 | "res/windres", 39 | ] 40 | 41 | [profile.release] 42 | debug = true 43 | panic = "abort" 44 | lto = true 45 | 46 | [profile.bench] 47 | lto = true 48 | debug = true 49 | 50 | [profile.dev.package] 51 | # A software-based compositor (`swrast`) is used when other backends are not 52 | # available for some reason. This is horrendously slow on debug builds and hurts 53 | # developer experience. To mitigate this issue, always enable optimization for 54 | # `tcw3_pal`. This isn't as fast as the release build due to lack of LTO, but 55 | # at least it's usable. 56 | tcw3_pal = { opt-level = 3 } 57 | 58 | [patch.crates-io.winapi] 59 | # Waiting for (GDI+ support) 60 | # to be merged 61 | git = "https://github.com/yvt/winapi-rs.git" 62 | rev = "a6df41df91d6b8c156cff69bede2da2196c663cb" 63 | 64 | [patch.crates-io.rob] 65 | # Waiting for the following PRs to be merged: 66 | # - “Make `from_ref`, `from_raw`, and `is_owned` `const fn`” 67 | # 68 | # - “Implement `Send` and `Sync`” 69 | # 70 | git = "https://github.com/yvt/rust-rob" 71 | rev = "c2d723c6c2672914c6d8ac9d0d3cc80b5d99cbb9" 72 | -------------------------------------------------------------------------------- /Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-apple-darwin.dependencies.std] 2 | # Build without the backtrace functionality. It's not used at all because we 3 | # rely on macOS's CrashReporter. 4 | features = [] 5 | default-features = false 6 | 7 | [target.x86_64-pc-windows-msvc.dependencies.std] 8 | # Build without the backtrace functionality. On panic, abort the process 9 | # immediately because the panic message won't be shown to the user anyway. 10 | features = ["panic_immediate_abort"] 11 | default-features = false 12 | -------------------------------------------------------------------------------- /build-mac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "`dirname "$0"`" 4 | 5 | xargo build --target x86_64-apple-darwin --release -p stella2 || exit $? 6 | cargo run --bin mkmacosbundle || exit $? 7 | -------------------------------------------------------------------------------- /ci/README.md: -------------------------------------------------------------------------------- 1 | # Azure pipeline definitions for Stella 2 2 | 3 | The following files are based on : `jobs/cargo-(build|check|clippy|doc|test).yml`, `jobs/rustfmt.yml`, and `steps/install-rust.yml`. 4 | 5 | > MIT License 6 | > 7 | > Copyright (c) 2019 Sylwester Rąpała 8 | > 9 | > Permission is hereby granted, free of charge, to any person obtaining a copy 10 | > of this software and associated documentation files (the "Software"), to deal 11 | > in the Software without restriction, including without limitation the rights 12 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | > copies of the Software, and to permit persons to whom the Software is 14 | > furnished to do so, subject to the following conditions: 15 | > 16 | > The above copyright notice and this permission notice shall be included in all 17 | > copies or substantial portions of the Software. 18 | > 19 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | > SOFTWARE. 26 | -------------------------------------------------------------------------------- /ci/common.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | rustVersion: nightly-2020-05-30 3 | linuxPrestep: | 4 | sudo apt-get update && 5 | sudo apt-get install -y \ 6 | libgtk-3-dev libglib2.0-dev libcairo2-dev libcairo-gobject2 libpango1.0-dev 7 | linuxVmImage: ubuntu-18.04 8 | -------------------------------------------------------------------------------- /ci/jobs/publish.yml: -------------------------------------------------------------------------------- 1 | # Builds a release package for each target platform. 2 | jobs: 3 | 4 | - job: publish 5 | displayName: Publish nightly build 6 | continueOnError: true 7 | strategy: 8 | matrix: 9 | MacOS: 10 | vmImage: macOS-10.14 11 | platformName: macOS 12 | Windows: 13 | vmImage: windows-2019 14 | platformName: Windows 15 | pool: 16 | vmImage: $(vmImage) 17 | variables: 18 | - template: ../common.yml 19 | 20 | steps: 21 | - template: ../steps/install-rust.yml 22 | parameters: 23 | rustup_toolchain: ${{ variables.rustVersion }} 24 | components: [ rust-src ] 25 | 26 | - template: ../steps/set-rustflags.yml 27 | parameters: 28 | # Work-around 29 | # (LTO builds on recent nightlies fail "to get bitcode from object file") 30 | embed_bitcode: yes 31 | 32 | - script: cargo install xargo --version 0.3.17 33 | displayName: Install xargo 34 | 35 | - script: ./build-mac.sh 36 | displayName: build-mac.sh 37 | condition: eq(variables['Agent.OS'], 'Darwin') 38 | 39 | - script: | 40 | xargo build --target x86_64-pc-windows-msvc --release -p stella2 41 | mkdir publish 42 | copy target\x86_64-pc-windows-msvc\release\stella2.exe publish\ 43 | copy target\x86_64-pc-windows-msvc\release\stella2.pdb publish\ 44 | displayName: xargo build 45 | condition: eq(variables['Agent.OS'], 'Windows_NT') 46 | 47 | - task: ArchiveFiles@2 48 | displayName: Archive the application package 49 | inputs: 50 | rootFolderOrFile: ./publish 51 | archiveType: zip 52 | archiveFile: $(Build.ArtifactStagingDirectory)/Stella2-$(Build.SourceVersion)-$(platformName).zip 53 | - task: PublishBuildArtifacts@1 54 | displayName: "Publish" 55 | inputs: 56 | artifactName: Stella2-nightly-$(platformName) 57 | -------------------------------------------------------------------------------- /ci/pipelines-nightly.yml: -------------------------------------------------------------------------------- 1 | trigger: none 2 | pr: none 3 | schedules: 4 | - cron: "0 0 * * *" 5 | displayName: Daily midnight build 6 | branches: 7 | include: 8 | - master 9 | 10 | stages: 11 | - stage: publish 12 | displayName: "Publish" 13 | jobs: 14 | - template: ./jobs/publish.yml 15 | -------------------------------------------------------------------------------- /ci/steps/install-rust.yml: -------------------------------------------------------------------------------- 1 | # defaults for any parameters that aren't specified 2 | parameters: 3 | rustup_toolchain: stable # Could be [stable, beta, nightly, 1.31, nightly-2018-08-01 ... ] 4 | components: [] # Rust commponents to be installed 5 | targets: [] # Rust targets to be installed 6 | steps: [] # Custom steps 7 | 8 | steps: 9 | # Linux and macOS. 10 | - script: | 11 | set -e 12 | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUSTUP_TOOLCHAIN 13 | echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" 14 | env: 15 | RUSTUP_TOOLCHAIN: ${{parameters.rustup_toolchain}} 16 | displayName: "Install rust (*nix)" 17 | condition: not(eq(variables['Agent.OS'], 'Windows_NT')) 18 | 19 | # Windows. 20 | - script: | 21 | curl -sSf -o rustup-init.exe https://win.rustup.rs 22 | rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN% --default-host x86_64-pc-windows-msvc 23 | set PATH=%PATH%;%USERPROFILE%\.cargo\bin 24 | echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin" 25 | env: 26 | RUSTUP_TOOLCHAIN: ${{parameters.rustup_toolchain}} 27 | displayName: "Install rust (windows)" 28 | condition: eq(variables['Agent.OS'], 'Windows_NT') 29 | 30 | # Install additional components: 31 | - ${{ each component in parameters.components }}: 32 | - script: rustup component add ${{ component }} 33 | displayName: "Adding commponent ${{ component }}" 34 | 35 | # Install additional targets: 36 | - ${{ each target in parameters.targets }}: 37 | - script: rustup target add ${{ target }} 38 | displayName: "Adding target '${{ target }}'" 39 | 40 | # Set correct rustup_toolchain 41 | - bash: | 42 | rustup default $RUSTUP_TOOLCHAIN 43 | rustup update $RUSTUP_TOOLCHAIN 44 | env: 45 | RUSTUP_TOOLCHAIN: ${{parameters.rustup_toolchain}} 46 | displayName: "Set correct Rust version" 47 | 48 | - ${{ parameters.steps }} 49 | 50 | # All platforms. 51 | - script: | 52 | set -e 53 | echo "Rust up version" 54 | rustup -V 55 | echo "rustup installed commponents list" 56 | rustup component list --installed 57 | echo "rustup show:" 58 | rustup show 59 | echo "rustc -Vv" 60 | rustc -Vv 61 | echo "cargo -V" 62 | cargo -V 63 | displayName: Query rustup, rust and cargo versions 64 | -------------------------------------------------------------------------------- /ci/steps/set-rustflags.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | target_cpu: nehalem # e.g., native, core2, skylake, znver2 3 | embed_bitcode: '' 4 | 5 | steps: 6 | - bash: | 7 | mkdir -p `dirname "$CONFIG"` 8 | echo "[build]" >> "$CONFIG" 9 | echo "rustflags = [" >> "$CONFIG" 10 | echo " \"-Ctarget-cpu=${{parameters.target_cpu}}\"," >> "$CONFIG" 11 | if [ "${{parameters.embed_bitcode}}" != "" ]; then 12 | echo " \"-Cembed-bitcode=${{parameters.embed_bitcode}}\"," >> "$CONFIG" 13 | fi 14 | echo "]" >> "$CONFIG" 15 | env: 16 | CONFIG: .cargo/config 17 | displayName: "Generate .cargo/config" 18 | -------------------------------------------------------------------------------- /harmony/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "harmony" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [features] 8 | default = [] 9 | 10 | [dependencies] 11 | miniserde = { version = "0.1.12", optional = true } 12 | -------------------------------------------------------------------------------- /res/mkmacosbundle/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stella2_mkmacosbundle" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | structopt = { version = "0.3.0", default-features = false } 10 | icon_baker = { path = "../../support/icon_baker" } 11 | libflate = "1.0.0" 12 | 13 | [[bin]] 14 | name = "mkmacosbundle" 15 | path = "main.rs" 16 | -------------------------------------------------------------------------------- /res/mkmacosbundle/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | stella2 9 | CFBundleIdentifier 10 | jp.yvt.stella2 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | Stella 2 15 | CFBundleIconFile 16 | stella2.icns 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSupportedPlatforms 22 | 23 | MacOSX 24 | 25 | CFBundleVersion 26 | 1 27 | LSMinimumSystemVersion 28 | 10.12 29 | NSHumanReadableCopyright 30 | Copyright © 2020 yvt. All rights reserved. 31 | NSMainNibFile 32 | MainMenu 33 | NSPrincipalClass 34 | NSApplication 35 | 36 | 37 | -------------------------------------------------------------------------------- /res/windres/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stella2_windres" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | 10 | [build-dependencies] 11 | embed-resource = "1.3.0" 12 | icon_baker = { path = "../../support/icon_baker" } 13 | libflate = "1.0.0" 14 | 15 | [lib] 16 | path = "lib.rs" 17 | -------------------------------------------------------------------------------- /res/windres/build.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | fs::{copy, read, File}, 4 | io::{BufWriter, Read}, 5 | path::Path, 6 | }; 7 | 8 | fn main() { 9 | if env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" { 10 | add_windows_resource(); 11 | } 12 | } 13 | 14 | /// Build Windows resources for the Stella2 application. 15 | /// 16 | /// When an executable is linked to this crate, the resource generated here 17 | /// are added to the executable. 18 | fn add_windows_resource() { 19 | let out_dir = env::var("OUT_DIR").unwrap(); 20 | 21 | // The resource script file 22 | let rc_path = Path::new(&out_dir).join("stella2.rc"); 23 | copy("stella2.rc", &rc_path).unwrap(); 24 | 25 | // The appllcation icon 26 | let ico_path = Path::new(&out_dir).join("stella2.ico"); 27 | { 28 | use icon_baker::{resample, Icon, SvgImage}; 29 | let mut ico = icon_baker::Ico::new(); 30 | 31 | // placeholder 32 | let svgz = read("../../tcw3/tests/horse.svgz").unwrap(); 33 | let svg_text_stream = libflate::gzip::Decoder::new(&svgz[..]).unwrap(); 34 | let mut svg_text = String::new(); 35 | { svg_text_stream }.read_to_string(&mut svg_text).unwrap(); 36 | let svg_img = SvgImage::parse_str(&svg_text, icon_baker::nsvg::Units::Pixel, 96.0) 37 | .unwrap() 38 | .into(); 39 | 40 | ico.add_entry(resample::linear, &svg_img, 16).unwrap(); 41 | ico.add_entry(resample::linear, &svg_img, 32).unwrap(); 42 | ico.add_entry(resample::linear, &svg_img, 48).unwrap(); 43 | ico.add_entry(resample::linear, &svg_img, 64).unwrap(); 44 | 45 | ico.write(&mut BufWriter::new(File::create(&ico_path).unwrap())) 46 | .unwrap(); 47 | } 48 | 49 | // The application manifest 50 | let manifest_path = Path::new(&out_dir).join("stella2.manifest"); 51 | copy("stella2.manifest", &manifest_path).unwrap(); 52 | 53 | embed_resource::compile(&rc_path); 54 | } 55 | -------------------------------------------------------------------------------- /res/windres/lib.rs: -------------------------------------------------------------------------------- 1 | //! Windows resources for Stella2 2 | //! 3 | //! [Resources] are read-only data embedded in an executable file. The purposes 4 | //! of resources include (but not limited to) providing an application icon to 5 | //! be displayed in Windows Explorer. 6 | //! 7 | //! Using [`embed-resource`], this crate produces an object file including 8 | //! resources. The resources are added to the executable file when the object 9 | //! file is linked. 10 | //! 11 | //! [Resources]: https://en.wikipedia.org/wiki/Resource_%28Windows%29 12 | //! [`embed-resource`]: https://crates.io/crates/embed-resource 13 | 14 | #[doc(hidden)] 15 | pub static DUMMY: u8 = 0; 16 | 17 | /// Ensures the object file containing Windows resources is linked. 18 | #[macro_export] 19 | macro_rules! attach_windres { 20 | () => { 21 | #[used] 22 | #[allow(dead_code)] 23 | static WINDRES_DUMMY: &'static u8 = &$crate::DUMMY; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /res/windres/stella2.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true/pm 6 | PerMonitorV2 7 | true 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /res/windres/stella2.rc: -------------------------------------------------------------------------------- 1 | #define APP_MANIFEST 1 2 | #define RT_MANIFEST 24 3 | #define IDI_ICON 0x101 4 | 5 | ///////////////////////////////////////////////////////////////////////////// 6 | // 7 | // Icon 8 | // 9 | 10 | // Icon with lowest ID value placed first to ensure application icon 11 | // remains consistent on all systems. 12 | IDI_ICON ICON "stella2.ico" 13 | 14 | 15 | ///////////////////////////////////////////////////////////////////////////// 16 | // 17 | // RT_MANIFEST 18 | // 19 | 20 | APP_MANIFEST RT_MANIFEST "stella2.manifest" 21 | 22 | ///////////////////////////////////////////////////////////////////////////// 23 | // 24 | // Version 25 | // 26 | 27 | VS_VERSION_INFO VERSIONINFO 28 | // TODO: Replace these with the information from `Cargo.toml` 29 | FILEVERSION 1,0,0,1 30 | PRODUCTVERSION 1,0,0,1 31 | FILEFLAGSMASK 0x3fL 32 | FILEFLAGS 0x0L 33 | FILEOS 0x40004L 34 | FILETYPE 0x1L 35 | FILESUBTYPE 0x0L 36 | BEGIN 37 | BLOCK "StringFileInfo" 38 | BEGIN 39 | BLOCK "040904b0" 40 | BEGIN 41 | VALUE "CompanyName", "Stella2 developer" 42 | VALUE "FileDescription", "Stella2 application" 43 | VALUE "FileVersion", "1.0.0.1" 44 | VALUE "InternalName", "Stella2" 45 | VALUE "LegalCopyright", "Copyright (C) 2020" 46 | VALUE "OriginalFilename", "Stella2" 47 | VALUE "ProductName", "Stella2" 48 | VALUE "ProductVersion", "1.0.0.1" 49 | END 50 | END 51 | BLOCK "VarFileInfo" 52 | BEGIN 53 | VALUE "Translation", 0x409, 1200 54 | END 55 | END 56 | 57 | ///////////////////////////////////////////////////////////////////////////// 58 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2020-05-30 2 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | with import {}; 2 | with lib; 3 | 4 | runCommand "dummy" rec { 5 | nativeBuildInputs = [ 6 | rustup pkgconfig 7 | ]; 8 | 9 | buildInputs = [ 10 | # needed by TCW3's testing backend 11 | glib pango harfbuzz 12 | ] ++ optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [ 13 | CoreText 14 | Foundation 15 | AppKit 16 | ]) ++ optionals (!stdenv.isDarwin) [ 17 | # needed by TCW3's GTK backend 18 | gtk3 19 | ]; 20 | } "" 21 | -------------------------------------------------------------------------------- /stella2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stella2" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "GPL-3.0-or-later" 7 | 8 | # Specify to run this program by default when you do `cargo run` without 9 | # specifying a binary name. 10 | default-run = "stella2" 11 | 12 | [dependencies] 13 | arrayvec = "0.5" 14 | cfg-if = "0.1.7" 15 | cggeom = { path = "../support/cggeom" } 16 | cgmath = "0.17.0" 17 | chrono = "0.4" 18 | dirs = "2.0.2" 19 | displaydoc = "0.1.5" 20 | enclose = "1.1.6" 21 | env_logger = "0.7.0" 22 | fslock = "0.1.4" 23 | harmony = { path = "../harmony", features = ["miniserde"] } 24 | iota = "0.2.1" 25 | miniserde = "0.1.12" 26 | nativedispatch = { path = "../support/nativedispatch" } 27 | stella2_assets = { path = "../stella2_assets" } 28 | stella2_meta = { path = "meta" } 29 | subscriber_list = { path = "../support/subscriber_list" } 30 | tcw3 = { path = "../tcw3" } 31 | 32 | [target.'cfg(target_os = "windows")'.dependencies] 33 | stella2_windres = { path = "../res/windres" } 34 | winapi = { version = "0.3.8", features = ["winuser", "libloaderapi"] } 35 | windebug_logger = "0.1.3" 36 | 37 | [target.'cfg(target_os = "macos")'.dependencies] 38 | cocoa = "0.20.0" 39 | objc = "0.2.3" 40 | 41 | [dependencies.log] 42 | version = "0.4" 43 | # Disable logging in release builds 44 | features = ["release_max_level_off"] 45 | -------------------------------------------------------------------------------- /stella2/meta/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stella2_meta" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "GPL-3.0-or-later" 7 | 8 | [build-dependencies] 9 | tcw3_designer = { path = "../../tcw3/designer" } 10 | tcw3_meta = { path = "../../tcw3/meta" } 11 | 12 | [lib] 13 | path = "lib.rs" 14 | -------------------------------------------------------------------------------- /stella2/meta/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tcw3_designer::BuildScriptConfig::new() 3 | .link("tcw3", tcw3_meta::DESIGNER_METADATA.into()) 4 | .run_and_exit_on_error(); 5 | } 6 | -------------------------------------------------------------------------------- /stella2/meta/lib.rs: -------------------------------------------------------------------------------- 1 | //! The TCW3 Designer meta crate for `stella2` 2 | include!(concat!(env!("OUT_DIR"), "/designer.rs")); 3 | -------------------------------------------------------------------------------- /stella2/meta/view/channellist.tcwdl: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | use tcw3::{ 3 | ui::{ 4 | theming::{self, Manager}, 5 | views::{ScrollableTable, table}, 6 | mixins::scrollwheel::ScrollAxisFlags, 7 | }, 8 | uicore::{HView, SizeTraits, ViewFlags}, 9 | pal, 10 | }; 11 | 12 | use crate::{model, stylesheet::elem_id}; 13 | 14 | #[widget] 15 | pub(crate) comp crate::view::channellist::ChannelListView { 16 | const wm: pal::Wm { pub set; } 17 | const style_manager: &Manager { pub set; get clone; } 18 | 19 | const view { pub get borrow; } = HView::new! { 20 | flags = ViewFlags::default() | ViewFlags::TAB_STOP | 21 | ViewFlags::ACCEPT_MOUSE_DRAG | ViewFlags::STRONG_FOCUS, 22 | layout = tcw3::ui::layouts::FillLayout::new(get!(table.view)), 23 | }; 24 | const style_elem: theming::HElem { pub get clone; } = get!(&elem).helem(); 25 | 26 | const elem: Rc = Rc::new(theming::Elem::new(get!(style_manager))); 27 | 28 | const table = ScrollableTable::new! { 29 | style_manager, 30 | scrollable_axes = ScrollAxisFlags::VERTICAL, 31 | flags = table::TableFlags::GROW_LAST_COL, 32 | size_traits = SizeTraits { 33 | preferred: [150.0, 200.0].into(), 34 | // This minimum size is kind of arbitrary 35 | min: [40.0, 40.0].into(), 36 | ..Default::default() 37 | }, 38 | }; 39 | 40 | on (init) { 41 | get!(&self).init(); 42 | get!(&elem).insert_child(get!(table.style_elem)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /stella2/meta/view/dpiscalewatcher.tcwdl: -------------------------------------------------------------------------------- 1 | use tcw3::{uicore::{HView, HWnd, ViewFlags}, pal}; 2 | 3 | #[widget] 4 | #[prototype_only] 5 | #[builder(simple)] 6 | pub(crate) comp crate::view::dpiscalewatcher::DpiScaleWatcher { 7 | const subview: HView { pub get clone; pub set; } 8 | const view_flags: ViewFlags { pub set; } 9 | 10 | const view: HView { pub get clone; } = ?; 11 | 12 | pub event dpi_scale_changed(wm: pal::Wm, hwnd: HWnd); 13 | } 14 | -------------------------------------------------------------------------------- /stella2/meta/view/logview.tcwdl: -------------------------------------------------------------------------------- 1 | use tcw3::{ 2 | ui::{ 3 | theming::{self, Manager}, 4 | mixins::scrollwheel::ScrollAxisFlags, 5 | views::{ScrollableTable, table}, 6 | }, 7 | uicore::{HView, SizeTraits, ViewFlags}, 8 | pal, 9 | }; 10 | 11 | use crate::{ 12 | model, 13 | stylesheet::elem_id, 14 | view::{dpiscalewatcher::DpiScaleWatcher, logview::GUTTER_WIDTH}, 15 | }; 16 | 17 | #[widget] 18 | pub(crate) comp crate::view::logview::LogView { 19 | const wm: pal::Wm { pub set; } 20 | const style_manager: &Manager { pub set; get clone; } 21 | 22 | const view: HView { pub get clone; } = get!(dpi_scale_watcher.view); 23 | 24 | const table = ScrollableTable::new! { 25 | style_manager, 26 | scrollable_axes = ScrollAxisFlags::VERTICAL, 27 | flags = table::TableFlags::GROW_LAST_COL, 28 | size_traits = SizeTraits { 29 | preferred: [300.0, 300.0].into(), 30 | // The vertical minimum size is kind of arbitrary 31 | min: [GUTTER_WIDTH * 2.0, 40.0].into(), 32 | ..Default::default() 33 | }, 34 | }; 35 | 36 | const dpi_scale_watcher = DpiScaleWatcher::new! { 37 | subview = get!(table.view), 38 | view_flags = ViewFlags::ACCEPT_MOUSE_DRAG | ViewFlags::TAB_STOP | 39 | ViewFlags::STRONG_FOCUS, 40 | }; 41 | 42 | on (init) get!(&self).init(); 43 | 44 | on (table.table.prearrange, dpi_scale_watcher.dpi_scale_changed) { 45 | get!(&self).update_row_visuals(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /stella2/meta/view/splitutils.tcwdl: -------------------------------------------------------------------------------- 1 | use tcw3::ui::views::Split; 2 | 3 | /// Attaches a handler to a given `Split` by calling `Split::set_on_drag` and 4 | /// raises `drag_complete` on mouse drag completion (whether cancelled or not). 5 | #[builder(simple)] 6 | #[prototype_only] 7 | pub comp crate::view::splitutils::SplitEventAdapter { 8 | /// This is actually given a type `for<'a> &'a Split`, which is not 9 | /// supported by Designer. 10 | pub const view: &Split { pub set; } 11 | 12 | pub event drag_complete(); 13 | } 14 | -------------------------------------------------------------------------------- /stella2/meta/view/toolbar.tcwdl: -------------------------------------------------------------------------------- 1 | use arrayvec::ArrayVec; 2 | use harmony::Elem; 3 | use tcw3::{ 4 | ui::{ 5 | views::{Label, Button}, 6 | theming::{self, Manager, StyledBox, HElem, Widget}, 7 | }, 8 | uicore::{HView, ViewFlags}, 9 | pal, 10 | }; 11 | 12 | use crate::{model, stylesheet::elem_id}; 13 | 14 | #[widget] 15 | pub(crate) comp crate::view::toolbar::ToolbarView { 16 | const wm: pal::Wm { pub set; } 17 | const style_manager: &Manager { pub set; } 18 | 19 | pub prop wnd_state: Elem; 20 | pub event dispatch(action: model::AppAction); 21 | 22 | pub const wrapper = StyledBox::new! { 23 | style_manager, 24 | // Define a draggable region 25 | view_flags = ViewFlags::ACCEPT_MOUSE_DRAG | ViewFlags::DRAG_AREA, 26 | class_set = elem_id::TOOLBAR, 27 | children = [ 28 | (0, Some(get!(&member_count_icon) as &dyn Widget)), 29 | (1, Some(get!(&member_count) as &dyn Widget)), 30 | (2, Some(get!(&separator) as &dyn Widget)), 31 | (3, Some(get!(&topic) as &dyn Widget)), 32 | (4, Some(get!(&toggle_sidebar_button) as &dyn Widget)), 33 | (5, Some(get!(&menu_button) as &dyn Widget)), 34 | ], 35 | }; 36 | 37 | pub const view: HView = get!(wrapper.view); 38 | pub const style_elem: HElem { pub get clone; } = get!(wrapper.style_elem); 39 | 40 | const separator = StyledBox::new! { 41 | style_manager, 42 | class_set = elem_id::TOOLBAR_SEPARATOR, 43 | }; 44 | 45 | const member_count_icon = StyledBox::new! { 46 | style_manager, 47 | class_set = elem_id::MEMBER_COUNT_ICON, 48 | }; 49 | const member_count = Label::new! { 50 | style_manager, 51 | text = "12", 52 | }; 53 | 54 | const topic = Label::new! { 55 | style_manager, 56 | text = "Company-wide announcements and work-based matter", 57 | }; 58 | 59 | const toggle_sidebar_button = Button::new! { 60 | style_manager, 61 | 62 | class_set = theming::ClassSet::BUTTON 63 | | [elem_id::SIDEBAR_SHOW, elem_id::SIDEBAR_HIDE] 64 | [get!(&wnd_state).sidebar_visible as usize], 65 | }; 66 | 67 | on (toggle_sidebar_button.activated) get!(&self).toggle_sidebar(); 68 | 69 | const menu_button = Button::new! { 70 | style_manager, 71 | class_set = theming::ClassSet::BUTTON | elem_id::SHOW_MENU, 72 | }; 73 | 74 | on (menu_button.activated) get!(&self).toggle_pref(); 75 | } 76 | -------------------------------------------------------------------------------- /stella2/src/config.rs: -------------------------------------------------------------------------------- 1 | //! Configuration system 2 | pub mod cmdline; 3 | pub mod lock; 4 | pub mod profile; 5 | pub mod viewpersistence; 6 | -------------------------------------------------------------------------------- /stella2/src/config/lock.rs: -------------------------------------------------------------------------------- 1 | use fslock::LockFile; 2 | use std::path::PathBuf; 3 | 4 | use super::profile::Profile; 5 | 6 | pub struct LockGuard { 7 | _inner: LockFile, 8 | } 9 | 10 | fn lockfile_path(profile: &Profile) -> PathBuf { 11 | profile.data_dir().join("lock") 12 | } 13 | 14 | /// Create a file in the specified profile directory and attempt to lock it 15 | /// to ensure only one instance of the application has access to the profile. 16 | pub fn try_lock(profile: &Profile) -> Result, fslock::Error> { 17 | let path = lockfile_path(profile); 18 | log::info!("Locking {:?}", path); 19 | let mut file = LockFile::open(&path)?; 20 | 21 | if file.try_lock()? { 22 | Ok(Some(LockGuard { _inner: file })) 23 | } else { 24 | log::warn!( 25 | "Could not lock {:?} - another instance may be already running", 26 | path 27 | ); 28 | Ok(None) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /stella2/src/model.rs: -------------------------------------------------------------------------------- 1 | use harmony::{set_field, Elem}; 2 | use miniserde::{Deserialize, Serialize}; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct AppState { 6 | pub main_wnd: Elem, 7 | /// Indicates whether the Preferences window is visible. 8 | pub pref_visible: bool, 9 | } 10 | 11 | #[derive(Debug, Clone, Serialize, Deserialize)] 12 | pub struct WndState { 13 | // UI state - It could be a local state of widget controllers, but we store 14 | // it here instead so that it can be intercepted by a persistence middleware 15 | pub sidebar_width: f32, 16 | pub editor_height: f32, 17 | pub sidebar_visible: bool, 18 | } 19 | 20 | impl AppState { 21 | pub fn new() -> Self { 22 | Self { 23 | main_wnd: Elem::new(WndState { 24 | sidebar_width: 200.0, 25 | editor_height: 50.0, 26 | sidebar_visible: true, 27 | }), 28 | pref_visible: false, 29 | } 30 | } 31 | } 32 | 33 | #[derive(Debug, Clone)] 34 | pub enum AppAction { 35 | Wnd(WndAction), 36 | /// Hides the Preferences window. 37 | HidePref, 38 | /// Toggles the visibility of the Preferences window. 39 | TogglePref, 40 | } 41 | 42 | #[derive(Debug, Clone)] 43 | pub enum WndAction { 44 | SetSidebarWidth(f32), 45 | SetEditorHeight(f32), 46 | ToggleSidebar, 47 | } 48 | 49 | impl AppState { 50 | pub fn reduce(this: Elem, action: &AppAction) -> Elem { 51 | match action { 52 | AppAction::Wnd(wnd_action) => set_field! { 53 | main_wnd: WndState::reduce(Elem::clone(&this.main_wnd), wnd_action), 54 | ..this 55 | }, 56 | AppAction::HidePref => set_field! { 57 | pref_visible: false, 58 | ..this 59 | }, 60 | AppAction::TogglePref => set_field! { 61 | pref_visible: !this.pref_visible, 62 | ..this 63 | }, 64 | } 65 | } 66 | } 67 | 68 | impl WndState { 69 | fn reduce(this: Elem, action: &WndAction) -> Elem { 70 | match action { 71 | WndAction::SetSidebarWidth(x) => set_field! { 72 | sidebar_width: *x, 73 | ..this 74 | }, 75 | WndAction::SetEditorHeight(x) => set_field! { 76 | editor_height: *x, 77 | ..this 78 | }, 79 | WndAction::ToggleSidebar => set_field! { 80 | sidebar_visible: !this.sidebar_visible, 81 | ..this 82 | }, 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /stella2/src/view/dpiscalewatcher.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::{Cell, RefCell}, 3 | rc::Rc, 4 | }; 5 | use subscriber_list::SubscriberList; 6 | 7 | use tcw3::{ 8 | pal, 9 | ui::layouts::FillLayout, 10 | uicore::{HView, HViewRef, HWndRef, Sub, ViewFlags, ViewListener, WndCb}, 11 | }; 12 | 13 | pub struct DpiScaleWatcher { 14 | shared: Rc, 15 | view: HView, 16 | } 17 | 18 | struct Shared { 19 | handlers: RefCell>, 20 | } 21 | 22 | impl DpiScaleWatcher { 23 | pub fn new(subview: HView, view_flags: ViewFlags) -> Self { 24 | let shared = Rc::new(Shared { 25 | handlers: RefCell::new(SubscriberList::new()), 26 | }); 27 | 28 | let view = HView::new(view_flags); 29 | view.set_layout(FillLayout::new(subview)); 30 | view.set_listener(DpiScaleWatcherViewListener { 31 | shared: Rc::clone(&shared), 32 | sub: Cell::default(), 33 | }); 34 | 35 | Self { shared, view } 36 | } 37 | 38 | pub fn view(&self) -> HView { 39 | self.view.clone() 40 | } 41 | 42 | pub fn subscribe_dpi_scale_changed(&self, cb: WndCb) -> Sub { 43 | self.shared.handlers.borrow_mut().insert(cb).untype() 44 | } 45 | } 46 | 47 | struct DpiScaleWatcherViewListener { 48 | shared: Rc, 49 | sub: Cell, 50 | } 51 | 52 | impl ViewListener for DpiScaleWatcherViewListener { 53 | fn mount(&self, _: pal::Wm, _: HViewRef<'_>, wnd: HWndRef<'_>) { 54 | let shared = Rc::clone(&self.shared); 55 | self.sub 56 | .set(wnd.subscribe_dpi_scale_changed(Box::new(move |wm, wnd| { 57 | for cb in shared.handlers.borrow().iter() { 58 | cb(wm, wnd); 59 | } 60 | }))); 61 | } 62 | 63 | fn unmount(&self, _: pal::Wm, _: HViewRef<'_>) { 64 | self.sub.take().unsubscribe().unwrap(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /stella2/src/view/radiolist.rs: -------------------------------------------------------------------------------- 1 | use tcw3::{ui::theming, uicore::HViewRef}; 2 | 3 | stella2_meta::designer_impl! { 4 | crate::view::radiolist::RadioListView 5 | } 6 | 7 | impl theming::Widget for RadioListView { 8 | fn view_ref(&self) -> HViewRef<'_> { 9 | self.view().as_ref() 10 | } 11 | 12 | fn style_elem(&self) -> Option { 13 | Some(self.style_elem()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /stella2/src/view/splitutils.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | use subscriber_list::SubscriberList; 3 | use tcw3::{ 4 | ui::views::{split::SplitDragListener, Split}, 5 | uicore::Sub, 6 | }; 7 | 8 | pub type Cb = Box; 9 | 10 | pub struct SplitEventAdapter { 11 | shared: Rc, 12 | } 13 | 14 | struct SplitEventAdapterShared { 15 | handlers: RefCell>, 16 | } 17 | 18 | impl SplitEventAdapter { 19 | pub fn new(view: &Split) -> Self { 20 | let shared = Rc::new(SplitEventAdapterShared { 21 | handlers: RefCell::new(SubscriberList::new()), 22 | }); 23 | 24 | let shared_weak = Rc::downgrade(&shared); 25 | view.set_on_drag(move |_| { 26 | let shared_weak = shared_weak.clone(); 27 | Box::new(OnDrop::new(move || { 28 | if let Some(shared) = shared_weak.upgrade() { 29 | // Raise `drag_complete` 30 | for cb in shared.handlers.borrow().iter() { 31 | cb(); 32 | } 33 | } 34 | })) 35 | }); 36 | 37 | Self { shared } 38 | } 39 | 40 | pub fn subscribe_drag_complete(&self, cb: Cb) -> Sub { 41 | self.shared.handlers.borrow_mut().insert(cb).untype() 42 | } 43 | } 44 | 45 | struct OnDrop(Option); 46 | 47 | impl OnDrop { 48 | fn new(x: F) -> Self { 49 | Self(Some(x)) 50 | } 51 | } 52 | 53 | /// The inner function is called when `` is dropped, 54 | /// i.e., a mouse drag gesture is finished 55 | impl SplitDragListener for OnDrop {} 56 | 57 | impl Drop for OnDrop { 58 | fn drop(&mut self) { 59 | (self.0.take().unwrap())(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /stella2/src/view/tabbar.rs: -------------------------------------------------------------------------------- 1 | use tcw3::{ui::theming, uicore::HViewRef}; 2 | 3 | stella2_meta::designer_impl! { 4 | crate::view::tabbar::TabbarView 5 | } 6 | 7 | impl theming::Widget for TabbarView { 8 | fn view_ref(&self) -> HViewRef<'_> { 9 | self.view().as_ref() 10 | } 11 | 12 | fn style_elem(&self) -> Option { 13 | Some(self.style_elem()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /stella2/src/view/toolbar.rs: -------------------------------------------------------------------------------- 1 | use tcw3::{ui::theming, uicore::HViewRef}; 2 | 3 | use crate::model; 4 | 5 | stella2_meta::designer_impl! { 6 | crate::view::toolbar::ToolbarView 7 | } 8 | 9 | impl ToolbarView { 10 | /// Handle `toggle_sidebar_button.activate` event. 11 | fn toggle_sidebar(&self) { 12 | // Toggle the sidebar 13 | self.raise_dispatch(model::AppAction::Wnd(model::WndAction::ToggleSidebar)); 14 | } 15 | 16 | /// Handle `menu_button.activate` event. 17 | fn toggle_pref(&self) { 18 | // Show or hide the Preferences window 19 | // TODO: Show a dropdown menu 20 | self.raise_dispatch(model::AppAction::TogglePref); 21 | } 22 | } 23 | 24 | impl theming::Widget for ToolbarView { 25 | fn view_ref(&self) -> HViewRef<'_> { 26 | self.view().as_ref() 27 | } 28 | 29 | fn style_elem(&self) -> Option { 30 | Some(self.style_elem()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /stella2_assets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stella2_assets" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | stvg_macro = { path = "../stvg/macro" } 9 | -------------------------------------------------------------------------------- /stella2_assets/src/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stella2_assets/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Image assets for Stella2 2 | 3 | pub type Stvg = (&'static [u8], [f32; 2]); 4 | 5 | macro_rules! stvg { 6 | ($path:literal) => { 7 | stvg_macro::include_stvg!($path) 8 | }; 9 | } 10 | 11 | pub static SEARCH: Stvg = stvg!("src/search.svg"); 12 | pub static CLOSE: Stvg = stvg!("src/close.svg"); 13 | 14 | pub static LIST_GROUP_OPEN: Stvg = stvg!("src/list_group_open.svg"); 15 | 16 | pub mod pref { 17 | use super::*; 18 | 19 | pub static TAB_ABOUT: Stvg = stvg!("src/pref/tab_about.svg"); 20 | pub static TAB_ACCOUNTS: Stvg = stvg!("src/pref/tab_accounts.svg"); 21 | pub static TAB_ADVANCED: Stvg = stvg!("src/pref/tab_advanced.svg"); 22 | pub static TAB_CONNECTION: Stvg = stvg!("src/pref/tab_connection.svg"); 23 | pub static TAB_GENERAL: Stvg = stvg!("src/pref/tab_general.svg"); 24 | } 25 | 26 | pub mod toolbar { 27 | use super::*; 28 | 29 | pub static SIDEBAR_HIDE: Stvg = stvg!("src/toolbar/sidebar_hide.svg"); 30 | pub static SIDEBAR_SHOW: Stvg = stvg!("src/toolbar/sidebar_show.svg"); 31 | pub static USER_OUTLINE: Stvg = stvg!("src/toolbar/user_outline.svg"); 32 | pub static MENU: Stvg = stvg!("src/toolbar/menu.svg"); 33 | } 34 | -------------------------------------------------------------------------------- /stella2_assets/src/list_group_open.svg: -------------------------------------------------------------------------------- 1 | list_group_open -------------------------------------------------------------------------------- /stella2_assets/src/pref/tab_about.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stella2_assets/src/pref/tab_accounts.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stella2_assets/src/pref/tab_advanced.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stella2_assets/src/pref/tab_connection.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stella2_assets/src/pref/tab_general.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stella2_assets/src/search.svg: -------------------------------------------------------------------------------- 1 | search -------------------------------------------------------------------------------- /stella2_assets/src/toolbar/menu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stella2_assets/src/toolbar/sidebar_hide.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stella2_assets/src/toolbar/sidebar_show.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stella2_assets/src/toolbar/user_outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stvg/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2020 yvt 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 | -------------------------------------------------------------------------------- /stvg/io/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stvg_io" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "MIT" 7 | 8 | [dependencies] 9 | cgmath = "0.17.0" 10 | rgb = "0.8.13" 11 | arrayvec = "0.5" 12 | -------------------------------------------------------------------------------- /stvg/io/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Implements the decoder and encoder of the StellaVG (Stella Vector Graphics) 2 | //! format. 3 | use cgmath::Point2; 4 | use rgb::RGBA8; 5 | use std::mem::size_of; 6 | 7 | mod dec; 8 | mod enc; 9 | 10 | pub use {dec::CmdDecoder, enc::CmdEncoder}; 11 | 12 | /// The op flags indicates which operation to perform. When multiple flags are 13 | /// specified, the corresponding operations are performed from LSB to MSB. 14 | mod op { 15 | /// Set the current fill color. Followed by a 4-byte color value. 16 | pub const SET_FILL_RGB: u8 = 1 << SET_FILL_RGB_SHIFT; 17 | pub const SET_FILL_RGB_SHIFT: u32 = 0; 18 | 19 | /// Fill the current path. 20 | pub const FILL: u8 = 1 << FILL_SHIFT; 21 | pub const FILL_SHIFT: u32 = 1; 22 | 23 | /// Clear the current path. 24 | pub const BEGIN_PATH: u8 = 1 << BEGIN_PATH_SHIFT; 25 | pub const BEGIN_PATH_SHIFT: u32 = 2; 26 | 27 | /// Add vertices to the current path. Does not implicitly clear the current 28 | /// path. Followed by path data. The path data is organized in the 29 | /// structure-of-arrays style in hopes of efficient application of 30 | /// data compression on the encoded data. 31 | /// 32 | /// This flag must be the last one. 33 | pub const CONTOUR: u8 = 1 << CONTOUR_SHIFT; 34 | pub const CONTOUR_SHIFT: u32 = 3; 35 | } 36 | 37 | const BYTES_PER_POINT: usize = 4; 38 | const CONTOUR_HDR_SIZE: usize = BYTES_PER_POINT + size_of::(); 39 | 40 | /// The number of fractional bits included in fixed-point numbers used by 41 | /// StellaVG. 42 | /// 43 | /// Coordinates are represented by fixed-point numbers of type `i16`. Since 44 | /// four bits (= this constant) are allocated for fractional digits, the maximum 45 | /// representable range is circa `[-2048, 2048]`. 46 | pub const FRAC_BITS: u32 = 4; 47 | 48 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 49 | pub enum Cmd { 50 | BeginPath, 51 | Fill, 52 | MoveTo(Point2), 53 | LineTo(Point2), 54 | QuadBezierTo([Point2; 2]), 55 | CubicBezierTo([Point2; 3]), 56 | SetFillRgb(RGBA8), 57 | } 58 | -------------------------------------------------------------------------------- /stvg/io/tests/test.rs: -------------------------------------------------------------------------------- 1 | use cgmath::Point2; 2 | use rgb::RGBA8; 3 | 4 | use stvg_io::Cmd; 5 | 6 | fn decode(b: &[u8]) -> Vec { 7 | stvg_io::CmdDecoder::from_bytes(b).collect() 8 | } 9 | 10 | fn encode(cmds: impl IntoIterator) -> Vec { 11 | cmds.into_iter() 12 | .collect::() 13 | .take_bytes() 14 | } 15 | 16 | #[test] 17 | fn roundtrip() { 18 | let cmds = vec![ 19 | Cmd::BeginPath, 20 | Cmd::MoveTo(Point2::new(1000, 2000)), 21 | Cmd::LineTo(Point2::new(1000, 2100)), 22 | Cmd::LineTo(Point2::new(3000, 500)), 23 | Cmd::QuadBezierTo([Point2::new(500, 200), Point2::new(800, 250)]), 24 | Cmd::CubicBezierTo([ 25 | Point2::new(600, 300), 26 | Point2::new(900, 350), 27 | Point2::new(1200, 450), 28 | ]), 29 | Cmd::Fill, 30 | Cmd::SetFillRgb(RGBA8::new(42, 43, 44, 45)), 31 | Cmd::Fill, 32 | Cmd::BeginPath, 33 | Cmd::MoveTo(Point2::new(1000, 2000)), 34 | Cmd::LineTo(Point2::new(1000, 2100)), 35 | Cmd::LineTo(Point2::new(3000, 500)), 36 | Cmd::Fill, 37 | ]; 38 | 39 | let bytes = encode(cmds.iter().cloned()); 40 | println!("{:?}", bytes); 41 | println!("len = {}", bytes.len()); 42 | 43 | let decoded_cmds = decode(&bytes); 44 | 45 | assert_eq!(decoded_cmds, cmds); 46 | } 47 | -------------------------------------------------------------------------------- /stvg/macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stvg_macro" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "MIT" 7 | 8 | [dependencies] 9 | stvg_macro_impl = { path = "./impl" } 10 | stvg_io = { path = "../io" } 11 | -------------------------------------------------------------------------------- /stvg/macro/impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stvg_macro_impl" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | stvg_io = { path = "../../io" } 9 | usvg = { version = "0.9.0", default-features = false } 10 | syn = "1" 11 | quote = "1" 12 | rgb = "0.8.13" 13 | cgmath = "0.17.0" 14 | 15 | [dependencies.pathfinder_geometry] 16 | git = "https://github.com/servo/pathfinder.git" 17 | rev = "678b6f12c7bc4b8076ed5c66bf77a60f7a56a9f6" 18 | 19 | [lib] 20 | path = "src/lib.rs" 21 | proc-macro = true 22 | -------------------------------------------------------------------------------- /stvg/macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Provides a macro for generating and embedding StellaVG data at compile time. 2 | //! 3 | //! # Examples 4 | //! 5 | //! ``` 6 | //! use stvg_macro::include_stvg; 7 | //! static TIGER: (&[u8], [f32; 2]) = include_stvg!("../tests/tiger.svgz"); 8 | //! println!("len = {}", TIGER.0.len()); 9 | //! println!("size = {:?}", TIGER.1); 10 | //! ``` 11 | 12 | /// Include the specified SVG file as StellaVG data (`([u8; _], [f32; 2])`). 13 | /// 14 | /// The path is relative to `$CARGO_MANIFEST_DIR`. 15 | /// 16 | /// Be aware that the range of coordinates are limited by the internal 17 | /// representation used by StellaVG. See [`stvg_io::FRAC_BITS`]. 18 | pub use stvg_macro_impl::include_stvg; 19 | -------------------------------------------------------------------------------- /stvg/tests/LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | # `tiger.svg` 3 | 4 | 5 | 6 | This work is licensed by the Ghostscript authors under GNU General Public License version 2. 7 | -------------------------------------------------------------------------------- /stvg/tests/tiger.svgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yvt/Stella2/79bb01d5d11643c50769986796b6fae572e13db7/stvg/tests/tiger.svgz -------------------------------------------------------------------------------- /support/alt_fp/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /support/alt_fp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alt_fp" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | readme = "README.md" 8 | repository = "https://github.com/yvt/alt_fp-rs" 9 | description = """ 10 | Provides alternative (faster in most cases) implementation for 11 | floating-point operations. 12 | """ 13 | keywords = ["floating-point", "performance", "simd"] 14 | 15 | [features] 16 | default = ["packed_simd"] 17 | 18 | [dependencies] 19 | packed_simd = { version = "0.3.0", optional = true } 20 | 21 | [dev-dependencies] 22 | bencher = "0.1.5" 23 | 24 | [[bench]] 25 | name = "castf32" 26 | harness = false -------------------------------------------------------------------------------- /support/alt_fp/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2017 yvt 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 | -------------------------------------------------------------------------------- /support/alt_fp/README.md: -------------------------------------------------------------------------------- 1 | # alt_fp 2 | 3 | [docs.rs](https://docs.rs/alt_fp/) 4 | 5 | This crate provides an alternative (faster in most cases) implementation for 6 | floating-point operations. 7 | 8 | License: MIT/Apache-2.0 9 | -------------------------------------------------------------------------------- /support/alt_fp/README.tpl: -------------------------------------------------------------------------------- 1 | # {{crate}} 2 | 3 | [docs.rs](https://docs.rs/alt_fp/) 4 | 5 | {{readme}} 6 | 7 | License: {{license}} 8 | -------------------------------------------------------------------------------- /support/alt_fp/benches/castf32.rs: -------------------------------------------------------------------------------- 1 | use bencher::{benchmark_group, benchmark_main, Bencher}; 2 | 3 | use alt_fp::{f32_to_u23, u23_to_f32}; 4 | 5 | fn run_u32_to_f32(b: &mut Bencher, cvt: impl Fn(u32) -> f32) { 6 | // Make sure the array is smaller than L1D$ so that the loop does not 7 | // get satured by the memory system 8 | let t1: Vec = (0..2048).map(|x| x & 31).collect(); 9 | let mut t2: Vec = vec![0.0; t1.len()]; 10 | 11 | b.iter(|| { 12 | for _ in 0..1000 { 13 | for (x, y) in t1.iter().zip(t2.iter_mut()) { 14 | *y = cvt(*x); 15 | } 16 | } 17 | }); 18 | } 19 | 20 | fn bench_u32_to_f32_sys(b: &mut Bencher) { 21 | run_u32_to_f32(b, |x| x as _); 22 | } 23 | 24 | fn bench_u32_to_f32(b: &mut Bencher) { 25 | run_u32_to_f32(b, u23_to_f32); 26 | } 27 | 28 | fn run_f32_to_u32(b: &mut Bencher, cvt: impl Fn(f32) -> u32) { 29 | // Make sure the array is smaller than L1D$ so that the loop does not 30 | // get satured by the memory system 31 | let t1: Vec = (0..2048).map(|x| (x & 31) as f32).collect(); 32 | let mut t2: Vec = vec![0; t1.len()]; 33 | 34 | b.iter(|| { 35 | for _ in 0..1000 { 36 | for (x, y) in t1.iter().zip(t2.iter_mut()) { 37 | *y = cvt(*x); 38 | } 39 | } 40 | }); 41 | } 42 | 43 | fn bench_f32_to_u32_sys(b: &mut Bencher) { 44 | run_f32_to_u32(b, |x| x as _); 45 | } 46 | 47 | fn bench_f32_to_u32(b: &mut Bencher) { 48 | run_f32_to_u32(b, f32_to_u23); 49 | } 50 | 51 | benchmark_group!( 52 | benches, 53 | bench_u32_to_f32_sys, 54 | bench_u32_to_f32, 55 | bench_f32_to_u32_sys, 56 | bench_f32_to_u32, 57 | ); 58 | benchmark_main!(benches); 59 | -------------------------------------------------------------------------------- /support/alt_fp/src/cast.rs: -------------------------------------------------------------------------------- 1 | /// Convert a 23-bit unsigned integer to a single-precision floating-point 2 | /// number. 3 | /// 4 | /// If the input is out of range, the result is unspecified. 5 | /// 6 | /// # Examples 7 | /// 8 | /// use alt_fp::u23_to_f32; 9 | /// assert_eq!(u23_to_f32(0), 0.0); 10 | /// assert_eq!(u23_to_f32(1), 1.0); 11 | /// assert_eq!(u23_to_f32(8388606), 8388606.0); 12 | /// assert_eq!(u23_to_f32(8388607), 8388607.0); 13 | /// 14 | #[inline] 15 | pub fn u23_to_f32(x: u32) -> f32 { 16 | ::from_bits(x | 0x4b00_0000) - 8_388_608.0 17 | } 18 | 19 | /// Convert a 16-bit unsigned integer to a single-precision floating-point 20 | /// number. 21 | /// 22 | /// # Examples 23 | /// 24 | /// use alt_fp::u16_to_f32; 25 | /// assert_eq!(u16_to_f32(0), 0.0); 26 | /// assert_eq!(u16_to_f32(1), 1.0); 27 | /// assert_eq!(u16_to_f32(65534), 65534.0); 28 | /// assert_eq!(u16_to_f32(65535), 65535.0); 29 | /// 30 | #[inline] 31 | pub fn u16_to_f32(x: u16) -> f32 { 32 | u23_to_f32(x as u32) 33 | } 34 | 35 | /// Convert a single-precision floating-point number to a 23-bit unsigned 36 | /// integer. 37 | /// 38 | /// The rounding mode is based on the default [floating-point environment]. 39 | /// Do not use `::trunc` to enforce the round-toward-zero rounding mode; 40 | /// the `as` operator would be faster on all modern x86 processors. 41 | /// 42 | /// [floating-point environment]: http://llvm.org/docs/LangRef.html#floatenv 43 | /// 44 | /// If the input is out of range, the result is unspecified. 45 | /// 46 | /// # Examples 47 | /// 48 | /// use alt_fp::f32_to_u23; 49 | /// assert_eq!(f32_to_u23(0.0), 0); 50 | /// assert_eq!(f32_to_u23(1.0), 1); 51 | /// assert_eq!(f32_to_u23(1.5), 2); 52 | /// assert_eq!(f32_to_u23(8388606.0), 8388606); 53 | /// assert_eq!(f32_to_u23(8388607.0), 8388607); 54 | /// 55 | #[inline] 56 | pub fn f32_to_u23(x: f32) -> u32 { 57 | (x + 8_388_608.0).to_bits() & 0x7f_ffff 58 | } 59 | -------------------------------------------------------------------------------- /support/alt_fp/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides an alternative (faster in most cases) implementation for 2 | //! floating-point operations. 3 | pub mod cast; 4 | pub mod cmp; 5 | pub mod fma; 6 | #[cfg(feature = "packed_simd")] 7 | pub mod simd; 8 | 9 | #[doc(no_inline)] 10 | pub use self::{cast::*, cmp::*, fma::*}; 11 | 12 | #[cfg(feature = "packed_simd")] 13 | #[doc(no_inline)] 14 | pub use self::simd::*; 15 | -------------------------------------------------------------------------------- /support/alt_fp/src/simd.rs: -------------------------------------------------------------------------------- 1 | use packed_simd::f32x4; 2 | #[allow(unused_imports)] 3 | use std::{ 4 | mem::transmute, 5 | ops::{Add, Mul}, 6 | }; 7 | 8 | #[cfg(target_feature = "sse3")] 9 | #[cfg(target_arch = "x86")] 10 | use std::arch::x86::*; 11 | #[cfg(target_feature = "sse3")] 12 | #[cfg(target_arch = "x86_64")] 13 | use std::arch::x86_64::*; 14 | 15 | pub trait SimdExt: Copy + Mul { 16 | type Element: Copy + Add; 17 | 18 | fn splat(e: Self::Element) -> Self; 19 | 20 | fn extract(self, index: usize) -> Self::Element; 21 | 22 | /// Horizontal sum of the first two vector elements. 23 | /// 24 | /// # Examples 25 | /// 26 | /// # use packed_simd::f32x4; 27 | /// use alt_fp::SimdExt; 28 | /// 29 | /// assert_eq!(f32x4::new(1.0, 2.0, 4.0, 8.0).sum2(), 1.0 + 2.0); 30 | /// 31 | #[inline] 32 | fn sum2(self) -> Self::Element { 33 | self.extract(0) + self.extract(1) 34 | } 35 | 36 | /// Perform a dot product using the first two vector elements and distribute 37 | /// the result to all lanes. 38 | /// 39 | /// # Examples 40 | /// 41 | /// # use packed_simd::f32x4; 42 | /// use alt_fp::SimdExt; 43 | /// 44 | /// assert_eq!( 45 | /// f32x4::new(1.0, 2.0, 4.0, 8.0) 46 | /// .dot2_splat(f32x4::new(3.0, 7.0, 1.0, 2.0)), 47 | /// f32x4::splat(1.0 * 3.0 + 2.0 * 7.0), 48 | /// ); 49 | /// 50 | #[inline] 51 | fn dot2_splat(self, other: Self) -> Self { 52 | Self::splat((self * other).sum2()) 53 | } 54 | } 55 | 56 | impl SimdExt for f32x4 { 57 | type Element = f32; 58 | 59 | #[inline] 60 | fn splat(e: Self::Element) -> Self { 61 | f32x4::splat(e) 62 | } 63 | 64 | #[inline] 65 | fn extract(self, index: usize) -> Self::Element { 66 | self.extract(index) 67 | } 68 | 69 | #[cfg(target_feature = "sse3")] 70 | #[inline] 71 | fn sum2(self) -> Self::Element { 72 | let r: f32x4 = unsafe { transmute(_mm_hadd_ps(transmute(self), transmute(self))) }; 73 | r.extract(0) 74 | } 75 | 76 | #[cfg(target_feature = "sse4.1")] 77 | #[inline] 78 | fn dot2_splat(self, other: Self) -> Self { 79 | unsafe { transmute(_mm_dp_ps(transmute(self), transmute(other), 0b0011_1111)) } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /support/array_intrusive_list/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "array_intrusive_list" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /support/as_any/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "as_any" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /support/atom2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atom2" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [target.'cfg(target_os = "windows")'.dependencies] 8 | winrt = { version = "0.6.0", default-features = false, optional = true } 9 | -------------------------------------------------------------------------------- /support/atom2/src/winrt_comptr.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use winrt::ComPtr; 3 | 4 | unsafe impl PtrSized for ComPtr { 5 | fn into_raw(this: Self) -> NonNull<()> { 6 | let ptr = NonNull::from(&*this); 7 | std::mem::forget(this); 8 | ptr.cast() 9 | } 10 | unsafe fn from_raw(ptr: NonNull<()>) -> Self { 11 | Self::wrap(ptr.as_ptr() as _) 12 | } 13 | } 14 | unsafe impl TypedPtrSized for ComPtr { 15 | type Target = T; 16 | } 17 | unsafe impl MutPtrSized for ComPtr {} 18 | unsafe impl TrivialPtrSized for ComPtr {} 19 | -------------------------------------------------------------------------------- /support/atom2/tests/atom.rs: -------------------------------------------------------------------------------- 1 | use atom2::Atom; 2 | use std::sync::{atomic::Ordering, Arc}; 3 | 4 | #[test] 5 | fn arc_into_inner_some() { 6 | let aa = Atom::new(Some(Arc::new(1))); 7 | assert_eq!(*aa.into_inner().unwrap(), 1); 8 | } 9 | 10 | #[test] 11 | fn arc_into_inner_none() { 12 | let aa: Atom> = Atom::empty(); 13 | assert!(aa.into_inner().is_none()); 14 | } 15 | 16 | #[test] 17 | fn arc_as_inner_ref_some() { 18 | let mut aa = Atom::new(Some(Arc::new(1))); 19 | assert_eq!(*aa.as_inner_ref().unwrap(), 1); 20 | } 21 | 22 | #[test] 23 | fn arc_as_inner_ref_none() { 24 | let mut aa: Atom> = Atom::empty(); 25 | assert!(aa.as_inner_ref().is_none()); 26 | } 27 | 28 | #[test] 29 | fn box_as_inner_mut_some() { 30 | let mut aa = Atom::new(Some(Box::new(1))); 31 | assert_eq!(*aa.as_inner_mut().unwrap(), 1); 32 | *aa.as_inner_mut().unwrap() = 2; 33 | assert_eq!(*aa.into_inner().unwrap(), 2); 34 | } 35 | 36 | #[test] 37 | fn box_as_inner_mut_none() { 38 | let mut aa: Atom> = Atom::empty(); 39 | assert!(aa.as_inner_mut().is_none()); 40 | } 41 | 42 | #[test] 43 | fn arc_load_some() { 44 | let mut aa = Atom::new(Some(Arc::new(1))); 45 | assert_eq!(*aa.load().unwrap(), 1); 46 | } 47 | 48 | #[test] 49 | fn arc_load_none() { 50 | let mut aa: Atom> = Atom::empty(); 51 | assert!(aa.load().is_none()); 52 | } 53 | 54 | #[test] 55 | fn arc_swap() { 56 | let aa = Atom::new(Some(Arc::new(1))); 57 | let old = aa.swap(Some(Arc::new(2)), Ordering::Relaxed); 58 | assert_eq!(*old.unwrap(), 1); 59 | assert_eq!(*aa.into_inner().unwrap(), 2); 60 | } 61 | 62 | #[test] 63 | fn arc_compare_and_swap1() { 64 | let cur = Some(Arc::new(1)); 65 | let aa = Atom::new(cur.clone()); 66 | let old = aa.compare_and_swap(&cur, Some(Arc::new(2)), Ordering::Relaxed); 67 | assert_eq!(*old.unwrap().unwrap(), 1); 68 | assert_eq!(*aa.into_inner().unwrap(), 2); 69 | } 70 | 71 | #[test] 72 | fn arc_compare_and_swap2() { 73 | let cur = Some(Arc::new(114514)); 74 | let aa = Atom::new(Some(Arc::new(1))); 75 | let old = aa.compare_and_swap(&cur, Some(Arc::new(2)), Ordering::Relaxed); 76 | assert_eq!(*old.unwrap_err().unwrap(), 2); 77 | assert_eq!(*aa.into_inner().unwrap(), 1); 78 | } 79 | -------------------------------------------------------------------------------- /support/boxed_slice_tools/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "boxed_slice_tools" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /support/boxed_slice_tools/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for boxed slices. 2 | //! 3 | //! The functions in this crate generates better code than `std` equivalents 4 | //! in general. 5 | 6 | /// Constructs a boxed slice in a similar way to `vec!`. 7 | /// 8 | /// Note: `boxed_slice![expr; n]` instantiates `n + 1` instances of the element 9 | /// type unlike `vec!`. 10 | /// 11 | /// # Examples 12 | /// 13 | /// ``` 14 | /// use boxed_slice_tools::boxed_slice; 15 | /// assert_eq!(*boxed_slice![42; 3], [42, 42, 42]); 16 | /// assert_eq!(*boxed_slice![1, 2, 3], [1, 2, 3]); 17 | /// ``` 18 | /// 19 | #[macro_export] 20 | macro_rules! boxed_slice { 21 | ($e:expr; $len:expr) => { 22 | $crate::repeating_by_clone(&$e, $len) 23 | }; 24 | ($($e:expr),*$(,)*) => { 25 | // `vec!` suffices in this case 26 | ::std::vec![$($e),*].into_boxed_slice() 27 | }; 28 | } 29 | 30 | /// Consturct a boxed slice using the given generator function. 31 | /// 32 | /// # Examples 33 | /// 34 | /// ``` 35 | /// use boxed_slice_tools::from_fn; 36 | /// assert_eq!(*from_fn(|i| i * 2, 3), [0, 2, 4]); 37 | /// ``` 38 | /// 39 | pub fn from_fn(mut gen: impl FnMut(usize) -> T, len: usize) -> Box<[T]> { 40 | let mut v = Vec::::with_capacity(len); 41 | debug_assert_eq!(v.capacity(), len); 42 | unsafe { 43 | for i in 0..len { 44 | v.as_mut_ptr().add(i).write(gen(i)); 45 | v.set_len(i + 1); 46 | } 47 | v.set_len(v.capacity()); 48 | } 49 | v.into_boxed_slice() 50 | } 51 | 52 | /// Construct a boxed slice by cloning the given prototype value. 53 | /// 54 | /// # Examples 55 | /// 56 | /// ``` 57 | /// use boxed_slice_tools::repeating_by_clone; 58 | /// assert_eq!(*repeating_by_clone(&42, 3), [42, 42, 42]); 59 | /// ``` 60 | /// 61 | pub fn repeating_by_clone(proto: &T, len: usize) -> Box<[T]> { 62 | from_fn(|_| proto.clone(), len) 63 | } 64 | 65 | /// Construct a boxed slice by filling it with default values. 66 | /// 67 | /// # Examples 68 | /// 69 | /// ``` 70 | /// use boxed_slice_tools::repeating_default; 71 | /// assert_eq!(*repeating_default::(3), [0, 0, 0]); 72 | /// ``` 73 | /// 74 | pub fn repeating_default(len: usize) -> Box<[T]> { 75 | from_fn(|_| T::default(), len) 76 | } 77 | -------------------------------------------------------------------------------- /support/cggeom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cggeom" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | 8 | [dependencies] 9 | cgmath = "0.17.0" 10 | quickcheck = { version = "0.9", optional = true } 11 | -------------------------------------------------------------------------------- /support/cggeom/src/average.rs: -------------------------------------------------------------------------------- 1 | /// A trait for types supporting the calculation of the average of two values. 2 | pub trait Average2 { 3 | /// Average two values. 4 | /// 5 | /// # Examples 6 | /// 7 | /// ``` 8 | /// use cggeom::Average2; 9 | /// assert_eq!(0u32.average2(&1000), 500); 10 | /// assert_eq!(127i8.average2(&-128), -1); 11 | /// assert_eq!(40.0f32.average2(&100.0f32), 70.0f32); 12 | /// ``` 13 | fn average2(&self, other: &Self) -> Self; 14 | } 15 | 16 | macro_rules! impl_float { 17 | ($($ty:ty),*) => {$( 18 | impl Average2 for $ty { 19 | fn average2(&self, other: &Self) -> Self { 20 | self + (other - self) * 0.5 21 | } 22 | } 23 | )*}; 24 | } 25 | 26 | macro_rules! impl_int { 27 | ($($ty:ty),*) => {$( 28 | impl Average2 for $ty { 29 | fn average2(&self, other: &Self) -> Self { 30 | (self >> 1) + (other >> 1) + ((self & 1) + (other & 1) >> 1) 31 | } 32 | } 33 | )*}; 34 | } 35 | 36 | impl_float!(f32, f64); 37 | impl_int!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); 38 | 39 | macro_rules! impl_struct { 40 | ($ty:ty, {$($field:ident),*}) => { 41 | impl Average2 for $ty { 42 | fn average2(&self, other: &Self) -> Self { 43 | Self { 44 | $($field: self.$field.average2(&other.$field)),* 45 | } 46 | } 47 | } 48 | }; 49 | } 50 | 51 | impl_struct!(cgmath::Vector1, { x }); 52 | impl_struct!(cgmath::Vector2, {x, y}); 53 | impl_struct!(cgmath::Vector3, {x, y, z}); 54 | impl_struct!(cgmath::Vector4, {x, y, z, w}); 55 | impl_struct!(cgmath::Point1, { x }); 56 | impl_struct!(cgmath::Point2, {x, y}); 57 | impl_struct!(cgmath::Point3, {x, y, z}); 58 | -------------------------------------------------------------------------------- /support/cggeom/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A helper library for `cgmath`. 2 | //! 3 | //! Provides additional types useful in computer graphics. 4 | pub extern crate cgmath; 5 | 6 | mod average; 7 | mod boxes; 8 | mod elementwise; 9 | mod twodim; 10 | 11 | pub use self::average::*; 12 | pub use self::boxes::*; 13 | pub use self::elementwise::*; 14 | pub use self::twodim::*; 15 | 16 | /// The prelude. 17 | pub mod prelude { 18 | #[doc(no_inline)] 19 | pub use crate::{ 20 | Average2, AxisAlignedBox, ElementWiseOp, ElementWisePartialOrd, Matrix3TwoDimExt, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /support/cggeom/src/twodim.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{BaseFloat, Matrix3, Rad, Vector2}; 2 | 3 | /// An extension trait for [`cgmath::Matrix3`] that adds methods for 4 | /// constructing 2D transformations. 5 | /// 6 | /// `cgmath` is primarily built for 3D applications. For instance, there are 7 | /// several creation methods that assume implicit semantics for the uses in such 8 | /// applications: `Matrix4` represents a 3D homogeneous transformation matrix 9 | /// and `Matrix3` a non-translating 3D transformation matrix. 10 | /// This means that methods for 2D transformations, e.g., 11 | /// `from_nonuniform_scale`, cannot be added to `Matrix3` without breaking the 12 | /// conventions (i.e., marking all of the 2D/3D operations as so), introducing 13 | /// inconsistencies (i.e., marking only the new 2D operations as so), or 14 | /// confusing users (i.e. `from_nonuniform_scale` could mean any of 2D and 15 | /// 3D for `Matrix3`). Thus [they gave up] supporting such problematic 16 | /// operations at all. 17 | /// 18 | /// [they gave up]: https://github.com/rustgd/cgmath/pull/469#issuecomment-436041377 19 | pub trait Matrix3TwoDimExt: Sized { 20 | /// Create a homogeneous transformation matrix from a translation vector. 21 | fn from_translation(v: Vector2) -> Self; 22 | /// Create a homogeneous transformation matrix from a scale value. 23 | fn from_scale_2d(value: S) -> Self; 24 | /// Create a homogeneous transformation matrix from a set of scale values. 25 | fn from_nonuniform_scale_2d(x: S, y: S) -> Self; 26 | /// Create a homogeneous transformation matrix from a rotation. 27 | fn from_angle>>(theta: A) -> Self; 28 | } 29 | 30 | impl Matrix3TwoDimExt for Matrix3 { 31 | #[inline] 32 | #[rustfmt::skip] 33 | fn from_translation(v: Vector2) -> Self { 34 | Self::new( 35 | S::one(), S::zero(), S::zero(), 36 | S::zero(), S::one(), S::zero(), 37 | v.x, v.y, S::one(), 38 | ) 39 | } 40 | 41 | #[inline] 42 | fn from_scale_2d(value: S) -> Self { 43 | Self::from_nonuniform_scale_2d(value, value) 44 | } 45 | 46 | #[inline] 47 | #[rustfmt::skip] 48 | fn from_nonuniform_scale_2d(x: S, y: S) -> Self { 49 | Self::new( 50 | x, S::zero(), S::zero(), 51 | S::zero(), y, S::zero(), 52 | S::zero(), S::zero(), S::one(), 53 | ) 54 | } 55 | 56 | #[inline] 57 | fn from_angle>>(theta: A) -> Self { 58 | Self::from_angle_z(theta) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /support/demotools/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "demotools" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /support/demotools/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for example programs 2 | use std::time::Instant; 3 | 4 | /// Measures the production rate of some quantity. 5 | #[derive(Debug)] 6 | pub struct RateCounter { 7 | last_measure: Instant, 8 | count: f64, 9 | last_rate: f64, 10 | } 11 | 12 | impl Default for RateCounter { 13 | fn default() -> Self { 14 | Self::new() 15 | } 16 | } 17 | 18 | impl RateCounter { 19 | pub fn new() -> Self { 20 | Self { 21 | last_measure: Instant::now(), 22 | count: 0.0, 23 | last_rate: 0.0, 24 | } 25 | } 26 | 27 | /// Log a quantity. 28 | /// 29 | /// Returns `true` if the value of `rate()` is updated. 30 | pub fn log(&mut self, value: f64) -> bool { 31 | self.count += value; 32 | 33 | let dt = self.last_measure.elapsed().as_secs_f64(); 34 | if dt >= 0.2 { 35 | self.last_rate = self.count / dt; 36 | self.count = 0.0; 37 | self.last_measure = Instant::now(); 38 | true 39 | } else { 40 | false 41 | } 42 | } 43 | 44 | /// Get the measured rate (`value` per second). 45 | pub fn rate(&self) -> f64 { 46 | self.last_rate 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /support/icon_baker/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | /target 4 | **/*.rs.bk 5 | Cargo.lock 6 | -------------------------------------------------------------------------------- /support/icon_baker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "icon_baker" 3 | version = "2.3.0" 4 | authors = ["Gark Garcia "] 5 | edition = "2018" 6 | description = "A simple solution for encoding common icon file formats." 7 | license-file = "LICENSE" 8 | repository = "https://github.com/GarkGarcia/icon_baker" 9 | homepage = "https://github.com/GarkGarcia/icon_baker" 10 | readme = "README.md" 11 | keywords = ["icon", "ico", "icns"] 12 | 13 | [dependencies] 14 | nsvg = { version = "0.5.0", default-features = false } 15 | ico = "0.1.0" 16 | icns = "0.3.0" 17 | image = { version = "0.23.2", default-features = false } 18 | -------------------------------------------------------------------------------- /support/icon_baker/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Thiago Brevidelli 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, 9 | and to permit persons to whom the Software is furnished 10 | to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice 13 | shall be included in all copies or substantial portions 14 | of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 17 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 18 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 19 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 20 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 21 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 24 | OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /support/icon_baker/src/ico.rs: -------------------------------------------------------------------------------- 1 | extern crate ico; 2 | 3 | use crate::{Error, Icon, Result, Size, SourceImage}; 4 | use image::RgbaImage; 5 | use std::{ 6 | fmt::{self, Debug, Formatter}, 7 | io::{self, Write}, 8 | result, 9 | }; 10 | 11 | const MIN_ICO_SIZE: Size = 1; 12 | const MAX_ICO_SIZE: Size = 256; 13 | 14 | /// A collection of entries stored in a single `.ico` file. 15 | #[derive(Clone)] 16 | pub struct Ico { 17 | icon_dir: ico::IconDir, 18 | } 19 | 20 | impl Icon for Ico { 21 | fn new() -> Self { 22 | Ico { 23 | icon_dir: ico::IconDir::new(ico::ResourceType::Icon), 24 | } 25 | } 26 | 27 | fn add_entry Result>( 28 | &mut self, 29 | mut filter: F, 30 | source: &SourceImage, 31 | size: Size, 32 | ) -> Result<()> { 33 | if size < MIN_ICO_SIZE || size > MAX_ICO_SIZE { 34 | return Err(Error::InvalidSize(size)); 35 | } 36 | 37 | let icon = filter(source, size)?; 38 | assert_eq!([icon.width(), icon.height()], [size; 2]); 39 | 40 | let size = icon.width(); 41 | let data = ico::IconImage::from_rgba_data(size, size, icon.into_vec()); 42 | 43 | let entry = ico::IconDirEntry::encode(&data).map_err(Error::Io)?; 44 | self.icon_dir.add_entry(entry); 45 | 46 | Ok(()) 47 | } 48 | 49 | fn write(&mut self, w: &mut W) -> io::Result<()> { 50 | self.icon_dir.write(w) 51 | } 52 | } 53 | 54 | impl Debug for Ico { 55 | fn fmt(&self, f: &mut Formatter) -> result::Result<(), fmt::Error> { 56 | let n_entries = self.icon_dir.entries().len(); 57 | let mut entries_str = String::with_capacity(42 * n_entries); 58 | 59 | for _ in 0..n_entries { 60 | entries_str.push_str("ico::IconDirEntry {{ /* fields omitted */ }}, "); 61 | } 62 | 63 | let icon_dir = format!( 64 | "ico::IconDir {{ restype: ico::ResourceType::Icon, entries: [{:?}] }}", 65 | entries_str 66 | ); 67 | 68 | write!(f, "icon_baker::Ico {{ icon_dir: {} }} ", icon_dir) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /support/icon_baker/tests/deref.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /support/iterpool/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iterpool" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | license = "MIT/Apache-2.0" 6 | edition = "2018" 7 | 8 | [dependencies] 9 | array_intrusive_list = { path = "../array_intrusive_list" } 10 | 11 | [dev-dependencies] 12 | criterion = "0.3" 13 | 14 | [[bench]] 15 | name = "pool" 16 | harness = false 17 | -------------------------------------------------------------------------------- /support/iterpool/src/intrusive_list.rs: -------------------------------------------------------------------------------- 1 | //! Intrusive doubly linked list for `Pool` and `IterablePool`. 2 | use crate::PoolPtr; 3 | 4 | /// Specialization of `ListHead` for `Pool` and `IterablePool`. 5 | pub type ListHead = array_intrusive_list::ListHead; 6 | /// Specialization of `Link` for `Pool` and `IterablePool`. 7 | pub type Link = array_intrusive_list::Link; 8 | #[doc(no_inline)] 9 | pub use array_intrusive_list::ListAccessorCell; 10 | -------------------------------------------------------------------------------- /support/leakypool/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "leakypool" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | quick-error = "1.2.3" 9 | tokenlock = "0.3.0" 10 | try_match = "0.2.1" 11 | -------------------------------------------------------------------------------- /support/minisort/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minisort" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [dev-dependencies] 8 | criterion = "0.3" 9 | quickcheck = "0.9" 10 | quickcheck_macros = "0.9" 11 | 12 | [[bench]] 13 | name = "bench" 14 | harness = false 15 | -------------------------------------------------------------------------------- /support/minisort/benches/bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; 2 | use minisort::{insertion_sort, qsort}; 3 | 4 | struct Xorshift32(u32); 5 | 6 | impl Iterator for Xorshift32 { 7 | type Item = u32; 8 | 9 | fn next(&mut self) -> Option { 10 | self.0 ^= self.0 << 13; 11 | self.0 ^= self.0 >> 17; 12 | self.0 ^= self.0 << 5; 13 | Some(self.0) 14 | } 15 | } 16 | 17 | fn fill(a: &mut [T], i: impl IntoIterator) { 18 | for (a, value) in a.iter_mut().zip(i) { 19 | *a = value; 20 | } 21 | } 22 | 23 | fn criterion_benchmark(c: &mut Criterion) { 24 | let sizes: Vec<_> = (0..10).map(|i| 1usize << i).collect(); 25 | 26 | let mut group = c.benchmark_group("sort"); 27 | for &size in &sizes { 28 | group.throughput(Throughput::Elements(size as u64)); 29 | group.bench_function(BenchmarkId::new("none", size), move |b| { 30 | let mut array = vec![0u32; size]; 31 | 32 | b.iter(|| { 33 | let array = black_box(&mut array[..]); 34 | fill(array, Xorshift32(42)); 35 | }); 36 | }); 37 | group.bench_function(BenchmarkId::new("insertion_sort", size), move |b| { 38 | let mut array = vec![0u32; size]; 39 | 40 | b.iter(|| { 41 | let array = black_box(&mut array[..]); 42 | fill(array, Xorshift32(42)); 43 | insertion_sort(array); 44 | }); 45 | }); 46 | 47 | group.bench_function(BenchmarkId::new("qsort", size), move |b| { 48 | let mut array = vec![0u32; size]; 49 | 50 | b.iter(|| { 51 | let array = black_box(&mut array[..]); 52 | fill(array, Xorshift32(42)); 53 | qsort(array); 54 | }); 55 | }); 56 | 57 | group.bench_function(BenchmarkId::new("std", size), move |b| { 58 | let mut array = vec![0u32; size]; 59 | 60 | b.iter(|| { 61 | let array = black_box(&mut array[..]); 62 | fill(array, Xorshift32(42)); 63 | array.sort(); 64 | }); 65 | }); 66 | 67 | group.bench_function(BenchmarkId::new("std_unstable", size), move |b| { 68 | let mut array = vec![0u32; size]; 69 | 70 | b.iter(|| { 71 | let array = black_box(&mut array[..]); 72 | fill(array, Xorshift32(42)); 73 | array.sort_unstable(); 74 | }); 75 | }); 76 | } 77 | } 78 | 79 | criterion_group!(benches, criterion_benchmark); 80 | criterion_main!(benches); 81 | -------------------------------------------------------------------------------- /support/minisort/src/insertion.rs: -------------------------------------------------------------------------------- 1 | //! Provides an insertion sort implementation. 2 | use std::{cmp::Ordering, mem::swap}; 3 | 4 | /// Sort the slice using the insertion sort method. 5 | /// 6 | /// # Performance 7 | /// 8 | /// It was never faster than `[T]::sort_unstable` for `a.len() > 16`. 9 | /// 10 | /// # Examples 11 | /// 12 | /// ``` 13 | /// let mut v = [-5, 4, 1, -3, 2]; 14 | /// 15 | /// minisort::insertion_sort(&mut v); 16 | /// assert!(v == [-5, -3, 1, 2, 4]); 17 | /// ``` 18 | pub fn insertion_sort(a: &mut [T]) { 19 | insertion_sort_inner(a, |x, y| x < y); 20 | } 21 | 22 | /// Sort the slice with a key extraction function. 23 | /// 24 | /// # Examples 25 | /// 26 | /// ``` 27 | /// let mut v = [-5i32, 4, 1, -3, 2]; 28 | /// 29 | /// minisort::insertion_sort_by_key(&mut v, |k| k.abs()); 30 | /// assert!(v == [1, 2, -3, 4, -5]); 31 | /// ``` 32 | pub fn insertion_sort_by_key(a: &mut [T], mut f: impl FnMut(&T) -> K) { 33 | insertion_sort_inner(a, |x, y| f(x) < f(y)); 34 | } 35 | 36 | /// Sort the slice with a comparator function. 37 | /// 38 | /// # Examples 39 | /// 40 | /// ``` 41 | /// let mut v = [5, 4, 1, 3, 2]; 42 | /// minisort::insertion_sort_by(&mut v, |a, b| a.cmp(b)); 43 | /// assert!(v == [1, 2, 3, 4, 5]); 44 | /// ``` 45 | pub fn insertion_sort_by(a: &mut [T], mut f: impl FnMut(&T, &T) -> Ordering) { 46 | insertion_sort_inner(a, |x, y| f(x, y) == Ordering::Less); 47 | } 48 | 49 | fn insertion_sort_inner(a: &mut [T], mut f: impl FnMut(&T, &T) -> bool) { 50 | for i in 1..a.len() { 51 | let mut ap = &mut a[0..=i]; 52 | 53 | while let [.., p1, p2] = ap { 54 | if f(p1, p2) { 55 | break; 56 | } 57 | swap(p1, p2); 58 | ap = ap.split_last_mut().unwrap().1; 59 | } 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use super::*; 66 | use quickcheck_macros::quickcheck; 67 | 68 | #[quickcheck] 69 | fn result_is_sorted(mut v: Vec) -> bool { 70 | insertion_sort(&mut v); 71 | v.is_sorted() 72 | } 73 | 74 | #[quickcheck] 75 | fn result_is_sorted_by_key(mut v: Vec<(i32, i32)>) -> bool { 76 | insertion_sort_by_key(&mut v, |e| e.1); 77 | v.is_sorted_by_key(|e| e.1) 78 | } 79 | 80 | #[quickcheck] 81 | fn result_is_sorted_by(mut v: Vec) -> bool { 82 | insertion_sort_by(&mut v, |x, y| y.cmp(x)); 83 | v.is_sorted_by(|x, y| Some(y.cmp(x))) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /support/minisort/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Provides a small sort implementation. 2 | #![cfg_attr(test, feature(is_sorted))] 3 | #![feature(specialization)] 4 | 5 | mod auto; 6 | mod cstdlib; 7 | mod insertion; 8 | pub use self::auto::*; 9 | pub use self::cstdlib::*; 10 | pub use self::insertion::*; 11 | -------------------------------------------------------------------------------- /support/nativedispatch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nativedispatch" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | 11 | [target.'cfg(target_os = "macos")'.dependencies] 12 | dispatch = "0.2.0" 13 | 14 | [target.'cfg(target_os = "windows")'.dependencies] 15 | winapi = { version = "0.3.8", features = ["winbase", "threadpoolapiset"] } 16 | lazy_static = "1" 17 | 18 | [target.'cfg(not(any(target_os = "macos", target_os = "windows")))'.dependencies] 19 | glib-sys = "0.9.1" 20 | lazy_static = "1" 21 | 22 | [dev-dependencies] 23 | criterion = "0.3" 24 | 25 | [[bench]] 26 | name = "bench" 27 | harness = false 28 | -------------------------------------------------------------------------------- /support/nativedispatch/src/dispatch.rs: -------------------------------------------------------------------------------- 1 | //! libdispatch backend 2 | use dispatch::ffi as disp; 3 | use std::ffi::c_void; 4 | 5 | use super::QueuePriority; 6 | 7 | #[derive(Debug, Clone, Copy)] 8 | pub struct QueueImpl { 9 | queue: disp::dispatch_queue_t, 10 | } 11 | 12 | unsafe impl Send for QueueImpl {} 13 | unsafe impl Sync for QueueImpl {} 14 | 15 | impl QueueImpl { 16 | pub fn global(pri: QueuePriority) -> Self { 17 | let queue = unsafe { 18 | disp::dispatch_get_global_queue( 19 | match pri { 20 | QueuePriority::High => disp::DISPATCH_QUEUE_PRIORITY_HIGH, 21 | QueuePriority::Medium => disp::DISPATCH_QUEUE_PRIORITY_DEFAULT, 22 | QueuePriority::Low => disp::DISPATCH_QUEUE_PRIORITY_LOW, 23 | QueuePriority::Background => disp::DISPATCH_QUEUE_PRIORITY_BACKGROUND, 24 | }, 25 | 0, 26 | ) 27 | }; 28 | 29 | // Global queues are implicitly `'static`. 30 | // 31 | // : 32 | // 33 | // > Although dispatch queues are reference-counted objects, you do not 34 | // > need to retain and release the global concurrent queues. Because 35 | // > they are global to your application, retain and release calls for 36 | // > these queues are ignored. 37 | 38 | Self { queue } 39 | } 40 | 41 | pub fn invoke(&self, work: impl FnOnce() + Send + 'static) { 42 | let (ctx, func) = ctx_and_fn(work); 43 | unsafe { 44 | disp::dispatch_async_f(self.queue, ctx, func); 45 | } 46 | } 47 | } 48 | 49 | fn ctx_and_fn(work: F) -> (*mut c_void, disp::dispatch_function_t) { 50 | extern "C" fn dispatch_work_trampoline(ctx: *mut c_void) { 51 | let work = unsafe { Box::from_raw(ctx as *mut F) }; 52 | work(); 53 | } 54 | 55 | let work = Box::new(work); 56 | (Box::into_raw(work) as _, dispatch_work_trampoline::) 57 | } 58 | -------------------------------------------------------------------------------- /support/nativedispatch/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a cross-platform interface to each target platform's 2 | //! thread pool facility. 3 | 4 | // -------------------------------------------------------------------------- 5 | // Backend implementations 6 | 7 | #[cfg(target_os = "macos")] 8 | mod dispatch; 9 | #[cfg(target_os = "macos")] 10 | use self::dispatch::QueueImpl; 11 | 12 | #[cfg(target_os = "windows")] 13 | mod windows; 14 | #[cfg(target_os = "windows")] 15 | use self::windows::QueueImpl; 16 | 17 | #[cfg(not(any(target_os = "macos", target_os = "windows")))] 18 | mod glib; 19 | #[cfg(not(any(target_os = "macos", target_os = "windows")))] 20 | use self::glib::QueueImpl; 21 | 22 | // -------------------------------------------------------------------------- 23 | 24 | #[derive(Debug, Clone)] 25 | pub struct Queue { 26 | imp: QueueImpl, 27 | } 28 | 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 30 | pub enum QueuePriority { 31 | High = 0, 32 | Medium = 1, 33 | Low = 2, 34 | Background = 3, 35 | } 36 | 37 | impl Queue { 38 | /// Get a global queue with a specified priority. 39 | pub fn global(pri: QueuePriority) -> Self { 40 | Self { 41 | imp: QueueImpl::global(pri), 42 | } 43 | } 44 | 45 | /// Get a global queue with `QueuePriority::High`. 46 | pub fn global_high() -> Self { 47 | Self::global(QueuePriority::High) 48 | } 49 | 50 | /// Get a global queue with `QueuePriority::Medium`. 51 | pub fn global_med() -> Self { 52 | Self::global(QueuePriority::Medium) 53 | } 54 | 55 | /// Get a global queue with `QueuePriority::Low`. 56 | pub fn global_low() -> Self { 57 | Self::global(QueuePriority::Low) 58 | } 59 | 60 | /// Get a global queue with `QueuePriority::Background`. 61 | pub fn global_bg() -> Self { 62 | Self::global(QueuePriority::Background) 63 | } 64 | 65 | /// Execute a closure asynchronously. 66 | pub fn invoke(&self, work: impl FnOnce() + Send + 'static) { 67 | self.imp.invoke(work) 68 | } 69 | } 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use super::*; 74 | use std::sync::{Arc, Barrier}; 75 | 76 | #[test] 77 | fn it_works() { 78 | let queue = Queue::global(QueuePriority::High); 79 | let barrier = Arc::new(Barrier::new(2)); 80 | 81 | let c = barrier.clone(); 82 | queue.invoke(move || { 83 | c.wait(); 84 | }); 85 | 86 | barrier.wait(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /support/nativedispatch/src/windows/utils.rs: -------------------------------------------------------------------------------- 1 | use winapi::um::errhandlingapi::GetLastError; 2 | 3 | /// Panic with an error code returned by `GetLastError` if the 4 | /// given value is equal to `T::default()` (e.g., `FALSE`, `false`). 5 | pub fn assert_win32_ok + Copy>(b: T) { 6 | if b == T::default() { 7 | panic_last_error(); 8 | } 9 | } 10 | 11 | /// Panic with an error code returned by `GetLastError`. 12 | #[cold] 13 | fn panic_last_error() -> ! { 14 | panic!("Win32 error 0x{:08x}", unsafe { GetLastError() }); 15 | } 16 | -------------------------------------------------------------------------------- /support/neo_linked_list/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "neo_linked_list" 3 | version = "0.1.0" 4 | authors = [ 5 | "yvt ", 6 | "The Rust Project Developers (alloc::collections::LinkedList)", 7 | ] 8 | edition = "2018" 9 | 10 | [dependencies] 11 | 12 | [dev-dependencies] 13 | rand = "0.7.2" -------------------------------------------------------------------------------- /support/neo_linked_list/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Provides a customized version of `std::collections::LinkedList`. 2 | pub mod cell; 3 | pub mod linked_list; 4 | 5 | pub use crate::{ 6 | cell::LinkedListCell, 7 | linked_list::{Iter, IterMut, LinkedList}, 8 | }; 9 | 10 | /// Implements `Unpin` regardless of whether the inner type has it or not. 11 | /// 12 | /// Useful for storing types that do not implement `Unpin` but are never used 13 | /// with `Pin`, in `LinkedList`. 14 | /// 15 | /// # Examples 16 | /// 17 | /// ``` 18 | /// use neo_linked_list::{LinkedList, AssertUnpin, linked_list::Node}; 19 | /// 20 | /// let mut d = LinkedList:: u32>>::new(); 21 | /// d.push_back_node(Node::pin(AssertUnpin::new(|| 42))); 22 | /// assert_eq!(42, (d.back().unwrap().inner)()); 23 | /// ``` 24 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 25 | pub struct AssertUnpin { 26 | pub inner: T, 27 | } 28 | 29 | impl Unpin for AssertUnpin {} 30 | 31 | impl AssertUnpin { 32 | pub const fn new(inner: T) -> Self { 33 | Self { inner } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /support/rope/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rope" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | 8 | [dependencies] 9 | arrayvec = "0.5" 10 | 11 | [dev-dependencies] 12 | bencher = "0.1.5" 13 | 14 | [[bench]] 15 | name = "rope" 16 | harness = false 17 | -------------------------------------------------------------------------------- /support/sorted_diff/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sorted_diff" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /support/subscriber_list/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "subscriber_list" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | 8 | [dependencies] 9 | iterpool = { path = "../iterpool" } 10 | -------------------------------------------------------------------------------- /support/unicount/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unicount" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | packed_simd = "0.3.0" 9 | 10 | [dev-dependencies] 11 | criterion = "0.3" 12 | log = "0.4" 13 | quickcheck = "0.9" 14 | quickcheck_macros = "0.9" 15 | 16 | [[bench]] 17 | name = "bench" 18 | harness = false 19 | -------------------------------------------------------------------------------- /support/utf16count/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "utf16count" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | packed_simd = "0.3.0" 9 | 10 | [dev-dependencies] 11 | criterion = "0.3" 12 | log = "0.4" 13 | quickcheck = "0.9" 14 | quickcheck_macros = "0.9" 15 | 16 | [[bench]] 17 | name = "bench" 18 | harness = false 19 | -------------------------------------------------------------------------------- /support/utf16count/benches/bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; 2 | use std::convert::TryFrom; 3 | use utf16count::utf16_len; 4 | 5 | struct Xorshift32(u32); 6 | 7 | impl Iterator for Xorshift32 { 8 | type Item = u32; 9 | 10 | fn next(&mut self) -> Option { 11 | self.0 ^= self.0 << 13; 12 | self.0 ^= self.0 >> 17; 13 | self.0 ^= self.0 << 5; 14 | Some(self.0) 15 | } 16 | } 17 | 18 | fn random_utf8(count: usize, rng: &mut Xorshift32) -> String { 19 | (0..count) 20 | .filter_map(|_| char::try_from(rng.next().unwrap() % 0x110000).ok()) 21 | .collect() 22 | } 23 | 24 | fn criterion_benchmark(c: &mut Criterion) { 25 | for &len in &[4, 16, 65536] { 26 | let mut group = c.benchmark_group("sort"); 27 | 28 | group.throughput(Throughput::Elements(len as u64)); 29 | 30 | group.bench_function(BenchmarkId::new("utf16count", len), move |b| { 31 | let st = random_utf8(len, &mut Xorshift32(42)); 32 | let st = &st[..]; 33 | b.iter(|| utf16_len(&st)); 34 | }); 35 | 36 | group.bench_function(BenchmarkId::new("str::encode_utf16", len), move |b| { 37 | let st = random_utf8(len, &mut Xorshift32(42)); 38 | let st = &st[..]; 39 | b.iter(|| st.encode_utf16().count()); 40 | }); 41 | } 42 | } 43 | 44 | criterion_group!(benches, criterion_benchmark); 45 | criterion_main!(benches); 46 | -------------------------------------------------------------------------------- /tcw3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tcw3" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "MIT" 7 | 8 | [features] 9 | testing = ["tcw3_pal/testing", "tcw3_testing/testing"] 10 | 11 | [dependencies] 12 | alt_fp = { path = "../support/alt_fp", features = ["packed_simd"] } 13 | array = "0.0.1" 14 | array_intrusive_list = { path = "../support/array_intrusive_list" } 15 | arrayvec = "0.5" 16 | as_any = { path = "../support/as_any" } 17 | bitflags = "1.1.0" 18 | boxed_slice_tools = { path = "../support/boxed_slice_tools" } 19 | cggeom = { path = "../support/cggeom" } 20 | cgmath = "0.17.0" 21 | derive_more = "0.99.1" 22 | flags-macro = "0.1.3" 23 | iota = "0.2.1" 24 | iterpool = { path = "../support/iterpool" } 25 | itertools = "0.9.0" 26 | lazy_static = "1" 27 | leakypool = { path = "../support/leakypool" } 28 | log = "0.4" 29 | minisort = { path = "../support/minisort" } 30 | ndarray = "0.13.0" 31 | neo_linked_list = { path = "../support/neo_linked_list" } 32 | owning_ref = "0.4.0" 33 | packed_simd = "0.3.0" 34 | rc-borrow = "1.3.0" 35 | rob = "0.1" 36 | rope = { path = "../support/rope" } 37 | sorted_diff = { path = "../support/sorted_diff" } 38 | stvg_macro = { path = "../stvg/macro" } 39 | subscriber_list = { path = "../support/subscriber_list" } 40 | svgbobdoc = "0.2" 41 | try_match = "0.2.1" 42 | unicount = { path = "../support/unicount" } 43 | 44 | tcw3_designer_runtime = { path = "designer_runtime" } 45 | tcw3_images = { path = "images" } 46 | tcw3_meta = { path = "meta" } 47 | tcw3_pal = { path = "pal" } 48 | tcw3_stvg = { path = "stvg" } 49 | tcw3_testing = { path = "testing" } 50 | 51 | [dependencies.momo] 52 | git = "https://github.com/yvt/momo.git" 53 | rev = "26101cc1dacfd4afe9906af464fcbecaca6a18e2" 54 | 55 | [dev-dependencies] 56 | enclose = "1.1.8" 57 | env_logger = "0.7.0" 58 | lipsum = "0.6.0" 59 | quickcheck = "0.9" 60 | quickcheck_macros = "0.9" 61 | structopt = "0.3.0" 62 | -------------------------------------------------------------------------------- /tcw3/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2020 yvt 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 | -------------------------------------------------------------------------------- /tcw3/assets/checkbox_light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tcw3/assets/checkbox_light_act.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tcw3/assets/checkbox_light_checked.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tcw3/assets/checkbox_light_checked_act.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tcw3/assets/radio_light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tcw3/assets/radio_light_act.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tcw3/assets/radio_light_checked.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tcw3/assets/radio_light_checked_act.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tcw3/assets/slider_knob.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tcw3/assets/slider_knob_act.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tcw3/designer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tcw3_designer" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["dylib"] 9 | 10 | [dependencies] 11 | arrayvec = "0.5" 12 | bincode = "1.2.0" 13 | bitflags = "1.1.0" 14 | codemap = "0.1.0" 15 | codemap-diagnostic = "0.1.1" 16 | displaydoc = "0.1.5" 17 | either = "1" 18 | env_logger = "0.7.0" 19 | log = "0.4" 20 | pathfinding = "2" 21 | quote = "1.0.0" 22 | serde = { version = "1.0", features = ["derive"] } 23 | try_match = "0.2.1" 24 | uuid = { version = "0.8.1", features = ["serde", "v4"] } 25 | 26 | [dependencies.syn] 27 | version = "1.0.8" 28 | default-features = false 29 | features = ["derive", "full", "parsing", "printing", "visit-mut"] 30 | 31 | [dependencies.proc-macro2] 32 | version = "1.0.4" 33 | default-features = false 34 | features = ["span-locations"] 35 | 36 | [dev-dependencies] 37 | regex = "1" 38 | lazy_static = "1" 39 | -------------------------------------------------------------------------------- /tcw3/designer/src/bin/tcw3codegen.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use tcw3_designer::BuildScriptConfig; 4 | 5 | fn main() { 6 | let args: Vec<_> = env::args_os().collect(); 7 | 8 | if args.len() != 4 { 9 | eprintln!("Usage: tcw3codegen INPUT.tcwdl CRATENAME OUTPUT.rs"); 10 | std::process::exit(1); 11 | } 12 | 13 | BuildScriptConfig::new() 14 | .root_source_file(&args[1]) 15 | .crate_name(args[2].to_str().expect("Crate name contains invalid UTF-8")) 16 | .out_source_file(&args[3]) 17 | .run_and_exit_on_error(); 18 | } 19 | -------------------------------------------------------------------------------- /tcw3/designer/src/codegen/implgen/dropgen.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | 3 | use super::{ 4 | fields, initgen::DepAnalysis, known_fields, paths, CompSharedTy, Ctx, InnerValueField, 5 | }; 6 | 7 | /// Generate `impl Drop for ComponentTypeShared`. 8 | pub fn gen_shared_drop(ctx: &Ctx<'_>, dep_analysis: &DepAnalysis, out: &mut String) { 9 | let comp_path = &ctx.cur_comp.path; 10 | 11 | let num_subs = dep_analysis.num_subs(); 12 | if num_subs == 0 { 13 | // Nothing to do in `drop` 14 | return; 15 | } 16 | 17 | writeln!( 18 | out, 19 | "impl {} for {} {{", 20 | paths::TRAIT_DROP, 21 | CompSharedTy(comp_path) 22 | ) 23 | .unwrap(); 24 | writeln!(out, " fn drop(&mut self) {{").unwrap(); 25 | writeln!(out, " unsafe {{").unwrap(); 26 | writeln!( 27 | out, 28 | " {meth}(self.{wm}, &mut self.{subs});", 29 | meth = ctx.path_unsubscribe_subs_unchecked(), 30 | wm = InnerValueField(known_fields::WM), 31 | subs = fields::SUBS, 32 | ) 33 | .unwrap(); 34 | writeln!(out, " }}").unwrap(); 35 | writeln!(out, " }}").unwrap(); 36 | writeln!(out, "}}").unwrap(); 37 | } 38 | -------------------------------------------------------------------------------- /tcw3/designer/src/codegen/implgen/iterutils.rs: -------------------------------------------------------------------------------- 1 | pub trait Iterutils: Iterator + Sized { 2 | /// Replace the element at the specified position. 3 | fn replace_at(self, index: usize, with: Self::Item) -> ReplaceAt { 4 | ReplaceAt { 5 | inner: self, 6 | count: index, 7 | with: Some(with), 8 | } 9 | } 10 | 11 | /// Like `filter_map`, but the function also receives the output index. 12 | fn filter_map_with_out_position(self, filter: F) -> FilterMapWithOutPosition 13 | where 14 | F: FnMut(Self::Item, usize) -> Option, 15 | { 16 | FilterMapWithOutPosition { 17 | inner: self, 18 | filter, 19 | i: 0, 20 | } 21 | } 22 | } 23 | 24 | impl Iterutils for T {} 25 | 26 | #[derive(Clone)] 27 | pub struct ReplaceAt { 28 | inner: I, 29 | count: usize, 30 | with: Option, 31 | } 32 | 33 | impl Iterator for ReplaceAt { 34 | type Item = I::Item; 35 | 36 | fn next(&mut self) -> Option { 37 | self.inner.next().map(|item| { 38 | if self.with.is_some() { 39 | if self.count == 0 { 40 | return self.with.take().unwrap(); 41 | } else { 42 | self.count -= 1; 43 | } 44 | } 45 | item 46 | }) 47 | } 48 | } 49 | 50 | #[derive(Clone)] 51 | pub struct FilterMapWithOutPosition { 52 | inner: I, 53 | filter: F, 54 | i: usize, 55 | } 56 | 57 | impl Iterator for FilterMapWithOutPosition 58 | where 59 | I: Iterator, 60 | F: FnMut(I::Item, usize) -> Option, 61 | { 62 | type Item = O; 63 | 64 | fn next(&mut self) -> Option { 65 | while let Some(inp) = self.inner.next() { 66 | if let Some(out) = (self.filter)(inp, self.i) { 67 | self.i += 1; 68 | return Some(out); 69 | } 70 | } 71 | None 72 | } 73 | 74 | fn size_hint(&self) -> (usize, Option) { 75 | (0, self.inner.size_hint().1) 76 | } 77 | } 78 | 79 | impl std::iter::FusedIterator for FilterMapWithOutPosition 80 | where 81 | I: std::iter::FusedIterator, 82 | F: FnMut(I::Item, usize) -> Option, 83 | { 84 | } 85 | -------------------------------------------------------------------------------- /tcw3/designer/src/codegen/implgen/weakrefgen.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | 3 | use super::{fields, methods, paths, CompSharedTy, CompTy, Ctx, WeakCompTy}; 4 | 5 | /// Generate `WeakComponent`, `Compoent::downgrade`, and 6 | /// `WeakComponent::upgrade`. 7 | pub fn gen_weakref_items(ctx: &Ctx<'_>, out: &mut String) { 8 | let comp = ctx.cur_comp; 9 | let comp_ident = &comp.ident.sym; 10 | 11 | writeln!(out, "#[allow(dead_code)]").unwrap(); 12 | writeln!( 13 | out, 14 | "{vis} struct {ty} {{", 15 | vis = comp.vis, 16 | ty = WeakCompTy(comp_ident) 17 | ) 18 | .unwrap(); 19 | writeln!( 20 | out, 21 | " {field}: {weak}<{ty}>,", 22 | field = fields::SHARED, 23 | weak = paths::WEAK, 24 | ty = CompSharedTy(comp_ident) 25 | ) 26 | .unwrap(); 27 | writeln!(out, "}}").unwrap(); 28 | 29 | // `ComponentType::downgrade` 30 | writeln!(out, "#[allow(dead_code)]").unwrap(); 31 | writeln!(out, "impl {} {{", CompTy(comp_ident)).unwrap(); 32 | writeln!( 33 | out, 34 | " {vis} fn {ident}(&self) -> {weakty} {{", 35 | vis = comp.vis, 36 | ident = methods::DOWNGRADE, 37 | weakty = WeakCompTy(comp_ident), 38 | ) 39 | .unwrap(); 40 | writeln!( 41 | out, 42 | " {weakty} {{ {field}: {rc}::downgrade(&self.{field}) }}", 43 | weakty = WeakCompTy(comp_ident), 44 | field = fields::SHARED, 45 | rc = paths::RC, 46 | ) 47 | .unwrap(); 48 | writeln!(out, " }}").unwrap(); 49 | writeln!(out, "}}").unwrap(); 50 | 51 | // `WeakComponentType::upgrade` 52 | writeln!(out, "#[allow(dead_code)]").unwrap(); 53 | writeln!(out, "impl {} {{", WeakCompTy(comp_ident)).unwrap(); 54 | writeln!( 55 | out, 56 | " {vis} fn {ident}(&self) -> {o}<{ty}> {{", 57 | vis = comp.vis, 58 | ident = methods::UPGRADE, 59 | o = paths::OPTION, 60 | ty = CompTy(comp_ident), 61 | ) 62 | .unwrap(); 63 | writeln!( 64 | out, 65 | " self.{field}.upgrade().map(|{field}| {ty} {{ {field} }})", 66 | field = fields::SHARED, 67 | ty = CompTy(comp_ident), 68 | ) 69 | .unwrap(); 70 | writeln!(out, " }}").unwrap(); 71 | writeln!(out, "}}").unwrap(); 72 | } 73 | -------------------------------------------------------------------------------- /tcw3/designer/src/codegen/prelude.txt: -------------------------------------------------------------------------------- 1 | // This "prelude" module is taken from `std::prelude::v1`. 2 | 3 | // Re-exported core operators 4 | pub use std::marker::{Send, Sized, Sync, Unpin}; 5 | pub use std::ops::{Drop, Fn, FnMut, FnOnce}; 6 | 7 | // Re-exported functions 8 | pub use std::mem::drop; 9 | 10 | // Re-exported types and traits 11 | pub use std::convert::{AsRef, AsMut, Into, From}; 12 | pub use std::iter::{Iterator, Extend, IntoIterator}; 13 | pub use std::iter::{DoubleEndedIterator, ExactSizeIterator}; 14 | pub use std::option::Option::{self, Some, None}; 15 | pub use std::result::Result::{self, Ok, Err}; 16 | 17 | // Re-exported built-in macros 18 | pub use std::prelude::v1::{ 19 | asm, 20 | assert, 21 | cfg, 22 | column, 23 | compile_error, 24 | concat, 25 | concat_idents, 26 | env, 27 | file, 28 | format_args, 29 | format_args_nl, 30 | global_asm, 31 | include, 32 | include_bytes, 33 | include_str, 34 | line, 35 | log_syntax, 36 | module_path, 37 | option_env, 38 | stringify, 39 | trace_macros, 40 | }; 41 | 42 | // FIXME: Attribute and derive macros are not documented because for them rustdoc generates 43 | // dead links which fail link checker testing. 44 | pub use std::prelude::v1::{ 45 | Clone, 46 | Copy, 47 | Debug, 48 | Default, 49 | Eq, 50 | Hash, 51 | Ord, 52 | PartialEq, 53 | PartialOrd, 54 | RustcDecodable, 55 | RustcEncodable, 56 | bench, 57 | global_allocator, 58 | test, 59 | test_case, 60 | }; 61 | 62 | // The file so far is equivalent to src/libcore/prelude/v1.rs, 63 | // and below to src/liballoc/prelude.rs. 64 | // Those files are duplicated rather than using glob imports 65 | // because we want docs to show these re-exports as pointing to within `std`. 66 | 67 | 68 | pub use std::boxed::Box; 69 | pub use std::borrow::ToOwned; 70 | pub use std::string::{String, ToString}; 71 | pub use std::vec::Vec; 72 | -------------------------------------------------------------------------------- /tcw3/designer/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(external_doc)] // `#[doc(include = ...)]` 2 | #![doc(include = "./lib.md")] 3 | mod codegen; 4 | mod metadata; 5 | 6 | pub use self::codegen::BuildScriptConfig; 7 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/comp_path_super.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp super::Foo {} 2 | //~^ ERROR `super` is not allowed to use 3 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/comp_path_unknown.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp unknown::Foo {} 2 | //~^ ERROR Can't find a crate named `unknown` 3 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/const_definite.tcwdl: -------------------------------------------------------------------------------- 1 | #[prototype_only] 2 | pub comp crate::Comp1 { 3 | // A definite value is not allowed in a `#[prototype_only]` component 4 | const field: u32 { pub set; } = 42; 5 | //~^ ERROR Fields cannot have a definite value in a `#[prototype_only]` component 6 | } 7 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/const_indefinite.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 { 2 | // `?` is not allowed in a non-`#[prototype_only]` component 3 | const field: u32 { pub set; } = ?; 4 | //~^ ERROR Fields cannot have a indefinite value in a non-`#[prototype_only]` component 5 | } 6 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/const_uninitable.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 { 2 | // `field` has no way to initialize 3 | const field: u32; 4 | //~^ ERROR Must have a default value or a setter because otherwise it's impossible to initialize 5 | } 6 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/const_watch.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 { 2 | const wm: tcw3_pal::Wm { pub set; } 3 | 4 | // `const` can't have a `watch` accessor 5 | const field: u32 { pub set; pub watch event(event1); } 6 | //~^ ERROR `watch` accessor is not allowed for `const` 7 | event event1(); 8 | } 9 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/input_circular.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp2 { 2 | //~^ ERROR A circular dependency was detected 3 | const wm: tcw3_pal::Wm { pub set; } 4 | 5 | wire wire1: u32 = get!(wire1); 6 | } 7 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/input_circular2.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp2 { 2 | //~^ ERROR A circular dependency was detected 3 | const wm: tcw3_pal::Wm { pub set; } 4 | 5 | wire wire1: u32 = get!(wire2); 6 | wire wire2: u32 = get!(wire1); 7 | } 8 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/input_circular_objinit.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 { 2 | const wm: tcw3_pal::Wm { pub set; } 3 | const const2: u32 { pub set; } 4 | const const3: u32 { pub set; } 5 | } 6 | pub comp crate::Comp2 { 7 | //~^ ERROR A circular dependency was detected 8 | const wm: tcw3_pal::Wm { pub set; } 9 | 10 | const const1 = crate::Comp1::new! { 11 | const2 = 42, 12 | 13 | // `const1.const2` is accessible only after `const1` is fully 14 | // constructed 15 | const3 = get!(const1.const2), 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/input_circular_this.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp2 { 2 | //~^ ERROR A circular dependency was detected 3 | // `this` is available only after all fields are initialized 4 | wire wire: *const () = get!(&self) as *const _ as *const (); 5 | } 6 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/input_field_not_comp.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp2 { 2 | const wm: tcw3_pal::Wm { pub set; } 3 | const const1: u32 { pub set; } 4 | 5 | // `const1` is not a component 6 | wire wire1: u32 = get!(const1.field); 7 | //~^ ERROR Can't refer to a field of something that is not a component 8 | } 9 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/input_field_not_comp2.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 { 2 | const wm: tcw3_pal::Wm { pub set; } 3 | prop const2: u32; 4 | } 5 | pub comp crate::Comp2 { 6 | const wm: tcw3_pal::Wm { pub set; } 7 | const const1: crate::Comp1 { pub set; } 8 | 9 | // `const1.const2` is not a component 10 | wire wire1: u32 = get!(const1.const2.field); 11 | //~^ ERROR Can't refer to a field of something that is not a component 12 | } 13 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/input_field_not_comp3.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 { 2 | pub event const2(); 3 | } 4 | pub comp crate::Comp2 { 5 | const wm: tcw3_pal::Wm { pub set; } 6 | const const1: crate::Comp1 { pub set; } 7 | 8 | // `const1.const2` is not a component 9 | wire wire1: u32 = get!(const1.const2.field); 10 | //~^ ERROR Events do not have a field 11 | } 12 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/input_field_unknown.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 {} 2 | pub comp crate::Comp2 { 3 | const wm: tcw3_pal::Wm { pub set; } 4 | const const1: crate::Comp1 { pub set; } 5 | 6 | // `const1.bad_field` refers to a non-existent field 7 | wire wire1: u32 = get!(const1.bad_field); 8 | //~^ ERROR `crate::Comp1` does not have a field named `bad_field` 9 | } 10 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/input_inline_unsyntactic.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp2 { 2 | const wm: tcw3_pal::Wm { pub set; } 3 | 4 | wire wire1: u32 = get!(@@@); 5 | //~^ ERROR expected identifier 6 | } 7 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/objinit_comp_unknown.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp2 { 2 | // `Comp1` is not a known component 3 | const comp1 = crate::Comp1::new! {}; 4 | //~^ ERROR does not refer to a component 5 | } 6 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/objinit_explicit_type.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 {} 2 | pub comp crate::Comp2 { 3 | // A field having obj-init must not be explicitly typed 4 | const comp1: crate::Comp1 = crate::Comp1::new! {}; 5 | //~^ ERROR Type mustn't be specified if the initializer is an object literal 6 | } 7 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/objinit_field_dupe.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 { 2 | const wm: tcw3_pal::Wm { pub set; } = unreachable!(); 3 | prop field: u32; 4 | } 5 | pub comp crate::Comp2 { 6 | const comp1 = crate::Comp1::new! { 7 | // `Comp1::field` is specified twice 8 | field = 42, 9 | //~^ ERROR Too many initializers for the field `field` 10 | field = 42, 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/objinit_field_short_badinput.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 { 2 | const comp1_field: u32 { set; } 3 | } 4 | pub comp crate::Comp2 { 5 | const comp1 = crate::Comp1::new! { 6 | // `Comp1` has `comp1_field`, but `Comp2` doesn't 7 | comp1_field, 8 | //~^ ERROR does not have a field named `comp1_field` 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/objinit_field_unknown.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 {} 2 | pub comp crate::Comp2 { 3 | const comp1 = crate::Comp1::new! { 4 | // `Comp1` does not have `unknown_field` 5 | unknown_field = 42, 6 | //~^ ERROR Component `Comp1` does not have a field named `unknown_field` 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/objinit_field_wrong_ty.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 { 2 | pub event evt(); 3 | } 4 | pub comp crate::Comp2 { 5 | const comp1 = crate::Comp1::new! { 6 | // `Comp1::evt` is actually `event` 7 | evt = 42, 8 | //~^ ERROR Component `Comp1` does not have a field named `evt` 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/objinit_settable.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 {} 2 | pub comp crate::Comp2 { 3 | // A field having obj-init must not be settable 4 | const comp1 { pub set; } = crate::Comp1::new! {}; 5 | //~^ ERROR Can't have a setter if the initializer is an object literal 6 | } 7 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/objinit_subexpr.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 {} 2 | pub comp crate::Comp2 { 3 | // Obj-init cannot be a subexpression 4 | const comp1 = &crate::Comp1::new! {}; 5 | //~^ ERROR `Component::new!` is unsupported in this position 6 | } 7 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/prop_uninitable.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 { 2 | const wm: tcw3_pal::Wm { pub set; } 3 | 4 | prop field: u32 {} 5 | //~^ ERROR Props must have a setter 6 | } 7 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/prop_unsettable.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 { 2 | const wm: tcw3_pal::Wm { pub set; } 3 | 4 | prop field: u32 {} = 42; 5 | //~^ ERROR Props must have a setter 6 | } 7 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/use_dupe.tcwdl: -------------------------------------------------------------------------------- 1 | // `A` is defined for multiple times 2 | use tcw3::{A, B as A}; 3 | //~^ ERROR is imported for multiple times 4 | 5 | pub comp crate::Comp1 { 6 | prop field: A; 7 | } 8 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/use_self.tcwdl: -------------------------------------------------------------------------------- 1 | // `self` is a virtual module tha cannot be imported by itself 2 | use self as Hoge; 3 | //~^ ERROR Importing `self` is not allowed 4 | 5 | pub comp crate::Comp1 { 6 | prop field: Hoge; 7 | } 8 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/use_super.tcwdl: -------------------------------------------------------------------------------- 1 | // `super` is not allowed because TCWDL doesn't have a notion of 2 | // "a current module" 3 | use super::Hoge; 4 | //~^ ERROR `super` is not allowed to use 5 | 6 | pub comp crate::Comp1 { 7 | prop field: Hoge; 8 | } 9 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/use_unknown.tcwdl: -------------------------------------------------------------------------------- 1 | // `self::Hoge2` resolves to `self::Hoge`, which is non-existent 2 | use self::Hoge as Hoge2; 3 | //~^ ERROR Could not resolve `Hoge` 4 | 5 | pub comp crate::Comp1 { 6 | prop field: Hoge2; 7 | } 8 | -------------------------------------------------------------------------------- /tcw3/designer/tests/bad/watch_nonnullary.tcwdl: -------------------------------------------------------------------------------- 1 | pub comp crate::Comp1 { 2 | const wm: tcw3_pal::Wm { pub set; } 3 | 4 | prop field: u32 { pub set; pub watch event(event1); } 5 | //~^ ERROR The event used for `watch` accessor must have no parameters 6 | event event1(x: u32); 7 | } 8 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tcw3_designer_tests_impl" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [features] 8 | testing = ["tcw3/testing"] 9 | 10 | [build-dependencies] 11 | tcw3_designer = { path = ".." } 12 | 13 | [dependencies] 14 | tcw3 = { path = "../.." } 15 | 16 | [lib] 17 | path = "lib.rs" 18 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tcw3_designer::BuildScriptConfig::new() 3 | .crate_name("tcw3_designer_tests_impl") 4 | .run_and_exit_on_error(); 5 | } 6 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/commit/remotetrigger.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use tcw3::testing::{prelude::*, use_testing_wm}; 3 | 4 | designer_impl! { crate::commit::remotetrigger::Comp } 5 | designer_impl! { crate::commit::remotetrigger::CompOther } 6 | 7 | #[use_testing_wm] 8 | #[test] 9 | fn watch_prop(twm: &dyn TestingWm) { 10 | let comp = CompBuilder::new().with_wm(twm.wm()).build(); 11 | assert_eq!(0, comp.state().get()); 12 | 13 | comp.other().raise_prop1_changed(); 14 | 15 | assert_eq!(0, comp.state().get()); 16 | 17 | twm.step_unsend(); 18 | assert_eq!(1, comp.state().get()); 19 | } 20 | 21 | #[use_testing_wm] 22 | #[test] 23 | fn watch_event(twm: &dyn TestingWm) { 24 | let comp = CompBuilder::new().with_wm(twm.wm()).build(); 25 | assert_eq!(0, comp.state().get()); 26 | 27 | comp.other().raise_event1(); 28 | 29 | // `on (event_input)` should handle events synchronously 30 | assert_eq!(4, comp.state().get()); 31 | } 32 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/commit/remotetrigger.tcwdl: -------------------------------------------------------------------------------- 1 | use tcw3::pal; 2 | 3 | use crate::commit::remotetrigger::{Comp, CompOther}; 4 | 5 | comp Comp { 6 | const wm: pal::Wm { set; } 7 | const state: std::cell::Cell = std::cell::Cell::new(0); 8 | 9 | const other = CompOther::new! { wm = get!(wm) }; 10 | 11 | on (other.prop1) get!(&state).set(get!(&state).get() + 1); 12 | on (other.event1) get!(&state).set(get!(&state).get() + 4); 13 | } 14 | 15 | comp CompOther { 16 | const wm: pal::Wm { set; } 17 | prop prop1: u32 { get; set; watch event(prop1_changed); } = 1; 18 | event event1(); 19 | event prop1_changed(); 20 | } 21 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/field/accessors.rs: -------------------------------------------------------------------------------- 1 | use tcw3::testing::{prelude::*, use_testing_wm}; 2 | 3 | designer_impl! { crate::field::accessors::Comp } 4 | 5 | #[use_testing_wm] 6 | #[test] 7 | fn get(twm: &dyn TestingWm) { 8 | let comp = CompBuilder::new().with_wm(twm.wm()).build(); 9 | assert_eq!(1, comp.prop1()); 10 | assert_eq!(2, *comp.prop2()); 11 | assert_eq!(3, comp.const1()); 12 | assert_eq!(4, *comp.const2()); 13 | } 14 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/field/accessors.tcwdl: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use tcw3::pal; 3 | 4 | comp crate::field::accessors::Comp { 5 | const wm: pal::Wm { set; } 6 | 7 | prop prop1: u32 { set; get clone; } = 1; 8 | prop prop2: u32 { set; get borrow; } = 2; 9 | const const1: u32 { set; get clone; } = 3; 10 | const const2: u32 { set; get borrow; } = 4; 11 | } 12 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/field/bug_type_deduction.rs: -------------------------------------------------------------------------------- 1 | designer_impl! { crate::field::bug_type_deduction::Comp } 2 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/field/bug_type_deduction.tcwdl: -------------------------------------------------------------------------------- 1 | use tcw3::pal; 2 | comp crate::field::bug_type_deduction::Comp { 3 | const wm: pal::Wm { set; } 4 | 5 | // An old implementation of Designer produced implementation code 6 | // that didn't type-check for these fields: 7 | // 8 | // error[E0282]: type annotations needed 9 | // | 10 | // 695 | let __tmp_1 = match () { () => { "a" . into ( ) } }; 11 | // | ------- consider giving `__tmp_1` a type 12 | // 696 | let __tmp_2 = match ((&__tmp_1), ) { (_input_0, ) => { _input_0 . len ( ) } }; 13 | // | ^^^ cannot infer type 14 | // 15 | const const1: String = "a".into(); 16 | const const2: usize = get!(&const1).len(); 17 | } 18 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/field/lifetime_elision.rs: -------------------------------------------------------------------------------- 1 | designer_impl! { crate::field::lifetime_elision::Comp } 2 | 3 | #[test] 4 | fn have_correct_types() { 5 | let comp = CompBuilder::new() 6 | .with_field1a("hello") 7 | .with_field1b("hello") 8 | .with_field2a(|st: &str| st) 9 | .with_field2b(|st: &str| st) 10 | .with_field3a(&|st: &str| st) 11 | .with_field3b(&|st: &str| st) 12 | .with_field4a(&&42u32) 13 | .with_field4b(&&42u32) 14 | .build(); 15 | 16 | let _x: &'static str = *comp.field1a(); 17 | let _x: &'static str = *comp.field1b(); 18 | let _x: fn(&'static str) -> &'static str = *comp.field2a(); // `'a` ← `'static` 19 | let _x: fn(&'static str) -> &'static str = *comp.field2b(); // `'a` ← `'static` 20 | let _x: &'static dyn Fn(&'static str) -> &'static str = *comp.field3a(); // `'a` ← `'static` 21 | let _x: &'static dyn Fn(&'static str) -> &'static str = *comp.field3b(); // `'a` ← `'static` 22 | let _x: &'static dyn std::cmp::PartialEq<&'static u32> = *comp.field4a(); // `'a` ← `'static` 23 | let _x: &'static dyn std::cmp::PartialEq<&'static u32> = *comp.field4b(); 24 | } 25 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/field/lifetime_elision.tcwdl: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use tcw3::pal; 3 | 4 | comp crate::field::lifetime_elision::Comp { 5 | // `&'static str` 6 | const field1a: &str { get; set; } 7 | 8 | // `&'static str` 9 | const field1b: &'_ str { get; set; } 10 | 11 | // `for <'a> fn(&'a str) -> &'a str` 12 | const field2a: fn(&str) -> &str { get; set; } 13 | 14 | // unchanged 15 | const field2b: for <'a> fn(&'a str) -> &'a str { get; set; } 16 | 17 | // `&'static dyn Fn(&str) -> &str` 18 | // (compiler further expands it to `&'static dyn for<'a> Fn(&'a str) -> &'a str`) 19 | const field3a: &dyn Fn(&str) -> &str { get; set; } 20 | 21 | // `&'static dyn for<'a> Fn(&'a str) -> &'a str` 22 | const field3b: &dyn for<'a> Fn(&'a str) -> &'a str { get; set; } 23 | 24 | // `&'static dyn for<'a> std::cmp::PartialEq<&'a u32>` 25 | const field4a: &dyn for<'a> std::cmp::PartialEq<&'a u32> { get; set; } 26 | 27 | // `&'static dyn std::cmp::PartialEq<&'static u32>` 28 | const field4b: &dyn std::cmp::PartialEq<&u32> { get; set; } 29 | } 30 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/field/prop.rs: -------------------------------------------------------------------------------- 1 | use tcw3::testing::{prelude::*, use_testing_wm}; 2 | 3 | designer_impl! { crate::field::prop::Comp } 4 | 5 | #[use_testing_wm] 6 | #[test] 7 | fn prop_init_default(twm: &dyn TestingWm) { 8 | let comp = CompBuilder::new().with_wm(twm.wm()).build(); 9 | assert_eq!(1, comp.prop1()); 10 | } 11 | 12 | #[use_testing_wm] 13 | #[test] 14 | fn prop_init(twm: &dyn TestingWm) { 15 | let comp = CompBuilder::new().with_wm(twm.wm()).with_prop1(2).build(); 16 | assert_eq!(2, comp.prop1()); 17 | } 18 | 19 | #[use_testing_wm] 20 | #[test] 21 | fn prop_set(twm: &dyn TestingWm) { 22 | let comp = CompBuilder::new().with_wm(twm.wm()).build(); 23 | comp.set_prop1(3); 24 | assert_eq!(1, comp.prop1()); 25 | twm.step_unsend(); 26 | assert_eq!(3, comp.prop1()); 27 | } 28 | 29 | #[use_testing_wm] 30 | #[test] 31 | fn prop_watch(twm: &dyn TestingWm) { 32 | let comp = CompBuilder::new().with_wm(twm.wm()).build(); 33 | comp.set_prop1(3); 34 | assert!(comp.prop1_history().borrow().is_empty()); 35 | twm.step_unsend(); 36 | assert_eq!(vec![(3, 3)], *comp.prop1_history().borrow()); 37 | } 38 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/field/prop.tcwdl: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use tcw3::pal; 3 | 4 | comp crate::field::prop::Comp { 5 | const wm: pal::Wm { set; } 6 | prop prop1: u32 { set; get; watch event(prop1_changed); } = 1; 7 | event prop1_changed(); 8 | 9 | const prop1_history: RefCell> = Default::default(); 10 | on (prop1_changed) { 11 | get!(&prop1_history).borrow_mut().push((get!(&self).prop1(), get!(prop1))) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/func/inline.rs: -------------------------------------------------------------------------------- 1 | designer_impl! { crate::func::inline::Comp } 2 | 3 | #[test] 4 | fn check_evaluated_values() { 5 | let comp = CompBuilder::new().build(); 6 | 7 | assert_eq!(*comp.const1(), 42); 8 | assert_eq!(*comp.const2(), 42 * 2); 9 | assert_eq!(*comp.const3(), 42 * 3); 10 | assert_eq!(comp.const4()[0], 42 * 4); 11 | assert_eq!(comp.const4()[1], 5); 12 | assert_eq!(comp.const5()[0][0], 1); 13 | assert_eq!(comp.const5()[1][0], 42 * 4); 14 | assert_eq!(comp.const5()[1][1], 5); 15 | assert_eq!(comp.const5()[2][0], 3); 16 | } 17 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/func/inline.tcwdl: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use tcw3::pal; 3 | 4 | comp crate::func::inline::Comp { 5 | const const1: u32 = 42; 6 | const const2: u32 = get!(const1) * 2; 7 | const const3: u32 = get!(self.const1) * 3; 8 | const const4: Vec = vec![get!(self.const1) * 4, 5]; 9 | const const5: Vec> = vec![vec![1], vec![get!(self.const1) * 4, 5], vec![3]]; 10 | } 11 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/interop/builder_simple.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | 3 | designer_impl! { crate::interop::builder_simple::Comp } 4 | 5 | #[test] 6 | fn check_inited_values() { 7 | let comp = CompBuilder::new().build(); 8 | 9 | assert_eq!(*comp.c1().const1(), 1); 10 | assert_eq!(*comp.c1().const2(), 2); 11 | assert_eq!(comp.c1().prop1(), 3); 12 | assert_eq!(*comp.c2().const1(), 0); 13 | assert_eq!(*comp.c2().const2(), 2); 14 | assert_eq!(comp.c2().prop1(), 0); 15 | } 16 | 17 | struct ExtComp { 18 | const1: u32, 19 | const2: u32, 20 | prop1: Cell, 21 | } 22 | 23 | impl ExtComp { 24 | fn new(const1: u32, const2: u32) -> Self { 25 | Self { 26 | const1, 27 | const2, 28 | prop1: Cell::new(0), 29 | } 30 | } 31 | 32 | fn const1(&self) -> &u32 { 33 | &self.const1 34 | } 35 | 36 | fn const2(&self) -> &u32 { 37 | &self.const2 38 | } 39 | 40 | fn prop1(&self) -> u32 { 41 | self.prop1.get() 42 | } 43 | 44 | fn set_prop1(&self, x: u32) { 45 | self.prop1.set(x); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/interop/builder_simple.tcwdl: -------------------------------------------------------------------------------- 1 | #[prototype_only] 2 | #[builder(simple)] 3 | comp crate::interop::builder_simple::ExtComp { 4 | const const1: u32 { get; set; } = ?; 5 | const const2: u32 { get; set; } 6 | prop prop1: u32 = ?; 7 | } 8 | 9 | comp crate::interop::builder_simple::Comp { 10 | const c1 = crate::interop::builder_simple::ExtComp::new! { 11 | const1 = 1, 12 | const2 = 2, 13 | prop1 = 3, 14 | }; 15 | const c2 = crate::interop::builder_simple::ExtComp::new! { 16 | const2 = 2, 17 | }; 18 | } -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/lib.rs: -------------------------------------------------------------------------------- 1 | //! Tests for TCW3 Designer-generated code. Please see `tcw3_designer`'s 2 | //! documentation for how all tests are organized. 3 | #![cfg(test)] 4 | 5 | include!(concat!(env!("OUT_DIR"), "/designer.rs")); 6 | 7 | mod commit { 8 | mod remotetrigger; 9 | } 10 | 11 | mod field { 12 | mod accessors; 13 | mod bug_type_deduction; 14 | mod lifetime_elision; 15 | mod prop; 16 | } 17 | 18 | mod func { 19 | mod inline; 20 | } 21 | 22 | mod interop { 23 | mod builder_simple; 24 | } 25 | 26 | mod misc { 27 | mod exprpath; 28 | mod genericresolve; 29 | mod primitives; 30 | mod weakref; 31 | } 32 | 33 | mod objinit { 34 | mod alias; 35 | mod shorthand; 36 | } 37 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/lib.tcwdl: -------------------------------------------------------------------------------- 1 | import!("commit/remotetrigger.tcwdl"); 2 | import!("field/accessors.tcwdl"); 3 | import!("field/bug_type_deduction.tcwdl"); 4 | import!("field/lifetime_elision.tcwdl"); 5 | import!("field/prop.tcwdl"); 6 | import!("func/inline.tcwdl"); 7 | import!("interop/builder_simple.tcwdl"); 8 | import!("misc/exprpath.tcwdl"); 9 | import!("misc/genericresolve.tcwdl"); 10 | import!("misc/primitives.tcwdl"); 11 | import!("misc/weakref.tcwdl"); 12 | import!("objinit/alias.tcwdl"); 13 | import!("objinit/shorthand.tcwdl"); 14 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/misc/exprpath.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | designer_impl! { crate::misc::exprpath::Comp1 } 4 | designer_impl! { crate::misc::exprpath::Comp2 } 5 | designer_impl! { crate::misc::exprpath::Comp3 } 6 | 7 | pub mod submod { 8 | // When the dynamic expressions in these components mention `doit`, it's 9 | // expanded to an absolute path by `use crate::…::submod`, so this function 10 | // will be used. If the expansion is not done correctly (e.g., in the 11 | // previous behavior), the above `designer_impl!` lines will refer to 12 | // `doit`, which is non-existent in their scope, causing a compilation 13 | // error. 14 | pub fn doit(_: u32) { 15 | dbg!(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/misc/exprpath.tcwdl: -------------------------------------------------------------------------------- 1 | use tcw3::pal; 2 | use crate::misc::exprpath::submod::doit; 3 | 4 | comp crate::misc::exprpath::Comp1 { 5 | on (init) doit(42); 6 | } 7 | 8 | comp crate::misc::exprpath::Comp2 { 9 | const c: () = doit(42); 10 | } 11 | 12 | comp crate::misc::exprpath::Comp3 { 13 | const wm: pal::Wm { set; } 14 | prop p: u32 = 42; 15 | wire w: () = doit(get!(p)); 16 | } 17 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/misc/genericresolve.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | designer_impl! { crate::misc::genericresolve::Comp } 4 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/misc/genericresolve.tcwdl: -------------------------------------------------------------------------------- 1 | use tcw3::pal; 2 | 3 | comp crate::misc::genericresolve::Comp { 4 | // Paths in generic arguments should be resolved and expanded to 5 | // a full path, too. 6 | const hoge: Vec { set; } 7 | } 8 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/misc/primitives.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | designer_impl! { crate::misc::primitives::Comp } 4 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/misc/primitives.tcwdl: -------------------------------------------------------------------------------- 1 | use tcw3::pal; 2 | 3 | comp crate::misc::primitives::Comp { 4 | const wm: pal::Wm { set; } 5 | 6 | // Primitive types should be treated as-is and shouldn't be 7 | prop f_bool: bool = false; 8 | prop f_char: char = 'x'; 9 | prop f_f32: f32 = 0.0; 10 | prop f_f64: f64 = 0.0; 11 | prop f_i128: i128 = 0; 12 | prop f_i16: i16 = 0; 13 | prop f_i32: i32 = 0; 14 | prop f_i64: i64 = 0; 15 | prop f_i8: i8 = 0; 16 | prop f_isize: isize = 0; 17 | prop f_str: Box = String::new().into_boxed_str(); 18 | prop f_u128: u128 = 0; 19 | prop f_u16: u16 = 0; 20 | prop f_u32: u32 = 0; 21 | prop f_u64: u64 = 0; 22 | prop f_u8: u8 = 0; 23 | prop f_usize: usize = 0; 24 | } 25 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/misc/weakref.rs: -------------------------------------------------------------------------------- 1 | designer_impl! { crate::misc::weakref::Comp } 2 | 3 | #[test] 4 | fn upgrade_alive() { 5 | let comp = CompBuilder::new().build(); 6 | comp.downgrade().upgrade().unwrap(); 7 | } 8 | 9 | #[test] 10 | fn upgrade_dead() { 11 | let comp = CompBuilder::new().build(); 12 | let weak = comp.downgrade(); 13 | drop(comp); 14 | assert!(weak.upgrade().is_none()); 15 | } 16 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/misc/weakref.tcwdl: -------------------------------------------------------------------------------- 1 | comp crate::misc::weakref::Comp {} 2 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/objinit/alias.rs: -------------------------------------------------------------------------------- 1 | designer_impl! { crate::objinit::alias::Comp } 2 | designer_impl! { crate::objinit::alias::CompOther } 3 | use {CompOther as CompOtherAlias, CompOtherBuilder as CompOtherAliasBuilder}; 4 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/objinit/alias.tcwdl: -------------------------------------------------------------------------------- 1 | use tcw3::pal; 2 | 3 | use crate::objinit::alias::{Comp, CompOther, CompOtherAlias}; 4 | 5 | comp Comp { 6 | const other = CompOtherAlias::new! {}; 7 | } 8 | 9 | #[alias(crate::objinit::alias::CompOtherAlias)] 10 | comp CompOther {} 11 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/objinit/shorthand.rs: -------------------------------------------------------------------------------- 1 | designer_impl! { crate::objinit::shorthand::Comp } 2 | designer_impl! { crate::objinit::shorthand::CompOther } 3 | 4 | #[test] 5 | fn check_inited_values() { 6 | let comp = CompBuilder::new().build(); 7 | 8 | assert_eq!(comp.const1(), comp.other().const1()); 9 | } 10 | -------------------------------------------------------------------------------- /tcw3/designer/tests_impl/objinit/shorthand.tcwdl: -------------------------------------------------------------------------------- 1 | use tcw3::pal; 2 | 3 | use crate::objinit::shorthand::{Comp, CompOther}; 4 | 5 | comp Comp { 6 | const const1: u32 = 42; 7 | 8 | // shorthand field initialization syntax 9 | const other = CompOther::new! { const1 }; 10 | } 11 | 12 | comp CompOther { 13 | const const1: u32 { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /tcw3/designer_runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tcw3_designer_runtime" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | owning_ref = "0.4.0" 9 | subscriber_list = { path = "../../support/subscriber_list" } 10 | harmony = { path = "../../harmony" } 11 | tcw3_pal = { path = "../pal" } 12 | -------------------------------------------------------------------------------- /tcw3/designer_runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The runtime components of TCW3 Designer. It's by no means intended to be 2 | //! used by hand-written code. 3 | //! 4 | //! # Re-exports 5 | //! 6 | //! This crate re-exports items from some crates so that the implementors 7 | //! of Designer components do not have to depend on `subscriber_list` by 8 | //! themselves. 9 | use std::{cell::Cell, mem::MaybeUninit}; 10 | use tcw3_pal as pal; 11 | use tcw3_pal::prelude::*; 12 | 13 | #[doc(no_inline)] 14 | pub use subscriber_list::{SubscriberList, UntypedSubscription as Sub}; 15 | 16 | #[doc(no_inline)] 17 | pub use owning_ref::OwningRef; 18 | 19 | #[doc(no_inline)] 20 | pub use harmony::ShallowEq; 21 | 22 | /// A placeholder value for unset mandatory parameters. 23 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 24 | pub struct Unset; 25 | 26 | /// Unwrap a `Option`. Does not check if it is `Some(_)` unless debug 27 | /// assertions are enabled. 28 | /// 29 | /// # Safety 30 | /// 31 | /// `x` must be `Some(_)`. 32 | #[inline] 33 | pub unsafe fn unwrap_unchecked(x: Option) -> T { 34 | debug_assert!(x.is_some(), "attempted to unwrap a None value"); 35 | x.unwrap_or_else(|| std::hint::unreachable_unchecked()) 36 | } 37 | 38 | /// Take the ownership of all `Sub` in `subs` and unsubscribe them later 39 | /// using `Wm::invoke`. 40 | /// 41 | /// # Safety 42 | /// 43 | /// All elements of `subs` must be in an initialized state. 44 | pub unsafe fn unsubscribe_subs_unchecked(wm: pal::Wm, subs: &mut [Cell>]) { 45 | // This requires only once allocation (because the size is pre-known) and 46 | // takes less space than `Vec` 47 | let mut subs: Box<[_]> = subs 48 | .iter_mut() 49 | .map(|s| std::mem::replace(s, Cell::new(MaybeUninit::uninit()))) 50 | .collect(); 51 | 52 | // Assumes this closure will be called eventually. 53 | wm.invoke(move |_| { 54 | for sub in subs.iter_mut() { 55 | let sub = sub.get_mut().as_mut_ptr().read(); 56 | sub.unsubscribe().unwrap(); 57 | } 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /tcw3/examples/tcw3_table.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | use tcw3::{ 3 | pal, 4 | pal::prelude::*, 5 | ui::{ 6 | layouts::FillLayout, 7 | prelude::*, 8 | theming, 9 | views::{table, table::LineTy, Label, ScrollableTable}, 10 | }, 11 | uicore::{HView, HWnd, HWndRef, SizeTraits, WndListener}, 12 | }; 13 | 14 | struct MyWndListener; 15 | 16 | impl WndListener for MyWndListener { 17 | fn close(&self, wm: pal::Wm, _: HWndRef<'_>) { 18 | wm.terminate(); 19 | } 20 | } 21 | 22 | struct TableModelQuery { 23 | style_manager: &'static theming::Manager, 24 | } 25 | 26 | impl table::TableModelQuery for TableModelQuery { 27 | fn new_view(&mut self, cell: table::CellIdx) -> (HView, Box) { 28 | let label = Label::new(self.style_manager); 29 | label.set_text(format!("{:?}", cell)); 30 | 31 | (label.view(), Box::new(())) 32 | } 33 | 34 | fn range_size(&mut self, line_ty: LineTy, range: Range, _approx: bool) -> f64 { 35 | (range.end - range.start) as f64 36 | * match line_ty { 37 | LineTy::Row => 20.0, 38 | LineTy::Col => 200.0, 39 | } 40 | } 41 | } 42 | 43 | fn main() { 44 | env_logger::init(); 45 | 46 | let wm = pal::Wm::global(); 47 | let style_manager = theming::Manager::global(wm); 48 | 49 | let wnd = HWnd::new(wm); 50 | wnd.set_visibility(true); 51 | wnd.set_listener(MyWndListener); 52 | 53 | let table = ScrollableTable::new(style_manager); 54 | 55 | table.table().set_size_traits(SizeTraits { 56 | preferred: [200.0, 300.0].into(), 57 | ..Default::default() 58 | }); 59 | 60 | // Set up the table model 61 | { 62 | let mut edit = table.table().edit().unwrap(); 63 | edit.set_model(TableModelQuery { style_manager }); 64 | edit.insert(LineTy::Row, 0..500_000_000_000_000); 65 | edit.insert(LineTy::Col, 0..300); 66 | edit.set_scroll_pos([0.0, edit.scroll_pos()[1]]); 67 | } 68 | 69 | wnd.content_view() 70 | .set_layout(FillLayout::new(table.view()).with_uniform_margin(10.0)); 71 | 72 | wm.enter_main_loop(); 73 | } 74 | -------------------------------------------------------------------------------- /tcw3/images/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tcw3_images" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "MIT" 7 | 8 | [dependencies] 9 | alt_fp = { path = "../../support/alt_fp" } 10 | array_intrusive_list = { path = "../../support/array_intrusive_list" } 11 | array = "0.0.1" 12 | cggeom = { path = "../../support/cggeom" } 13 | cgmath = "0.17.0" 14 | leakypool = { path = "../../support/leakypool" } 15 | packed_simd = "0.3.0" 16 | quick-error = "1.2.3" 17 | 18 | tcw3_pal = { path = "../pal" } 19 | -------------------------------------------------------------------------------- /tcw3/images/src/bitmap.rs: -------------------------------------------------------------------------------- 1 | use tcw3_pal::Bitmap; 2 | 3 | use super::{Bmp, HImg, Img}; 4 | 5 | /// [`Img`] that provides a specified `Bitmap`. 6 | #[derive(Debug, Clone)] 7 | pub struct BitmapImg { 8 | bitmap: Bitmap, 9 | dpi_scale: f32, 10 | } 11 | 12 | impl BitmapImg { 13 | /// Construct a `BitmapImg`. 14 | pub fn new(bitmap: Bitmap, dpi_scale: f32) -> Self { 15 | Self { bitmap, dpi_scale } 16 | } 17 | 18 | /// Convert `self` to a `HImg`. 19 | /// 20 | /// This method just calls `HImg::new(self)`. 21 | pub fn into_hbmp(self) -> HImg { 22 | HImg::new(self) 23 | } 24 | } 25 | 26 | impl Img for BitmapImg { 27 | fn new_bmp(&self, _dpi_scale: f32) -> Bmp { 28 | (self.bitmap.clone(), self.dpi_scale) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tcw3/images/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Manages DPI-independent images. Provides an application-global image 2 | //! manager that automatically rasterizes and caches images for requested 3 | //! DPI scale values. 4 | //! 5 | //! This crate is reexported by TCW3 as `tcw3::images`. 6 | mod bitmap; 7 | mod canvas; 8 | mod figures; 9 | mod img; 10 | pub use self::{bitmap::*, canvas::*, figures::*, img::*}; 11 | 12 | // Re-exports for macros defined in this crate 13 | #[doc(hidden)] 14 | pub use tcw3_pal::RGBAF32; 15 | -------------------------------------------------------------------------------- /tcw3/meta/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tcw3_meta" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [build-dependencies] 8 | tcw3_designer = { path = "../designer" } 9 | 10 | [lib] 11 | path = "lib.rs" 12 | -------------------------------------------------------------------------------- /tcw3/meta/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tcw3_designer::BuildScriptConfig::new() 3 | .tcw3_path("crate") 4 | .designer_runtime_path("crate::designer_runtime") 5 | .run_and_exit_on_error(); 6 | } 7 | -------------------------------------------------------------------------------- /tcw3/meta/lib.rs: -------------------------------------------------------------------------------- 1 | //! The TCW3 Designer meta crate for `tcw3` 2 | include!(concat!(env!("OUT_DIR"), "/designer.rs")); 3 | -------------------------------------------------------------------------------- /tcw3/meta/lib.tcwdl: -------------------------------------------------------------------------------- 1 | import!("uicore.tcwdl"); 2 | import!("theming/manager.tcwdl"); 3 | import!("theming/view.tcwdl"); 4 | import!("views/button.tcwdl"); 5 | import!("views/checkbox.tcwdl"); 6 | import!("views/entry.tcwdl"); 7 | import!("views/label.tcwdl"); 8 | import!("views/slider.tcwdl"); 9 | import!("views/spacer.tcwdl"); 10 | import!("views/split.tcwdl"); 11 | import!("views/table.tcwdl"); 12 | -------------------------------------------------------------------------------- /tcw3/meta/theming/manager.tcwdl: -------------------------------------------------------------------------------- 1 | use crate::ui::theming::{ClassSet, HElem, Manager, ElemChangeCb}; 2 | 3 | #[prototype_only] 4 | #[builder(simple)] 5 | pub comp crate::ui::theming::Elem { 6 | const style_manager: &Manager { pub set; } 7 | 8 | prop on_change: ElemChangeCb { pub set; } = ?; 9 | 10 | prop class_set: ClassSet { pub set; pub get; } = ?; 11 | 12 | const helem: HElem { pub get clone; } = ?; 13 | } 14 | -------------------------------------------------------------------------------- /tcw3/meta/theming/view.tcwdl: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ui::theming::{ClassSet, HElem, Manager, StyledBoxOverride, Widget, Role}, 3 | uicore::{HView, ViewFlags}, 4 | }; 5 | 6 | #[prototype_only] 7 | #[widget] 8 | #[builder(simple)] 9 | pub comp crate::ui::theming::StyledBox { 10 | const style_manager: &Manager { pub set; } 11 | const view_flags: ViewFlags { pub set; } = ?; 12 | 13 | prop class_set: ClassSet { pub set; pub get; } = ?; 14 | prop auto_class_set: ClassSet { pub set; pub get; } = ?; 15 | prop r#override: Box { pub set; } = ?; 16 | 17 | // It can't really store references or unsized values, so this is a hack 18 | prop children: [(Role, Option<&dyn Widget>)] { pub set; } = ?; 19 | 20 | // Work-around for the lack of indexed prop support. Props of 21 | // non-`'static` reference types are not supported by Designer, actually. 22 | // These fields expose the subset feature of `children`, and are more 23 | // convenient when you only need to assign `roles::GENERIC`. 24 | prop subview_generic: Option { pub set; } = ?; 25 | prop subelement_generic: HElem { pub set; } = ?; 26 | prop child_generic: &dyn Widget { pub set; } = ?; 27 | 28 | const view: HView { pub get clone; } = ?; 29 | const style_elem: HElem { pub get clone; } = ?; 30 | } 31 | -------------------------------------------------------------------------------- /tcw3/meta/uicore.tcwdl: -------------------------------------------------------------------------------- 1 | use cggeom::Box2; 2 | 3 | use crate::{pal, uicore::{HWnd, ViewFlags, CursorShape, Layout, ViewListener}}; 4 | 5 | /// A view handle type. 6 | #[prototype_only] 7 | #[builder(simple)] 8 | pub comp crate::uicore::HView { 9 | // TODO: `flags` is actually a `prop`. But some flags can be set only 10 | // through `new` 11 | const flags: ViewFlags { pub set; pub get clone; } = ?; 12 | 13 | /// Sets a new `ViewListener` for the view. 14 | prop listener: Box { pub set; } = ?; 15 | 16 | /// Sets a new `Layout` for the view. 17 | prop layout: Box { pub set; } = ?; 18 | 19 | /// Sets or retrieves the desired apperance of the mouse cursor for a given 20 | /// view. 21 | prop cursor_shape: Option { pub set; pub get clone; } = ?; 22 | 23 | /// Retrieves the frame (bounding rectangle) of a view in the superview's 24 | /// coordinate space. 25 | wire frame: Box2 { pub get clone; } = ?; 26 | 27 | /// Retrieves the frame (bounding rectangle) of a view in the containing 28 | /// window's coordinate space. 29 | wire global_frame: Box2 { pub get clone; } = ?; 30 | 31 | /// Retrieves the containing window for the view. 32 | wire containing_wnd: Option { pub get clone; } = ?; 33 | } 34 | -------------------------------------------------------------------------------- /tcw3/meta/views/button.tcwdl: -------------------------------------------------------------------------------- 1 | use crate::{ui::theming::{ClassSet, StyledBox, HElem, Manager}, uicore::HView, pal}; 2 | 3 | #[prototype_only] 4 | #[widget] 5 | #[builder(simple)] 6 | pub comp crate::ui::views::Button { 7 | const style_manager: &Manager { pub set; } 8 | 9 | pub event activated(wm: pal::Wm); 10 | 11 | prop caption: String { pub set; } = ?; 12 | prop class_set: ClassSet { pub set; get clone; } = ?; 13 | 14 | const view: HView { pub get clone; } = ?; 15 | const style_elem: HElem { pub get clone; } = ?; 16 | } 17 | -------------------------------------------------------------------------------- /tcw3/meta/views/checkbox.tcwdl: -------------------------------------------------------------------------------- 1 | use crate::{ui::theming::{ClassSet, StyledBox, HElem, Manager}, uicore::HView, pal}; 2 | 3 | #[prototype_only] 4 | #[widget] 5 | #[builder(simple)] 6 | pub comp crate::ui::views::Checkbox { 7 | const style_manager: &Manager { pub set; } 8 | 9 | pub event activated(wm: pal::Wm); 10 | 11 | prop caption: String { pub set; } = ?; 12 | prop class_set: ClassSet { pub set; pub get clone; } = ?; 13 | prop checked: bool { pub set; pub get clone; } = ?; 14 | 15 | const view: HView { pub get clone; } = ?; 16 | const style_elem: HElem { pub get clone; } = ?; 17 | } 18 | 19 | #[prototype_only] 20 | #[widget] 21 | #[builder(simple)] 22 | pub comp crate::ui::views::RadioButton { 23 | const style_manager: &Manager { pub set; } 24 | 25 | pub event activated(wm: pal::Wm); 26 | 27 | prop caption: String { pub set; } = ?; 28 | prop class_set: ClassSet { pub set; pub get clone; } = ?; 29 | prop checked: bool { pub set; pub get clone; } = ?; 30 | 31 | const view: HView { pub get clone; } = ?; 32 | const style_elem: HElem { pub get clone; } = ?; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /tcw3/meta/views/entry.tcwdl: -------------------------------------------------------------------------------- 1 | use crate::{ui::theming::{ClassSet, StyledBox, HElem, Manager}, uicore::HView}; 2 | 3 | #[prototype_only] 4 | #[widget] 5 | #[builder(simple)] 6 | pub comp crate::ui::views::Entry { 7 | const wm: crate::pal::Wm { pub set; } 8 | const style_manager: &Manager { pub set; } 9 | 10 | prop class_set: ClassSet { pub set; get clone; } = ?; 11 | 12 | const core: crate::ui::views::EntryCore { pub get borrow; } = ?; 13 | 14 | const view: HView { pub get clone; } = ?; 15 | const style_elem: HElem { pub get clone; } = ?; 16 | 17 | /// Set or retrieve the text content. 18 | /// 19 | /// When you assign to this property, if the new value is different from the 20 | /// current one, it resets various internal states such as an undo history. 21 | /// Otherwise, it does nothing. 22 | prop text: String { pub set; pub get clone; pub watch event(changed); } = ?; 23 | 24 | /// Raised after the text content is modified. 25 | /// 26 | /// The event may be raised spuriously, i.e., even when the text content 27 | /// is not actually modified. 28 | pub event changed(wm: pal::Wm); 29 | } 30 | 31 | #[prototype_only] 32 | #[widget] 33 | #[builder(simple)] 34 | pub comp crate::ui::views::EntryCore { 35 | const wm: crate::pal::Wm { pub set; } 36 | const style_manager: &Manager { pub set; } 37 | 38 | const view: HView { pub get clone; } = ?; 39 | const style_elem: HElem { pub get clone; } = ?; 40 | 41 | /// Set or retrieve the text content. 42 | /// 43 | /// When you assign to this property, if the new value is different from the 44 | /// current one, it resets various internal states such as an undo history. 45 | /// Otherwise, it does nothing. 46 | prop text: String { pub set; pub get clone; pub watch event(changed); } = ?; 47 | 48 | /// Raised after the text content is modified. 49 | /// 50 | /// The event may be raised spuriously, i.e., even when the text content 51 | /// is not actually modified. 52 | pub event changed(wm: pal::Wm); 53 | } 54 | -------------------------------------------------------------------------------- /tcw3/meta/views/label.tcwdl: -------------------------------------------------------------------------------- 1 | use crate::{ui::theming::{ClassSet, StyledBox, HElem, Manager}, uicore::HView}; 2 | 3 | #[prototype_only] 4 | #[widget] 5 | #[builder(simple)] 6 | pub comp crate::ui::views::Label { 7 | const style_manager: &Manager { pub set; } 8 | 9 | prop text: String { pub set; } = ?; 10 | prop class_set: ClassSet { pub set; get clone; } = ?; 11 | 12 | const view: HView { pub get clone; } = ?; 13 | const style_elem: HElem { pub get clone; } = ?; 14 | } 15 | -------------------------------------------------------------------------------- /tcw3/meta/views/slider.tcwdl: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ui::{ 3 | theming::{ClassSet, StyledBox, HElem, Manager}, 4 | views::slider::SliderTraits, 5 | }, 6 | uicore::HView, 7 | utils::resetiter, 8 | }; 9 | 10 | /// A high-level interface for `SliderRaw`. 11 | #[prototype_only] 12 | #[widget] 13 | #[alias(pub crate::ui::views::Slider)] 14 | #[builder(simple)] 15 | pub comp crate::ui::views::slider::Slider { 16 | const wm: crate::pal::Wm { pub set; } 17 | const style_manager: &Manager { pub set; } 18 | /// Specifies the direction of the slider widget. 19 | const vertical: bool { pub set; } 20 | 21 | /// Sets or retrieves the class set of the inner `StyledBox`. 22 | /// 23 | /// It defaults to `ClassSet::SLIDER`. Some bits (e.g., `ACTIVE`) are 24 | /// internally enforced and cannot be modified. 25 | prop class_set: ClassSet { pub set; get clone; } = ?; 26 | 27 | /// Retrieves a handle to the view representing the widget. 28 | const view: HView { pub get clone; } = ?; 29 | /// Retrieves a handle to the styling element representing the widget. 30 | const style_elem: HElem { pub get clone; } = ?; 31 | 32 | /// Assigns a `SliderTraits`. 33 | /// 34 | /// The default value is `SmoothSliderTraits::new()`. 35 | prop traits: impl Into> + 'static { pub set; } = ?; 36 | 37 | /// Sets or retrieves the current value in range `[0, 1]`. 38 | /// 39 | /// Changing the value cancels any ongoing mouse drag operation. 40 | prop value: f64 { pub set; pub get clone; pub watch event(changed); } = ?; 41 | 42 | /// Retrieves the current uncommitted value. 43 | wire uncommitted_value: f64 { pub get clone; pub watch event(changing); } = ?; 44 | 45 | /// Raised whenever `value` changes. 46 | pub event changed(wm: pal::Wm); 47 | 48 | /// Raised whenever `uncommitted_value` changes. 49 | pub event changing(wm: pal::Wm); 50 | 51 | /// Sets the tick mark positions. 52 | prop ticks: impl resetiter::IntoResetIter + 'static { pub set; } = ?; 53 | 54 | /// Arranges tick marks uniformly by automatically assiging a value to 55 | /// `ticks`. 56 | /// 57 | /// The value must not be zero. 58 | prop uniform_ticks: usize { pub set; } = ?; 59 | 60 | /// Sets custom label views attached to specified values. 61 | prop labels: impl AsRef<[(Role, Option<(f64, &dyn Widget)>)]> { pub set; } = ?; 62 | } 63 | -------------------------------------------------------------------------------- /tcw3/meta/views/spacer.tcwdl: -------------------------------------------------------------------------------- 1 | use cgmath::Vector2; 2 | use crate::{uicore::{HView, SizeTraits}, ui::layouts::EmptyLayout, pal}; 3 | 4 | /// Represents a spacer widget, which has size traits but no graphical contents. 5 | #[widget] 6 | pub comp crate::ui::views::SpacerWidget { 7 | pub const wm: pal::Wm { pub set; } 8 | 9 | /// Sets `SizeTraits::min`. 10 | pub prop min: Vector2 = [0.0; 2].into(); 11 | 12 | /// Sets `SizeTraits::max`. 13 | pub prop max: Vector2 = [std::f32::INFINITY; 2].into(); 14 | 15 | /// Sets `SizeTraits::preferred`. 16 | pub prop preferred: Vector2 = [0.0; 2].into(); 17 | 18 | /// Retrieves `HView` representing the widget. 19 | pub const view = HView::new! { 20 | layout = EmptyLayout::new(SizeTraits { 21 | min: get!(min), 22 | max: get!(max), 23 | preferred: get!(preferred), 24 | }), 25 | }; 26 | } 27 | 28 | /// Represents a fixed-size spacer widget, which has size traits but no 29 | /// graphical contents. 30 | #[widget] 31 | pub comp crate::ui::views::FixedSpacer { 32 | pub const wm: pal::Wm { pub set; } 33 | 34 | /// Sets the widget size 35 | pub prop size: Vector2 = [0.0; 2].into(); 36 | 37 | /// Retrieves `HView` representing the widget. 38 | pub const view = HView::new! { 39 | layout = { 40 | let size = get!(size); 41 | EmptyLayout::new(SizeTraits { 42 | min: size, 43 | max: size, 44 | preferred: size, 45 | }) 46 | }, 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /tcw3/meta/views/split.tcwdl: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | pal, 3 | ui::theming::{ClassSet, StyledBox, HElem, Manager, Widget}, 4 | ui::views::split::SplitDragListener, 5 | uicore::HView, 6 | }; 7 | 8 | #[prototype_only] 9 | #[widget] 10 | #[alias(pub crate::ui::views::Split)] 11 | #[builder(simple)] 12 | pub comp crate::ui::views::split::Split { 13 | const style_manager: &Manager { pub set; } 14 | 15 | /// Specifies the split direction. 16 | const vertical: bool { pub set; } 17 | 18 | /// Specifies which panel should be resized when the overall size is 19 | /// changed. It must be one of Some(0), Some(1), and None. 20 | const fix: Option { pub set; } = ?; 21 | 22 | const view: HView { pub get clone; } = ?; 23 | const style_elem: HElem { pub get clone; } = ?; 24 | 25 | prop class_set: ClassSet { pub set; get clone; } = ?; 26 | 27 | /// Sets or retrieves a raw (unclipped) value representing the split position. 28 | /// 29 | /// The interpretation of this value differs depending on the value of `fix`. 30 | /// If `fix` is `Some(_)`, it represents the absolute size of the 31 | /// corresponding panel. Otherwise, it represents the percentage of the 32 | /// area occupied by the first panel. 33 | /// 34 | /// The returned value is raw and unclipped, meaning it does not take the 35 | /// size contraints of the panels into consideration. Also, it does not 36 | /// change when the overall size is changed. 37 | prop value: f32 { pub set; pub get clone; } = ?; 38 | 39 | /// Set the views placed in the panels. 40 | prop subviews: [HView; 2] { pub set; } = ?; 41 | 42 | /// Set the child styling elements. 43 | prop subelements: [Option; 2] { pub set; } = ?; 44 | 45 | /// Set the subviews and child styling elements at once. 46 | prop children: [&dyn Widget; 2] { pub set; } = ?; 47 | 48 | /// Set the panel to zoom into. Defaults to `None` (both panels are 49 | /// displayed). The value must be one of `Some(0)`, `Some(1)`, and `None`. 50 | prop zoom: Option { pub set; pub get clone; } = ?; 51 | 52 | /// Set the factory function for gesture event handlers used when the user 53 | /// resizes the panels. 54 | /// 55 | /// The function is called when the user starts a mouse drag gesture. 56 | prop on_drag: impl Fn(Wm) -> Box { pub set; } = ?; 57 | } 58 | -------------------------------------------------------------------------------- /tcw3/meta/views/table.tcwdl: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ui::{ 3 | table::TableFlags, 4 | theming::{ClassSet, HElem, Manager}, 5 | mixins::scrollwheel::ScrollAxisFlags, 6 | }, 7 | uicore::{HView, SizeTraits}, 8 | pal, 9 | }; 10 | 11 | #[prototype_only] 12 | #[widget] 13 | #[alias(pub crate::ui::views::Table)] 14 | #[builder(simple)] 15 | pub comp crate::ui::views::table::Table { 16 | /// Get a handle to the view representing the widget. 17 | const view: HView { pub get clone; } = ?; 18 | 19 | /// Set new size traits. 20 | /// 21 | /// Must not have an active edit (the table model must be in the unlocked 22 | /// state). 23 | prop size_traits: SizeTraits { pub set; } = ?; 24 | 25 | /// Set new table flags. 26 | prop flags: TableFlags { pub set; } = ?; 27 | 28 | /// The event that is raised whenever the table model is updated. 29 | event model_update(); 30 | 31 | /// The event that is raised in `Layout::arrange` to make modifications to 32 | /// the table model. 33 | /// 34 | /// This may be useful to resize lines based on the current size of the 35 | /// table view. 36 | event prearrange(); 37 | } 38 | 39 | #[prototype_only] 40 | #[widget] 41 | #[alias(pub crate::ui::views::ScrollableTable)] 42 | #[builder(simple)] 43 | pub comp crate::ui::views::table::ScrollableTable { 44 | const style_manager: &Manager { pub set; } 45 | 46 | /// Get a handle to the view representing the widget. 47 | const view: HView { pub get clone; } = ?; 48 | 49 | /// Get the styling element representing the widget. 50 | const style_elem: HElem { pub get clone; } = ?; 51 | 52 | /// Get a reference to the inner `Table`. 53 | const table: crate::ui::views::Table { pub get borrow; } = ?; 54 | 55 | /// Set new size traits (delegated to the inner `Table`). 56 | /// 57 | /// Must not have an active edit (the table model must be in the unlocked 58 | /// state). 59 | prop size_traits: SizeTraits { pub set; } = ?; 60 | 61 | /// Set new table flags. 62 | prop flags: TableFlags { pub set; } = ?; 63 | 64 | /// Set the axes for which scrolling is allowed. 65 | /// 66 | /// This might not take effect for an ongoing scroll gesture (if any). 67 | prop scrollable_axes: ScrollAxisFlags { pub set; } = ?; 68 | 69 | /// Set the class set of the inner `StyledBox`. 70 | /// 71 | /// It defaults to `ClassSet::SCROLL_CONTAINER`. Some bits (e.g., 72 | /// `HAS_HORIZONTAL_SCROLLBAR`) are internally enforced and cannot be 73 | /// modified. 74 | prop class_set: ClassSet { pub set; get clone; } = ?; 75 | 76 | 77 | } 78 | -------------------------------------------------------------------------------- /tcw3/pal/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | if env::var("CARGO_CFG_TARGET_OS").unwrap() == "macos" { 5 | cc::Build::new() 6 | .file("src/macos/TCWWindowController.m") 7 | .file("src/macos/TCWWindowView.m") 8 | .file("src/macos/TCWGestureHandlerView.m") 9 | .file("src/macos/Timers.m") 10 | .flag("-fobjc-arc") 11 | .flag("-fobjc-weak") 12 | .compile("tcwsupport_macos"); 13 | } else if env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" { 14 | // `CompareObjectHandles` is in `WindowsApp.lib`. 15 | // 16 | println!("cargo:rustc-link-lib=dylib=WindowsApp"); 17 | 18 | assert!(cc::Build::new().get_compiler().is_like_msvc()); 19 | 20 | cc::Build::new() 21 | .file("src/windows/comp.cpp") 22 | .flag("/std:c++17") // assume MSVC 23 | .compile("tcwsupport_windows"); 24 | } else { 25 | // Try to match the settings to that of `gtk-sys` 26 | let gtk_lib = pkg_config::Config::new() 27 | .atleast_version("3.14") 28 | .cargo_metadata(false) 29 | .probe("gtk+-3.0") 30 | .unwrap(); 31 | 32 | let mut build = cc::Build::new(); 33 | for path in gtk_lib.include_paths.iter() { 34 | build.include(path); 35 | } 36 | 37 | build.file("src/gtk/wndwidget.c").compile("tcwsupport_gtk"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tcw3/pal/macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tcw3_pal_macro" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [features] 8 | macos = [] 9 | windows = [] 10 | gtk = [] 11 | 12 | [dependencies] 13 | bitflags = "1.1.0" 14 | enum-utils = "0.1.2" 15 | proc-macro-error = "1" 16 | proc-macro2 = "1" 17 | quote = "1" 18 | syn = { version = "1", features = ["full"] } 19 | 20 | [lib] 21 | path = "src/lib.rs" 22 | proc-macro = true 23 | -------------------------------------------------------------------------------- /tcw3/pal/macro/src/accel/testing.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | 3 | use super::ActionBinding; 4 | 5 | pub(super) fn gen_action_binding<'a>( 6 | crate_path: &'a syn::Path, 7 | actions: impl IntoIterator + 'a, 8 | ) -> impl Iterator + 'a { 9 | actions 10 | .into_iter() 11 | .map(move |binding| { 12 | let action = &binding.action; 13 | binding.triggers.iter().map(move |trigger| { 14 | let source = syn::LitStr::new(&trigger.source.to_string(), trigger.source.span()); 15 | let pattern = &trigger.pattern; 16 | quote::quote! { 17 | #crate_path::testing::wmapi::ActionBinding { 18 | source: #source, 19 | pattern: #pattern, 20 | action: #action, 21 | } 22 | } 23 | }) 24 | }) 25 | .flatten() 26 | } 27 | -------------------------------------------------------------------------------- /tcw3/pal/macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "windows", feature(or_patterns))] // `|` in subpatterns 2 | //! Provides the internal implementation of `tcw3_pal::new_accel`. 3 | extern crate proc_macro; 4 | 5 | mod accel; 6 | mod keycode; 7 | 8 | #[proc_macro] 9 | #[proc_macro_error::proc_macro_error] 10 | pub fn accel_table_inner(params: proc_macro::TokenStream) -> proc_macro::TokenStream { 11 | accel::accel_table_inner(params) 12 | } 13 | -------------------------------------------------------------------------------- /tcw3/pal/src/gtk/timer.rs: -------------------------------------------------------------------------------- 1 | use glib::source::SourceId; 2 | use leakypool::{LazyToken, LeakyPool, PoolPtr, SingletonToken, SingletonTokenId}; 3 | 4 | leakypool::singleton_tag!(struct Tag); 5 | type InvokePool = LeakyPool<(u64, Option), LazyToken>>; 6 | type InvokePoolPtr = PoolPtr<(u64, Option), SingletonTokenId>; 7 | 8 | pub struct TimerPool { 9 | // TODO: `SourceId` doesn't use `NonZero`... maybe send a PR 10 | pool: InvokePool, 11 | next_token: u64, 12 | } 13 | 14 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 15 | pub struct HInvoke { 16 | ptr: InvokePoolPtr, 17 | token: u64, 18 | } 19 | 20 | impl TimerPool { 21 | /// This can be called only once because `TimerPool` uses `SingletonToken`. 22 | pub const fn new() -> Self { 23 | Self { 24 | pool: LeakyPool::new(), 25 | next_token: 0, 26 | } 27 | } 28 | 29 | pub fn insert(&mut self, f: impl FnOnce(HInvoke) -> SourceId) -> HInvoke { 30 | let token = self.next_token; 31 | debug_assert_ne!(self.next_token, u64::max_value(), "token exhausted"); 32 | self.next_token += 1; 33 | 34 | let ptr = self.pool.allocate((token, None)); 35 | let hinvoke = HInvoke { ptr, token }; 36 | 37 | let source_id = f(hinvoke.clone()); 38 | self.pool[ptr].1 = Some(source_id); 39 | 40 | hinvoke 41 | } 42 | 43 | pub fn remove(&mut self, invoke: &HInvoke) -> Option { 44 | let ent = self.pool.get(invoke.ptr)?; 45 | if ent.0 != invoke.token { 46 | return None; 47 | } 48 | 49 | self.pool.deallocate(invoke.ptr).unwrap().1 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tcw3/pal/src/gtk/window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | // Defined in `window.rs` 7 | extern void tcw_wnd_widget_draw_handler(size_t wnd_ptr, cairo_t *cr); 8 | extern void tcw_wnd_widget_dpi_scale_changed_handler(size_t wnd_ptr); 9 | extern int tcw_wnd_widget_nc_hit_test_handler(size_t wnd_ptr, float x, float y); 10 | extern int tcw_wnd_widget_key_press_handler(size_t wnd_ptr, GdkEventKey *event); 11 | extern int tcw_wnd_widget_key_release_handler(size_t wnd_ptr, 12 | GdkEventKey *event); 13 | extern void tcw_wnd_widget_button_handler(size_t wnd_ptr, float x, float y, 14 | int is_pressed, int button); 15 | extern void tcw_wnd_widget_motion_handler(size_t wnd_ptr, float x, float y); 16 | extern void tcw_wnd_widget_leave_handler(size_t wnd_ptr); 17 | extern void tcw_wnd_widget_discrete_scroll_handler(size_t wnd_ptr, float x, 18 | float y, float delta_x, 19 | float delta_y); 20 | extern void tcw_wnd_widget_smooth_scroll_handler(size_t wnd_ptr, float x, 21 | float y, float delta_x, 22 | float delta_y, uint32_t time); 23 | extern void tcw_wnd_widget_smooth_scroll_stop_handler(size_t wnd_ptr, 24 | uint32_t time); 25 | -------------------------------------------------------------------------------- /tcw3/pal/src/macos/TCWGestureHandlerView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #import 3 | 4 | @class TCWWindowController; 5 | 6 | /** 7 | * This view receives pointer events and redirects them to appropriate handler 8 | * functions. 9 | * 10 | * It initially serves as the responder for all pointer events within a 11 | * window. When the start of a gesture (e.g., scroll wheel with inertia 12 | * scrolling) is detected, it transitions into the state where it only handles 13 | * the events associated with the gesture so that they can be discerned from 14 | * other events. Meanwhile a new instance of `TCWGestureHandlerView` is created 15 | * to capture the non-gesture events. 16 | */ 17 | @interface TCWGestureHandlerView 18 | : NSView 19 | 20 | - (id)initWithController:(TCWWindowController *)controller; 21 | 22 | /** 23 | * Cancel the current gesture associated with this view. This method 24 | * calls event callbacks, but does not call `gestureEndedInView:`. 25 | */ 26 | - (void)cancelGesture; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /tcw3/pal/src/macos/TCWWindowController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #import 3 | 4 | #import "TCWBridge.h" 5 | 6 | @class TCWGestureHandlerView; 7 | 8 | @interface TCWWindowController : NSObject 9 | @property TCWListenerUserData listenerUserData; 10 | 11 | /** 12 | * This method is used by `TCWGestureHandlerView` to convert the event's 13 | * position to content view coordinates. 14 | */ 15 | - (NSPoint)locationOfEvent:(NSEvent *)event; 16 | @end 17 | -------------------------------------------------------------------------------- /tcw3/pal/src/macos/TCWWindowView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #import 3 | 4 | #import "TCWBridge.h" 5 | 6 | @class TCWWindowController; 7 | 8 | @interface TCWWindowView : NSView 9 | 10 | - (id)initWithController:(TCWWindowController *)_controller; 11 | - (void)setCursorShape:(TCW3CursorShape)shape; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /tcw3/pal/src/macos/Timers.m: -------------------------------------------------------------------------------- 1 | #import "TCWBridge.h" 2 | #import 3 | 4 | @interface TCWDeferredInvocation : NSObject 5 | - (id)initWithUserData:(TCWInvokeUserData)ud; 6 | - (void)fire:(NSTimer *)timer; 7 | - (void)dealloc; 8 | @end 9 | 10 | @implementation TCWDeferredInvocation { 11 | TCWInvokeUserData ud; 12 | bool done; 13 | } 14 | 15 | - (id)initWithUserData:(TCWInvokeUserData)_ud { 16 | if (self = [super init]) { 17 | self->ud = _ud; 18 | self->done = false; 19 | } 20 | 21 | return self; 22 | } 23 | 24 | - (void)fire:(NSTimer *)timer { 25 | (void)timer; 26 | 27 | NSAssert(!self->done, @"The function has already been called."); 28 | self->done = true; 29 | tcw_invoke_fire(self->ud); 30 | } 31 | 32 | - (void)dealloc { 33 | if (!self->done) { 34 | tcw_invoke_cancel(self->ud); 35 | } 36 | } 37 | 38 | @end 39 | 40 | extern NSTimer *TCWInvokeAfter(double delay, double tolerance, TCWInvokeUserData ud) { 41 | TCWDeferredInvocation *invocation = 42 | [[TCWDeferredInvocation alloc] initWithUserData:ud]; 43 | 44 | NSTimer *timer = [NSTimer timerWithTimeInterval:delay 45 | target:invocation 46 | selector:@selector(fire:) 47 | userInfo:nil 48 | repeats:NO]; 49 | 50 | timer.tolerance = tolerance; 51 | 52 | [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 53 | 54 | return timer; 55 | } 56 | -------------------------------------------------------------------------------- /tcw3/pal/src/macos/utils.rs: -------------------------------------------------------------------------------- 1 | // Based on 2 | use cocoa::{ 3 | base::{id, nil}, 4 | foundation::NSAutoreleasePool, 5 | }; 6 | use objc::{ 7 | class, msg_send, 8 | runtime::{BOOL, NO}, 9 | sel, sel_impl, 10 | }; 11 | use std::ops::Deref; 12 | 13 | #[derive(Debug, PartialEq, Eq, Hash)] 14 | #[repr(transparent)] 15 | pub struct IdRef(id); 16 | 17 | impl IdRef { 18 | pub fn new(i: id) -> IdRef { 19 | IdRef(i) 20 | } 21 | 22 | #[allow(dead_code)] 23 | pub fn retain(i: id) -> IdRef { 24 | if i != nil { 25 | let _: id = unsafe { msg_send![i, retain] }; 26 | } 27 | IdRef(i) 28 | } 29 | 30 | pub fn non_nil(self) -> Option { 31 | if self.0 == nil { 32 | None 33 | } else { 34 | Some(self) 35 | } 36 | } 37 | } 38 | 39 | impl Drop for IdRef { 40 | fn drop(&mut self) { 41 | if self.0 != nil { 42 | with_autorelease_pool(|| unsafe { 43 | let _: () = msg_send![self.0, release]; 44 | }); 45 | } 46 | } 47 | } 48 | 49 | impl Deref for IdRef { 50 | type Target = id; 51 | fn deref(&self) -> &id { 52 | &self.0 53 | } 54 | } 55 | 56 | impl Clone for IdRef { 57 | fn clone(&self) -> IdRef { 58 | if self.0 != nil { 59 | let _: id = unsafe { msg_send![self.0, retain] }; 60 | } 61 | IdRef(self.0) 62 | } 63 | } 64 | 65 | #[derive(Debug)] 66 | pub struct AutoreleasePool(id); 67 | 68 | impl AutoreleasePool { 69 | pub fn new() -> Self { 70 | Self(unsafe { NSAutoreleasePool::new(nil) }) 71 | } 72 | } 73 | 74 | impl Drop for AutoreleasePool { 75 | fn drop(&mut self) { 76 | let () = unsafe { msg_send![self.0, release] }; 77 | } 78 | } 79 | 80 | pub fn with_autorelease_pool(f: impl FnOnce() -> T) -> T { 81 | let _arp = AutoreleasePool::new(); 82 | f() 83 | } 84 | 85 | #[allow(dead_code)] 86 | pub fn is_main_thread() -> bool { 87 | let result: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] }; 88 | result != NO 89 | } 90 | -------------------------------------------------------------------------------- /tcw3/pal/src/swrast.rs: -------------------------------------------------------------------------------- 1 | //! Software-based compositor. 2 | //! 3 | //! # Restrictions 4 | //! 5 | //! - The maximum render target size is 16384×16384. 6 | //! - The coordinates of all elements (including those clipped) must fit in 7 | //! range ±16384. 8 | //! - The only supported pixel format is ARGB8888. 9 | //! - There's some limit on the nesting level of layers. 10 | //! - There's a reasonable limit on the number of displayed layers. 11 | //! 12 | 13 | /// `log2(TILE)` 14 | const TILE_SHIFT: u32 = 4; 15 | /// The tile size. 16 | const TILE: usize = 1 << TILE_SHIFT; 17 | 18 | /// `log2(CLIP_SUB)` 19 | const CLIP_SUB_SHIFT: u32 = 16; 20 | /// See `bin::Elem::clip_dist`. 21 | const CLIP_SUB: i32 = 1 << CLIP_SUB_SHIFT; 22 | 23 | /// `log2(UV_SUB)` 24 | const UV_SUB_SHIFT: u32 = 16; 25 | /// The precision of UV coordinates. 26 | const UV_SUB: i32 = 1 << UV_SUB_SHIFT; 27 | 28 | /// The number of internal layers. Must be `<= NUM_GROUPS`. 29 | const NUM_LAYERS: usize = 16; 30 | 31 | /// The number of groups. 32 | const NUM_GROUPS: usize = 32; 33 | 34 | mod binner; 35 | mod binrast; 36 | mod layers; 37 | mod rast; 38 | mod utils; 39 | 40 | pub(crate) use self::{ 41 | binner::{Binner, Bmp}, 42 | layers::{HLayer, HWnd, Screen}, 43 | }; 44 | -------------------------------------------------------------------------------- /tcw3/pal/src/testing_dis.rs: -------------------------------------------------------------------------------- 1 | //! The testing backend (disabled). 2 | //! 3 | //! Add a feature flag `testing` to enable the testing backend. 4 | use std::panic; 5 | 6 | #[path = "testing/logging.rs"] 7 | #[allow(dead_code)] 8 | mod logging; 9 | #[path = "testing/wmapi.rs"] 10 | pub mod wmapi; 11 | pub use self::{logging::Logger, wmapi::TestingWm}; 12 | 13 | /// Call `with_testing_wm` if the testing backend is enabled. Otherwise, 14 | /// output a warning message and return without calling the givne function. 15 | /// 16 | /// This function is available even if the `testing` feature flag is disabled. 17 | pub fn run_test(_cb: impl FnOnce(&dyn TestingWm) + Send + panic::UnwindSafe + 'static) { 18 | #[allow(clippy::explicit_write)] // bypass output redirection 19 | { 20 | use std::io::Write; 21 | writeln!( 22 | std::io::stderr(), 23 | "warning: testing backend is disabled, skipping some tests" 24 | ) 25 | .unwrap(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tcw3/pal/src/windows/comp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace winrt; 5 | using namespace Windows::System; 6 | 7 | namespace abi = ABI::Windows::System; 8 | 9 | /// Perform a one-time initialization for this module. Must be called on a main 10 | /// thread. 11 | extern "C" HRESULT tcw_comp_init() { 12 | // Create a dispatcher queue for the current thread 13 | DispatcherQueueOptions options { 14 | sizeof(DispatcherQueueOptions), 15 | DQTYPE_THREAD_CURRENT, 16 | DQTAT_COM_ASTA, 17 | }; 18 | 19 | static DispatcherQueueController ctrler{nullptr}; 20 | return CreateDispatcherQueueController( 21 | options, 22 | reinterpret_cast(put_abi(ctrler)) 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /tcw3/pal/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::{env::var_os, time::Duration}; 3 | 4 | pub fn set_timelimit_default() { 5 | std::thread::spawn(|| { 6 | std::thread::sleep(Duration::from_secs(30)); 7 | eprintln!("!!! Time limit exceeed."); 8 | std::process::abort(); 9 | }); 10 | } 11 | 12 | pub fn exit_if_native_backend_tests_are_disabled() { 13 | if let Some(value) = var_os("ST_SKIP_NATIVE_BACKEND_TESTS") { 14 | if !value.is_empty() && value != "0" { 15 | println!("Skipping because ST_SKIP_NATIVE_BACKEND_TESTS is set"); 16 | std::process::exit(0); 17 | } 18 | } 19 | } 20 | 21 | pub fn try_init_logger_for_default_harness() { 22 | let _ = env_logger::builder().is_test(true).try_init(); 23 | } 24 | -------------------------------------------------------------------------------- /tcw3/pal/tests/futures.rs: -------------------------------------------------------------------------------- 1 | use futures::{channel::oneshot::channel, task::LocalSpawnExt}; 2 | use std::time::Duration; 3 | use tcw3_pal::{prelude::*, Wm}; 4 | 5 | mod common; 6 | 7 | fn main() { 8 | env_logger::init(); 9 | common::set_timelimit_default(); 10 | common::exit_if_native_backend_tests_are_disabled(); 11 | 12 | let wm = Wm::global(); 13 | 14 | let (send, recv) = channel(); 15 | 16 | // Send a payload sometime later 17 | std::thread::spawn(move || { 18 | std::thread::sleep(Duration::from_millis(100)); 19 | send.send(()).unwrap(); 20 | }); 21 | 22 | // This task run on the main thread, and exits the program gracefully 23 | // upon receiving a payload via the channel 24 | wm.spawner() 25 | .spawn_local(async move { 26 | let () = recv.await.unwrap(); 27 | println!("Received a payload (test passed)"); 28 | 29 | wm.terminate(); 30 | }) 31 | .unwrap(); 32 | 33 | wm.enter_main_loop(); 34 | } 35 | -------------------------------------------------------------------------------- /tcw3/pal/tests/terminate_with_pending_invoke.rs: -------------------------------------------------------------------------------- 1 | use log::{error, info}; 2 | use std::time::Duration; 3 | use tcw3_pal::{prelude::*, Wm}; 4 | 5 | mod common; 6 | 7 | fn main() { 8 | env_logger::init(); 9 | common::set_timelimit_default(); 10 | common::exit_if_native_backend_tests_are_disabled(); 11 | 12 | let wm = Wm::global(); 13 | 14 | wm.invoke_after( 15 | Duration::from_millis(100)..Duration::from_millis(1000), 16 | |wm| { 17 | info!("Calling `terminate`. The program should exit soon..."); 18 | wm.terminate(); 19 | }, 20 | ); 21 | wm.invoke_after(Duration::from_secs(5)..Duration::from_secs(5), |_| { 22 | error!("The program did not quit soon enough."); 23 | std::process::abort(); 24 | }); 25 | 26 | wm.enter_main_loop(); 27 | } 28 | -------------------------------------------------------------------------------- /tcw3/pal/tests/timer.rs: -------------------------------------------------------------------------------- 1 | use futures::task::LocalSpawnExt; 2 | use log::info; 3 | use std::time::{Duration, Instant}; 4 | use tcw3_pal::{prelude::*, Wm}; 5 | 6 | mod common; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | common::set_timelimit_default(); 11 | common::exit_if_native_backend_tests_are_disabled(); 12 | 13 | let wm = Wm::global(); 14 | 15 | wm.spawner() 16 | .spawn_local(async move { 17 | let d_200_ms = Duration::from_millis(200); 18 | let d_600_ms = Duration::from_millis(600); 19 | let d_1200_ms = Duration::from_millis(1200); 20 | 21 | // Successful sleep operation 22 | let sleep1 = wm.sleep(d_600_ms..d_1200_ms); 23 | let sleep1_b = sleep1.clone(); 24 | let start = Instant::now(); 25 | assert!(sleep1.poll_without_context().is_pending()); 26 | std::thread::sleep(d_200_ms); 27 | assert!(sleep1.poll_without_context().is_pending()); 28 | sleep1.await.unwrap(); 29 | 30 | info!( 31 | "sleep1 resolved after {:?} (expected to be in range {:?})", 32 | start.elapsed(), 33 | d_600_ms..d_1200_ms 34 | ); 35 | 36 | // A completed sleep operation can't be cancelled anymore 37 | assert!(!sleep1_b.cancel()); 38 | 39 | // Cancelled sleep operation 40 | let sleep2 = wm.sleep(d_600_ms..d_1200_ms); 41 | let start = Instant::now(); 42 | assert!(sleep2.poll_without_context().is_pending()); 43 | std::thread::sleep(d_200_ms); 44 | assert!(sleep2.poll_without_context().is_pending()); 45 | assert!(sleep2.cancel()); 46 | assert!(sleep2.poll_without_context().is_ready()); 47 | assert!(!sleep2.cancel()); 48 | sleep2.await.err().unwrap(); 49 | 50 | info!( 51 | "sleep2 cancelled after {:?} (expected to be around {:?})", 52 | start.elapsed(), 53 | d_200_ms 54 | ); 55 | 56 | println!("Test passed"); 57 | wm.terminate(); 58 | }) 59 | .unwrap(); 60 | 61 | wm.enter_main_loop(); 62 | } 63 | -------------------------------------------------------------------------------- /tcw3/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(unsized_locals)] // Call `dyn FnOnce` 2 | #![feature(const_if_match)] // `if` and `match` in `const fn` 3 | #![feature(const_fn)] // conditional expressions in `const fn` 4 | #![feature(external_doc)] // `#[doc(include = ...)]` 5 | #![allow(clippy::float_cmp)] 6 | // this lint is ridiculous 7 | // The size on memory hardly relates to how they are passed via a parameter 8 | #![allow(clippy::trivially_copy_pass_by_ref)] 9 | #![doc(include = "./lib.md")] 10 | 11 | pub use tcw3_designer_runtime as designer_runtime; 12 | pub use tcw3_images as images; 13 | pub use tcw3_pal as pal; 14 | pub use tcw3_stvg as stvg; 15 | pub use tcw3_testing as testing; 16 | 17 | // Re-export `rob` to use from `stylesheet!` 18 | #[doc(hidden)] 19 | pub use rob; 20 | 21 | pub mod ui; 22 | pub mod uicore; 23 | pub mod utils { 24 | pub mod resetiter; 25 | #[doc(no_inline)] 26 | pub use self::resetiter::ResetIter; 27 | } 28 | 29 | pub mod prelude { 30 | #[doc(no_inline)] 31 | pub use crate::{ 32 | pal::prelude::*, 33 | uicore::WmExt, 34 | utils::resetiter::{IntoResetIter, ResetIter, ResetIterExt}, 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /tcw3/src/ui/layouts/empty.rs: -------------------------------------------------------------------------------- 1 | use cgmath::Vector2; 2 | 3 | use crate::uicore::{HView, Layout, LayoutCtx, SizeTraits}; 4 | 5 | /// A `Layout` that doesn't have subviews. 6 | #[derive(Debug, Clone)] 7 | pub struct EmptyLayout { 8 | size_traits: SizeTraits, 9 | } 10 | 11 | impl EmptyLayout { 12 | pub fn new(size_traits: SizeTraits) -> Self { 13 | Self { size_traits } 14 | } 15 | } 16 | 17 | impl Layout for EmptyLayout { 18 | fn subviews(&self) -> &[HView] { 19 | &[] 20 | } 21 | 22 | fn size_traits(&self, _: &LayoutCtx<'_>) -> SizeTraits { 23 | self.size_traits 24 | } 25 | 26 | fn arrange(&self, _: &mut LayoutCtx<'_>, _: Vector2) {} 27 | 28 | fn has_same_subviews(&self, other: &dyn Layout) -> bool { 29 | // See if `other` has the same type 30 | as_any::Downcast::is::(other) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tcw3/src/ui/mod.rs: -------------------------------------------------------------------------------- 1 | //! Provides standard UI components (views, layouts, ...). 2 | pub mod layouts { 3 | mod abs; 4 | mod empty; 5 | mod fill; 6 | mod table; 7 | pub use self::{abs::*, empty::*, fill::*, table::*}; 8 | } 9 | 10 | /// Reusable building blocks for creating UI components. 11 | pub mod mixins { 12 | pub mod button; 13 | pub mod canvas; 14 | pub mod scrollwheel; 15 | pub use self::{button::ButtonMixin, canvas::CanvasMixin, scrollwheel::ScrollWheelMixin}; 16 | } 17 | 18 | pub mod views { 19 | mod button; 20 | mod checkbox; 21 | mod entry; 22 | mod label; 23 | pub mod scrollbar; 24 | pub mod slider; 25 | mod spacer; 26 | pub mod split; 27 | pub mod table; 28 | pub use self::{ 29 | button::Button, 30 | checkbox::{Checkbox, RadioButton}, 31 | entry::{Entry, EntryCore}, 32 | label::Label, 33 | scrollbar::ScrollbarRaw, 34 | slider::{Slider, SliderRaw}, 35 | spacer::{new_spacer, Spacer}, 36 | split::Split, 37 | table::{ScrollableTable, Table}, 38 | }; 39 | tcw3_meta::designer_impl! { crate::ui::views::SpacerWidget } 40 | tcw3_meta::designer_impl! { crate::ui::views::FixedSpacer } 41 | } 42 | 43 | /// Theming support 44 | pub mod theming { 45 | mod manager; 46 | mod style; 47 | mod stylesheet; 48 | mod view; 49 | mod widget; 50 | 51 | pub use self::{ 52 | manager::{Elem, ElemChangeCb, HElem, Manager, PropKindFlags}, 53 | style::{ 54 | elem_id, mk_prop_by_snake_name, mk_prop_value_by_prop_snake_name, 55 | mk_wrap_dynvalue_by_prop_snake_name, mk_wrap_value_by_prop_snake_name, roles, ClassSet, 56 | Col, ElemClassPath, GetPropValue, LayerId, LayerXform, Layouter, Metrics, Prop, 57 | PropValue, Role, Row, 58 | }, 59 | stylesheet::*, 60 | view::{ModifyArrangementArgs, StyledBox, StyledBoxOverride}, 61 | widget::Widget, 62 | }; 63 | } 64 | 65 | mod types; 66 | pub use self::types::AlignFlags; 67 | 68 | mod scrolling { 69 | pub mod lineset; 70 | pub mod piecewise; 71 | pub mod tableremap; 72 | } 73 | 74 | /// Text editing support 75 | pub mod editing { 76 | pub mod history; 77 | } 78 | 79 | /// Re-exports some traits from the `ui` module. 80 | pub mod prelude { 81 | pub use super::{ 82 | theming::GetPropValue, 83 | views::table::{TableModelEdit, TableModelEditExt}, 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /tcw3/src/ui/scrolling/lineset/debug.rs: -------------------------------------------------------------------------------- 1 | use itertools::{unfold, Itertools}; 2 | use std::{fmt, ops::Range}; 3 | 4 | use super::{Index, Lineset, Size}; 5 | 6 | enum DebugItem { 7 | LineGr { 8 | index_range: Range, 9 | pos_range: Range, 10 | }, 11 | LodGrStart { 12 | index: Index, 13 | lod: u8, 14 | }, 15 | } 16 | 17 | impl fmt::Debug for DebugItem { 18 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 19 | match self { 20 | DebugItem::LineGr { 21 | index_range, 22 | pos_range, 23 | } => write!( 24 | f, 25 | "LineGr {{ index: {:8?}, pos: {:8?} }}", 26 | index_range, pos_range 27 | ), 28 | DebugItem::LodGrStart { index, lod } => { 29 | write!(f, "LodGr {{ index: {:8?}.., lod: {:?} }}", index, lod) 30 | } 31 | } 32 | } 33 | } 34 | 35 | impl DebugItem { 36 | fn index(&self) -> Index { 37 | match self { 38 | DebugItem::LineGr { index_range, .. } => index_range.start, 39 | DebugItem::LodGrStart { index, .. } => *index, 40 | } 41 | } 42 | } 43 | 44 | impl fmt::Debug for Lineset { 45 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 46 | let line_grs = unfold((self.line_grs.iter(), 0, 0), |(line_gr_it, index, pos)| { 47 | line_gr_it.next().map(|line_gr| { 48 | let last_index = *index; 49 | let last_pos = *pos; 50 | *index += line_gr.num_lines; 51 | *pos += line_gr.size; 52 | DebugItem::LineGr { 53 | index_range: last_index..*index, 54 | pos_range: last_pos..*pos, 55 | } 56 | }) 57 | }); 58 | 59 | let lod_grs = self.lod_grs.iter().map(|lod_gr| DebugItem::LodGrStart { 60 | index: lod_gr.index, 61 | lod: lod_gr.lod, 62 | }); 63 | 64 | f.debug_list() 65 | .entries(line_grs.merge_by(lod_grs, |a, b| a.index() < b.index())) 66 | .finish() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tcw3/src/ui/scrolling/lineset/multiset.rs: -------------------------------------------------------------------------------- 1 | const ELEM_MAX: u8 = 64; 2 | 3 | /// A multiset for integers in range `0..ELEM_MAX`. It can contain up to 255 4 | /// elements (limited by `u8`). And it supports querying the minimum element. 5 | /// 6 | /// All operations are O(1). 7 | pub struct Minimultiset { 8 | counts: [u8; ELEM_MAX as usize], 9 | /// Each bit indicates whether the corresponding element in `counts` is 10 | /// non-zero or not. Must have `ELEM_MAX` bits. 11 | nonzero: u64, 12 | } 13 | 14 | impl Minimultiset { 15 | pub fn new() -> Self { 16 | Self { 17 | counts: [0; ELEM_MAX as usize], 18 | nonzero: 0, 19 | } 20 | } 21 | 22 | /// Insert an element. 23 | /// 24 | /// The behavior is unspecified if the element count overflows. 25 | pub fn insert(&mut self, x: u8) { 26 | assert!(x < ELEM_MAX); 27 | self.counts[x as usize] += 1; 28 | self.nonzero |= 1 << x; 29 | } 30 | 31 | /// Remove an element. 32 | /// 33 | /// The behavior is unspecified if the element does not exist. 34 | pub fn remove(&mut self, x: u8) { 35 | assert!(x < ELEM_MAX); 36 | self.counts[x as usize] -= 1; 37 | if self.counts[x as usize] == 0 { 38 | self.nonzero &= !(1u64 << x); 39 | } 40 | } 41 | 42 | /// Get the minimum element. 43 | /// 44 | /// The behavior is unspecified if the multiset is empty. 45 | pub fn min(&mut self) -> u8 { 46 | debug_assert_ne!(self.nonzero, 0); 47 | self.nonzero.trailing_zeros() as u8 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | 55 | #[test] 56 | fn test() { 57 | let mut set = Minimultiset::new(); 58 | set.insert(3); 59 | assert_eq!(set.min(), 3); 60 | set.insert(3); 61 | assert_eq!(set.min(), 3); 62 | set.insert(7); 63 | assert_eq!(set.min(), 3); 64 | set.remove(3); 65 | assert_eq!(set.min(), 3); 66 | set.remove(3); 67 | assert_eq!(set.min(), 7); 68 | set.insert(5); 69 | assert_eq!(set.min(), 5); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tcw3/src/ui/theming/widget.rs: -------------------------------------------------------------------------------- 1 | use super::manager::HElem; 2 | use crate::uicore::{HView, HViewRef}; 3 | 4 | pub trait Widget { 5 | fn view_ref(&self) -> HViewRef<'_>; 6 | fn style_elem(&self) -> Option; 7 | } 8 | 9 | impl Widget for (HView, Option) { 10 | fn view_ref(&self) -> HViewRef<'_> { 11 | self.0.as_ref() 12 | } 13 | fn style_elem(&self) -> Option { 14 | self.1 15 | } 16 | } 17 | 18 | impl Widget for (HViewRef<'_>, Option) { 19 | fn view_ref(&self) -> HViewRef<'_> { 20 | self.0 21 | } 22 | fn style_elem(&self) -> Option { 23 | self.1 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tcw3/src/ui/views/spacer.rs: -------------------------------------------------------------------------------- 1 | use cggeom::ElementWiseOp; 2 | use cgmath::Vector2; 3 | use momo::momo; 4 | 5 | use crate::{ 6 | ui::layouts::EmptyLayout, 7 | uicore::{HView, SizeTraits, ViewFlags}, 8 | }; 9 | 10 | /// Construct a spacer widget, which has size traits but no graphical contents. 11 | pub fn new_spacer(size_traits: SizeTraits) -> HView { 12 | let view = HView::new(ViewFlags::default()); 13 | view.set_layout(EmptyLayout::new(size_traits)); 14 | view 15 | } 16 | 17 | /// The builder for a spacer widget, which has size traits but no graphical 18 | /// contents. This is an ergonomic wrapper for [`new_spacer`] and [`SizeTraits`]. 19 | /// 20 | /// [`SizeTraits`]: crate::uicore::SizeTraits 21 | /// 22 | /// # Examples 23 | /// 24 | /// ``` 25 | /// use tcw3::ui::views::Spacer; 26 | /// let view = Spacer::new().with_min([4.0, 0.0]).into_view(); 27 | /// ``` 28 | #[derive(Debug, Default, Clone, Copy)] 29 | pub struct Spacer { 30 | size_traits: SizeTraits, 31 | } 32 | 33 | impl Spacer { 34 | /// Construst a `Spacer`. 35 | pub fn new() -> Self { 36 | Default::default() 37 | } 38 | 39 | /// Update `SizeTraits::min` and return a new `Spacer`, consuming `self`. 40 | pub fn with_min(self, min: impl Into>) -> Self { 41 | Self { 42 | size_traits: self.size_traits.with_min(min.into()), 43 | } 44 | } 45 | 46 | /// Update `SizeTraits::max` and return a new `Spacer`, consuming `self`. 47 | pub fn with_max(self, max: impl Into>) -> Self { 48 | Self { 49 | size_traits: self.size_traits.with_max(max.into()), 50 | } 51 | } 52 | 53 | /// Update `SizeTraits::preferred` and return a new `Spacer`, consuming `self`. 54 | pub fn with_preferred(self, preferred: impl Into>) -> Self { 55 | Self { 56 | size_traits: self.size_traits.with_preferred(preferred.into()), 57 | } 58 | } 59 | 60 | /// Update `SizeTraits::{min, max, preferred}` and return a new `Spacer`, 61 | /// consuming `self`. 62 | #[momo] 63 | pub fn with_fixed(self, size: impl Into>) -> Self { 64 | Self { 65 | size_traits: SizeTraits { 66 | min: size, 67 | max: size, 68 | preferred: size, 69 | }, 70 | } 71 | } 72 | 73 | /// Create a `HView`, consuming `self`. 74 | pub fn into_view(self) -> HView { 75 | new_spacer(SizeTraits { 76 | preferred: self 77 | .size_traits 78 | .preferred 79 | .element_wise_max(&self.size_traits.min), 80 | ..self.size_traits 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tcw3/src/ui/views/table/fixedpoint.rs: -------------------------------------------------------------------------------- 1 | //! Fixed-point arithmetics for line size calculation. 2 | //! 3 | //! Line sizes use fixed-point numbers to prevent floating-point error 4 | //! accumulation during the operation of a lineset. This module provides 5 | //! functions for conversion between fixed-point numbers (used by `Lineset`) and 6 | //! floating-point numbers (used by `Table`'s public interface). 7 | 8 | const FACTOR: f64 = 16.0; 9 | 10 | pub fn fp_to_fix(x: f64) -> i64 { 11 | (x * FACTOR) as i64 12 | } 13 | 14 | pub fn fix_to_f32(x: i64) -> f32 { 15 | x as f32 / FACTOR as f32 16 | } 17 | 18 | pub fn fix_to_fp(x: i64) -> f64 { 19 | x as f64 / FACTOR as f64 20 | } 21 | -------------------------------------------------------------------------------- /tcw3/src/ui/views/table/listener.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use super::Inner; 4 | use crate::{ 5 | pal, 6 | pal::prelude::*, 7 | uicore::{HViewRef, HWndRef, UpdateCtx, ViewListener}, 8 | }; 9 | 10 | #[derive(Debug)] 11 | pub(super) struct TableViewListener { 12 | inner: Rc, 13 | layer: RefCell>, 14 | } 15 | 16 | impl TableViewListener { 17 | pub(super) fn new(inner: Rc) -> Self { 18 | Self { 19 | inner, 20 | layer: RefCell::new(None), 21 | } 22 | } 23 | } 24 | 25 | impl ViewListener for TableViewListener { 26 | fn mount(&self, wm: pal::Wm, _: HViewRef<'_>, _: HWndRef<'_>) { 27 | let layer = wm.new_layer(pal::LayerAttrs { 28 | flags: Some(pal::LayerFlags::MASK_TO_BOUNDS), 29 | ..Default::default() 30 | }); 31 | 32 | let old_layer = self.layer.replace(Some(layer)); 33 | 34 | assert!(old_layer.is_none()); 35 | } 36 | 37 | fn unmount(&self, wm: pal::Wm, _: HViewRef<'_>) { 38 | if let Some(layer) = self.layer.replace(None) { 39 | wm.remove_layer(&layer); 40 | } 41 | } 42 | 43 | fn position(&self, _: pal::Wm, view: HViewRef<'_>) { 44 | view.pend_update(); 45 | } 46 | 47 | fn update(&self, wm: pal::Wm, view: HViewRef<'_>, ctx: &mut UpdateCtx<'_>) { 48 | let layer = self.layer.borrow(); 49 | let layer = layer.as_ref().expect("not mounted"); 50 | 51 | let mut new_attrs = pal::LayerAttrs { 52 | bounds: Some(view.global_frame()), 53 | ..Default::default() 54 | }; 55 | 56 | if let Some(sublayers) = ctx.sublayers().take() { 57 | new_attrs.sublayers = Some(sublayers); 58 | } 59 | wm.set_layer_attr(&layer, new_attrs); 60 | 61 | if ctx.layers().len() != 1 { 62 | ctx.set_layers(vec![(*layer).clone()]); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tcw3/src/uicore/images.rs: -------------------------------------------------------------------------------- 1 | use tcw3_images::{dpi_scale_add_ref, dpi_scale_release}; 2 | use tcw3_pal::{self as pal, prelude::*}; 3 | 4 | use super::HWndRef; 5 | 6 | /// Register a hook (`subscribe_dpi_scale_changed`) on `HWnd` to keep the list 7 | /// of known DPI scale values up-to-date based on currently open windows. 8 | pub(crate) fn handle_new_wnd(hwnd: HWndRef<'_>) { 9 | use std::cell::Cell; 10 | 11 | struct ListenerState { 12 | wm: pal::Wm, 13 | dpi_scale: Cell, 14 | } 15 | 16 | impl Drop for ListenerState { 17 | fn drop(&mut self) { 18 | // This method is called when the window is destroyed. 19 | // Use `invoke` because we don't know the state of the call stack 20 | // when `drop` is called. 21 | let dpi_scale = self.dpi_scale.get(); 22 | self.wm.invoke(move |wm| { 23 | dpi_scale_release(wm, dpi_scale); 24 | }); 25 | } 26 | } 27 | 28 | let state = ListenerState { 29 | wm: hwnd.wm(), 30 | dpi_scale: Cell::new(hwnd.dpi_scale()), 31 | }; 32 | dpi_scale_add_ref(hwnd.wm(), state.dpi_scale.get()); 33 | 34 | hwnd.subscribe_dpi_scale_changed(Box::new(move |wm, hwnd| { 35 | let state = &state; 36 | let new_dpi_scale = hwnd.dpi_scale(); 37 | if new_dpi_scale != state.dpi_scale.get() { 38 | dpi_scale_add_ref(wm, new_dpi_scale); 39 | dpi_scale_release(wm, state.dpi_scale.get()); 40 | state.dpi_scale.set(new_dpi_scale); 41 | } 42 | })); 43 | } 44 | -------------------------------------------------------------------------------- /tcw3/src/uicore/invocation.rs: -------------------------------------------------------------------------------- 1 | use neo_linked_list::{linked_list::Node, AssertUnpin, LinkedListCell}; 2 | use std::pin::Pin; 3 | 4 | use crate::pal::{prelude::*, MtSticky, Wm}; 5 | 6 | #[allow(clippy::type_complexity)] 7 | static ON_UPDATE_DISPATCHES: MtSticky>> = Init::INIT; 8 | 9 | /// Implements `WmExt::invoke_on_update`. 10 | pub fn invoke_on_update(wm: Wm, f: impl FnOnce(Wm) + 'static) { 11 | invoke_on_update_inner(wm, Node::pin(AssertUnpin::new(f))); 12 | } 13 | 14 | fn invoke_on_update_inner(wm: Wm, f: Pin>>>) { 15 | let queue = ON_UPDATE_DISPATCHES.get_with_wm(wm); 16 | if queue.is_empty() { 17 | wm.invoke(process_pending_invocations); 18 | } 19 | queue.push_back_node(f); 20 | } 21 | 22 | /// Process pending invocations. 23 | pub fn process_pending_invocations(wm: Wm) { 24 | loop { 25 | let f = ON_UPDATE_DISPATCHES.get_with_wm(wm).pop_front_node(); 26 | if let Some(f) = f { 27 | blackbox(move || { 28 | (Pin::into_inner(f).element.inner)(wm); 29 | }); 30 | } else { 31 | break; 32 | } 33 | } 34 | } 35 | 36 | /// Limits the stack usage of repeated calls to an unsized closure. 37 | /// (See The Rust Unstable Book, `unsized_locals` for more.) 38 | #[inline(never)] 39 | pub(super) fn blackbox(f: impl FnOnce() -> R) -> R { 40 | f() 41 | } 42 | -------------------------------------------------------------------------------- /tcw3/stvg/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tcw3_stvg" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | license = "MIT" 7 | 8 | [features] 9 | testing = ["tcw3_testing/testing"] 10 | 11 | [dependencies] 12 | tcw3_pal = { path = "../pal" } 13 | tcw3_images = { path = "../images" } 14 | stvg_io = { path = "../../stvg/io" } 15 | cgmath = "0.17.0" 16 | cggeom = { path = "../../support/cggeom" } 17 | 18 | [dev-dependencies] 19 | stvg_macro = { path = "../../stvg/macro" } 20 | tcw3_testing = { path = "../testing" } 21 | -------------------------------------------------------------------------------- /tcw3/testing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tcw3_testing" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [features] 8 | testing = ["tcw3_pal/testing", "env_logger"] 9 | 10 | [dependencies] 11 | env_logger = { version = "0.7.0", optional = true } 12 | log = "0.4" 13 | 14 | tcw3_pal = { path = "../pal" } 15 | tcw3_testing_macros = { path = "./macros" } 16 | -------------------------------------------------------------------------------- /tcw3/testing/macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tcw3_testing_macros" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | syn = { version = "1", features = ["full"] } 9 | quote = "1" 10 | proc-macro2 = "1" 11 | proc-macro-error = "1" 12 | 13 | [lib] 14 | path = "src/lib.rs" 15 | proc-macro = true 16 | -------------------------------------------------------------------------------- /tcw3/tests/LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | # `horse.svg` 3 | 4 | Traced by me. The logo of the Rust programming language is owned by Mozilla. The character is owned by Hasbro, obviously. 5 | -------------------------------------------------------------------------------- /tcw3/tests/horse.svgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yvt/Stella2/79bb01d5d11643c50769986796b6fae572e13db7/tcw3/tests/horse.svgz -------------------------------------------------------------------------------- /tcw3/tests/view_layout.rs: -------------------------------------------------------------------------------- 1 | use cggeom::box2; 2 | use try_match::try_match; 3 | 4 | use tcw3::{ 5 | pal, 6 | testing::{prelude::*, use_testing_wm}, 7 | ui::{layouts::AbsLayout, AlignFlags}, 8 | uicore::{HView, HViewRef, HWnd, SizeTraits, ViewFlags, ViewListener}, 9 | }; 10 | 11 | struct VL; 12 | 13 | impl ViewListener for VL { 14 | fn position(&self, _: pal::Wm, view: HViewRef<'_>) { 15 | assert_eq!( 16 | view.frame(), 17 | box2! { min: [-20.0, 30.0], max: [80.0, 50.0] } 18 | ); 19 | assert_eq!( 20 | view.global_frame(), 21 | box2! { min: [-20.0, 30.0], max: [80.0, 50.0] } 22 | ); 23 | assert_eq!( 24 | view.global_visible_frame(), 25 | box2! { min: [0.0, 30.0], max: [80.0, 50.0] } 26 | ); 27 | } 28 | } 29 | 30 | #[use_testing_wm] 31 | #[test] 32 | fn position_event(twm: &dyn TestingWm) { 33 | let wm = twm.wm(); 34 | let wnd = HWnd::new(wm); 35 | 36 | let view = HView::new(ViewFlags::empty()); 37 | view.set_listener(VL); 38 | 39 | wnd.content_view().set_layout(AbsLayout::new( 40 | SizeTraits { 41 | min: [100.0, 100.0].into(), 42 | max: [100.0, 100.0].into(), 43 | preferred: [100.0, 100.0].into(), 44 | }, 45 | vec![( 46 | view.clone(), 47 | box2! { min: [-20.0, 30.0], max: [80.0, 50.0] }, 48 | AlignFlags::JUSTIFY, 49 | )], 50 | )); 51 | 52 | wnd.set_visibility(true); 53 | twm.step_unsend(); 54 | 55 | let pal_hwnd = try_match!([x] = twm.hwnds().as_slice() => x.clone()) 56 | .expect("could not get a single window"); 57 | 58 | twm.set_wnd_focused(&pal_hwnd, true); 59 | twm.step_unsend(); 60 | } 61 | --------------------------------------------------------------------------------