Compare commits

...

4 Commits

19 changed files with 909 additions and 336 deletions

305
Cargo.lock generated
View File

@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.0.4" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -70,9 +70,9 @@ dependencies = [
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.2" version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53"
[[package]] [[package]]
name = "bit-set" name = "bit-set"
@ -139,7 +139,7 @@ checksum = "ab3603c4028a5e368d09b51c8b624b9a46edcd7c3778284077a6125af73c9f0a"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"cairo-sys-rs", "cairo-sys-rs",
"glib", "glib 0.17.10",
"libc", "libc",
"once_cell", "once_cell",
"thiserror", "thiserror",
@ -151,7 +151,7 @@ version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "691d0c66b1fb4881be80a760cb8fe76ea97218312f9dfe2c9cc0f496ca279cb1" checksum = "691d0c66b1fb4881be80a760cb8fe76ea97218312f9dfe2c9cc0f496ca279cb1"
dependencies = [ dependencies = [
"glib-sys", "glib-sys 0.17.10",
"libc", "libc",
"system-deps", "system-deps",
] ]
@ -190,9 +190,9 @@ dependencies = [
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.26" version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" checksum = "f56b4c72906975ca04becb8a30e102dfecddd0c06181e3e95ddc444be28881f8"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
@ -201,7 +201,7 @@ dependencies = [
"serde", "serde",
"time", "time",
"wasm-bindgen", "wasm-bindgen",
"winapi", "windows-targets",
] ]
[[package]] [[package]]
@ -349,8 +349,8 @@ name = "cyberpunk-splash"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cairo-rs", "cairo-rs",
"gio", "gio 0.17.10",
"glib", "glib 0.17.10",
"gtk4", "gtk4",
] ]
@ -365,8 +365,8 @@ dependencies = [
"futures", "futures",
"gdk4", "gdk4",
"geo-types", "geo-types",
"gio", "gio 0.17.10",
"glib", "glib 0.17.10",
"glib-build-tools 0.16.3", "glib-build-tools 0.16.3",
"gtk4", "gtk4",
"ifc", "ifc",
@ -442,9 +442,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
dependencies = [ dependencies = [
"errno-dragonfly", "errno-dragonfly",
"libc", "libc",
@ -713,8 +713,8 @@ checksum = "695d6bc846438c5708b07007537b9274d883373dd30858ca881d7d71b5540717"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"gdk-pixbuf-sys", "gdk-pixbuf-sys",
"gio", "gio 0.17.10",
"glib", "glib 0.17.10",
"libc", "libc",
"once_cell", "once_cell",
] ]
@ -725,9 +725,9 @@ version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9285ec3c113c66d7d0ab5676599176f1f42f4944ca1b581852215bf5694870cb" checksum = "9285ec3c113c66d7d0ab5676599176f1f42f4944ca1b581852215bf5694870cb"
dependencies = [ dependencies = [
"gio-sys", "gio-sys 0.17.10",
"glib-sys", "glib-sys 0.17.10",
"gobject-sys", "gobject-sys 0.17.10",
"libc", "libc",
"system-deps", "system-deps",
] ]
@ -742,10 +742,10 @@ dependencies = [
"cairo-rs", "cairo-rs",
"gdk-pixbuf", "gdk-pixbuf",
"gdk4-sys", "gdk4-sys",
"gio", "gio 0.17.10",
"glib", "glib 0.17.10",
"libc", "libc",
"pango", "pango 0.17.10",
] ]
[[package]] [[package]]
@ -756,11 +756,11 @@ checksum = "1bc92aa1608c089c49393d014c38ac0390d01e4841e1fedaa75dbcef77aaed64"
dependencies = [ dependencies = [
"cairo-sys-rs", "cairo-sys-rs",
"gdk-pixbuf-sys", "gdk-pixbuf-sys",
"gio-sys", "gio-sys 0.17.10",
"glib-sys", "glib-sys 0.17.10",
"gobject-sys", "gobject-sys 0.17.10",
"libc", "libc",
"pango-sys", "pango-sys 0.17.10",
"pkg-config", "pkg-config",
"system-deps", "system-deps",
] ]
@ -818,8 +818,27 @@ dependencies = [
"futures-core", "futures-core",
"futures-io", "futures-io",
"futures-util", "futures-util",
"gio-sys", "gio-sys 0.17.10",
"glib", "glib 0.17.10",
"libc",
"once_cell",
"pin-project-lite",
"smallvec",
"thiserror",
]
[[package]]
name = "gio"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7884cba6b1c5db1607d970cadf44b14a43913d42bc68766eea6a5e2fe0891524"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"gio-sys 0.18.1",
"glib 0.18.1",
"libc", "libc",
"once_cell", "once_cell",
"pin-project-lite", "pin-project-lite",
@ -833,8 +852,21 @@ version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ccf87c30a12c469b6d958950f6a9c09f2be20b7773f7e70d20b867fdf2628c3" checksum = "0ccf87c30a12c469b6d958950f6a9c09f2be20b7773f7e70d20b867fdf2628c3"
dependencies = [ dependencies = [
"glib-sys", "glib-sys 0.17.10",
"gobject-sys", "gobject-sys 0.17.10",
"libc",
"system-deps",
"winapi",
]
[[package]]
name = "gio-sys"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2"
dependencies = [
"glib-sys 0.18.1",
"gobject-sys 0.18.0",
"libc", "libc",
"system-deps", "system-deps",
"winapi", "winapi",
@ -852,10 +884,33 @@ dependencies = [
"futures-executor", "futures-executor",
"futures-task", "futures-task",
"futures-util", "futures-util",
"gio-sys", "gio-sys 0.17.10",
"glib-macros", "glib-macros 0.17.10",
"glib-sys", "glib-sys 0.17.10",
"gobject-sys", "gobject-sys 0.17.10",
"libc",
"memchr",
"once_cell",
"smallvec",
"thiserror",
]
[[package]]
name = "glib"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "331156127e8166dd815cf8d2db3a5beb492610c716c03ee6db4f2d07092af0a7"
dependencies = [
"bitflags 2.4.0",
"futures-channel",
"futures-core",
"futures-executor",
"futures-task",
"futures-util",
"gio-sys 0.18.1",
"glib-macros 0.18.0",
"glib-sys 0.18.1",
"gobject-sys 0.18.0",
"libc", "libc",
"memchr", "memchr",
"once_cell", "once_cell",
@ -890,6 +945,20 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "glib-macros"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "179643c50bf28d20d2f6eacd2531a88f2f5d9747dd0b86b8af1e8bb5dd0de3c0"
dependencies = [
"heck",
"proc-macro-crate",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.29",
]
[[package]] [[package]]
name = "glib-sys" name = "glib-sys"
version = "0.17.10" version = "0.17.10"
@ -900,6 +969,16 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "glib-sys"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
dependencies = [
"libc",
"system-deps",
]
[[package]] [[package]]
name = "gm-control-panel" name = "gm-control-panel"
version = "0.1.0" version = "0.1.0"
@ -908,8 +987,8 @@ dependencies = [
"config-derive", "config-derive",
"futures", "futures",
"gdk4", "gdk4",
"gio", "gio 0.17.10",
"glib", "glib 0.17.10",
"glib-build-tools 0.16.3", "glib-build-tools 0.16.3",
"gtk4", "gtk4",
"libadwaita", "libadwaita",
@ -924,7 +1003,18 @@ version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd34c3317740a6358ec04572c1bcfd3ac0b5b6529275fae255b237b314bb8062" checksum = "cd34c3317740a6358ec04572c1bcfd3ac0b5b6529275fae255b237b314bb8062"
dependencies = [ dependencies = [
"glib-sys", "glib-sys 0.17.10",
"libc",
"system-deps",
]
[[package]]
name = "gobject-sys"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
dependencies = [
"glib-sys 0.18.1",
"libc", "libc",
"system-deps", "system-deps",
] ]
@ -935,7 +1025,7 @@ version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "def4bb01265b59ed548b05455040d272d989b3012c42d4c1bbd39083cb9b40d9" checksum = "def4bb01265b59ed548b05455040d272d989b3012c42d4c1bbd39083cb9b40d9"
dependencies = [ dependencies = [
"glib", "glib 0.17.10",
"graphene-sys", "graphene-sys",
"libc", "libc",
] ]
@ -946,7 +1036,7 @@ version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1856fc817e6a6675e36cea0bd9a3afe296f5d9709d1e2d3182803ac77f0ab21d" checksum = "1856fc817e6a6675e36cea0bd9a3afe296f5d9709d1e2d3182803ac77f0ab21d"
dependencies = [ dependencies = [
"glib-sys", "glib-sys 0.17.10",
"libc", "libc",
"pkg-config", "pkg-config",
"system-deps", "system-deps",
@ -970,11 +1060,11 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"cairo-rs", "cairo-rs",
"gdk4", "gdk4",
"glib", "glib 0.17.10",
"graphene-rs", "graphene-rs",
"gsk4-sys", "gsk4-sys",
"libc", "libc",
"pango", "pango 0.17.10",
] ]
[[package]] [[package]]
@ -985,11 +1075,11 @@ checksum = "c07a84fb4dcf1323d29435aa85e2f5f58bef564342bef06775ec7bd0da1f01b0"
dependencies = [ dependencies = [
"cairo-sys-rs", "cairo-sys-rs",
"gdk4-sys", "gdk4-sys",
"glib-sys", "glib-sys 0.17.10",
"gobject-sys", "gobject-sys 0.17.10",
"graphene-sys", "graphene-sys",
"libc", "libc",
"pango-sys", "pango-sys 0.17.10",
"system-deps", "system-deps",
] ]
@ -1005,15 +1095,15 @@ dependencies = [
"futures-channel", "futures-channel",
"gdk-pixbuf", "gdk-pixbuf",
"gdk4", "gdk4",
"gio", "gio 0.17.10",
"glib", "glib 0.17.10",
"graphene-rs", "graphene-rs",
"gsk4", "gsk4",
"gtk4-macros", "gtk4-macros",
"gtk4-sys", "gtk4-sys",
"libc", "libc",
"once_cell", "once_cell",
"pango", "pango 0.17.10",
] ]
[[package]] [[package]]
@ -1039,13 +1129,13 @@ dependencies = [
"cairo-sys-rs", "cairo-sys-rs",
"gdk-pixbuf-sys", "gdk-pixbuf-sys",
"gdk4-sys", "gdk4-sys",
"gio-sys", "gio-sys 0.17.10",
"glib-sys", "glib-sys 0.17.10",
"gobject-sys", "gobject-sys 0.17.10",
"graphene-sys", "graphene-sys",
"gsk4-sys", "gsk4-sys",
"libc", "libc",
"pango-sys", "pango-sys 0.17.10",
"system-deps", "system-deps",
] ]
@ -1107,8 +1197,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"cairo-rs", "cairo-rs",
"coordinates", "coordinates",
"gio", "gio 0.17.10",
"glib", "glib 0.17.10",
"glib-build-tools 0.16.3", "glib-build-tools 0.16.3",
"gtk4", "gtk4",
"image", "image",
@ -1335,6 +1425,7 @@ dependencies = [
"sgf", "sgf",
"thiserror", "thiserror",
"typeshare", "typeshare",
"uuid 1.4.1",
] ]
[[package]] [[package]]
@ -1342,14 +1433,14 @@ name = "kifu-gtk"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cairo-rs", "cairo-rs",
"gio", "gio 0.17.10",
"glib", "glib 0.17.10",
"glib-build-tools 0.17.10", "glib-build-tools 0.17.10",
"gtk4", "gtk4",
"image", "image",
"kifu-core", "kifu-core",
"libadwaita", "libadwaita",
"pango", "pango 0.18.0",
"sgf", "sgf",
"tokio", "tokio",
] ]
@ -1375,12 +1466,12 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"gdk-pixbuf", "gdk-pixbuf",
"gdk4", "gdk4",
"gio", "gio 0.17.10",
"glib", "glib 0.17.10",
"gtk4", "gtk4",
"libadwaita-sys", "libadwaita-sys",
"libc", "libc",
"pango", "pango 0.17.10",
] ]
[[package]] [[package]]
@ -1390,12 +1481,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4231cb2499a9f0c4cdfa4885414b33e39901ddcac61150bc0bb4ff8a57ede404" checksum = "4231cb2499a9f0c4cdfa4885414b33e39901ddcac61150bc0bb4ff8a57ede404"
dependencies = [ dependencies = [
"gdk4-sys", "gdk4-sys",
"gio-sys", "gio-sys 0.17.10",
"glib-sys", "glib-sys 0.17.10",
"gobject-sys", "gobject-sys 0.17.10",
"gtk4-sys", "gtk4-sys",
"libc", "libc",
"pango-sys", "pango-sys 0.17.10",
"system-deps", "system-deps",
] ]
@ -1435,9 +1526,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" checksum = "f478948fd84d9f8e86967bf432640e46adfb5a4bd4f14ef7e864ab38220534ae"
[[package]] [[package]]
name = "memoffset" name = "memoffset"
@ -1593,11 +1684,11 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.56" version = "0.10.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 2.4.0",
"cfg-if", "cfg-if",
"foreign-types", "foreign-types",
"libc", "libc",
@ -1625,9 +1716,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.91" version = "0.9.92"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" checksum = "db7e971c2c2bba161b2d2fdf37080177eff520b3bc044787c7f1f5f9e78d869b"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -1642,11 +1733,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35be456fc620e61f62dff7ff70fbd54dcbaf0a4b920c0f16de1107c47d921d48" checksum = "35be456fc620e61f62dff7ff70fbd54dcbaf0a4b920c0f16de1107c47d921d48"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"gio", "gio 0.17.10",
"glib", "glib 0.17.10",
"libc", "libc",
"once_cell", "once_cell",
"pango-sys", "pango-sys 0.17.10",
]
[[package]]
name = "pango"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06a9e54b831d033206160096b825f2070cf5fda7e35167b1c01e9e774f9202d1"
dependencies = [
"gio 0.18.1",
"glib 0.18.1",
"libc",
"once_cell",
"pango-sys 0.18.0",
] ]
[[package]] [[package]]
@ -1655,8 +1759,20 @@ version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3da69f9f3850b0d8990d462f8c709561975e95f689c1cdf0fecdebde78b35195" checksum = "3da69f9f3850b0d8990d462f8c709561975e95f689c1cdf0fecdebde78b35195"
dependencies = [ dependencies = [
"glib-sys", "glib-sys 0.17.10",
"gobject-sys", "gobject-sys 0.17.10",
"libc",
"system-deps",
]
[[package]]
name = "pango-sys"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5"
dependencies = [
"glib-sys 0.18.1",
"gobject-sys 0.18.0",
"libc", "libc",
"system-deps", "system-deps",
] ]
@ -1759,9 +1875,9 @@ dependencies = [
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.12" version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@ -1953,25 +2069,25 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.9.3" version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-automata", "regex-automata",
"regex-syntax 0.7.4", "regex-syntax 0.7.5",
] ]
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.3.6" version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-syntax 0.7.4", "regex-syntax 0.7.5",
] ]
[[package]] [[package]]
@ -1982,9 +2098,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.7.4" version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
@ -2046,9 +2162,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.8" version = "0.38.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964"
dependencies = [ dependencies = [
"bitflags 2.4.0", "bitflags 2.4.0",
"errno", "errno",
@ -2134,18 +2250,18 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.186" version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.186" version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2189,6 +2305,7 @@ name = "sgf"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"cool_asserts",
"nom", "nom",
"serde", "serde",
"thiserror", "thiserror",
@ -2584,9 +2701,9 @@ dependencies = [
[[package]] [[package]]
name = "url" name = "url"
version = "2.4.0" version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna",

View File

@ -15,6 +15,7 @@ serde_json = { version = "1" }
serde = { version = "1", features = [ "derive" ] } serde = { version = "1", features = [ "derive" ] }
thiserror = { version = "1" } thiserror = { version = "1" }
typeshare = { version = "1" } typeshare = { version = "1" }
uuid = { version = "1.4", features = [ "v4" ] }
[dev-dependencies] [dev-dependencies]
cool_asserts = { version = "2" } cool_asserts = { version = "2" }

View File

@ -1,6 +1,9 @@
use crate::{ use crate::{
types::{AppState, Config, ConfigOption, DatabasePath, GameState, Player, Rank}, types::{AppState, Config, ConfigOption, DatabasePath, GameState, Player, Rank},
ui::{configuration, home, playing_field, ConfigurationView, HomeView, PlayingFieldView}, ui::{
configuration, home, playing_field, review, ConfigurationView, GameReviewView, HomeView,
PlayingFieldView,
},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
@ -8,6 +11,7 @@ use std::{
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use typeshare::typeshare; use typeshare::typeshare;
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare] #[typeshare]
@ -17,11 +21,33 @@ pub enum CoreRequest {
CreateGame(CreateGameRequest), CreateGame(CreateGameRequest),
Home, Home,
OpenConfiguration, OpenConfiguration,
OpenGameReview(GameId),
PlayingField, PlayingField,
PlayStone(PlayStoneRequest), PlayStone(PlayStoneRequest),
StartGame, StartGame,
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
#[serde(tag = "type", content = "content")]
pub enum CoreResponse {
ConfigurationView(ConfigurationView),
HomeView(HomeView),
GameReview(GameReviewView),
PlayingFieldView(PlayingFieldView),
UpdatedConfigurationView(ConfigurationView),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[typeshare]
pub struct GameId(String);
impl GameId {
pub fn new() -> Self {
GameId(Uuid::new_v4().hyphenated().to_string())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare] #[typeshare]
#[serde(tag = "type", content = "content")] #[serde(tag = "type", content = "content")]
@ -65,16 +91,6 @@ impl From<HotseatPlayerRequest> for Player {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
#[serde(tag = "type", content = "content")]
pub enum CoreResponse {
ConfigurationView(ConfigurationView),
HomeView(HomeView),
PlayingFieldView(PlayingFieldView),
UpdatedConfigurationView(ConfigurationView),
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CoreApp { pub struct CoreApp {
config: Arc<RwLock<Config>>, config: Arc<RwLock<Config>>,
@ -131,6 +147,11 @@ impl CoreApp {
CoreRequest::OpenConfiguration => { CoreRequest::OpenConfiguration => {
CoreResponse::ConfigurationView(configuration(&self.config.read().unwrap())) CoreResponse::ConfigurationView(configuration(&self.config.read().unwrap()))
} }
CoreRequest::OpenGameReview(game_id) => {
let state = self.state.read().unwrap();
let game = state.database.get(&game_id).unwrap();
CoreResponse::GameReview(review(game))
}
CoreRequest::PlayingField => { CoreRequest::PlayingField => {
let app_state = self.state.read().unwrap(); let app_state = self.state.read().unwrap();
let game = app_state.game.as_ref().unwrap(); let game = app_state.game.as_ref().unwrap();

View File

@ -1,8 +1,9 @@
use std::{ffi::OsStr, io::Read, os::unix::ffi::OsStrExt, path::PathBuf};
use sgf::{go, parse_sgf, Game}; use sgf::{go, parse_sgf, Game};
use std::{collections::HashMap, ffi::OsStr, io::Read, os::unix::ffi::OsStrExt, path::PathBuf};
use thiserror::Error; use thiserror::Error;
use crate::api::GameId;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
#[error("Database permission denied")] #[error("Database permission denied")]
@ -20,12 +21,12 @@ impl From<std::io::Error> for Error {
#[derive(Debug)] #[derive(Debug)]
pub struct Database { pub struct Database {
path: PathBuf, path: PathBuf,
games: Vec<go::Game>, games: HashMap<GameId, go::Game>,
} }
impl Database { impl Database {
pub fn open_path(path: PathBuf) -> Result<Database, Error> { pub fn open_path(path: PathBuf) -> Result<Database, Error> {
let mut games: Vec<go::Game> = Vec::new(); let mut games: HashMap<GameId, go::Game> = HashMap::new();
let extension = PathBuf::from("sgf").into_os_string(); let extension = PathBuf::from("sgf").into_os_string();
@ -43,7 +44,9 @@ impl Database {
Ok(sgfs) => { Ok(sgfs) => {
for sgf in sgfs { for sgf in sgfs {
match sgf { match sgf {
Game::Go(game) => games.push(game), Game::Go(game) => {
games.insert(GameId::new(), game);
}
Game::Unsupported(_) => {} Game::Unsupported(_) => {}
} }
} }
@ -63,9 +66,13 @@ impl Database {
self.games.len() self.games.len()
} }
pub fn all_games(&self) -> impl Iterator<Item = &go::Game> { pub fn all_games(&self) -> impl Iterator<Item = (&GameId, &go::Game)> {
self.games.iter() self.games.iter()
} }
pub fn get(&self, game_id: &GameId) -> Option<&go::Game> {
self.games.get(game_id)
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -2,9 +2,12 @@ use serde::{Deserialize, Serialize};
use sgf::go::{Game, GameResult, Win}; use sgf::go::{Game, GameResult, Win};
use typeshare::typeshare; use typeshare::typeshare;
use crate::api::GameId;
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[typeshare] #[typeshare]
pub struct GamePreviewElement { pub struct GamePreviewElement {
pub id: GameId,
pub date: String, pub date: String,
pub name: String, pub name: String,
pub black_player: String, pub black_player: String,
@ -13,7 +16,7 @@ pub struct GamePreviewElement {
} }
impl GamePreviewElement { impl GamePreviewElement {
pub fn new(game: &Game) -> GamePreviewElement { pub fn new(id: &GameId, game: &Game) -> GamePreviewElement {
let black_player = match game.info.black_player { let black_player = match game.info.black_player {
Some(ref black_player) => black_player.clone(), Some(ref black_player) => black_player.clone(),
None => "unknown".to_owned(), None => "unknown".to_owned(),
@ -55,6 +58,7 @@ impl GamePreviewElement {
}; };
GamePreviewElement { GamePreviewElement {
id: id.clone(),
date: game date: game
.info .info
.date .date

View File

@ -0,0 +1,50 @@
use crate::Color;
use serde::{Deserialize, Serialize};
use sgf::go::Game;
use typeshare::typeshare;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct GameReviewView {
pub black_player: String,
pub white_player: String,
pub tree: Node,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct Position {
pub column: u8,
pub row: u8,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct Node {
pub color: Color,
pub position: Position,
pub children: Vec<Node>,
}
pub fn review(game: &Game) -> GameReviewView {
GameReviewView {
black_player: "savanni".to_owned(),
white_player: "kat".to_owned(),
tree: Node {
color: Color::Black,
position: Position { column: 3, row: 3 },
children: vec![Node {
color: Color::White,
position: Position {
column: 15,
row: 15,
},
children: vec![Node {
color: Color::Black,
position: Position { column: 15, row: 3 },
children: vec![],
}],
}],
},
}
}

View File

@ -1,4 +1,7 @@
use crate::ui::{Action, GamePreviewElement}; use crate::{
api::GameId,
ui::{Action, GamePreviewElement},
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sgf::go::Game; use sgf::go::Game;
use typeshare::typeshare; use typeshare::typeshare;
@ -56,7 +59,7 @@ pub struct HomeView {
pub start_game: Action<()>, pub start_game: Action<()>,
} }
pub fn home<'a>(games: impl Iterator<Item = &'a Game>) -> HomeView { pub fn home<'a>(games: impl Iterator<Item = (&'a GameId, &'a Game)>) -> HomeView {
let black_player = PlayerElement::Hotseat(HotseatPlayerElement { let black_player = PlayerElement::Hotseat(HotseatPlayerElement {
placeholder: Some("black player".to_owned()), placeholder: Some("black player".to_owned()),
default_rank: None, default_rank: None,
@ -70,7 +73,9 @@ pub fn home<'a>(games: impl Iterator<Item = &'a Game>) -> HomeView {
HomeView { HomeView {
black_player, black_player,
white_player, white_player,
games: games.map(GamePreviewElement::new).collect(), games: games
.map(|(id, game)| GamePreviewElement::new(id, game))
.collect(),
start_game: Action { start_game: Action {
id: "start-game-action".to_owned(), id: "start-game-action".to_owned(),
label: "New Game".to_owned(), label: "New Game".to_owned(),

View File

@ -4,6 +4,9 @@ pub use configuration::{configuration, ConfigurationView};
mod elements; mod elements;
pub use elements::{game_preview::GamePreviewElement, menu::Menu, Action, Field, Toggle}; pub use elements::{game_preview::GamePreviewElement, menu::Menu, Action, Field, Toggle};
mod game_review;
pub use game_review::{review, GameReviewView, Node};
mod playing_field; mod playing_field;
pub use playing_field::{playing_field, PlayingFieldView}; pub use playing_field::{playing_field, PlayingFieldView};

View File

@ -2,7 +2,7 @@ use adw::prelude::*;
use kifu_core::{CoreApp, CoreRequest, CoreResponse}; use kifu_core::{CoreApp, CoreRequest, CoreResponse};
use kifu_gtk::{ use kifu_gtk::{
perftrace, perftrace,
ui::{AppWindow, ConfigurationPage, Home, PlayingField}, ui::{AppWindow, ConfigurationPage, GameReview, Home, PlayingField},
CoreApi, CoreApi,
}; };
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
@ -18,6 +18,9 @@ fn handle_response(api: CoreApi, app_window: &AppWindow, message: CoreResponse)
window.set_visible_page(&config_page); window.set_visible_page(&config_page);
window.present(); window.present();
}), }),
CoreResponse::GameReview(view) => perftrace("GameReview", || {
app_window.set_content(&GameReview::new(api, view));
}),
CoreResponse::HomeView(view) => perftrace("HomeView", || { CoreResponse::HomeView(view) => perftrace("HomeView", || {
let api = api.clone(); let api = api.clone();

View File

@ -0,0 +1,51 @@
use crate::{
ui::{Board, ReviewTree},
CoreApi,
};
use glib::Object;
use gtk::{prelude::*, subclass::prelude::*};
use kifu_core::ui::GameReviewView;
use std::{cell::RefCell, rc::Rc};
#[derive(Default)]
pub struct GameReviewPrivate {
board: Rc<RefCell<Option<Board>>>,
tree: ReviewTree,
}
#[glib::object_subclass]
impl ObjectSubclass for GameReviewPrivate {
const NAME: &'static str = "GameReview";
type Type = GameReview;
type ParentType = gtk::Box;
}
impl ObjectImpl for GameReviewPrivate {}
impl WidgetImpl for GameReviewPrivate {}
impl BoxImpl for GameReviewPrivate {}
glib::wrapper! {
pub struct GameReview(ObjectSubclass<GameReviewPrivate>)
@extends gtk::Box, gtk::Widget,
@implements gtk::Orientable;
}
impl GameReview {
pub fn new(api: CoreApi, view: GameReviewView) -> Self {
let s: Self = Object::builder().build();
s.set_orientation(gtk::Orientation::Horizontal);
let review_area = gtk::ScrolledWindow::builder()
.hscrollbar_policy(gtk::PolicyType::Automatic)
.vscrollbar_policy(gtk::PolicyType::Automatic)
.hexpand(true)
.vexpand(true)
.build();
review_area.set_child(Some(&s.imp().tree));
s.append(&review_area);
s.imp().tree.set_tree(view.tree);
s
}
}

View File

@ -137,7 +137,7 @@ impl Home {
.build(); .build();
s.append(&new_game_button); s.append(&new_game_button);
let library = Library::new(); let library = Library::new(api.clone());
let library_view = gtk::ScrolledWindow::builder() let library_view = gtk::ScrolledWindow::builder()
.hscrollbar_policy(gtk::PolicyType::Never) .hscrollbar_policy(gtk::PolicyType::Never)
.min_content_width(360) .min_content_width(360)

View File

@ -1,5 +1,6 @@
use crate::ui::GamePreview; use crate::{ui::GamePreview, CoreApi};
use adw::{prelude::*, subclass::prelude::*}; use adw::{prelude::*, subclass::prelude::*};
use gio::ListModel;
use glib::Object; use glib::Object;
use gtk::{glib, prelude::*, subclass::prelude::*}; use gtk::{glib, prelude::*, subclass::prelude::*};
use kifu_core::ui::GamePreviewElement; use kifu_core::ui::GamePreviewElement;
@ -45,38 +46,6 @@ impl Default for LibraryPrivate {
let model = gio::ListStore::new(glib::types::Type::OBJECT); let model = gio::ListStore::new(glib::types::Type::OBJECT);
model.extend_from_slice(&vector); model.extend_from_slice(&vector);
/*
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(move |_, list_item| {
let preview = GamePreview::new();
list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be a ListItem")
.set_child(Some(&preview));
});
factory.connect_bind(move |_, list_item| {
let game_element = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem")
.item()
.and_downcast::<GameObject>()
.expect("The item has to be a GameObject.");
let preview = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem")
.child()
.and_downcast::<GamePreview>()
.expect("The child has to be a GamePreview object.");
match game_element.game() {
Some(game) => preview.set_game(game),
None => (),
};
});
*/
let selection_model = gtk::NoSelection::new(Some(model.clone())); let selection_model = gtk::NoSelection::new(Some(model.clone()));
let list_view = gtk::ColumnView::builder().model(&selection_model).build(); let list_view = gtk::ColumnView::builder().model(&selection_model).build();
@ -86,10 +55,10 @@ impl Default for LibraryPrivate {
{ {
let factory = gtk::SignalListItemFactory::new(); let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(|_, list_item| { factory.connect_setup(|_, list_item| {
list_item let item = list_item.downcast_ref::<gtk::ListItem>().unwrap();
.downcast_ref::<gtk::ListItem>()
.unwrap() item.set_activatable(true);
.set_child(Some( item.set_child(Some(
&gtk::Label::builder() &gtk::Label::builder()
.halign(gtk::Align::Start) .halign(gtk::Align::Start)
.ellipsize(pango::EllipsizeMode::End) .ellipsize(pango::EllipsizeMode::End)
@ -149,10 +118,23 @@ glib::wrapper! {
} }
impl Library { impl Library {
pub fn new() -> Self { pub fn new(api: CoreApi) -> Self {
let s: Self = Object::builder().build(); let s: Self = Object::builder().build();
s.set_child(Some(&s.imp().list_view)); s.set_child(Some(&s.imp().list_view));
s.imp().list_view.connect_activate({
let s = s.clone();
move |_, row_id| {
let object = s.imp().model.item(row_id).unwrap();
// let list_item = object.downcast_ref::<gtk::ListItem>().unwrap();
// let game = list_item.item().and_downcast::<GameObject>().unwrap();
// let game_id = game.game().unwrap().id;
// let game = object.downcast_ref::<gtk::ListItem>()
let game = object.downcast::<GameObject>().unwrap();
let game_id = game.game().unwrap().id;
api.dispatch(kifu_core::CoreRequest::OpenGameReview(game_id));
}
});
s s
} }

View File

@ -3,6 +3,9 @@ use gio::resources_lookup_data;
use glib::IsA; use glib::IsA;
use gtk::{prelude::*, STYLE_PROVIDER_PRIORITY_USER}; use gtk::{prelude::*, STYLE_PROVIDER_PRIORITY_USER};
mod board;
pub use board::Board;
mod chat; mod chat;
pub use chat::Chat; pub use chat::Chat;
@ -12,6 +15,12 @@ pub use config::ConfigurationPage;
mod game_preview; mod game_preview;
pub use game_preview::GamePreview; pub use game_preview::GamePreview;
mod game_review;
pub use game_review::GameReview;
mod home;
pub use home::Home;
mod library; mod library;
pub use library::Library; pub use library::Library;
@ -21,11 +30,8 @@ pub use player_card::PlayerCard;
mod playing_field; mod playing_field;
pub use playing_field::PlayingField; pub use playing_field::PlayingField;
mod home; mod review_tree;
pub use home::Home; pub use review_tree::ReviewTree;
mod board;
pub use board::Board;
#[cfg(feature = "screenplay")] #[cfg(feature = "screenplay")]
pub use playing_field::playing_field_view; pub use playing_field::playing_field_view;

View File

@ -12,7 +12,7 @@ pub struct PlayingFieldPrivate {
board: Rc<RefCell<Option<Board>>>, board: Rc<RefCell<Option<Board>>>,
player_card_white: Rc<RefCell<Option<PlayerCard>>>, player_card_white: Rc<RefCell<Option<PlayerCard>>>,
player_card_black: Rc<RefCell<Option<PlayerCard>>>, player_card_black: Rc<RefCell<Option<PlayerCard>>>,
chat: Rc<RefCell<Option<Chat>>>, // chat: Rc<RefCell<Option<Chat>>>,
} }
impl Default for PlayingFieldPrivate { impl Default for PlayingFieldPrivate {
@ -21,7 +21,7 @@ impl Default for PlayingFieldPrivate {
board: Default::default(), board: Default::default(),
player_card_white: Rc::new(RefCell::new(None)), player_card_white: Rc::new(RefCell::new(None)),
player_card_black: Rc::new(RefCell::new(None)), player_card_black: Rc::new(RefCell::new(None)),
chat: Rc::new(RefCell::new(None)), // chat: Rc::new(RefCell::new(None)),
} }
} }
} }
@ -47,7 +47,7 @@ impl PlayingField {
let player_card_white = PlayerCard::new(view.player_card_white); let player_card_white = PlayerCard::new(view.player_card_white);
let player_card_black = PlayerCard::new(view.player_card_black); let player_card_black = PlayerCard::new(view.player_card_black);
let chat = Chat::new(view.chat); // let chat = Chat::new(view.chat);
*s.imp().board.borrow_mut() = Some(Board::new(api)); *s.imp().board.borrow_mut() = Some(Board::new(api));
s.imp() s.imp()
@ -57,11 +57,11 @@ impl PlayingField {
.map(|board| s.attach(board, 1, 1, 1, 2)); .map(|board| s.attach(board, 1, 1, 1, 2));
s.attach(&player_card_black, 2, 1, 1, 1); s.attach(&player_card_black, 2, 1, 1, 1);
s.attach(&player_card_white, 3, 1, 1, 1); s.attach(&player_card_white, 3, 1, 1, 1);
s.attach(&chat, 2, 2, 2, 1); // s.attach(&chat, 2, 2, 2, 1);
*s.imp().player_card_white.borrow_mut() = Some(player_card_white); *s.imp().player_card_white.borrow_mut() = Some(player_card_white);
*s.imp().player_card_black.borrow_mut() = Some(player_card_black); *s.imp().player_card_black.borrow_mut() = Some(player_card_black);
*s.imp().chat.borrow_mut() = Some(chat); // *s.imp().chat.borrow_mut() = Some(chat);
s.imp().board.borrow().as_ref().map(|board| { s.imp().board.borrow().as_ref().map(|board| {
board.set_board(view.board); board.set_board(view.board);

View File

@ -0,0 +1,139 @@
use adw::{prelude::*, subclass::prelude::*};
use cairo::Context;
use glib::Object;
use kifu_core::ui::Node;
use std::{cell::RefCell, f64::consts::PI, rc::Rc};
const NODE_SIZE: f64 = 10.;
const NODE_ROW_HEIGHT: f64 = 30.;
const NODE_COLUMN_WIDTH: f64 = 30.;
#[derive(Default)]
pub struct ReviewTreePrivate {
tree: Rc<RefCell<Option<Node>>>,
}
#[glib::object_subclass]
impl ObjectSubclass for ReviewTreePrivate {
const NAME: &'static str = "ReviewTree";
type Type = ReviewTree;
type ParentType = gtk::DrawingArea;
}
impl ObjectImpl for ReviewTreePrivate {}
impl WidgetImpl for ReviewTreePrivate {}
impl DrawingAreaImpl for ReviewTreePrivate {}
glib::wrapper! {
pub struct ReviewTree(ObjectSubclass<ReviewTreePrivate>)
@extends gtk::DrawingArea, gtk::Widget;
}
impl ReviewTree {
pub fn new() -> Self {
let s: Self = Object::builder().build();
s.set_draw_func({
let s = s.clone();
move |_, context, width, height| {
println!("drawing area: {} {}", width, height);
let style_context = WidgetExt::style_context(&s);
let bg = style_context.lookup_color("view_bg_color").unwrap();
let fg = style_context.lookup_color("view_fg_color").unwrap();
context.set_source_rgb(bg.red() as f64, bg.green() as f64, bg.blue() as f64);
let _ = context.paint();
if let Some(tree) = &*s.imp().tree.borrow() {
let (width, height) = max_tree_dimensions(tree);
println!("tree dimensions: {} {}", width, height);
s.set_width_request(
width as i32 * (NODE_SIZE as i32 + NODE_COLUMN_WIDTH as i32) + 20,
);
s.set_height_request(
height as i32 * (NODE_SIZE as i32 + NODE_ROW_HEIGHT as i32) + 20,
);
context.set_source_rgb(fg.red() as f64, fg.green() as f64, fg.blue() as f64);
draw_tree(&context, &tree);
}
}
});
s
}
pub fn set_tree(&self, tree: Node) {
*self.imp().tree.borrow_mut() = Some(tree);
self.queue_draw();
}
}
impl Default for ReviewTree {
fn default() -> Self {
Self::new()
}
}
fn draw_node(context: &Context, x: f64, y: f64) {
context.arc(x, y, NODE_SIZE, 0., 2. * PI);
let _ = context.fill();
}
fn draw_tree(context: &Context, tree: &Node) {
let mut row: Vec<&Node> = vec![];
let mut next_row: Vec<&Node> = vec![];
let mut width: Vec<usize> = vec![];
let mut x = 0;
let mut y = 0;
row.push(tree);
width.push(1);
while row.len() != 0 {
for node in row.into_iter() {
draw_node(
context,
10. + (x as f64) * (NODE_SIZE + NODE_COLUMN_WIDTH),
10. + (y as f64) * (NODE_SIZE + NODE_ROW_HEIGHT),
);
next_row.append(&mut node.children.iter().map(|n| n).collect::<Vec<&Node>>());
x = x + 1;
}
x = 0;
y = y + 1;
row = next_row;
next_row = vec![];
}
}
fn max_tree_dimensions(tree: &Node) -> (usize, usize) {
let mut row: Vec<&Node> = vec![];
let mut next_row: Vec<&Node> = vec![];
let mut width: Vec<usize> = vec![];
row.push(tree);
width.push(1);
while row.len() != 0 {
println!("new row");
for node in row.into_iter() {
println!(
"{:?} {:?} {}",
node.color,
node.position,
node.children.len()
);
next_row.append(&mut node.children.iter().map(|n| n).collect::<Vec<&Node>>());
}
width.push(next_row.len());
row = next_row;
next_row = vec![];
}
(
width.iter().fold(0, |a, b| if a > *b { a } else { *b }),
width.len(),
)
}

View File

@ -11,3 +11,6 @@ nom = { version = "7" }
serde = { version = "1", features = [ "derive" ] } serde = { version = "1", features = [ "derive" ] }
thiserror = { version = "1"} thiserror = { version = "1"}
typeshare = { version = "1" } typeshare = { version = "1" }
[dev-dependencies]
cool_asserts = { version = "2" }

View File

@ -74,6 +74,7 @@ use crate::{
Error, Error,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ops::Deref;
use typeshare::typeshare; use typeshare::typeshare;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -84,11 +85,18 @@ pub struct Game {
pub tree: Tree, pub tree: Tree,
} }
impl Deref for Game {
type Target = Tree;
fn deref(&self) -> &Self::Target {
&self.tree
}
}
impl TryFrom<Tree> for Game { impl TryFrom<Tree> for Game {
type Error = Error; type Error = Error;
fn try_from(tree: Tree) -> Result<Self, Self::Error> { fn try_from(tree: Tree) -> Result<Self, Self::Error> {
let board_size = match tree.sequence[0].find_prop("SZ") { let board_size = match tree.root.find_prop("SZ") {
Some(prop) => Size::try_from(prop.values[0].as_str())?, Some(prop) => Size::try_from(prop.values[0].as_str())?,
None => Size { None => Size {
width: 19, width: 19,
@ -96,40 +104,37 @@ impl TryFrom<Tree> for Game {
}, },
}; };
let mut info = GameInfo::default(); let mut info = GameInfo::default();
info.app_name = tree.sequence[0] info.app_name = tree.root.find_prop("AP").map(|prop| prop.values[0].clone());
.find_prop("AP")
.map(|prop| prop.values[0].clone());
info.game_name = tree.sequence[0] info.game_name = tree.root.find_prop("GN").map(|prop| prop.values[0].clone());
.find_prop("GN")
.map(|prop| prop.values[0].clone());
info.black_player = tree.sequence[0] info.black_player = tree.root.find_prop("PB").map(|prop| prop.values.join(", "));
.find_prop("PB")
.map(|prop| prop.values.join(", "));
info.black_rank = tree.sequence[0] info.black_rank = tree
.root
.find_prop("BR") .find_prop("BR")
.and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok()); .and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok());
info.white_player = tree.sequence[0] info.white_player = tree.root.find_prop("PW").map(|prop| prop.values.join(", "));
.find_prop("PW")
.map(|prop| prop.values.join(", "));
info.white_rank = tree.sequence[0] info.white_rank = tree
.root
.find_prop("WR") .find_prop("WR")
.and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok()); .and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok());
info.result = tree.sequence[0] info.result = tree
.root
.find_prop("RE") .find_prop("RE")
.and_then(|prop| GameResult::try_from(prop.values[0].as_str()).ok()); .and_then(|prop| GameResult::try_from(prop.values[0].as_str()).ok());
info.time_limits = tree.sequence[0] info.time_limits = tree
.root
.find_prop("TM") .find_prop("TM")
.and_then(|prop| prop.values[0].parse::<u64>().ok()) .and_then(|prop| prop.values[0].parse::<u64>().ok())
.and_then(|seconds| Some(std::time::Duration::from_secs(seconds))); .and_then(|seconds| Some(std::time::Duration::from_secs(seconds)));
info.date = tree.sequence[0] info.date = tree
.root
.find_prop("DT") .find_prop("DT")
.and_then(|prop| { .and_then(|prop| {
let v = prop let v = prop
@ -149,21 +154,13 @@ impl TryFrom<Tree> for Game {
}) })
.unwrap_or(vec![]); .unwrap_or(vec![]);
info.event = tree.sequence[0] info.event = tree.root.find_prop("EV").map(|prop| prop.values.join(", "));
.find_prop("EV")
.map(|prop| prop.values.join(", "));
info.round = tree.sequence[0] info.round = tree.root.find_prop("RO").map(|prop| prop.values.join(", "));
.find_prop("RO")
.map(|prop| prop.values.join(", "));
info.source = tree.sequence[0] info.source = tree.root.find_prop("SO").map(|prop| prop.values.join(", "));
.find_prop("SO")
.map(|prop| prop.values.join(", "));
info.game_keeper = tree.sequence[0] info.game_keeper = tree.root.find_prop("US").map(|prop| prop.values.join(", "));
.find_prop("US")
.map(|prop| prop.values.join(", "));
Ok(Game { Ok(Game {
board_size, board_size,
@ -307,7 +304,7 @@ mod tests {
use super::*; use super::*;
use crate::{ use crate::{
date::Date, date::Date,
tree::{parse_collection, Size}, tree::{parse_collection, Property, Size},
}; };
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
@ -390,4 +387,123 @@ mod tests {
assert_eq!(tree.info.game_keeper, Some("Arno Hollosi".to_owned())); assert_eq!(tree.info.game_keeper, Some("Arno Hollosi".to_owned()));
}); });
} }
#[test]
fn it_presents_the_mainline_of_game_without_branches() {
with_file(
std::path::Path::new("test_data/2020 USGO DDK, Round 1.sgf"),
|trees| {
assert_eq!(trees.len(), 1);
let tree = &trees[0];
let node = &tree.root;
assert_eq!(node.properties.len(), 16);
let expected_properties = vec![
Property {
ident: "GM".to_owned(),
values: vec!["1".to_owned()],
},
Property {
ident: "FF".to_owned(),
values: vec!["4".to_owned()],
},
Property {
ident: "CA".to_owned(),
values: vec!["UTF-8".to_owned()],
},
Property {
ident: "AP".to_owned(),
values: vec!["CGoban:3".to_owned()],
},
Property {
ident: "ST".to_owned(),
values: vec!["2".to_owned()],
},
Property {
ident: "RU".to_owned(),
values: vec!["AGA".to_owned()],
},
Property {
ident: "SZ".to_owned(),
values: vec!["19".to_owned()],
},
Property {
ident: "KM".to_owned(),
values: vec!["7.50".to_owned()],
},
Property {
ident: "TM".to_owned(),
values: vec!["1800".to_owned()],
},
Property {
ident: "OT".to_owned(),
values: vec!["5x30 byo-yomi".to_owned()],
},
Property {
ident: "PW".to_owned(),
values: vec!["Geckoz".to_owned()],
},
Property {
ident: "PB".to_owned(),
values: vec!["savanni".to_owned()],
},
Property {
ident: "BR".to_owned(),
values: vec!["23k".to_owned()],
},
Property {
ident: "DT".to_owned(),
values: vec!["2020-08-05".to_owned()],
},
Property {
ident: "PC".to_owned(),
values: vec!["The KGS Go Server at http://www.gokgs.com/".to_owned()],
},
Property {
ident: "RE".to_owned(),
values: vec!["W+17.50".to_owned()],
},
];
for i in 0..16 {
assert_eq!(node.properties[i], expected_properties[i]);
}
let node = node.next().unwrap();
let expected_properties = vec![
Property {
ident: "B".to_owned(),
values: vec!["pp".to_owned()],
},
Property {
ident: "BL".to_owned(),
values: vec!["1795.449".to_owned()],
},
Property {
ident: "C".to_owned(),
values: vec!["Geckoz [?]: Good game\nsavanni [23k?]: There we go! This UI is... tough.\nsavanni [23k?]: Have fun! Talk to you at the end.\nGeckoz [?]: Yeah, OGS is much better; I'm a UX professional\n".to_owned()],
}
];
for i in 0..3 {
assert_eq!(node.properties[i], expected_properties[i]);
}
let node = node.next().unwrap();
let expected_properties = vec![
Property {
ident: "W".to_owned(),
values: vec!["dp".to_owned()],
},
Property {
ident: "WL".to_owned(),
values: vec!["1765.099".to_owned()],
},
];
for i in 0..2 {
assert_eq!(node.properties[i], expected_properties[i]);
}
},
);
}
} }

View File

@ -65,7 +65,7 @@ pub fn parse_sgf(input: &str) -> Result<Vec<Game>, Error> {
let (_, trees) = parse_collection::<nom::error::VerboseError<&str>>(input)?; let (_, trees) = parse_collection::<nom::error::VerboseError<&str>>(input)?;
Ok(trees Ok(trees
.into_iter() .into_iter()
.map(|t| match t.sequence[0].find_prop("GM") { .map(|t| match t.root.find_prop("GM") {
Some(prop) if prop.values == vec!["1".to_owned()] => { Some(prop) if prop.values == vec!["1".to_owned()] => {
Game::Go(go::Game::try_from(t).expect("properly structured game tree")) Game::Go(go::Game::try_from(t).expect("properly structured game tree"))
} }

View File

@ -5,7 +5,6 @@ use nom::{
character::complete::{alpha1, digit1, multispace0, multispace1, none_of}, character::complete::{alpha1, digit1, multispace0, multispace1, none_of},
combinator::{opt, value}, combinator::{opt, value},
multi::{many0, many1, separated_list1}, multi::{many0, many1, separated_list1},
sequence::delimited,
IResult, IResult,
}; };
use std::num::ParseIntError; use std::num::ParseIntError;
@ -54,29 +53,19 @@ impl TryFrom<&str> for Size {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Tree { pub struct Tree {
pub sequence: Vec<Node>, pub root: Node,
pub sub_sequences: Vec<Tree>,
} }
impl ToString for Tree { impl ToString for Tree {
fn to_string(&self) -> String { fn to_string(&self) -> String {
let sequence = self format!("({})", self.root.to_string())
.sequence
.iter()
.map(|node| node.to_string())
.collect::<String>();
let subsequences = self
.sub_sequences
.iter()
.map(|seq| seq.to_string())
.collect::<String>();
format!("({}{})", sequence, subsequences)
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Node { pub struct Node {
pub properties: Vec<Property>, pub properties: Vec<Property>,
pub next: Vec<Node>,
} }
impl ToString for Node { impl ToString for Node {
@ -86,7 +75,21 @@ impl ToString for Node {
.iter() .iter()
.map(|prop| prop.to_string()) .map(|prop| prop.to_string())
.collect::<String>(); .collect::<String>();
format!(";{}", props)
let next = if self.next.len() == 1 {
self.next
.iter()
.map(|node| node.to_string())
.collect::<Vec<String>>()
.join("")
} else {
self.next
.iter()
.map(|node| format!("({})", node.to_string()))
.collect::<Vec<String>>()
.join("")
};
format!(";{}{}", props, next)
} }
} }
@ -97,6 +100,10 @@ impl Node {
.find(|prop| prop.ident == ident) .find(|prop| prop.ident == ident)
.cloned() .cloned()
} }
pub fn next<'a>(&'a self) -> Option<&'a Node> {
self.next.get(0)
}
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -119,40 +126,40 @@ impl ToString for Property {
pub fn parse_collection<'a, E: nom::error::ParseError<&'a str>>( pub fn parse_collection<'a, E: nom::error::ParseError<&'a str>>(
input: &'a str, input: &'a str,
) -> IResult<&'a str, Vec<Tree>, E> { ) -> IResult<&'a str, Vec<Tree>, E> {
separated_list1(multispace1, parse_tree)(input) let (input, roots) = separated_list1(multispace1, parse_tree)(input)?;
let trees = roots
.into_iter()
.map(|root| Tree { root })
.collect::<Vec<Tree>>();
Ok((input, trees))
} }
// note: must preserve unknown properties // note: must preserve unknown properties
// note: must fix or preserve illegally formatted game-info properties // note: must fix or preserve illegally formatted game-info properties
// note: must correct or delete illegally foramtted properties, but display a warning // note: must correct or delete illegally foramtted properties, but display a warning
fn parse_tree<'a, E: nom::error::ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Tree, E> { fn parse_tree<'a, E: nom::error::ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Node, E> {
let (input, _) = multispace0(input)?; let (input, _) = multispace0(input)?;
delimited(tag("("), parse_sequence, tag(")"))(input) let (input, _) = tag("(")(input)?;
} let (input, node) = parse_node(input)?;
let (input, _) = multispace0(input)?;
let (input, _) = tag(")")(input)?;
fn parse_sequence<'a, E: nom::error::ParseError<&'a str>>( Ok((input, node))
input: &'a str,
) -> IResult<&'a str, Tree, E> {
let (input, _) = multispace0(input)?;
let (input, nodes) = many1(parse_node)(input)?;
let (input, _) = multispace0(input)?;
let (input, sub_sequences) = many0(parse_tree)(input)?;
let (input, _) = multispace0(input)?;
Ok((
input,
Tree {
sequence: nodes,
sub_sequences,
},
))
} }
fn parse_node<'a, E: nom::error::ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Node, E> { fn parse_node<'a, E: nom::error::ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Node, E> {
let (input, _) = multispace0(input)?; let (input, _) = multispace0(input)?;
let (input, _) = tag(";")(input)?; let (input, _) = opt(tag(";"))(input)?;
let (input, properties) = many1(parse_property)(input)?; let (input, properties) = many1(parse_property)(input)?;
Ok((input, Node { properties }))
let (input, next) = opt(parse_node)(input)?;
let (input, mut next_seq) = many0(parse_tree)(input)?;
let mut next = next.map(|n| vec![n]).unwrap_or(vec![]);
next.append(&mut next_seq);
Ok((input, Node { properties, next }))
} }
fn parse_property<'a, E: nom::error::ParseError<&'a str>>( fn parse_property<'a, E: nom::error::ParseError<&'a str>>(
@ -219,8 +226,6 @@ pub fn parse_size<'a, E: nom::error::ParseError<&'a str>>(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::{fs::File, io::Read};
use super::*; use super::*;
const EXAMPLE: &'static str = "(;FF[4]C[root](;C[a];C[b](;C[c]) const EXAMPLE: &'static str = "(;FF[4]C[root](;C[a];C[b](;C[c])
@ -259,7 +264,8 @@ mod test {
properties: vec![Property { properties: vec![Property {
ident: "B".to_owned(), ident: "B".to_owned(),
values: vec!["ab".to_owned()] values: vec!["ab".to_owned()]
}] }],
next: vec![]
} }
); );
@ -273,6 +279,25 @@ mod test {
properties: vec![Property { properties: vec![Property {
ident: "B".to_owned(), ident: "B".to_owned(),
values: vec!["ab".to_owned()] values: vec!["ab".to_owned()]
}],
next: vec![Node {
properties: vec![Property {
ident: "W".to_owned(),
values: vec!["dp".to_owned()]
}],
next: vec![Node {
properties: vec![
Property {
ident: "B".to_owned(),
values: vec!["pq".to_owned()]
},
Property {
ident: "C".to_owned(),
values: vec!["some comments".to_owned()]
}
],
next: vec![],
}]
}] }]
} }
); );
@ -286,21 +311,17 @@ mod test {
assert_eq!( assert_eq!(
sequence, sequence,
Tree {
sequence: vec![
Node { Node {
properties: vec![Property { properties: vec![Property {
ident: "B".to_owned(), ident: "B".to_owned(),
values: vec!["ab".to_owned()] values: vec!["ab".to_owned()]
}] }],
}, next: vec![Node {
Node {
properties: vec![Property { properties: vec![Property {
ident: "W".to_owned(), ident: "W".to_owned(),
values: vec!["dp".to_owned()] values: vec!["dp".to_owned()]
}] }],
}, next: vec![Node {
Node {
properties: vec![ properties: vec![
Property { Property {
ident: "B".to_owned(), ident: "B".to_owned(),
@ -310,114 +331,158 @@ mod test {
ident: "C".to_owned(), ident: "C".to_owned(),
values: vec!["some comments".to_owned()] values: vec!["some comments".to_owned()]
} }
]
}
], ],
sub_sequences: vec![], next: vec![],
} }]
}],
},
); );
} }
#[test] #[test]
fn it_can_parse_a_sequence_with_subsequences() { fn it_can_parse_a_branching_sequence() {
let text = "(;C[a];C[b](;C[c])(;C[d];C[e]))"; let text = "(;C[a];C[b](;C[c])(;C[d];C[e]))";
let (_, sequence) = parse_tree::<nom::error::VerboseError<&str>>(text).unwrap(); let (_, tree) = parse_tree::<nom::error::VerboseError<&str>>(text).unwrap();
let main_sequence = vec![ let expected = Node {
Node {
properties: vec![Property { properties: vec![Property {
ident: "C".to_owned(), ident: "C".to_owned(),
values: vec!["a".to_owned()], values: vec!["a".to_owned()],
}], }],
}, next: vec![Node {
Node {
properties: vec![Property { properties: vec![Property {
ident: "C".to_owned(), ident: "C".to_owned(),
values: vec!["b".to_owned()], values: vec!["b".to_owned()],
}], }],
}, next: vec![
]; Node {
let subsequence_1 = Tree {
sequence: vec![Node {
properties: vec![Property { properties: vec![Property {
ident: "C".to_owned(), ident: "C".to_owned(),
values: vec!["c".to_owned()], values: vec!["c".to_owned()],
}], }],
}], next: vec![],
sub_sequences: vec![], },
};
let subsequence_2 = Tree {
sequence: vec![
Node { Node {
properties: vec![Property { properties: vec![Property {
ident: "C".to_owned(), ident: "C".to_owned(),
values: vec!["d".to_owned()], values: vec!["d".to_owned()],
}], }],
}, next: vec![Node {
Node {
properties: vec![Property { properties: vec![Property {
ident: "C".to_owned(), ident: "C".to_owned(),
values: vec!["e".to_owned()], values: vec!["e".to_owned()],
}], }],
next: vec![],
}],
}, },
], ],
sub_sequences: vec![], }],
}; };
assert_eq!( assert_eq!(tree, expected);
sequence,
Tree {
sequence: main_sequence,
sub_sequences: vec![subsequence_1, subsequence_2],
}
);
} }
#[test] #[test]
fn it_can_parse_example_1() { fn it_can_parse_example_1() {
let (_, ex_tree) = parse_tree::<nom::error::VerboseError<&str>>(EXAMPLE).unwrap(); let (_, tree) = parse_tree::<nom::error::VerboseError<&str>>(EXAMPLE).unwrap();
assert_eq!(ex_tree.sequence.len(), 1);
assert_eq!(ex_tree.sequence[0].properties.len(), 2); let j = Node {
assert_eq!( properties: vec![Property {
ex_tree.sequence[0].properties[0], ident: "C".to_owned(),
values: vec!["j".to_owned()],
}],
next: vec![],
};
let i = Node {
properties: vec![Property {
ident: "C".to_owned(),
values: vec!["i".to_owned()],
}],
next: vec![],
};
let h = Node {
properties: vec![Property {
ident: "C".to_owned(),
values: vec!["h".to_owned()],
}],
next: vec![i],
};
let g = Node {
properties: vec![Property {
ident: "C".to_owned(),
values: vec!["g".to_owned()],
}],
next: vec![h],
};
let f = Node {
properties: vec![Property {
ident: "C".to_owned(),
values: vec!["f".to_owned()],
}],
next: vec![g, j],
};
let e = Node {
properties: vec![Property {
ident: "C".to_owned(),
values: vec!["e".to_owned()],
}],
next: vec![],
};
let d = Node {
properties: vec![Property {
ident: "C".to_owned(),
values: vec!["d".to_owned()],
}],
next: vec![e],
};
let c = Node {
properties: vec![Property {
ident: "C".to_owned(),
values: vec!["c".to_owned()],
}],
next: vec![],
};
let b = Node {
properties: vec![Property {
ident: "C".to_owned(),
values: vec!["b".to_owned()],
}],
next: vec![c, d],
};
let a = Node {
properties: vec![Property {
ident: "C".to_owned(),
values: vec!["a".to_owned()],
}],
next: vec![b],
};
let expected = Node {
properties: vec![
Property { Property {
ident: "FF".to_owned(), ident: "FF".to_owned(),
values: vec!["4".to_owned()] values: vec!["4".to_owned()],
} },
); Property {
assert_eq!(ex_tree.sub_sequences.len(), 2); ident: "C".to_owned(),
values: vec!["root".to_owned()],
},
],
next: vec![a, f],
};
assert_eq!(ex_tree.sub_sequences[0].sequence.len(), 2); assert_eq!(tree, expected);
assert_eq!(
ex_tree.sub_sequences[0].sequence,
vec![
Node {
properties: vec![Property {
ident: "C".to_owned(),
values: vec!["a".to_owned()]
}]
},
Node {
properties: vec![Property {
ident: "C".to_owned(),
values: vec!["b".to_owned()]
}]
},
]
);
assert_eq!(ex_tree.sub_sequences[0].sub_sequences.len(), 2);
} }
#[test] #[test]
fn it_can_regenerate_the_tree() { fn it_can_regenerate_the_tree() {
let (_, tree1) = parse_tree::<nom::error::VerboseError<&str>>(EXAMPLE).unwrap(); let (_, tree1) = parse_tree::<nom::error::VerboseError<&str>>(EXAMPLE).unwrap();
let tree1 = Tree { root: tree1 };
assert_eq!( assert_eq!(
tree1.to_string(), tree1.to_string(),
"(;FF[4]C[root](;C[a];C[b](;C[c])(;C[d];C[e]))(;C[f](;C[g];C[h];C[i])(;C[j])))" "(;FF[4]C[root](;C[a];C[b](;C[c])(;C[d];C[e]))(;C[f](;C[g];C[h];C[i])(;C[j])))"
); );
let (_, tree2) = parse_tree::<nom::error::VerboseError<&str>>(&tree1.to_string()).unwrap(); let (_, tree2) = parse_tree::<nom::error::VerboseError<&str>>(&tree1.to_string()).unwrap();
assert_eq!(tree1, tree2); assert_eq!(tree1, Tree { root: tree2 });
} }
#[test] #[test]