Create a cyberpunk-style splash screen with a title and a countdown #39

Merged
savanni merged 11 commits from cyberpunk-display into main 2023-04-13 23:14:29 +00:00
5 changed files with 4264 additions and 0 deletions

729
cyberpunk-splash/Cargo.lock generated Normal file
View File

@ -0,0 +1,729 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cairo-rs"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8af54f5d48af1226928adc1f57edd22f5df1349e7da1fc96ae15cf43db0e871"
dependencies = [
"bitflags",
"cairo-sys-rs",
"glib",
"libc",
"once_cell",
"thiserror",
]
[[package]]
name = "cairo-sys-rs"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55382a01d30e5e53f185eee269124f5e21ab526595b872751278dfbb463594e"
dependencies = [
"glib-sys",
"libc",
"system-deps",
]
[[package]]
name = "cfg-expr"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a35b255461940a32985c627ce82900867c61db1659764d3675ea81963f72a4c6"
dependencies = [
"smallvec",
]
[[package]]
name = "cyberpunk-splash"
version = "0.1.0"
dependencies = [
"cairo-rs",
"gio",
"glib",
"gtk4",
]
[[package]]
name = "field-offset"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3cf3a800ff6e860c863ca6d4b16fd999db8b752819c1606884047b73e468535"
dependencies = [
"memoffset",
"rustc_version",
]
[[package]]
name = "futures-channel"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-executor"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-macro"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.13",
]
[[package]]
name = "futures-task"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]]
name = "futures-util"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
"futures-core",
"futures-macro",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "gdk-pixbuf"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b023fbe0c6b407bd3d9805d107d9800da3829dc5a676653210f1d5f16d7f59bf"
dependencies = [
"bitflags",
"gdk-pixbuf-sys",
"gio",
"glib",
"libc",
"once_cell",
]
[[package]]
name = "gdk-pixbuf-sys"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b41bd2b44ed49d99277d3925652a163038bd5ed943ec9809338ffb2f4391e3b"
dependencies = [
"gio-sys",
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
]
[[package]]
name = "gdk4"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3abf96408a26e3eddf881a7f893a1e111767137136e347745e8ea6ed12731ff"
dependencies = [
"bitflags",
"cairo-rs",
"gdk-pixbuf",
"gdk4-sys",
"gio",
"glib",
"libc",
"pango",
]
[[package]]
name = "gdk4-sys"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc92aa1608c089c49393d014c38ac0390d01e4841e1fedaa75dbcef77aaed64"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
"gio-sys",
"glib-sys",
"gobject-sys",
"libc",
"pango-sys",
"pkg-config",
"system-deps",
]
[[package]]
name = "gio"
version = "0.17.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2261a3b4e922ec676d1c27ac466218c38cf5dcb49a759129e54bb5046e442125"
dependencies = [
"bitflags",
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"gio-sys",
"glib",
"libc",
"once_cell",
"pin-project-lite",
"smallvec",
"thiserror",
]
[[package]]
name = "gio-sys"
version = "0.17.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b1d43b0d7968b48455244ecafe41192871257f5740aa6b095eb19db78e362a5"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"winapi",
]
[[package]]
name = "glib"
version = "0.17.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfb53061756195d76969292c2d2e329e01259276524a9bae6c9b73af62854773"
dependencies = [
"bitflags",
"futures-channel",
"futures-core",
"futures-executor",
"futures-task",
"futures-util",
"gio-sys",
"glib-macros",
"glib-sys",
"gobject-sys",
"libc",
"memchr",
"once_cell",
"smallvec",
"thiserror",
]
[[package]]
name = "glib-macros"
version = "0.17.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc4cf346122086f196260783aa58987190dbd5f43bfab01946d2bf9786e8d9ef"
dependencies = [
"anyhow",
"heck",
"proc-macro-crate",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "glib-sys"
version = "0.17.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f00ad0a1bf548e61adfff15d83430941d9e1bb620e334f779edd1c745680a5"
dependencies = [
"libc",
"system-deps",
]
[[package]]
name = "gobject-sys"
version = "0.17.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e75b0000a64632b2d8ca3cf856af9308e3a970844f6e9659bd197f026793d0"
dependencies = [
"glib-sys",
"libc",
"system-deps",
]
[[package]]
name = "graphene-rs"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21cf11565bb0e4dfc2f99d4775b6c329f0d40a2cff9c0066214d31a0e1b46256"
dependencies = [
"glib",
"graphene-sys",
"libc",
]
[[package]]
name = "graphene-sys"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf80a4849a8d9565410a8fec6fc3678e9c617f4ac7be182ca55ab75016e07af9"
dependencies = [
"glib-sys",
"libc",
"pkg-config",
"system-deps",
]
[[package]]
name = "gsk4"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f01ef44fa7cac15e2da9978529383e6bee03e570ba5bf7036b4c10a15cc3a3c"
dependencies = [
"bitflags",
"cairo-rs",
"gdk4",
"glib",
"graphene-rs",
"gsk4-sys",
"libc",
"pango",
]
[[package]]
name = "gsk4-sys"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c07a84fb4dcf1323d29435aa85e2f5f58bef564342bef06775ec7bd0da1f01b0"
dependencies = [
"cairo-sys-rs",
"gdk4-sys",
"glib-sys",
"gobject-sys",
"graphene-sys",
"libc",
"pango-sys",
"system-deps",
]
[[package]]
name = "gtk4"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e30e124b5a605f6f5513db13958bfcd51d746607b20bc7bb718b33e303274ed"
dependencies = [
"bitflags",
"cairo-rs",
"field-offset",
"futures-channel",
"gdk-pixbuf",
"gdk4",
"gio",
"glib",
"graphene-rs",
"gsk4",
"gtk4-macros",
"gtk4-sys",
"libc",
"once_cell",
"pango",
]
[[package]]
name = "gtk4-macros"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f041a797fb098bfb06e432c61738133604bfa3af57f13f1da3b9d46271422ef0"
dependencies = [
"anyhow",
"proc-macro-crate",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "gtk4-sys"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f8283f707b07e019e76c7f2934bdd4180c277e08aa93f4c0d8dd07b7a34e22f"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
"gdk4-sys",
"gio-sys",
"glib-sys",
"gobject-sys",
"graphene-sys",
"gsk4-sys",
"libc",
"pango-sys",
"system-deps",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "libc"
version = "0.2.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "pango"
version = "0.17.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52c280b82a881e4208afb3359a8e7fde27a1b272280981f1f34610bed5770d37"
dependencies = [
"bitflags",
"gio",
"glib",
"libc",
"once_cell",
"pango-sys",
]
[[package]]
name = "pango-sys"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4293d0f0b5525eb5c24734d30b0ed02cd02aa734f216883f376b54de49625de8"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
"once_cell",
"toml_edit",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "semver"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
[[package]]
name = "serde"
version = "1.0.159"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
[[package]]
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
[[package]]
name = "slab"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "system-deps"
version = "6.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555fc8147af6256f3931a36bb83ad0023240ce9cf2b319dec8236fd1f220b05f"
dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml",
"version-compare",
]
[[package]]
name = "thiserror"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.13",
]
[[package]]
name = "toml"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "version-compare"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winnow"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
dependencies = [
"memchr",
]

2836
cyberpunk-splash/Cargo.nix Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
[package]
name = "cyberpunk-splash"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cairo-rs = { version = "0.17" }
gio = { version = "0.17" }
glib = { version = "0.17" }
gtk = { version = "0.6", package = "gtk4" }

View File

@ -0,0 +1,682 @@
use cairo::{
Context, FontSlant, FontWeight, Format, ImageSurface, LineCap, LinearGradient, Pattern,
TextExtents,
};
use glib::{GString, Object};
use gtk::{gdk::Key, prelude::*, subclass::prelude::*, EventControllerKey};
use std::{
cell::RefCell,
rc::Rc,
sync::{Arc, RwLock},
time::{Duration, Instant},
};
const WIDTH: i32 = 1600;
const HEIGHT: i32 = 600;
#[derive(Clone, Copy, Debug)]
enum Event {
Frames(u8),
Time(Duration),
}
#[derive(Clone, Copy, Debug)]
pub enum State {
Running {
last_update: Instant,
deadline: Instant,
timeout: Option<TimeoutAnimation>,
},
Paused {
time_remaining: Duration,
timeout: Option<TimeoutAnimation>,
},
}
impl State {
fn new(countdown: Duration) -> Self {
Self::Paused {
time_remaining: countdown,
timeout: None,
}
}
fn start(&mut self) {
if let Self::Paused {
time_remaining,
timeout,
} = self
{
*self = Self::Running {
last_update: Instant::now(),
deadline: Instant::now() + *time_remaining,
timeout: timeout.clone(),
};
}
}
fn pause(&mut self) {
if let Self::Running {
deadline, timeout, ..
} = self
{
*self = Self::Paused {
time_remaining: *deadline - Instant::now(),
timeout: timeout.clone(),
}
}
}
fn start_pause(&mut self) {
match self {
Self::Running { .. } => self.pause(),
Self::Paused { .. } => self.start(),
}
}
fn run(&mut self, now: Instant) {
if let Self::Running {
last_update,
deadline,
timeout,
} = self
{
*last_update = now;
if let Some(ref mut timeout) = timeout {
// TODO: figure out the actual number of frames
timeout.tick(1);
}
if *last_update > *deadline && timeout.is_none() {
*timeout = Some(TimeoutAnimation {
intensity: 1.,
duration: 1.,
ascending: false,
});
}
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct TimeoutAnimation {
intensity: f64,
duration: f64,
ascending: bool,
}
impl TimeoutAnimation {
fn tick(&mut self, frames_elapsed: u8) {
let step_size = 1. / (self.duration * 60.);
if self.ascending {
self.intensity = self.intensity + step_size * frames_elapsed as f64;
if self.intensity > 1. {
self.intensity = 1.0;
self.ascending = false;
}
} else {
self.intensity = self.intensity - step_size * frames_elapsed as f64;
if self.intensity < 0. {
self.intensity = 0.0;
self.ascending = true;
}
}
}
}
pub struct SplashPrivate {
text: Rc<RefCell<String>>,
background: Rc<RefCell<Pattern>>,
time_extents: Rc<RefCell<Option<TextExtents>>>,
width: Rc<RefCell<i32>>,
height: Rc<RefCell<i32>>,
state: Rc<RefCell<State>>,
}
impl SplashPrivate {
fn set_text(&self, text: String) {
*self.text.borrow_mut() = text;
}
fn set_state(&self, state: State) {
*self.state.borrow_mut() = state;
}
fn redraw_background(&self) {
let background =
ImageSurface::create(Format::Rgb24, *self.width.borrow(), *self.height.borrow())
.unwrap();
let context = Context::new(background).unwrap();
context.push_group();
context.set_source_rgb(0., 0., 0.);
let _ = context.paint();
context.select_font_face("Alegreya Sans SC", FontSlant::Normal, FontWeight::Bold);
context.set_font_size(128.);
let center_x = *self.width.borrow() as f64 / 2.;
let center_y = *self.height.borrow() as f64 / 2.;
let title_extents = context.text_extents(&self.text.borrow()).unwrap();
let title_width = title_extents.width();
let title_height = title_extents.height();
{
let start_length = center_x - title_width / 2. - title_height - 20.;
let title_cutout = AsymLineCutout {
orientation: gtk::Orientation::Horizontal,
start_x: 20.,
start_y: center_y - 20. - title_height / 2.,
start_length,
total_length: *self.width.borrow() as f64 - 120.,
cutout_length: title_width,
height: title_height,
invert: false,
};
context.set_line_cap(LineCap::Round);
context.set_source_rgb(0.7, 0., 1.);
context.set_line_width(2.);
title_cutout.draw(&context);
let _ = context.stroke();
}
{
let title_baseline_x = center_x - title_width / 2.;
let title_baseline_y = center_y - 20.;
let gradient = LinearGradient::new(
title_baseline_x,
title_baseline_y - title_height,
title_baseline_x,
title_baseline_y,
);
gradient.add_color_stop_rgb(0.2, 0.7, 0.0, 1.0);
gradient.add_color_stop_rgb(0.8, 0.2, 0.0, 1.0);
context.move_to(title_baseline_x, title_baseline_y);
let _ = context.set_source(gradient);
let _ = context.show_text(&self.text.borrow());
}
{
context.set_source_rgb(0.7, 0., 1.);
AsymLine {
orientation: gtk::Orientation::Horizontal,
start_x: 100.,
start_y: *self.height.borrow() as f64 / 2. + 100.,
start_length: 400.,
height: 50.,
total_length: 650.,
invert: true,
}
.draw(&context);
let _ = context.stroke();
}
{
context.set_source_rgb(0.7, 0., 1.);
AsymLine {
orientation: gtk::Orientation::Horizontal,
start_x: *self.width.borrow() as f64 / 2. + 100.,
start_y: *self.height.borrow() as f64 / 2. + 200.,
start_length: 600.,
height: 50.,
total_length: 650.,
invert: false,
}
.draw(&context);
let _ = context.stroke();
}
let background = context.pop_group().unwrap();
*self.background.borrow_mut() = background;
}
}
#[glib::object_subclass]
impl ObjectSubclass for SplashPrivate {
const NAME: &'static str = "Splash";
type Type = Splash;
type ParentType = gtk::DrawingArea;
fn new() -> SplashPrivate {
// Set up a default plain black background
let background = ImageSurface::create(Format::Rgb24, WIDTH, HEIGHT).unwrap();
let context = Context::new(background).unwrap();
context.push_group();
context.set_source_rgb(0., 0., 0.);
let _ = context.paint();
let background = context.pop_group().unwrap();
SplashPrivate {
text: Rc::new(RefCell::new(String::from(""))),
background: Rc::new(RefCell::new(background)),
time_extents: Rc::new(RefCell::new(None)),
width: Rc::new(RefCell::new(WIDTH)),
height: Rc::new(RefCell::new(HEIGHT)),
state: Rc::new(RefCell::new(State::new(Duration::ZERO))),
}
}
}
impl ObjectImpl for SplashPrivate {}
impl WidgetImpl for SplashPrivate {}
impl DrawingAreaImpl for SplashPrivate {}
glib::wrapper! {
pub struct Splash(ObjectSubclass<SplashPrivate>) @extends gtk::DrawingArea, gtk::Widget;
}
impl Splash {
pub fn new(text: String, state: State) -> Self {
let s: Self = Object::builder().build();
s.set_width_request(WIDTH);
s.set_height_request(HEIGHT);
s.imp().set_text(text);
s.imp().set_state(state);
s.imp().redraw_background();
s.set_draw_func({
let s = s.clone();
move |_, context, width, height| {
let background = s.imp().background.borrow();
let _ = context.set_source(&*background);
let _ = context.paint();
let state = s.imp().state.borrow().clone();
let time = match state {
State::Running { deadline, .. } => deadline - Instant::now(),
State::Paused { time_remaining, .. } => time_remaining,
};
let minutes = time.as_secs() / 60;
let seconds = time.as_secs() % 60;
let center_x = width as f64 / 2.;
let center_y = height as f64 / 2.;
{
context.select_font_face(
"Alegreya Sans SC",
FontSlant::Normal,
FontWeight::Bold,
);
context.set_font_size(128.);
let time = format!("{:02}' {:02}\"", minutes, seconds);
let time_extents = context.text_extents(&time).unwrap();
let mut saved_extents = s.imp().time_extents.borrow_mut();
if saved_extents.is_none() {
*saved_extents = Some(time_extents.clone());
}
let time_baseline_x = center_x - time_extents.width() / 2.;
let time_baseline_y = center_y + 100.;
let gradient = LinearGradient::new(
time_baseline_x,
time_baseline_y - time_extents.height(),
time_baseline_x,
time_baseline_y,
);
let (running, timeout_animation) = match state {
State::Running { timeout, .. } => (true, timeout.clone()),
State::Paused { timeout, .. } => (false, timeout.clone()),
};
match timeout_animation {
Some(ref animation) => {
gradient.add_color_stop_rgba(0.2, 0.2, 0.0, 1.0, animation.intensity);
gradient.add_color_stop_rgba(0.8, 0.7, 0.0, 1.0, animation.intensity);
let _ = context.set_source(gradient);
}
None => {
if running {
gradient.add_color_stop_rgb(0.2, 0.2, 0.0, 1.0);
gradient.add_color_stop_rgb(0.8, 0.7, 0.0, 1.0);
let _ = context.set_source(gradient);
} else {
context.set_source_rgb(0.3, 0.3, 0.3);
}
}
}
context.move_to(time_baseline_x, time_baseline_y);
let _ = context.show_text(&time);
};
match *s.imp().time_extents.borrow() {
Some(extents) => {
context.set_source_rgb(0.7, 0.0, 1.0);
let time_meter = SlashMeter {
orientation: gtk::Orientation::Horizontal,
start_x: center_x + extents.width() / 2. + 50.,
start_y: center_y + 100.,
count: 5,
fill_count: minutes as u8,
height: 60.,
length: 100.,
};
time_meter.draw(&context);
}
None => {}
}
}
});
s.connect_resize(|s, width, height| {
*s.imp().width.borrow_mut() = width;
*s.imp().height.borrow_mut() = height;
s.imp().redraw_background();
});
s
}
pub fn set_state(&self, state: State) {
self.imp().set_state(state);
self.queue_draw();
}
}
struct AsymLineCutout {
orientation: gtk::Orientation,
start_x: f64,
start_y: f64,
start_length: f64,
total_length: f64,
cutout_length: f64,
height: f64,
invert: bool,
}
impl AsymLineCutout {
fn draw(&self, context: &Context) {
let dodge = if self.invert {
self.height
} else {
-self.height
};
match self.orientation {
gtk::Orientation::Horizontal => {
context.move_to(self.start_x, self.start_y);
context.line_to(self.start_x + self.start_length, self.start_y);
context.line_to(
self.start_x + self.start_length + self.height,
self.start_y + dodge,
);
context.line_to(
self.start_x + self.start_length + self.height + self.cutout_length,
self.start_y + dodge,
);
context.line_to(
self.start_x
+ self.start_length
+ self.height
+ self.cutout_length
+ (self.height / 2.),
self.start_y + dodge / 2.,
);
context.line_to(self.total_length, self.start_y + dodge / 2.);
}
gtk::Orientation::Vertical => {
context.move_to(self.start_x, self.start_y);
context.line_to(self.start_x, self.start_y + self.start_length);
context.line_to(
self.start_x + dodge,
self.start_y + self.start_length + self.height,
);
context.line_to(
self.start_x + dodge,
self.start_y + self.start_length + self.height + self.cutout_length,
);
context.line_to(
self.start_x + dodge / 2.,
self.start_y
+ self.start_length
+ self.height
+ self.cutout_length
+ (self.height / 2.),
);
context.line_to(self.start_x + dodge / 2., self.total_length);
}
_ => panic!("unknown orientation"),
}
}
}
struct AsymLine {
orientation: gtk::Orientation,
start_x: f64,
start_y: f64,
start_length: f64,
height: f64,
total_length: f64,
invert: bool,
}
impl AsymLine {
fn draw(&self, context: &Context) {
let dodge = if self.invert {
self.height
} else {
-self.height
};
match self.orientation {
gtk::Orientation::Horizontal => {
context.move_to(self.start_x, self.start_y);
context.line_to(self.start_x + self.start_length, self.start_y);
context.line_to(
self.start_x + self.start_length + self.height,
self.start_y + dodge,
);
context.line_to(self.start_x + self.total_length, self.start_y + dodge);
}
gtk::Orientation::Vertical => {}
_ => panic!("unknown orientation"),
}
}
}
struct RoundedRectangle {
x: f64,
y: f64,
width: f64,
height: f64,
}
impl RoundedRectangle {
fn draw(&self, context: &Context) {
context.arc(
self.x,
self.y - self.height / 2.,
self.height / 2.,
0.5 * std::f64::consts::PI,
1.5 * std::f64::consts::PI,
);
let _ = context.fill();
context.arc(
self.x + self.width,
self.y - self.height / 2.,
self.height / 2.,
1.5 * std::f64::consts::PI,
0.5 * std::f64::consts::PI,
);
let _ = context.fill();
context.rectangle(self.x, self.y, self.width, -self.height);
let _ = context.fill();
}
}
struct SlashMeter {
orientation: gtk::Orientation,
start_x: f64,
start_y: f64,
count: u8,
fill_count: u8,
height: f64,
length: f64,
}
impl SlashMeter {
fn draw(&self, context: &Context) {
match self.orientation {
gtk::Orientation::Horizontal => {
let angle: f64 = 0.8;
let run = self.height / angle.tan();
let width = self.length as f64 / (self.count as f64 * 2.);
for c in 0..self.count {
context.set_line_width(1.);
let start_x = self.start_x + c as f64 * width * 2.;
context.move_to(start_x, self.start_y);
context.line_to(start_x + run, self.start_y - self.height);
context.line_to(start_x + run + width, self.start_y - self.height);
context.line_to(start_x + width, self.start_y);
context.line_to(start_x, self.start_y);
if c < self.fill_count {
let _ = context.fill();
} else {
let _ = context.stroke();
}
}
}
gtk::Orientation::Vertical => {}
_ => panic!("unknown orientation"),
}
}
}
fn main() {
let app = gtk::Application::builder()
.application_id("com.luminescent-dreams.cyberpunk-splash")
.flags(gio::ApplicationFlags::HANDLES_OPEN)
.build();
app.add_main_option(
"title",
glib::char::Char::from(b't'),
glib::OptionFlags::IN_MAIN,
glib::OptionArg::String,
"",
None,
);
app.add_main_option(
"countdown",
glib::char::Char::from(b'c'),
glib::OptionFlags::IN_MAIN,
glib::OptionArg::String,
"",
None,
);
let state = Arc::new(RwLock::new(State::new(Duration::from_secs(5 * 60))));
let title = Arc::new(RwLock::new("".to_owned()));
app.connect_command_line(|_, _args| {
println!("connect_command_line");
1
});
app.connect_handle_local_options({
let title = title.clone();
let state = state.clone();
move |_, options| {
println!("connect_handle_local_options");
*title.write().unwrap() = options.lookup::<String>("title").unwrap().unwrap();
let countdown = match options.lookup::<String>("countdown") {
Ok(Some(countdown_str)) => {
let parts = countdown_str.split(':').collect::<Vec<&str>>();
let duration = match parts.len() {
2 => {
let minutes = parts[0].parse::<u64>().unwrap();
let seconds = parts[1].parse::<u64>().unwrap();
Duration::from_secs(minutes * 60 + seconds)
}
1 => {
let seconds = parts[1].parse::<u64>().unwrap();
Duration::from_secs(seconds)
}
_ => Duration::from_secs(300),
};
duration
}
_ => Duration::from_secs(300),
};
match *state.write().unwrap() {
State::Running {
ref mut deadline, ..
} => *deadline = Instant::now() + countdown,
State::Paused {
ref mut time_remaining,
..
} => *time_remaining = countdown,
}
-1
}
});
app.connect_open(move |app, files, args| {
println!("called open");
println!("files: {}", files.len());
println!("args: {}", args);
app.activate();
});
app.connect_activate(move |app| {
let (gtk_tx, gtk_rx) =
gtk::glib::MainContext::channel::<State>(gtk::glib::PRIORITY_DEFAULT);
let window = gtk::ApplicationWindow::new(app);
window.present();
let splash = Splash::new(title.read().unwrap().clone(), state.read().unwrap().clone());
window.set_child(Some(&splash));
window.connect_maximized_notify(|window| {
window.fullscreen();
});
let keyboard_events = EventControllerKey::new();
keyboard_events.connect_key_released({
let window = window.clone();
let state = state.clone();
move |_, key, _, _| {
let name = key
.name()
.map(|s| s.as_str().to_owned())
.unwrap_or("".to_owned());
match name.as_ref() {
"Escape" => window.unfullscreen(),
"space" => state.write().unwrap().start_pause(),
_ => {}
}
}
});
window.add_controller(keyboard_events);
gtk_rx.attach(None, move |state| {
splash.set_state(state);
Continue(true)
});
std::thread::spawn({
let state = state.clone();
move || {
state.write().unwrap().start();
loop {
std::thread::sleep(Duration::from_millis(1000 / 60));
state.write().unwrap().run(Instant::now());
let _ = gtk_tx.send(state.read().unwrap().clone());
}
}
});
});
app.run();
}

View File

@ -78,6 +78,11 @@
rootFeatures = [ "screenplay" ]; rootFeatures = [ "screenplay" ];
release = false; release = false;
}).rootCrate.build; }).rootCrate.build;
cyberpunk-splash = (import ./cyberpunk-splash/Cargo.nix {
inherit pkgs;
buildRustCrateForPkgs = customBuildInfo;
release = true;
}).rootCrate.build;
}; };
}; };
} }