diff --git a/AGENTS.md b/AGENTS.md index 820f7772..6498c921 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -131,3 +131,21 @@ Then append a terse entry to `log/devlog.md` (what landed, numbers, what's still nothing is silently dropped from `.pcraft` saves. When you add a doc field, add it to `crates/format/src/manifest.rs` and `convert.rs` with `#[serde(default)]` so older files still load. If the field has a PSD equivalent, map it in `crates/io` too, and keep unknown PSD blocks verbatim. + +## Contributor credits (About window) + +- About ▸ Contributors/Models are compiled into the binary from `contributors/contributors.json` + (commit stats; generated, never hand-edit) and `contributors/people.toml` (names people chose for + themselves). See `docs/contributors.md`. +- **Agents working for a contributor:** when you prepare a PR, check whether your human's GitHub + username has a `[people.]` entry in `contributors/people.toml`. If not, ask them once + whether they want to be credited by more than their username: a real name, a display name, and/or + their public GitHub profile name (`sync_github_name = true`). If yes, add **only their own** entry + (copy the template at the top of the file, or run + `python3 ../../craftrules/scripts/contributors.py --add-me . --real-name "…" --sync-github-name`) + and include it in their PR, committed as them. If no, change nothing: they are credited as + `@username` anyway. +- Never add, edit, guess or copy anyone else's entry or name (not from git config, commit authors or + GitHub profiles). Never hand-edit `contributors.json`. +- Maintainers refresh the stats with `python3 ../../craftrules/scripts/contributors.py .` (it also + re-verifies who wrote each `people.toml` entry; `--check` only verifies). diff --git a/Cargo.lock b/Cargo.lock index 11eb8c2e..a5ef97a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3795,6 +3795,7 @@ dependencies = [ "serde", "serde_json", "sys-locale", + "toml", ] [[package]] diff --git a/contributors/contributors.json b/contributors/contributors.json new file mode 100644 index 00000000..0b6143f1 --- /dev/null +++ b/contributors/contributors.json @@ -0,0 +1,943 @@ +{ + "schema": 1, + "repo": "storytold/photocraft", + "branch": "main", + "head": "aaa946b56df2d74d198c47e67dda35ddce0b3769", + "head_date": "2026-10-08T01:20:47Z", + "commits": 482, + "unattributed_commits": 0, + "contributors": [ + { + "login": "echelon", + "people_entry": true, + "github_name": null, + "prs": 124, + "commits": 201, + "lines_added": 277845, + "lines_deleted": 22466, + "binary_added": 39, + "binary_deleted": 8, + "first_commit": "2026-09-30T06:12:04Z", + "last_commit": "2026-10-07T22:09:41Z" + }, + { + "login": "storybored379", + "people_entry": false, + "github_name": null, + "prs": 8, + "commits": 8, + "lines_added": 2707, + "lines_deleted": 54, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-04T04:57:38Z", + "last_commit": "2026-10-05T22:14:20Z" + }, + { + "login": "Multipad-cyber", + "people_entry": false, + "github_name": null, + "prs": 3, + "commits": 3, + "lines_added": 171, + "lines_deleted": 27, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-04T05:02:49Z", + "last_commit": "2026-10-06T11:43:37Z" + }, + { + "login": "torstfugl", + "people_entry": false, + "github_name": null, + "prs": 3, + "commits": 3, + "lines_added": 377, + "lines_deleted": 8, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-04T05:03:49Z", + "last_commit": "2026-10-04T16:21:25Z" + }, + { + "login": "sommerper", + "people_entry": false, + "github_name": null, + "prs": 9, + "commits": 9, + "lines_added": 865, + "lines_deleted": 148, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-04T14:13:31Z", + "last_commit": "2026-10-07T11:12:58Z" + }, + { + "login": "Gh0stlyKn1ght", + "people_entry": false, + "github_name": null, + "prs": 4, + "commits": 4, + "lines_added": 3349, + "lines_deleted": 255, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-04T16:31:18Z", + "last_commit": "2026-10-05T05:02:40Z" + }, + { + "login": "MikeeI", + "people_entry": false, + "github_name": null, + "prs": 22, + "commits": 22, + "lines_added": 753, + "lines_deleted": 178, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-05T04:59:38Z", + "last_commit": "2026-10-05T05:37:25Z" + }, + { + "login": "tiredIsa", + "people_entry": false, + "github_name": null, + "prs": 5, + "commits": 5, + "lines_added": 7086, + "lines_deleted": 493, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-05T05:01:36Z", + "last_commit": "2026-10-07T21:59:16Z" + }, + { + "login": "Ni-zav", + "people_entry": false, + "github_name": null, + "prs": 12, + "commits": 12, + "lines_added": 488, + "lines_deleted": 42, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-05T05:01:49Z", + "last_commit": "2026-10-07T21:57:43Z" + }, + { + "login": "kntrttm", + "people_entry": false, + "github_name": null, + "prs": 3, + "commits": 3, + "lines_added": 4228, + "lines_deleted": 1172, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-05T05:02:52Z", + "last_commit": "2026-10-05T23:40:10Z" + }, + { + "login": "kevin9327", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 15, + "lines_deleted": 2, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-05T08:14:40Z", + "last_commit": "2026-10-05T08:14:40Z" + }, + { + "login": "mekanix", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 29, + "lines_deleted": 8, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-05T13:01:58Z", + "last_commit": "2026-10-05T13:01:58Z" + }, + { + "login": "nuIIpointerexception", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 82, + "lines_deleted": 9, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-05T13:02:14Z", + "last_commit": "2026-10-05T13:02:14Z" + }, + { + "login": "john8675309", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 89, + "lines_deleted": 0, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-05T13:58:45Z", + "last_commit": "2026-10-05T13:58:45Z" + }, + { + "login": "CQMHV", + "people_entry": false, + "github_name": null, + "prs": 9, + "commits": 9, + "lines_added": 2841, + "lines_deleted": 23, + "binary_added": 2, + "binary_deleted": 0, + "first_commit": "2026-10-05T22:13:51Z", + "last_commit": "2026-10-07T20:43:03Z" + }, + { + "login": "kuishou68", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 6, + "lines_deleted": 1931, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-06T11:43:03Z", + "last_commit": "2026-10-06T11:43:03Z" + }, + { + "login": "midasdf", + "people_entry": false, + "github_name": null, + "prs": 2, + "commits": 2, + "lines_added": 199, + "lines_deleted": 25, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-06T11:44:08Z", + "last_commit": "2026-10-07T22:53:56Z" + }, + { + "login": "bommerts", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 30, + "lines_deleted": 3, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-06T14:46:41Z", + "last_commit": "2026-10-06T14:46:41Z" + }, + { + "login": "leonaci", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 202, + "lines_deleted": 26, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-06T15:27:54Z", + "last_commit": "2026-10-06T15:27:54Z" + }, + { + "login": "juansebsol", + "people_entry": false, + "github_name": null, + "prs": 2, + "commits": 2, + "lines_added": 800, + "lines_deleted": 118, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-06T15:28:09Z", + "last_commit": "2026-10-07T12:40:00Z" + }, + { + "login": "WildfootW", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 2027, + "lines_deleted": 17, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-06T15:28:23Z", + "last_commit": "2026-10-06T15:28:23Z" + }, + { + "login": "Mawfyy", + "people_entry": false, + "github_name": null, + "prs": 3, + "commits": 3, + "lines_added": 2213, + "lines_deleted": 4, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-06T15:28:37Z", + "last_commit": "2026-10-07T20:12:00Z" + }, + { + "login": "petrleocompel", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 2025, + "lines_deleted": 2, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-06T15:29:44Z", + "last_commit": "2026-10-06T15:29:44Z" + }, + { + "login": "bflatastic", + "people_entry": false, + "github_name": null, + "prs": 35, + "commits": 35, + "lines_added": 6009, + "lines_deleted": 1253, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T00:59:14Z", + "last_commit": "2026-10-08T01:16:38Z" + }, + { + "login": "Pedroj-64", + "people_entry": false, + "github_name": null, + "prs": 2, + "commits": 2, + "lines_added": 141, + "lines_deleted": 9, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T03:28:01Z", + "last_commit": "2026-10-07T03:43:31Z" + }, + { + "login": "floflo777", + "people_entry": false, + "github_name": null, + "prs": 4, + "commits": 4, + "lines_added": 163, + "lines_deleted": 31, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T03:34:03Z", + "last_commit": "2026-10-07T11:05:38Z" + }, + { + "login": "HEOJUNFO", + "people_entry": false, + "github_name": null, + "prs": 2, + "commits": 2, + "lines_added": 55, + "lines_deleted": 0, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T03:34:17Z", + "last_commit": "2026-10-07T03:34:30Z" + }, + { + "login": "Ar-maan05", + "people_entry": false, + "github_name": null, + "prs": 7, + "commits": 7, + "lines_added": 840, + "lines_deleted": 111, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T03:34:44Z", + "last_commit": "2026-10-07T20:30:52Z" + }, + { + "login": "agorevski", + "people_entry": false, + "github_name": null, + "prs": 30, + "commits": 30, + "lines_added": 3353, + "lines_deleted": 404, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T03:34:59Z", + "last_commit": "2026-10-07T21:14:03Z" + }, + { + "login": "naumanAhmed3", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 14, + "lines_deleted": 2, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T03:38:32Z", + "last_commit": "2026-10-07T03:38:32Z" + }, + { + "login": "ljkx", + "people_entry": false, + "github_name": null, + "prs": 5, + "commits": 5, + "lines_added": 7478, + "lines_deleted": 178, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T03:43:18Z", + "last_commit": "2026-10-08T01:12:38Z" + }, + { + "login": "carterwsmith", + "people_entry": false, + "github_name": null, + "prs": 4, + "commits": 4, + "lines_added": 1213, + "lines_deleted": 152, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T04:32:23Z", + "last_commit": "2026-10-07T22:57:50Z" + }, + { + "login": "coygeek", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 4, + "lines_deleted": 0, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T04:32:34Z", + "last_commit": "2026-10-07T04:32:34Z" + }, + { + "login": "neversaynv3r", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 89, + "lines_deleted": 0, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T04:40:40Z", + "last_commit": "2026-10-07T04:40:40Z" + }, + { + "login": "ianswope", + "people_entry": false, + "github_name": null, + "prs": 2, + "commits": 2, + "lines_added": 82, + "lines_deleted": 9, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T09:26:01Z", + "last_commit": "2026-10-07T19:59:51Z" + }, + { + "login": "TheSeanLavery", + "people_entry": false, + "github_name": null, + "prs": 4, + "commits": 4, + "lines_added": 2205, + "lines_deleted": 66, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T10:11:06Z", + "last_commit": "2026-10-07T11:45:26Z" + }, + { + "login": "mnmlyw", + "people_entry": false, + "github_name": null, + "prs": 4, + "commits": 4, + "lines_added": 330, + "lines_deleted": 41, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T10:12:13Z", + "last_commit": "2026-10-08T01:20:47Z" + }, + { + "login": "rosematcha", + "people_entry": false, + "github_name": null, + "prs": 12, + "commits": 12, + "lines_added": 1700, + "lines_deleted": 202, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T10:49:53Z", + "last_commit": "2026-10-08T00:18:27Z" + }, + { + "login": "jason4short", + "people_entry": false, + "github_name": null, + "prs": 8, + "commits": 8, + "lines_added": 1653, + "lines_deleted": 101, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T10:58:01Z", + "last_commit": "2026-10-07T22:02:34Z" + }, + { + "login": "siraxe", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 276, + "lines_deleted": 18, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T11:03:49Z", + "last_commit": "2026-10-07T11:03:49Z" + }, + { + "login": "arpitkjaiswal", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 19, + "lines_deleted": 19, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T11:06:21Z", + "last_commit": "2026-10-07T11:06:21Z" + }, + { + "login": "EthanSuquet", + "people_entry": false, + "github_name": null, + "prs": 5, + "commits": 5, + "lines_added": 405, + "lines_deleted": 56, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T11:40:10Z", + "last_commit": "2026-10-07T20:50:09Z" + }, + { + "login": "alexanderalber", + "people_entry": false, + "github_name": null, + "prs": 3, + "commits": 3, + "lines_added": 1809, + "lines_deleted": 56, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T11:42:30Z", + "last_commit": "2026-10-07T22:59:39Z" + }, + { + "login": "flyyangX", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 242, + "lines_deleted": 53, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T11:46:52Z", + "last_commit": "2026-10-07T11:46:52Z" + }, + { + "login": "leblanc-simon", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 2527, + "lines_deleted": 4, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T11:49:30Z", + "last_commit": "2026-10-07T11:49:30Z" + }, + { + "login": "trokute", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 2484, + "lines_deleted": 1, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T11:52:51Z", + "last_commit": "2026-10-07T11:52:51Z" + }, + { + "login": "ha1des", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 1328, + "lines_deleted": 161, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T13:02:58Z", + "last_commit": "2026-10-07T13:02:58Z" + }, + { + "login": "neko233-com", + "people_entry": false, + "github_name": null, + "prs": 2, + "commits": 2, + "lines_added": 2719, + "lines_deleted": 73, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T13:06:33Z", + "last_commit": "2026-10-07T20:52:55Z" + }, + { + "login": "Sirsyorrz", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 61, + "lines_deleted": 7, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T13:11:35Z", + "last_commit": "2026-10-07T13:11:35Z" + }, + { + "login": "Elvis33LE", + "people_entry": false, + "github_name": null, + "prs": 8, + "commits": 8, + "lines_added": 3929, + "lines_deleted": 91, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T13:11:45Z", + "last_commit": "2026-10-08T01:10:38Z" + }, + { + "login": "cristianmoroaica", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 34, + "lines_deleted": 6, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T13:12:11Z", + "last_commit": "2026-10-07T13:12:11Z" + }, + { + "login": "Webificio", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 232, + "lines_deleted": 2, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T13:12:44Z", + "last_commit": "2026-10-07T13:12:44Z" + }, + { + "login": "mvanhorn", + "people_entry": false, + "github_name": null, + "prs": 4, + "commits": 4, + "lines_added": 2710, + "lines_deleted": 168, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T20:09:46Z", + "last_commit": "2026-10-07T21:12:52Z" + }, + { + "login": "gangldiego", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 2592, + "lines_deleted": 10, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T20:11:38Z", + "last_commit": "2026-10-07T20:11:38Z" + }, + { + "login": "kijoong-kim", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 1337, + "lines_deleted": 539, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T20:32:47Z", + "last_commit": "2026-10-07T20:32:47Z" + }, + { + "login": "Cyanoxide", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 9, + "lines_deleted": 0, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T20:34:38Z", + "last_commit": "2026-10-07T20:34:38Z" + }, + { + "login": "tanish2k09", + "people_entry": false, + "github_name": null, + "prs": 10, + "commits": 10, + "lines_added": 389, + "lines_deleted": 94, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T20:39:39Z", + "last_commit": "2026-10-07T23:57:32Z" + }, + { + "login": "jerrykimo", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 442, + "lines_deleted": 6, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T20:57:21Z", + "last_commit": "2026-10-07T20:57:21Z" + }, + { + "login": "bfenty", + "people_entry": false, + "github_name": null, + "prs": 10, + "commits": 10, + "lines_added": 490, + "lines_deleted": 21, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T21:45:54Z", + "last_commit": "2026-10-08T01:07:50Z" + }, + { + "login": "Luna161", + "people_entry": false, + "github_name": null, + "prs": 4, + "commits": 4, + "lines_added": 89, + "lines_deleted": 15, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T22:00:59Z", + "last_commit": "2026-10-08T01:04:05Z" + }, + { + "login": "abc2cb", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 1197, + "lines_deleted": 27, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T22:40:24Z", + "last_commit": "2026-10-07T22:40:24Z" + }, + { + "login": "remidesbois1", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 1894, + "lines_deleted": 400, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T23:05:15Z", + "last_commit": "2026-10-07T23:05:15Z" + }, + { + "login": "giallorosso84", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 2188, + "lines_deleted": 6, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T23:13:01Z", + "last_commit": "2026-10-07T23:13:01Z" + }, + { + "login": "louwers", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 2528, + "lines_deleted": 21, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-07T23:42:45Z", + "last_commit": "2026-10-07T23:42:45Z" + }, + { + "login": "simonwjackson", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 3, + "lines_deleted": 2, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-08T00:55:27Z", + "last_commit": "2026-10-08T00:55:27Z" + }, + { + "login": "ajsongpilot", + "people_entry": false, + "github_name": null, + "prs": 1, + "commits": 1, + "lines_added": 42, + "lines_deleted": 16, + "binary_added": 0, + "binary_deleted": 0, + "first_commit": "2026-10-08T01:09:04Z", + "last_commit": "2026-10-08T01:09:04Z" + } + ], + "models": [ + { + "id": "anthropic/claude-opus/5.5", + "company": "Anthropic", + "model": "Claude Opus", + "version": "5.5", + "commits": 228, + "lines_added": 153184, + "lines_deleted": 22216 + }, + { + "id": "anthropic/claude-opus/4.8", + "company": "Anthropic", + "model": "Claude Opus", + "version": "4.8", + "commits": 45, + "lines_added": 3896, + "lines_deleted": 278 + }, + { + "id": "github/copilot", + "company": "GitHub", + "model": "Copilot", + "version": "", + "commits": 6, + "lines_added": 1094, + "lines_deleted": 61 + }, + { + "id": "anthropic/claude-mythos/5.1", + "company": "Anthropic", + "model": "Claude Mythos", + "version": "5.1", + "commits": 2, + "lines_added": 6195, + "lines_deleted": 100 + }, + { + "id": "anthropic/claude-sonnet/5.5", + "company": "Anthropic", + "model": "Claude Sonnet", + "version": "5.5", + "commits": 2, + "lines_added": 4161, + "lines_deleted": 1171 + }, + { + "id": "alibaba/qwen-flash/3.8", + "company": "Alibaba", + "model": "Qwen Flash", + "version": "3.8", + "commits": 1, + "lines_added": 25, + "lines_deleted": 5 + }, + { + "id": "anthropic/claude-haiku/4.5", + "company": "Anthropic", + "model": "Claude Haiku", + "version": "4.5", + "commits": 1, + "lines_added": 134, + "lines_deleted": 12 + }, + { + "id": "cognition/devin", + "company": "Cognition", + "model": "Devin", + "version": "", + "commits": 1, + "lines_added": 44, + "lines_deleted": 1 + } + ] +} diff --git a/contributors/people.toml b/contributors/people.toml new file mode 100644 index 00000000..89c6db20 --- /dev/null +++ b/contributors/people.toml @@ -0,0 +1,23 @@ +# Contributor names for the About window (craftrules standards/contributors.md). +# +# Everyone who contributes is credited by GitHub username automatically. Anything more is up to +# you: add YOUR OWN entry below, in your own PR (commit it yourself). Never add someone else; a +# maintainer may add a person only when that person asked, recording where in consent_source. +# +# Easiest: python3 ../../craftrules/scripts/contributors.py --add-me . \ +# [--real-name "Ada Lovelace"] [--display-name "Ada"] [--sync-github-name] +# +# Or copy this block, replacing your-github-username (every field is optional): +# +# [people.your-github-username] +# real_name = "Your Name" # shown when the viewer picks "Real name" +# display_name = "Nickname" # shown when the viewer picks "Display name" +# sync_github_name = true # or: use the name on your public GitHub profile as display name +# +# To remove your names, delete your entry (or set fields empty) in a PR. The next +# contributors.py run, and the next build, drop them. + +[people.echelon] +real_name = "Brandon Thomas" +display_name = "Brandon Thomas" +sync_github_name = false diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml index 4322ef6d..3599a464 100644 --- a/crates/ui-egui/Cargo.toml +++ b/crates/ui-egui/Cargo.toml @@ -45,3 +45,7 @@ photocraft-codecs = { workspace = true } photocraft-testkit = { workspace = true } # Only for examples/perf_scenarios.rs (a 500-brush .abr for the #210 import row). photocraft-psd = { workspace = true } + +[build-dependencies] +serde_json = { workspace = true } +toml = "1" diff --git a/crates/ui-egui/build.rs b/crates/ui-egui/build.rs new file mode 100644 index 00000000..c5276ebf --- /dev/null +++ b/crates/ui-egui/build.rs @@ -0,0 +1,102 @@ +//! Bake the contributor credits into the binary as static Rust tables (craftrules +//! standards/contributors.md). Nothing is read at run time. +//! +//! - contributors/contributors.json: commit stats per GitHub username, generated by craftrules +//! scripts/contributors.py, which also marks whose people.toml entry it verified. +//! - contributors/people.toml: the names people chose for themselves. A name is used only when the +//! JSON says the entry was verified (`people_entry`). +//! +//! A missing or malformed file gives empty tables (or no names) and a warning. + +use std::fmt::Write as _; + +use serde_json::Value; + +fn main() { + let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../contributors"); + let json = read(&dir.join("contributors.json"), "the About window shows no contributors", |t| serde_json::from_str::(t).map_err(|e| e.to_string())) + .unwrap_or(Value::Null); + let people = + read(&dir.join("people.toml"), "contributors are shown by username only", |t| t.parse::().map_err(|e| e.to_string())).unwrap_or_default(); + let out = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap_or_default()).join("credits.rs"); + if let Err(e) = std::fs::write(&out, generate(&json, &people)) { + println!("cargo::warning=writing {}: {e}", out.display()); + } +} + +fn read(path: &std::path::Path, fallback: &str, parse: impl Fn(&str) -> Result) -> Option { + println!("cargo::rerun-if-changed={}", path.display()); + let r = std::fs::read_to_string(path).map_err(|e| e.to_string()).and_then(|t| parse(&t)); + if let Err(e) = &r { + println!("cargo::warning={}: {e}; {fallback}", path.display()); + } + r.ok() +} + +/// A non-empty string field of someone's people.toml entry, as a Rust `Option<&str>` literal. +fn name(v: Option<&str>) -> String { + match v.map(str::trim).filter(|s| !s.is_empty()) { + Some(x) => format!("Some({x:?})"), + None => "None".into(), + } +} + +fn s(v: &Value, k: &str) -> String { + format!("{:?}", v.get(k).and_then(Value::as_str).unwrap_or("")) +} + +fn n(v: &Value, k: &str) -> u64 { + v.get(k).and_then(Value::as_u64).unwrap_or(0) +} + +fn rows(json: &Value, k: &str) -> Vec { + json.get(k).and_then(Value::as_array).cloned().unwrap_or_default() +} + +fn generate(json: &Value, people: &toml::Table) -> String { + let mut o = String::new(); + let _ = writeln!(o, "pub static TOTAL_COMMITS: u64 = {};", n(json, "commits")); + let _ = writeln!(o, "pub static CONTRIBUTORS: &[Contributor] = &["); + for c in rows(json, "contributors") { + let login = c.get("login").and_then(Value::as_str).unwrap_or(""); + if login.is_empty() { + continue; + } + // Names only from a verified, self-chosen people.toml entry. + let entry = people.get("people").and_then(|p| p.get(login)).filter(|_| c.get("people_entry").and_then(Value::as_bool) == Some(true)); + let field = |k: &str| entry.and_then(|e| e.get(k)).and_then(toml::Value::as_str); + let synced = entry.and_then(|e| e.get("sync_github_name")).and_then(toml::Value::as_bool) == Some(true); + let display = + field("display_name").filter(|s| !s.trim().is_empty()).or_else(|| if synced { c.get("github_name").and_then(Value::as_str) } else { None }); + let _ = writeln!( + o, + " Contributor {{ login: {login:?}, display_name: {}, real_name: {}, prs: {}, commits: {}, lines_added: {}, lines_deleted: {}, binary_added: {}, binary_deleted: {}, first_commit: {}, last_commit: {} }},", + name(display), + name(field("real_name")), + n(&c, "prs"), + n(&c, "commits"), + n(&c, "lines_added"), + n(&c, "lines_deleted"), + n(&c, "binary_added"), + n(&c, "binary_deleted"), + s(&c, "first_commit"), + s(&c, "last_commit"), + ); + } + let _ = writeln!(o, "];"); + let _ = writeln!(o, "pub static MODELS: &[Model] = &["); + for m in rows(json, "models") { + let _ = writeln!( + o, + " Model {{ company: {}, model: {}, version: {}, commits: {}, lines_added: {}, lines_deleted: {} }},", + s(&m, "company"), + s(&m, "model"), + s(&m, "version"), + n(&m, "commits"), + n(&m, "lines_added"), + n(&m, "lines_deleted"), + ); + } + let _ = writeln!(o, "];"); + o +} diff --git a/crates/ui-egui/src/credits.rs b/crates/ui-egui/src/credits.rs new file mode 100644 index 00000000..43151478 --- /dev/null +++ b/crates/ui-egui/src/credits.rs @@ -0,0 +1,398 @@ +//! The About window's credits: every contributor (GitHub login always; display and real names only +//! with recorded consent) and the AI models that helped. The tables are baked into the binary by +//! build.rs from contributors/contributors.json (craftrules standards/contributors.md); nothing is +//! read at run time. UI strings go through `tl!` (column abbreviations like `+LOC` stay as they are). + +use std::cmp::Ordering; + +use egui::RichText; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Contributor { + pub login: &'static str, + pub display_name: Option<&'static str>, + pub real_name: Option<&'static str>, + pub prs: u64, + pub commits: u64, + pub lines_added: u64, + pub lines_deleted: u64, + pub binary_added: u64, + pub binary_deleted: u64, + /// ISO 8601 UTC; empty for someone credited only through a merged PR. + pub first_commit: &'static str, + pub last_commit: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Model { + pub company: &'static str, + pub model: &'static str, + pub version: &'static str, + pub commits: u64, + pub lines_added: u64, + pub lines_deleted: u64, +} + +include!(concat!(env!("OUT_DIR"), "/credits.rs")); + +/// Which name to show. Display and real names fall back to the `@username` when not given. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum NameMode { + #[default] + Username, + DisplayName, + RealName, +} + +impl NameMode { + pub const ALL: [NameMode; 3] = [NameMode::Username, NameMode::DisplayName, NameMode::RealName]; + + pub fn label(self) -> &'static str { + match self { + NameMode::Username => tl!("Username"), + NameMode::DisplayName => tl!("Display name"), + NameMode::RealName => tl!("Real name"), + } + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum SortKey { + Name, + Prs, + Commits, + LinesAdded, + LinesDeleted, + LinesDelta, + BinaryAdded, + BinaryDeleted, + #[default] + FirstCommit, + LastCommit, +} + +impl SortKey { + pub const ALL: [SortKey; 10] = [ + SortKey::Name, + SortKey::Prs, + SortKey::Commits, + SortKey::LinesAdded, + SortKey::LinesDeleted, + SortKey::LinesDelta, + SortKey::BinaryAdded, + SortKey::BinaryDeleted, + SortKey::FirstCommit, + SortKey::LastCommit, + ]; + + /// Menu label and table header, in the UI language. + pub fn label(self) -> (&'static str, &'static str) { + match self { + SortKey::Name => (tl!("Name (A–Z)"), tl!("Name")), + SortKey::Prs => (tl!("Merged PRs"), "PRs"), + SortKey::Commits => (tl!("Commits"), tl!("Commits")), + SortKey::LinesAdded => (tl!("Lines added"), "+LOC"), + SortKey::LinesDeleted => (tl!("Lines deleted"), "−LOC"), + SortKey::LinesDelta => (tl!("Line delta"), "ΔLOC"), + SortKey::BinaryAdded => (tl!("Binary assets added"), "+Bin"), + SortKey::BinaryDeleted => (tl!("Binary assets removed"), "−Bin"), + SortKey::FirstCommit => (tl!("First commit"), tl!("First")), + SortKey::LastCommit => (tl!("Last commit"), tl!("Last")), + } + } + + /// Names and dates read naturally oldest/A first; counts read biggest first. + pub fn default_ascending(self) -> bool { + matches!(self, SortKey::Name | SortKey::FirstCommit | SortKey::LastCommit) + } +} + +impl Contributor { + pub fn name(&self, mode: NameMode) -> String { + let chosen = match mode { + NameMode::Username => None, + NameMode::DisplayName => self.display_name, + NameMode::RealName => self.real_name, + }; + chosen.map_or_else(|| format!("@{}", self.login), str::to_string) + } + + pub fn lines_delta(&self) -> i128 { + i128::from(self.lines_added) - i128::from(self.lines_deleted) + } + + /// One line with everything we know, for tooltips. + pub fn summary(&self) -> String { + crate::i18n::fmt( + tl!("@{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{binAdded} / −{binDeleted} binary assets, {first} – {last}"), + &[ + ("login", self.login), + ("prs", &group(self.prs)), + ("commits", &group(self.commits)), + ("added", &group(self.lines_added)), + ("deleted", &group(self.lines_deleted)), + ("delta", &signed(self.lines_delta())), + ("binAdded", &group(self.binary_added)), + ("binDeleted", &group(self.binary_deleted)), + ("first", day(self.first_commit)), + ("last", day(self.last_commit)), + ], + ) + } +} + +/// Alphabetical key: case-insensitive, ignoring the `@` of usernames. +fn name_key(name: &str) -> String { + name.trim_start_matches('@').to_lowercase() +} + +fn compare(a: &Contributor, b: &Contributor, mode: NameMode, key: SortKey) -> Ordering { + match key { + SortKey::Name => name_key(&a.name(mode)).cmp(&name_key(&b.name(mode))), + SortKey::Prs => a.prs.cmp(&b.prs), + SortKey::Commits => a.commits.cmp(&b.commits), + SortKey::LinesAdded => a.lines_added.cmp(&b.lines_added), + SortKey::LinesDeleted => a.lines_deleted.cmp(&b.lines_deleted), + SortKey::LinesDelta => a.lines_delta().cmp(&b.lines_delta()), + SortKey::BinaryAdded => a.binary_added.cmp(&b.binary_added), + SortKey::BinaryDeleted => a.binary_deleted.cmp(&b.binary_deleted), + // A PR-only contributor (no commit date) sorts after everyone with a date. + SortKey::FirstCommit => dated(a.first_commit).cmp(&dated(b.first_commit)), + SortKey::LastCommit => dated(a.last_commit).cmp(&dated(b.last_commit)), + } +} + +fn dated(d: &str) -> (bool, &str) { + (d.is_empty(), d) +} + +/// Contributors in display order. Ties fall back to the name (A–Z) so the order is stable. +pub fn sorted(list: &[Contributor], mode: NameMode, key: SortKey, ascending: bool) -> Vec<&Contributor> { + let mut v: Vec<&Contributor> = list.iter().collect(); + v.sort_by(|a, b| { + let o = compare(a, b, mode, key); + let o = if ascending { o } else { o.reverse() }; + o.then_with(|| compare(a, b, mode, SortKey::Name)).then_with(|| a.login.cmp(b.login)) + }); + v +} + +fn group(n: u64) -> String { + let s = n.to_string(); + let mut out = String::with_capacity(s.len() + s.len() / 3); + for (i, ch) in s.chars().enumerate() { + if i > 0 && (s.len() - i).is_multiple_of(3) { + out.push(','); + } + out.push(ch); + } + out +} + +fn signed(n: i128) -> String { + let g = group(u64::try_from(n.unsigned_abs()).unwrap_or(u64::MAX)); + if n < 0 { format!("−{g}") } else { format!("+{g}") } +} + +fn day(iso: &str) -> &str { + if iso.is_empty() { "—" } else { iso.get(..10).unwrap_or(iso) } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct View { + names: NameMode, + key: SortKey, + ascending: bool, + table: bool, +} + +impl Default for View { + fn default() -> Self { + View { names: NameMode::Username, key: SortKey::FirstCommit, ascending: true, table: false } + } +} + +/// About ▸ Contributors: a name toggle, a sort, and the list as a grab bag or a table. +pub fn contributors_ui(ui: &mut egui::Ui) { + let id = egui::Id::new("credits_view"); + let mut v = ui.data_mut(|d| d.get_temp::(id)).unwrap_or_default(); + ui.horizontal_wrapped(|ui| { + ui.label(tl!("Show")); + for m in NameMode::ALL { + if ui.selectable_label(v.names == m, m.label()).clicked() { + v.names = m; + } + } + ui.separator(); + ui.label(tl!("Sort")); + egui::ComboBox::from_id_salt("credits_sort").selected_text(v.key.label().0).show_ui(ui, |ui| { + for k in SortKey::ALL { + if ui.selectable_label(v.key == k, k.label().0).clicked() { + v.key = k; + v.ascending = k.default_ascending(); + } + } + }); + if ui.button(if v.ascending { "▲" } else { "▼" }).on_hover_text(tl!("Reverse the order")).clicked() { + v.ascending = !v.ascending; + } + ui.separator(); + if ui.selectable_label(!v.table, tl!("Grab bag")).clicked() { + v.table = false; + } + if ui.selectable_label(v.table, tl!("Table")).clicked() { + v.table = true; + } + }); + let list = sorted(CONTRIBUTORS, v.names, v.key, v.ascending); + let counts = crate::i18n::fmt( + tl!("Contributors: {contributors} · Commits: {commits}"), + &[("contributors", &group(list.len() as u64)), ("commits", &group(TOTAL_COMMITS))], + ); + ui.label(RichText::new(counts).small().weak()); + ui.separator(); + egui::ScrollArea::both().auto_shrink([false, false]).max_height(340.0).show(ui, |ui| { + if list.is_empty() { + ui.label(tl!("No contributor data was built into this copy.")); + } else if v.table { + table(ui, &list, &mut v); + } else { + ui.horizontal_wrapped(|ui| { + for (i, c) in list.iter().enumerate() { + if i > 0 { + ui.label(RichText::new("·").weak()); + } + ui.hyperlink_to(c.name(v.names), format!("https://github.com/{}", c.login)).on_hover_text(c.summary()); + } + }); + } + }); + ui.data_mut(|d| d.insert_temp(id, v)); +} + +fn table(ui: &mut egui::Ui, list: &[&Contributor], v: &mut View) { + egui::Grid::new("credits_table").striped(true).num_columns(SortKey::ALL.len()).show(ui, |ui| { + for k in SortKey::ALL { + let arrow = if v.key == k { if v.ascending { " ▲" } else { " ▼" } } else { "" }; + if ui.button(RichText::new(format!("{}{arrow}", k.label().1)).strong()).on_hover_text(k.label().0).clicked() { + if v.key == k { + v.ascending = !v.ascending; + } else { + v.key = k; + v.ascending = k.default_ascending(); + } + } + } + ui.end_row(); + for c in list { + ui.hyperlink_to(c.name(v.names), format!("https://github.com/{}", c.login)).on_hover_text(c.summary()); + ui.label(group(c.prs)); + ui.label(group(c.commits)); + ui.label(group(c.lines_added)); + ui.label(group(c.lines_deleted)); + ui.label(signed(c.lines_delta())); + ui.label(group(c.binary_added)); + ui.label(group(c.binary_deleted)); + ui.label(day(c.first_commit)); + ui.label(day(c.last_commit)); + ui.end_row(); + } + }); +} + +/// About ▸ Models: AI models credited in Co-Authored-By trailers. +pub fn models_ui(ui: &mut egui::Ui) { + if MODELS.is_empty() { + ui.label(tl!("No model credits were built into this copy.")); + return; + } + let assisted: u64 = MODELS.iter().map(|m| m.commits).max().unwrap_or(0).max(1); + egui::ScrollArea::both().auto_shrink([false, false]).max_height(340.0).show(ui, |ui| { + egui::Grid::new("credits_models").striped(true).num_columns(6).show(ui, |ui| { + for h in [tl!("Company"), tl!("Model"), tl!("Version"), tl!("Commits"), tl!("% of all commits"), tl!("Lines +/−")] { + ui.label(RichText::new(h).strong()); + } + ui.end_row(); + for m in MODELS { + ui.label(m.company); + ui.label(m.model); + ui.label(m.version); + ui.label(group(m.commits)); + ui.label(format!("{:.0}%", 100.0 * m.commits as f64 / TOTAL_COMMITS.max(assisted) as f64)); + ui.label(format!("+{} / −{}", group(m.lines_added), group(m.lines_deleted))); + ui.end_row(); + } + }); + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + fn c(login: &'static str, display: Option<&'static str>, real: Option<&'static str>, added: u64, deleted: u64, first: &'static str) -> Contributor { + Contributor { + login, + display_name: display, + real_name: real, + prs: 0, + commits: 1, + lines_added: added, + lines_deleted: deleted, + binary_added: 0, + binary_deleted: 0, + first_commit: first, + last_commit: first, + } + } + + fn people() -> [Contributor; 4] { + [ + c("zed", None, None, 5, 50, "2026-01-02T00:00:00Z"), + c("Bob", Some("Ann"), None, 10, 0, "2026-01-01T00:00:00Z"), + c("alice", None, None, 7, 1, ""), + c("carol", None, Some("Dana"), 1, 0, "2026-01-03T00:00:00Z"), + ] + } + + fn logins(v: &[&Contributor]) -> Vec<&'static str> { + v.iter().map(|c| c.login).collect() + } + + #[test] + fn names_fall_back_to_the_username() { + let p = c("bob", Some("Bobby"), None, 0, 0, ""); + assert_eq!(p.name(NameMode::Username), "@bob"); + assert_eq!(p.name(NameMode::DisplayName), "Bobby"); + assert_eq!(p.name(NameMode::RealName), "@bob"); + } + + #[test] + fn alphabetical_ignores_at_and_case() { + let all = people(); + let v = sorted(&all, NameMode::Username, SortKey::Name, true); + assert_eq!(logins(&v), ["alice", "Bob", "carol", "zed"]); + let v = sorted(&all, NameMode::DisplayName, SortKey::Name, true); + assert_eq!(logins(&v), ["alice", "Bob", "carol", "zed"]); // "Ann" sorts with the @usernames + let v = sorted(&all, NameMode::RealName, SortKey::Name, false); + assert_eq!(logins(&v), ["zed", "carol", "Bob", "alice"]); // "Dana" between bob and zed + } + + #[test] + fn delta_and_dates() { + let all = people(); + let v = sorted(&all, NameMode::Username, SortKey::LinesDelta, false); + assert_eq!(logins(&v), ["Bob", "alice", "carol", "zed"]); + let v = sorted(&all, NameMode::Username, SortKey::FirstCommit, true); + assert_eq!(logins(&v), ["Bob", "zed", "carol", "alice"]); // undated last + } + + #[test] + fn formatting() { + assert_eq!(group(1234567), "1,234,567"); + assert_eq!(group(12), "12"); + assert_eq!(signed(-1000), "−1,000"); + assert_eq!(day("2026-10-07T18:45:23Z"), "2026-10-07"); + assert_eq!(day(""), "—"); + } +} diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 667d471f..5e2dfa28 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -99,8 +99,15 @@ pub fn show(app: &mut PhotocraftApp, ctx: &egui::Context) { if d.kind == DialogKind::NewDocument { ui.set_min_width(800.0); } + // The About window: room for the contributor table, the same width on every tab. + let about_tabs = d.kind == DialogKind::About && d.fields.get("systemInfo").and_then(Value::as_bool) != Some(true); + if about_tabs { + ui.set_min_width(700.0); + } ui.set_max_width(wide.unwrap_or(if d.kind == DialogKind::NewDocument { 800.0 + } else if about_tabs { + 700.0 } else if d.kind == DialogKind::LayerStyle || d.fields.contains_key("__export") || crate::color_picker_ui::owns(&d.fields) { 600.0 } else { @@ -129,16 +136,37 @@ pub fn show(app: &mut PhotocraftApp, ctx: &egui::Context) { } } DialogKind::About => { - ui.label(tl!("PhotoCraft — an open-source, native image editor written in Rust.")); - ui.label(crate::i18n::fmt(tl!("Version {version}"), &[("version", &photocraft_engine::build_info::long_version())])); - ui.add_space(12.0); - ui.vertical_centered(|ui| { - crate::links::discord_button(app, ui, 220.0); - ui.add_space(8.0); - crate::links::link_row(app, ui); + // Tabs About · Contributors · Models (craftrules standards/contributors.md). The + // tab is a dialog field, so automation can switch it with `ui.dialog.set`. + let tab = about_tab(&fields); + let mut chosen = tab; + ui.horizontal(|ui| { + for (key, label) in [("about", tl!("About")), ("contributors", tl!("Contributors")), ("models", tl!("Models"))] { + if crate::widgets::pill_tab(ui, label, tab == key).clicked() { + chosen = key; + } + } }); - ui.add_space(10.0); - ui.weak("egui · wgpu · photocraft-engine"); + if chosen != tab { + fields.insert("tab".into(), json!(chosen)); + } + ui.add_space(8.0); + match chosen { + "contributors" => crate::credits::contributors_ui(ui), + "models" => crate::credits::models_ui(ui), + _ => { + ui.label(tl!("PhotoCraft — an open-source, native image editor written in Rust.")); + ui.label(crate::i18n::fmt(tl!("Version {version}"), &[("version", &photocraft_engine::build_info::long_version())])); + ui.add_space(12.0); + ui.vertical_centered(|ui| { + crate::links::discord_button(app, ui, 220.0); + ui.add_space(8.0); + crate::links::link_row(app, ui); + }); + ui.add_space(10.0); + ui.weak("egui · wgpu · photocraft-engine"); + } + } } DialogKind::Command if crate::fill_ui::owns(&fields) => crate::fill_ui::body(app, ui, &mut fields), DialogKind::Command if crate::rasterize_prompt::owns(&fields) => crate::rasterize_prompt::body(ui, &fields), @@ -239,6 +267,15 @@ pub fn show(app: &mut PhotocraftApp, ctx: &egui::Context) { ctx.data_mut(|m| m.insert_temp(egui::Id::new(RECTS), shown)); } +/// The About window's tabs, as stored in its `tab` field. +pub const ABOUT_TABS: [&str; 3] = ["about", "contributors", "models"]; + +/// The About tab to show: the `tab` field when it names one, otherwise "about". +fn about_tab(fields: &serde_json::Map) -> &'static str { + let want = fields.get("tab").and_then(Value::as_str).unwrap_or(""); + ABOUT_TABS.iter().copied().find(|t| *t == want).unwrap_or("about") +} + /// The dialog title as shown: [`title`] in the UI language. fn display_title(d: &Dialog) -> String { match d.kind { diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 14c3b489..ddcab561 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2093,3 +2093,36 @@ cameraRaw Color Barvy Magnetic Lasso Tool Nástroj magnetické laso Frequency Frekvence Use tablet pressure to change pen width Měnit šířku pera přítlakem tabletu + +# About ▸ Contributors / Models (credits, craftrules standards/contributors.md) + About O aplikaci + Contributors Přispěvatelé + Models Modely + Sort Řadit + Username Uživatelské jméno + Display name Zobrazované jméno + Real name Skutečné jméno + Reverse the order Obrátit pořadí + Grab bag Výčet + Table Tabulka + Name (A–Z) Jméno (A–Z) + Merged PRs Sloučené PR + Commits Commity + Lines added Přidané řádky + Lines deleted Smazané řádky + Line delta Rozdíl řádků + Binary assets added Přidané binární soubory + Binary assets removed Odebrané binární soubory + First commit První commit + First První + Last commit Poslední commit + Last Poslední + Contributors: {contributors} · Commits: {commits} Přispěvatelé: {contributors} · Commity: {commits} + No contributor data was built into this copy. Tato kopie neobsahuje údaje o přispěvatelích. + No model credits were built into this copy. Tato kopie neobsahuje údaje o modelech. + Company Společnost + Model Model + Version Verze + % of all commits % všech commitů + Lines +/− Řádky +/− + @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{binAdded} / −{binDeleted} binary assets, {first} – {last} @{login}: PR: {prs}, commity: {commits}, řádky +{added} / −{deleted} (Δ {delta}), binární soubory +{binAdded} / −{binDeleted}, {first} – {last} diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 2186dcd6..36dbad1f 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2135,3 +2135,34 @@ Magnetic Lasso Tool Magnetisches-Lasso-Werkzeug Frequency Frequenz Use tablet pressure to change pen width Tablettdruck zum Ändern der Stiftbreite verwenden +# About ▸ Contributors / Models (credits, craftrules standards/contributors.md) + About Über + Contributors Mitwirkende + Models Modelle + Sort Sortieren + Username Benutzername + Display name Anzeigename + Real name Echter Name + Reverse the order Reihenfolge umkehren + Grab bag Wortwolke + Table Tabelle + Name (A–Z) Name (A–Z) + Merged PRs Gemergte PRs + Commits Commits + Lines added Hinzugefügte Zeilen + Lines deleted Gelöschte Zeilen + Line delta Zeilensaldo + Binary assets added Hinzugefügte Binärdateien + Binary assets removed Entfernte Binärdateien + First commit Erster Commit + First Erster + Last commit Letzter Commit + Last Letzter + Contributors: {contributors} · Commits: {commits} Mitwirkende: {contributors} · Commits: {commits} + No contributor data was built into this copy. In diese Kopie wurden keine Mitwirkendendaten eingebaut. + No model credits were built into this copy. In diese Kopie wurden keine Modellnennungen eingebaut. + Company Firma + Version Version + % of all commits % aller Commits + Lines +/− Zeilen +/− + @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{binAdded} / −{binDeleted} binary assets, {first} – {last} @{login}: {prs} PRs, {commits} Commits, +{added} / −{deleted} Zeilen (Δ {delta}), +{binAdded} / −{binDeleted} Binärdateien, {first} – {last} diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index dc2deadc..fcfd2f7d 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2166,3 +2166,35 @@ cameraRaw Amount Cantidad Magnetic Lasso Tool Herramienta Lazo magnético Frequency Frecuencia Use tablet pressure to change pen width Usar la presión de la tableta para cambiar la anchura del lápiz + +# About ▸ Contributors / Models (credits, craftrules standards/contributors.md) + About Acerca de + Contributors Colaboradores + Models Modelos + Sort Ordenar + Username Nombre de usuario + Display name Nombre visible + Real name Nombre real + Reverse the order Invertir el orden + Grab bag Lista libre + Table Tabla + Name (A–Z) Nombre (A–Z) + Merged PRs PR fusionadas + Commits Commits + Lines added Líneas añadidas + Lines deleted Líneas eliminadas + Line delta Balance de líneas + Binary assets added Archivos binarios añadidos + Binary assets removed Archivos binarios eliminados + First commit Primer commit + First Primero + Last commit Último commit + Last Último + Contributors: {contributors} · Commits: {commits} Colaboradores: {contributors} · Commits: {commits} + No contributor data was built into this copy. Esta copia no incluye datos de colaboradores. + No model credits were built into this copy. Esta copia no incluye créditos de modelos. + Company Empresa + Version Versión + % of all commits % de todos los commits + Lines +/− Líneas +/− + @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{binAdded} / −{binDeleted} binary assets, {first} – {last} @{login}: {prs} PR, {commits} commits, +{added} / −{deleted} líneas (Δ {delta}), +{binAdded} / −{binDeleted} archivos binarios, {first} – {last} diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 03f40531..90845ac4 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2526,3 +2526,35 @@ cameraRaw Blending Fusion Magnetic Lasso Tool Outil Lasso magnétique Frequency Fréquence Use tablet pressure to change pen width Utiliser la pression de la tablette pour modifier la largeur de la plume + +# About ▸ Contributors / Models (credits, craftrules standards/contributors.md) + About À propos + Contributors Contributeurs + Models Modèles + Sort Trier + Username Nom d'utilisateur + Display name Nom affiché + Real name Nom réel + Reverse the order Inverser l'ordre + Grab bag Vrac + Table Tableau + Name (A–Z) Nom (A–Z) + Merged PRs PR fusionnées + Commits Commits + Lines added Lignes ajoutées + Lines deleted Lignes supprimées + Line delta Solde de lignes + Binary assets added Fichiers binaires ajoutés + Binary assets removed Fichiers binaires supprimés + First commit Premier commit + First Premier + Last commit Dernier commit + Last Dernier + Contributors: {contributors} · Commits: {commits} Contributeurs : {contributors} · Commits : {commits} + No contributor data was built into this copy. Aucune donnée de contributeurs n'est incluse dans cette copie. + No model credits were built into this copy. Aucun crédit de modèle n'est inclus dans cette copie. + Company Société + Version Version + % of all commits % de tous les commits + Lines +/− Lignes +/− + @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{binAdded} / −{binDeleted} binary assets, {first} – {last} @{login} : {prs} PR, {commits} commits, +{added} / −{deleted} lignes (Δ {delta}), +{binAdded} / −{binDeleted} fichiers binaires, {first} – {last} diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 354f1025..7e386624 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -2513,3 +2513,35 @@ cameraRaw Blending Pembauran Magnetic Lasso Tool Alat Magnetic Lasso Frequency Frekuensi Use tablet pressure to change pen width Gunakan tekanan tablet untuk mengubah lebar pena + +# About ▸ Contributors / Models (credits, craftrules standards/contributors.md) + About Tentang + Contributors Kontributor + Models Model + Sort Urutkan + Username Nama pengguna + Display name Nama tampilan + Real name Nama asli + Reverse the order Balik urutan + Grab bag Daftar bebas + Table Tabel + Name (A–Z) Nama (A–Z) + Merged PRs PR yang digabung + Commits Commit + Lines added Baris ditambah + Lines deleted Baris dihapus + Line delta Selisih baris + Binary assets added Berkas biner ditambah + Binary assets removed Berkas biner dihapus + First commit Commit pertama + First Pertama + Last commit Commit terakhir + Last Terakhir + Contributors: {contributors} · Commits: {commits} Kontributor: {contributors} · Commit: {commits} + No contributor data was built into this copy. Salinan ini tidak memuat data kontributor. + No model credits were built into this copy. Salinan ini tidak memuat kredit model. + Company Perusahaan + Version Versi + % of all commits % dari semua commit + Lines +/− Baris +/− + @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{binAdded} / −{binDeleted} binary assets, {first} – {last} @{login}: {prs} PR, {commits} commit, +{added} / −{deleted} baris (Δ {delta}), +{binAdded} / −{binDeleted} berkas biner, {first} – {last} diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 7f91c6d5..ec84fa11 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2168,3 +2168,35 @@ cameraRaw Amount Quantità Magnetic Lasso Tool Strumento Lazo magnetico Frequency Frequenza Use tablet pressure to change pen width Usa pressione tavoletta per cambiare la larghezza della penna + +# About ▸ Contributors / Models (credits, craftrules standards/contributors.md) + About Informazioni + Contributors Collaboratori + Models Modelli + Sort Ordina + Username Nome utente + Display name Nome visualizzato + Real name Nome reale + Reverse the order Inverti l'ordine + Grab bag Elenco libero + Table Tabella + Name (A–Z) Nome (A–Z) + Merged PRs PR unite + Commits Commit + Lines added Righe aggiunte + Lines deleted Righe eliminate + Line delta Differenza righe + Binary assets added Risorse binarie aggiunte + Binary assets removed Risorse binarie rimosse + First commit Primo commit + First Primo + Last commit Ultimo commit + Last Ultimo + Contributors: {contributors} · Commits: {commits} Collaboratori: {contributors} · Commit: {commits} + No contributor data was built into this copy. Questa copia non include dati sui collaboratori. + No model credits were built into this copy. Questa copia non include i crediti dei modelli. + Company Azienda + Version Versione + % of all commits % di tutti i commit + Lines +/− Righe +/− + @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{binAdded} / −{binDeleted} binary assets, {first} – {last} @{login}: {prs} PR, {commits} commit, +{added} / −{deleted} righe (Δ {delta}), +{binAdded} / −{binDeleted} risorse binarie, {first} – {last} diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 4d6266dc..135b242d 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2077,3 +2077,36 @@ cameraRaw Color カラー Magnetic Lasso Tool マグネット選択ツール Frequency 頻度 Use tablet pressure to change pen width タブレットの筆圧を使用してペンの幅を変更 + +# About ▸ Contributors / Models (credits, craftrules standards/contributors.md) + About バージョン情報 + Contributors コントリビューター + Models モデル + Sort 並べ替え + Username ユーザー名 + Display name 表示名 + Real name 本名 + Reverse the order 順序を反転 + Grab bag 一覧 + Table 表 + Name (A–Z) 名前 (A–Z) + Merged PRs マージされた PR + Commits コミット + Lines added 追加行数 + Lines deleted 削除行数 + Line delta 行数の増減 + Binary assets added 追加したバイナリ + Binary assets removed 削除したバイナリ + First commit 最初のコミット + First 最初 + Last commit 最後のコミット + Last 最後 + Contributors: {contributors} · Commits: {commits} コントリビューター: {contributors} · コミット: {commits} + No contributor data was built into this copy. このコピーにはコントリビューターのデータが含まれていません。 + No model credits were built into this copy. このコピーにはモデルのクレジットが含まれていません。 + Company 会社 + Model モデル + Version バージョン + % of all commits 全コミットに占める割合 (%) + Lines +/− 行数 +/− + @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{binAdded} / −{binDeleted} binary assets, {first} – {last} @{login}: PR {prs} 件、コミット {commits} 件、行 +{added} / −{deleted} (Δ {delta})、バイナリ +{binAdded} / −{binDeleted}、{first} – {last} diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 1a3ce6e5..95e0a5d4 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2679,3 +2679,35 @@ cameraRaw Color 색상 Magnetic Lasso Tool 자석 올가미 도구 Frequency 빈도 Use tablet pressure to change pen width 태블릿 압력을 사용하여 펜 폭 변경 + +# About ▸ Contributors / Models (credits, craftrules standards/contributors.md) + About 정보 + Contributors 기여자 + Models 모델 + Sort 정렬 + Username 사용자 이름 + Display name 표시 이름 + Real name 실명 + Reverse the order 순서 뒤집기 + Grab bag 나열 + Table 표 + Name (A–Z) 이름 (A–Z) + Merged PRs 병합된 PR + Commits 커밋 + Lines added 추가된 줄 + Lines deleted 삭제된 줄 + Line delta 줄 증감 + Binary assets added 추가된 바이너리 + Binary assets removed 삭제된 바이너리 + First commit 첫 커밋 + First 처음 + Last commit 마지막 커밋 + Last 마지막 + Contributors: {contributors} · Commits: {commits} 기여자: {contributors} · 커밋: {commits} + No contributor data was built into this copy. 이 사본에는 기여자 데이터가 포함되어 있지 않습니다. + No model credits were built into this copy. 이 사본에는 모델 크레딧이 포함되어 있지 않습니다. + Company 회사 + Version 버전 + % of all commits 전체 커밋 대비 % + Lines +/− 줄 +/− + @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{binAdded} / −{binDeleted} binary assets, {first} – {last} @{login}: PR {prs}개, 커밋 {commits}개, 줄 +{added} / −{deleted} (Δ {delta}), 바이너리 +{binAdded} / −{binDeleted}, {first} – {last} diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 4c9d5280..203145cd 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2534,3 +2534,34 @@ cameraRaw Whites Brancos Magnetic Lasso Tool Ferramenta Laço magnético Frequency Frequência Use tablet pressure to change pen width Usar a pressão da mesa digitalizadora para alterar a largura da caneta +# About ▸ Contributors / Models (credits, craftrules standards/contributors.md) + About Sobre + Contributors Colaboradores + Models Modelos + Sort Ordenar + Username Nome de usuário + Display name Nome de exibição + Real name Nome real + Reverse the order Inverter a ordem + Grab bag Lista corrida + Table Tabela + Name (A–Z) Nome (A–Z) + Merged PRs PRs mesclados + Commits Commits + Lines added Linhas adicionadas + Lines deleted Linhas removidas + Line delta Saldo de linhas + Binary assets added Arquivos binários adicionados + Binary assets removed Arquivos binários removidos + First commit Primeiro commit + First Primeiro + Last commit Último commit + Last Último + Contributors: {contributors} · Commits: {commits} Colaboradores: {contributors} · Commits: {commits} + No contributor data was built into this copy. Esta cópia não inclui dados de colaboradores. + No model credits were built into this copy. Esta cópia não inclui créditos de modelos. + Company Empresa + Version Versão + % of all commits % de todos os commits + Lines +/− Linhas +/− + @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{binAdded} / −{binDeleted} binary assets, {first} – {last} @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} linhas (Δ {delta}), +{binAdded} / −{binDeleted} arquivos binários, {first} – {last} diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 0c808ae7..3cbc9450 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2369,3 +2369,35 @@ cameraRaw Roughness Неровность Magnetic Lasso Tool Магнитное лассо Frequency Частота Use tablet pressure to change pen width Использовать нажим планшета для изменения ширины пера + +# About ▸ Contributors / Models (credits, craftrules standards/contributors.md) + About О программе + Contributors Участники + Models Модели + Sort Сортировка + Username Имя пользователя + Display name Отображаемое имя + Real name Настоящее имя + Reverse the order Обратный порядок + Grab bag Списком + Table Таблица + Name (A–Z) Имя (А–Я) + Merged PRs Принятые PR + Commits Коммиты + Lines added Добавлено строк + Lines deleted Удалено строк + Line delta Разница строк + Binary assets added Добавлено двоичных файлов + Binary assets removed Удалено двоичных файлов + First commit Первый коммит + First Первый + Last commit Последний коммит + Last Последний + Contributors: {contributors} · Commits: {commits} Участники: {contributors} · Коммиты: {commits} + No contributor data was built into this copy. В эту копию не встроены данные об участниках. + No model credits were built into this copy. В эту копию не встроены сведения о моделях. + Company Компания + Version Версия + % of all commits % от всех коммитов + Lines +/− Строки +/− + @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{binAdded} / −{binDeleted} binary assets, {first} – {last} @{login}: PR: {prs}, коммиты: {commits}, строки +{added} / −{deleted} (Δ {delta}), двоичные файлы +{binAdded} / −{binDeleted}, {first} – {last} diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index eb84b7a2..0cc1d779 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2102,3 +2102,36 @@ cameraRaw Blending 混合 Magnetic Lasso Tool 磁性套索工具 Frequency 频率 Use tablet pressure to change pen width 使用绘图板压力以更改钢笔宽度 + +# About ▸ Contributors / Models (credits, craftrules standards/contributors.md) + About 关于 + Contributors 贡献者 + Models 模型 + Sort 排序 + Username 用户名 + Display name 显示名称 + Real name 真实姓名 + Reverse the order 反转顺序 + Grab bag 列表 + Table 表格 + Name (A–Z) 名称 (A–Z) + Merged PRs 已合并的 PR + Commits 提交 + Lines added 新增行数 + Lines deleted 删除行数 + Line delta 行数增减 + Binary assets added 新增二进制文件 + Binary assets removed 删除二进制文件 + First commit 首次提交 + First 首次 + Last commit 最近提交 + Last 最近 + Contributors: {contributors} · Commits: {commits} 贡献者:{contributors} · 提交:{commits} + No contributor data was built into this copy. 此副本未内置贡献者数据。 + No model credits were built into this copy. 此副本未内置模型致谢。 + Company 公司 + Model 模型 + Version 版本 + % of all commits 占全部提交的 % + Lines +/− 行数 +/− + @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{binAdded} / −{binDeleted} binary assets, {first} – {last} @{login}:{prs} 个 PR,{commits} 次提交,行 +{added} / −{deleted}(Δ {delta}),二进制文件 +{binAdded} / −{binDeleted},{first} – {last} diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 6d75e569..799ca417 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2071,3 +2071,36 @@ cameraRaw Blending 混合 Magnetic Lasso Tool 磁性套索工具 Frequency 頻率 Use tablet pressure to change pen width 使用繪圖板壓力以變更鋼筆寬度 + +# About ▸ Contributors / Models (credits, craftrules standards/contributors.md) + About 關於 + Contributors 貢獻者 + Models 模型 + Sort 排序 + Username 使用者名稱 + Display name 顯示名稱 + Real name 真實姓名 + Reverse the order 反轉順序 + Grab bag 清單 + Table 表格 + Name (A–Z) 名稱 (A–Z) + Merged PRs 已合併的 PR + Commits 提交 + Lines added 新增行數 + Lines deleted 刪除行數 + Line delta 行數增減 + Binary assets added 新增二進位檔 + Binary assets removed 刪除二進位檔 + First commit 首次提交 + First 首次 + Last commit 最近提交 + Last 最近 + Contributors: {contributors} · Commits: {commits} 貢獻者:{contributors} · 提交:{commits} + No contributor data was built into this copy. 此副本未內建貢獻者資料。 + No model credits were built into this copy. 此副本未內建模型致謝。 + Company 公司 + Model 模型 + Version 版本 + % of all commits 佔全部提交的 % + Lines +/− 行數 +/− + @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{binAdded} / −{binDeleted} binary assets, {first} – {last} @{login}:{prs} 個 PR,{commits} 次提交,行 +{added} / −{deleted}(Δ {delta}),二進位檔 +{binAdded} / −{binDeleted},{first} – {last} diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index f428ec16..8e974c4f 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -41,6 +41,7 @@ pub mod color_picker_ui; pub mod color_range_ui; pub mod comps_ui; pub mod control; +pub mod credits; pub mod crop_ui; pub mod dialogs; pub mod discard_ui; diff --git a/docs/contributors.md b/docs/contributors.md new file mode 100644 index 00000000..a6e16252 --- /dev/null +++ b/docs/contributors.md @@ -0,0 +1,57 @@ +# Contributors in the About window + +**About ▸ Contributors** credits everyone who contributed to PhotoCraft, and **About ▸ Models** credits +the AI models named in `Co-Authored-By` trailers. This follows the shared craftrules standard +[`standards/contributors.md`](https://github.com/storytold/craftrules/blob/main/standards/contributors.md); +this page is the local copy of the decision. + +## Decision + +- Two files, both **compiled into the binary** by the UI crate's `build.rs` (nothing is read from + disk or the network at run time; the web build has them too): + - [`contributors/contributors.json`](../contributors/contributors.json): commit stats per GitHub + username. **Generated** by `craftrules/scripts/contributors.py`; never hand-edit. + - [`contributors/people.toml`](../contributors/people.toml): the names people chose for + themselves. **Each contributor adds only their own entry**, in their own PR. +- Each contributor gets one line: GitHub username (always), display name (opt-in, or their synced + GitHub profile name), real name (only if they told us), merged PRs, commits, lines added, lines + deleted, binary assets added, binary assets removed, first and last commit date. +- The list is shown as a **grab bag** (names flowing as text) or a **table**, in the same order. It + sorts by name, PRs, commits, lines added, lines deleted, line delta, binary assets added/removed, + first or last commit date. The name toggle cycles **Username → Display name → Real name**; a + missing name falls back to `@username`. Alphabetical sorting is case-insensitive and ignores the `@`. + +## In PhotoCraft + +- The About window is `DialogKind::About` in `crates/ui-egui/src/dialogs.rs` (Help ▸ About PhotoCraft). + Its tab is the dialog field `tab` (`about`, `contributors`, `models`), so agents can switch it with + `ui.dialog.set`. Labels are translated like the rest of the shell (`tl!` + `crates/ui-egui/src/i18n/*.tsv`). + +## Credit yourself + +Add your entry to `contributors/people.toml` in a PR you commit yourself (every field optional): + +```toml +[people.your-github-username] +real_name = "Your Name" +display_name = "Nickname" +sync_github_name = true # use the name on your public GitHub profile as display name +``` + +or, with craftrules checked out next to `craft-apps/`: + +```sh +python3 ../../craftrules/scripts/contributors.py --add-me . --real-name "Your Name" --sync-github-name +``` + +Names show after the next refresh: the script checks that each entry was committed by its own +user (or by a maintainer with a `consent_source`). Delete your entry to remove your names. + +## Refreshing (maintainers) + +```sh +python3 ../../craftrules/scripts/contributors.py . # regenerate + verify; needs git, Python 3.11+, gh +python3 ../../craftrules/scripts/contributors.py --check . # verify people.toml only +``` + +Commit the updated `contributors/contributors.json`. Git author names and emails are never recorded.