Community links: Help menu (Discord, ArtCraft website, app page, GitHub), Discord button in the header, links on the About and Home screens; engine help.* commands return the URLs (menu clicks open them via the host's browser service)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4878f1c5dd
commit
7d393add4a
|
|
@ -203,6 +203,10 @@ Generated-in-code art is original and has no file to list. This covers the defau
|
|||
| `assets/icons/library.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
|
||||
| `assets/icons/link-2-off.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
|
||||
| `assets/icons/link.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
|
||||
| `assets/icons/external-link.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | Help → app page link |
|
||||
| `assets/icons/git-branch.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | Help → GitHub link |
|
||||
| `assets/icons/globe.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | Help → website link |
|
||||
| `assets/icons/message-circle.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | Discord button and Help → Discord link |
|
||||
| `assets/icons/lock-open.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
|
||||
| `assets/icons/lock.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
|
||||
| `assets/icons/map.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
|
||||
|
|
|
|||
|
|
@ -793,6 +793,17 @@ dependencies = [
|
|||
"objc2 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.3"
|
||||
|
|
@ -1234,6 +1245,15 @@ version = "0.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.34"
|
||||
|
|
@ -1621,6 +1641,110 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"utf8_iter",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"smallvec",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
"icu_properties_data",
|
||||
"icu_provider",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
|
||||
dependencies = [
|
||||
"idna_adapter",
|
||||
"smallvec",
|
||||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.10"
|
||||
|
|
@ -1916,6 +2040,12 @@ version = "0.12.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
version = "1.0.0"
|
||||
|
|
@ -2705,6 +2835,15 @@ dependencies = [
|
|||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
|
|
@ -3612,6 +3751,16 @@ dependencies = [
|
|||
"strict-num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.13.3"
|
||||
|
|
@ -3784,6 +3933,18 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "usvg"
|
||||
version = "0.45.1"
|
||||
|
|
@ -3834,6 +3995,12 @@ dependencies = [
|
|||
"xmlwriter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.26.1"
|
||||
|
|
@ -3858,6 +4025,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"vectorcraft-engine",
|
||||
"vectorcraft-ui-egui",
|
||||
"webbrowser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4455,6 +4623,22 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webbrowser"
|
||||
version = "1.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62c35be770821a214dbc362fc26908c853e776c0004294d0b10b8a6bad582f94"
|
||||
dependencies = [
|
||||
"jni",
|
||||
"log",
|
||||
"ndk-context",
|
||||
"objc2 0.6.4",
|
||||
"objc2-app-kit 0.3.2",
|
||||
"objc2-foundation 0.3.2",
|
||||
"url",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.12"
|
||||
|
|
@ -5007,6 +5191,12 @@ dependencies = [
|
|||
"read-fonts 0.39.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.21.0"
|
||||
|
|
@ -5263,6 +5453,39 @@ dependencies = [
|
|||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.8"
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ eframe = { version = "0.36", default-features = false }
|
|||
egui_kittest = "0.36"
|
||||
rfd = "0.17"
|
||||
arboard = { version = "3.6", default-features = false }
|
||||
webbrowser = "1"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unsafe_code = "deny"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ log = { workspace = true }
|
|||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
rfd = { workspace = true }
|
||||
arboard = { workspace = true }
|
||||
webbrowser = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
muda = { version = "0.21", default-features = false }
|
||||
|
|
|
|||
|
|
@ -108,6 +108,10 @@ fn services() -> Services {
|
|||
write: Some(Box::new(|p: &str, b: &[u8]| std::fs::write(p, b).map_err(|e| e.to_string()))),
|
||||
// Menu-bar Paste never sees egui's Paste event, so read the clipboard directly.
|
||||
clipboard_read: Some(Box::new(|| arboard::Clipboard::new().ok()?.get_text().ok())),
|
||||
// Help → Discord / website / GitHub, the Discord button, About and Home links.
|
||||
open_url: Some(Box::new(|url: &str| {
|
||||
let _ = webbrowser::open(url);
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
<!-- @license lucide-static v1.49.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-external-link"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M15 3h6v6" />
|
||||
<path d="M10 14 21 3" />
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 416 B |
|
|
@ -0,0 +1,17 @@
|
|||
<!-- @license lucide-static v1.49.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-git-branch"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M15 6a9 9 0 0 0-9 9V3" />
|
||||
<circle cx="18" cy="6" r="3" />
|
||||
<circle cx="6" cy="18" r="3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 394 B |
|
|
@ -0,0 +1,17 @@
|
|||
<!-- @license lucide-static v1.49.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-globe"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
|
||||
<path d="M2 12h20" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 407 B |
|
|
@ -0,0 +1,15 @@
|
|||
<!-- @license lucide-static v1.49.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-message-circle"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 432 B |
|
|
@ -0,0 +1,51 @@
|
|||
//! Help → community and project links (the ArtCraft Discord, website, this app's page and its
|
||||
//! GitHub repository). The commands return the URL; the frontend opens it.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// The app's id on getartcraft.com and GitHub.
|
||||
pub const APP_ID: &str = "vectorcraft";
|
||||
pub const DISCORD_URL: &str = "https://discord.gg/artcraft";
|
||||
pub const WEBSITE_URL: &str = "https://getartcraft.com";
|
||||
|
||||
/// This app's page on the website.
|
||||
pub fn app_page_url() -> String {
|
||||
format!("{WEBSITE_URL}/apps/{APP_ID}")
|
||||
}
|
||||
|
||||
/// This app's source repository.
|
||||
pub fn github_url() -> String {
|
||||
format!("https://github.com/storytold/{APP_ID}")
|
||||
}
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
cmd!(query "help.discord", "Join Our Discord", ["Help"], None, "{} → {url} the ArtCraft community Discord", always, |_, _| url(DISCORD_URL.into())),
|
||||
cmd!(query "help.website", "ArtCraft Website", ["Help"], None, "{} → {url}", always, |_, _| url(WEBSITE_URL.into())),
|
||||
cmd!(query "help.appPage", "VectorCraft on getartcraft.com", ["Help"], None, "{} → {url} this app's page", always, |_, _| url(app_page_url())),
|
||||
cmd!(query "help.github", "VectorCraft on GitHub", ["Help"], None, "{} → {url} source code, issues and releases", always, |_, _| url(github_url())),
|
||||
cmd!(query "help.links", "Links", [], None, "{} → {discord, website, appPage, github}", always, |_, _| {
|
||||
Ok(json!({ "discord": DISCORD_URL, "website": WEBSITE_URL, "appPage": app_page_url(), "github": github_url() }))
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
fn url(u: String) -> Result<Value> {
|
||||
Ok(json!({ "url": u }))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn links() {
|
||||
let mut s = Session::new();
|
||||
assert_eq!(s.execute("help.discord", &json!({})).unwrap()["url"], "https://discord.gg/artcraft");
|
||||
assert_eq!(s.execute("help.appPage", &json!({})).unwrap()["url"], "https://getartcraft.com/apps/vectorcraft");
|
||||
assert_eq!(s.execute("help.github", &json!({})).unwrap()["url"], "https://github.com/storytold/vectorcraft");
|
||||
assert_eq!(s.execute("help.links", &json!({})).unwrap()["website"], "https://getartcraft.com");
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ mod edit;
|
|||
mod effectcmd;
|
||||
mod file;
|
||||
mod fonts;
|
||||
pub mod help;
|
||||
mod layer;
|
||||
mod live;
|
||||
pub(crate) mod menucmds;
|
||||
|
|
@ -156,6 +157,7 @@ pub fn command_specs() -> &'static [CommandSpec] {
|
|||
v.extend(textedit::specs());
|
||||
v.extend(textstyles::specs());
|
||||
v.extend(fonts::specs());
|
||||
v.extend(help::specs());
|
||||
v.extend(threads::specs());
|
||||
v.extend(docmenu::specs());
|
||||
v.extend(docinfo::specs());
|
||||
|
|
|
|||
|
|
@ -851,6 +851,10 @@ fn home(app: &mut VectorcraftApp, ui: &mut Ui, rect: egui::Rect) {
|
|||
}
|
||||
}
|
||||
});
|
||||
ui.add_space(28.0);
|
||||
ui.label(egui::RichText::new("Community").font(theme::semibold(14.0)).color(t.text));
|
||||
ui.add_space(10.0);
|
||||
crate::community::links(app, ui);
|
||||
}
|
||||
|
||||
fn kurbo_flatten(p: &BezPath, tol: f64, f: &mut impl FnMut(PathEl)) {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ pub fn app_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
|||
menus::menu_bar(app, ui);
|
||||
}
|
||||
let full = ui.max_rect();
|
||||
let right = egui::Rect::from_min_max(egui::pos2(full.right() - 340.0, full.top()), full.right_bottom());
|
||||
let right = egui::Rect::from_min_max(egui::pos2(full.right() - 440.0, full.top()), full.right_bottom());
|
||||
let mut rui = ui.new_child(egui::UiBuilder::new().max_rect(right).layout(egui::Layout::right_to_left(egui::Align::Center)));
|
||||
let ui = &mut rui;
|
||||
// Workspace switcher: shows the current workspace, opens Window → Workspace.
|
||||
|
|
@ -52,6 +52,8 @@ pub fn app_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
|||
app.ui.palette_open = true;
|
||||
app.ui.palette_query.clear();
|
||||
}
|
||||
ui.add_space(10.0);
|
||||
crate::community::discord_button(app, ui, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
//! Community and project links: the Discord button (header, About, Home) and the link list.
|
||||
//! Icons are Lucide's (ISC); Discord's own logo is a trademark, so the button uses a chat icon
|
||||
//! on Discord's colour with the word "Discord".
|
||||
|
||||
use egui::{Color32, CornerRadius, Sense, Ui, vec2};
|
||||
|
||||
use crate::theme::{self, Tokens};
|
||||
use crate::{VectorcraftApp, icons};
|
||||
|
||||
/// Discord's brand colour (a colour, not a logo).
|
||||
const DISCORD: Color32 = Color32::from_rgb(0x58, 0x65, 0xF2);
|
||||
|
||||
/// A Discord pill button: compact for the header, large for About / Home.
|
||||
pub fn discord_button(app: &mut VectorcraftApp, ui: &mut Ui, large: bool) -> egui::Response {
|
||||
let (h, font, label) = if large { (36.0, theme::semibold(14.0), "Join our Discord") } else { (24.0, theme::semibold(12.0), "Discord") };
|
||||
let galley = ui.painter().layout_no_wrap(label.to_string(), font, Color32::WHITE);
|
||||
let icon = h * 0.55;
|
||||
let w = galley.size().x + icon + h * 0.9;
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(w, h), Sense::click());
|
||||
let fill = if resp.hovered() { DISCORD.gamma_multiply(0.85) } else { DISCORD };
|
||||
ui.painter().rect_filled(r, CornerRadius::same((h / 2.0) as u8), fill);
|
||||
let ir = egui::Rect::from_center_size(r.left_center() + vec2(h * 0.35 + icon / 2.0, 0.0), vec2(icon, icon));
|
||||
icons::paint(ui, "message-circle", ir, Color32::WHITE);
|
||||
ui.painter().galley(egui::pos2(ir.right() + h * 0.2, r.center().y - galley.size().y / 2.0), galley, Color32::WHITE);
|
||||
let resp = resp.on_hover_text("Join the ArtCraft community on Discord (discord.gg/artcraft)");
|
||||
if resp.clicked() {
|
||||
app.open_link("help.discord");
|
||||
}
|
||||
resp
|
||||
}
|
||||
|
||||
/// One link row: icon, label, opens the command's URL.
|
||||
fn link(app: &mut VectorcraftApp, ui: &mut Ui, icon: &str, label: &str, cmd: &str, url: &str) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
ui.horizontal(|ui| {
|
||||
icons::icon(ui, icon, 16.0, t.icon);
|
||||
let r = ui.add(egui::Button::new(egui::RichText::new(label).color(t.accent).size(13.0)).frame(false)).on_hover_text(url);
|
||||
if r.clicked() {
|
||||
app.open_link(cmd);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// The Discord button and the website / app page / GitHub links.
|
||||
pub fn links(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
discord_button(app, ui, true);
|
||||
ui.add_space(8.0);
|
||||
let l = app.session.execute("help.links", &serde_json::json!({})).unwrap_or_default();
|
||||
let s = |k: &str| l[k].as_str().unwrap_or("").to_string();
|
||||
link(app, ui, "globe", "ArtCraft website", "help.website", &s("website"));
|
||||
link(app, ui, "external-link", "VectorCraft on getartcraft.com", "help.appPage", &s("appPage"));
|
||||
link(app, ui, "git-branch", "Source code on GitHub", "help.github", &s("github"));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn link_clicks_open_the_right_urls() {
|
||||
use std::sync::{Arc, Mutex};
|
||||
let opened = Arc::new(Mutex::new(vec![]));
|
||||
let o = opened.clone();
|
||||
let services = crate::Services { open_url: Some(Box::new(move |u: &str| o.lock().unwrap().push(u.to_string()))), ..Default::default() };
|
||||
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), services);
|
||||
for id in ["help.discord", "help.website", "help.appPage", "help.github"] {
|
||||
crate::menus::invoke(&mut app, id, serde_json::json!({}));
|
||||
}
|
||||
assert_eq!(
|
||||
*opened.lock().unwrap(),
|
||||
[
|
||||
"https://discord.gg/artcraft",
|
||||
"https://getartcraft.com",
|
||||
"https://getartcraft.com/apps/vectorcraft",
|
||||
"https://github.com/storytold/vectorcraft"
|
||||
]
|
||||
);
|
||||
// The control channel / MCP path returns the URL without opening a browser.
|
||||
let v = app.run("help.discord", serde_json::json!({})).unwrap();
|
||||
assert_eq!(v["url"], "https://discord.gg/artcraft");
|
||||
assert_eq!(opened.lock().unwrap().len(), 4);
|
||||
let ctx = egui::Context::default();
|
||||
theme::install_fonts(&ctx);
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| links(&mut app, ui));
|
||||
out.textures_delta.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -251,10 +251,26 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
|||
.resizable(false)
|
||||
.open(&mut open)
|
||||
.anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
|
||||
.frame(egui::Frame::window(&ctx.global_style()).inner_margin(egui::Margin::same(18)))
|
||||
.show(ctx, |ui| {
|
||||
ui.label(egui::RichText::new("VectorCraft").font(theme::semibold(22.0)));
|
||||
ui.label(format!("Version {} — open-source vector illustration in pure Rust.", env!("CARGO_PKG_VERSION")));
|
||||
ui.label("MIT OR Apache-2.0. Fonts: Source Sans 3, Inter, JetBrains Mono (OFL). Icons: Lucide (ISC) + VectorCraft.");
|
||||
ui.set_width(380.0);
|
||||
ui.horizontal(|ui| {
|
||||
let (r, _) = ui.allocate_exact_size(egui::vec2(44.0, 44.0), egui::Sense::hover());
|
||||
crate::brand::paint_mark(ui.painter(), r);
|
||||
ui.vertical(|ui| {
|
||||
ui.label(egui::RichText::new("VectorCraft").font(theme::semibold(22.0)));
|
||||
ui.label(format!("Version {} — open-source vector illustration in pure Rust.", env!("CARGO_PKG_VERSION")));
|
||||
});
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
crate::community::links(app, ui);
|
||||
ui.add_space(12.0);
|
||||
ui.label(
|
||||
egui::RichText::new(
|
||||
"Part of ArtCraft. MIT OR Apache-2.0. Fonts: Source Sans 3, Inter, JetBrains Mono (OFL). Icons: Lucide (ISC) + VectorCraft.",
|
||||
)
|
||||
.size(11.0),
|
||||
);
|
||||
});
|
||||
app.ui.about = open;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,6 +182,10 @@ pub static ICONS: &[(&str, &[u8])] = &[
|
|||
("layout-grid", include_bytes!("../../../assets/icons/layout-grid.svg")),
|
||||
("library", include_bytes!("../../../assets/icons/library.svg")),
|
||||
("link", include_bytes!("../../../assets/icons/link.svg")),
|
||||
("external-link", include_bytes!("../../../assets/icons/external-link.svg")),
|
||||
("git-branch", include_bytes!("../../../assets/icons/git-branch.svg")),
|
||||
("globe", include_bytes!("../../../assets/icons/globe.svg")),
|
||||
("message-circle", include_bytes!("../../../assets/icons/message-circle.svg")),
|
||||
("link-2-off", include_bytes!("../../../assets/icons/link-2-off.svg")),
|
||||
("lock", include_bytes!("../../../assets/icons/lock.svg")),
|
||||
("lock-open", include_bytes!("../../../assets/icons/lock-open.svg")),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
mod brand;
|
||||
pub mod canvas;
|
||||
pub mod chrome;
|
||||
pub mod community;
|
||||
pub mod control;
|
||||
pub mod cursors;
|
||||
pub mod dialogs;
|
||||
|
|
@ -44,6 +45,9 @@ pub type WriteFn = Box<dyn FnMut(&str, &[u8]) -> Result<(), String>>;
|
|||
pub type Inbox = Arc<Mutex<Vec<(String, Vec<u8>)>>>;
|
||||
pub type DownloadFn = Box<dyn FnMut(&str, &[u8])>;
|
||||
|
||||
/// Opens a URL in the system browser.
|
||||
pub type OpenUrlFn = Box<dyn FnMut(&str)>;
|
||||
|
||||
/// Platform services injected by the host app (desktop or web).
|
||||
#[derive(Default)]
|
||||
pub struct Services {
|
||||
|
|
@ -62,6 +66,8 @@ pub struct Services {
|
|||
/// Read the system clipboard's text (desktop). Without it, pasted text only arrives with
|
||||
/// egui's Paste event (web, and keyboard paste everywhere).
|
||||
pub clipboard_read: Option<Box<dyn FnMut() -> Option<String>>>,
|
||||
/// Open a URL in the system browser (desktop). Without it, egui opens it (a new tab on the web).
|
||||
pub open_url: Option<OpenUrlFn>,
|
||||
}
|
||||
|
||||
/// Cached canvas raster.
|
||||
|
|
@ -131,6 +137,8 @@ pub struct VectorcraftApp {
|
|||
clipboard_out: Option<String>,
|
||||
clipboard_published: Option<String>,
|
||||
pub(crate) clipboard_in: Option<String>,
|
||||
/// A URL to open through egui next frame (when the host has no `open_url` service).
|
||||
pending_url: Option<String>,
|
||||
}
|
||||
|
||||
impl VectorcraftApp {
|
||||
|
|
@ -157,6 +165,7 @@ impl VectorcraftApp {
|
|||
clipboard_out: None,
|
||||
clipboard_published: None,
|
||||
clipboard_in: None,
|
||||
pending_url: None,
|
||||
control_rx: None,
|
||||
pending_screenshots: vec![],
|
||||
queued_screenshots: vec![],
|
||||
|
|
@ -244,6 +253,25 @@ impl VectorcraftApp {
|
|||
}
|
||||
}
|
||||
|
||||
/// Open a link in the browser (Help → Discord, website, GitHub…).
|
||||
pub fn open_url(&mut self, url: &str) {
|
||||
match self.services.open_url.as_mut() {
|
||||
Some(open) => open(url),
|
||||
None => self.pending_url = Some(url.to_string()),
|
||||
}
|
||||
self.ui.status = format!("Opened {url}");
|
||||
}
|
||||
|
||||
/// Run a Help link command and open the URL it returns.
|
||||
pub fn open_link(&mut self, id: &str) {
|
||||
if let Ok(v) = self.run(id, serde_json::json!({}))
|
||||
&& let Some(u) = v["url"].as_str()
|
||||
{
|
||||
let u = u.to_string();
|
||||
self.open_url(&u);
|
||||
}
|
||||
}
|
||||
|
||||
/// Select a tool (also used by the toolbar and shortcuts).
|
||||
pub fn select_tool(&mut self, id: &str) {
|
||||
let v = self.view_info();
|
||||
|
|
@ -388,6 +416,9 @@ impl VectorcraftApp {
|
|||
}
|
||||
self.collect_screenshots(ctx);
|
||||
self.issue_screenshots(ctx);
|
||||
if let Some(u) = self.pending_url.take() {
|
||||
ctx.open_url(egui::OpenUrl::new_tab(u));
|
||||
}
|
||||
if let Some(t) = self.clipboard_out.take() {
|
||||
ctx.copy_text(t);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -512,6 +512,7 @@ pub fn menu_tree() -> Vec<(&'static str, Vec<Item>)> {
|
|||
"VectorCraft",
|
||||
vec![
|
||||
c("About VectorCraft", "help.about"),
|
||||
c("Join Our Discord", "help.discord"),
|
||||
Sep,
|
||||
c("Settings…", "edit.preferences"),
|
||||
Sep,
|
||||
|
|
@ -1058,7 +1059,20 @@ pub fn menu_tree() -> Vec<(&'static str, Vec<Item>)> {
|
|||
sub("Symbol Libraries", vec![todo("Arrows"), todo("Charts"), todo("Web Buttons and Bars")]),
|
||||
],
|
||||
),
|
||||
("Help", vec![c("Search Commands…", "help.commandPalette"), todos("VectorCraft Help…", "F1"), Sep, c("About VectorCraft", "help.about")]),
|
||||
(
|
||||
"Help",
|
||||
vec![
|
||||
c("Join Our Discord", "help.discord"),
|
||||
c("ArtCraft Website", "help.website"),
|
||||
c("VectorCraft on getartcraft.com", "help.appPage"),
|
||||
c("VectorCraft on GitHub", "help.github"),
|
||||
Sep,
|
||||
c("Search Commands…", "help.commandPalette"),
|
||||
todos("VectorCraft Help…", "F1"),
|
||||
Sep,
|
||||
c("About VectorCraft", "help.about"),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -1186,6 +1200,11 @@ pub fn invoke(app: &mut VectorcraftApp, id: &str, p: Value) {
|
|||
}
|
||||
return;
|
||||
}
|
||||
// Help links: the command returns the URL; a menu click opens it (agents just get the URL).
|
||||
if matches!(id, "help.discord" | "help.website" | "help.appPage" | "help.github") {
|
||||
app.open_link(id);
|
||||
return;
|
||||
}
|
||||
if let Err(e) = app.run(id, p) {
|
||||
app.status(e);
|
||||
} else if matches!(id, "object.pattern.make" | "object.pattern.edit") {
|
||||
|
|
|
|||
Loading…
Reference in New Issue