Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d5fb5136f7 | |||
| f162240fcf | |||
| 8033af3f03 | |||
| 14b6964db0 | |||
| 61201a1173 |
@@ -0,0 +1,13 @@
|
||||
# Strip upstream (ArtCraft / Discord) branding. Run: perl tools/craft-skills/skills/debrand/debrand.pl . (--check in CI)
|
||||
delete docs/brand
|
||||
# MIT/Apache texts stay verbatim. The copyright holder line stays everywhere (legal requirement).
|
||||
keep ^(LICENSE-(MIT|APACHE)|\.debrand)$
|
||||
dropline ^ATTRIBUTION\.md$ :: \|\s*`docs/brand/
|
||||
sub (?s)\nThe ArtCraft name, wordmark and logos in docs/brand/.*?LICENSE-brand\.txt\.\n =>
|
||||
sub (?s)\nThe ArtCraft name, wordmark and logos in \[`docs/brand/`\].*?must remove them\.\n =>
|
||||
sub The one exception is the ArtCraft brand files in `docs/brand/`: ArtCraft trademarks, not open source, used under `docs/brand/LICENSE-brand\.txt`\. =>
|
||||
sub \| \[Website\]\(https://getartcraft\.com/apps/\w+\) (?=\|) =>
|
||||
sub https://getartcraft\.com/apps/gridcraft => https://github.com/storytold/gridcraft
|
||||
sub https://discord\.gg/artcraft => https://github.com/storytold/gridcraft/issues
|
||||
forbid (?i)artcraft(?! Team and the GridCraft contributors)
|
||||
forbid (?i)discord
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "tools/craft-skills"]
|
||||
path = tools/craft-skills
|
||||
url = https://git.marcospaulo.dev.br/storytold/craft-skills.git
|
||||
@@ -17,7 +17,6 @@ Standards and learnings shared across the crafting apps live in `../../craftrule
|
||||
- Prefer art generated in code: the UI icon set (`crates/ui-egui/src/icons.rs`), table and cell style palettes, themes and sample workbooks are drawn/defined in code and original.
|
||||
- Never copy Office's icons, ribbon artwork, theme XML, table-style definitions, templates, clip art or wording beyond feature names. Excel feature *names* (menu labels, function names) are fine.
|
||||
- Screenshots of Microsoft software are never committed (they live only in `plan/excel/screenshots/`).
|
||||
- The ArtCraft brand files in `docs/brand/` are ArtCraft trademarks under `docs/brand/LICENSE-brand.txt`, the one exception.
|
||||
|
||||
## Never crash
|
||||
People trust GridCraft with their numbers; a crash loses their work. **This outranks feature work.** Standard: [`craftrules/standards/never-crash.md`](https://github.com/storytold/craftrules/blob/main/standards/never-crash.md).
|
||||
|
||||
@@ -2,21 +2,12 @@
|
||||
|
||||
Every non-code asset in this repository (images, icons, fonts, example files, presets) is listed here with its author, source and licence. `cargo xtask assets` (part of `cargo xtask ci`) fails if an asset file is missing from this table.
|
||||
|
||||
**Policy (mandatory):** GridCraft contains **no Microsoft, Adobe, Avid or Autodesk iconography, images, artwork, fonts, templates, themes, table styles or sample files**. Every asset is original work by GridCraft contributors or third-party material under an open licence (OSI open source, public domain / CC0, or Creative Commons that allows redistribution). Screenshots of Microsoft software are never committed. Font files are not added here: shared fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), an optional build input (`CRAFT_FONTS_DIR`). The one exception is the ArtCraft brand files in `docs/brand/`: ArtCraft trademarks, not open source, used under `docs/brand/LICENSE-brand.txt`.
|
||||
**Policy (mandatory):** GridCraft contains **no Microsoft, Adobe, Avid or Autodesk iconography, images, artwork, fonts, templates, themes, table styles or sample files**. Every asset is original work by GridCraft contributors or third-party material under an open licence (OSI open source, public domain / CC0, or Creative Commons that allows redistribution). Screenshots of Microsoft software are never committed. Font files are not added here: shared fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), an optional build input (`CRAFT_FONTS_DIR`).
|
||||
|
||||
Generated-in-code art is original and has no file to list: the UI icon set (`crates/ui-egui/src/icons.rs`), themes (`crates/engine/src/cmd/view.rs`), cell styles (`crates/engine/src/cmd/format.rs`), table styles (`crates/engine/src/tables.rs`), chart palettes and the sample workbooks (`crates/engine/src/sample.rs`).
|
||||
|
||||
| Asset | Author | Source | Licence | Notes |
|
||||
|---|---|---|---|---|
|
||||
| `docs/brand/artcraft-logo.svg` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms (`docs/brand/LICENSE-brand.txt`) | Trademark, not open source |
|
||||
| `docs/brand/artcraft-logo.png` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms | Trademark |
|
||||
| `docs/brand/artcraft-logo-white.svg` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms | Trademark |
|
||||
| `docs/brand/artcraft-logo-white.png` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms | Trademark |
|
||||
| `docs/brand/artcraft-mark.svg` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms | Trademark |
|
||||
| `docs/brand/artcraft-mark.png` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms | Trademark |
|
||||
| `docs/brand/artcraft-mark-black.svg` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms | Trademark |
|
||||
| `docs/brand/artcraft-mark-black.png` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms | Trademark |
|
||||
| `docs/brand/LICENSE-brand.txt` | (licence text) | craftrules | — | |
|
||||
| `assets/app-icon/gridcraft.svg` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | PLACEHOLDER app icon (ledger page); replaced by the owner's creature art later |
|
||||
| `packaging/macos/dmg/background.svg` | @XusBadia | original (hand-written SVG, derived from the GridCraft app icon `assets/app-icon/gridcraft.svg`, referenced via `<image>`, not copied; text is outlined glyph paths; `packaging/macos/dmg/generate.py` renders it to `background.tiff`) | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | GridCraft macOS DMG window background for the Finder window; added 2026-10-08 |
|
||||
| `packaging/macos/dmg/background.tiff` | @XusBadia | Rendered from `packaging/macos/dmg/background.svg` by `packaging/macos/dmg/generate.py` | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | 1x + 2x HiDPI TIFF for the macOS DMG window; includes the app icon (see its row) and LittleCMS's built-in sRGB profile ("No copyright, use freely") |
|
||||
|
||||
@@ -17,7 +17,6 @@ Standards and learnings shared across the crafting apps live in `../../craftrule
|
||||
- Prefer art generated in code: the UI icon set (`crates/ui-egui/src/icons.rs`), table and cell style palettes, themes and sample workbooks are drawn/defined in code and original.
|
||||
- Never copy Office's icons, ribbon artwork, theme XML, table-style definitions, templates, clip art or wording beyond feature names. Excel feature *names* (menu labels, function names) are fine.
|
||||
- Screenshots of Microsoft software are never committed (they live only in `plan/excel/screenshots/`).
|
||||
- The ArtCraft brand files in `docs/brand/` are ArtCraft trademarks under `docs/brand/LICENSE-brand.txt`, the one exception.
|
||||
|
||||
## Never crash
|
||||
People trust GridCraft with their numbers; a crash loses their work. **This outranks feature work.** Standard: [`craftrules/standards/never-crash.md`](https://github.com/storytold/craftrules/blob/main/standards/never-crash.md).
|
||||
|
||||
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
|
||||
default-members = ["crates/*", "apps/gridcraft", "apps/gridcraft-cli", "xtask"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.6.1"
|
||||
version = "0.6.2"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.90"
|
||||
@@ -41,6 +41,7 @@ sha1 = { version = "0.11", default-features = false }
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg"] }
|
||||
|
||||
# ui
|
||||
craft-ui = { version = "0.1.0", git = "https://git.marcospaulo.dev.br/masi/craft-libs", rev = "7fcee66b8eef8eac3c7644fd024e1f1b051f5a3e", features = ["serde"] }
|
||||
egui = "0.36"
|
||||
egui_extras = { version = "0.36", default-features = false }
|
||||
eframe = { version = "0.36", default-features = false }
|
||||
|
||||
@@ -3,10 +3,6 @@ Copyright (c) 2026 ArtCraft Team and the GridCraft contributors.
|
||||
Licensed under the MIT License or the Apache License, Version 2.0, at your option
|
||||
(LICENSE-MIT, LICENSE-APACHE).
|
||||
|
||||
The ArtCraft name, wordmark and logos in docs/brand/ are trademarks of the ArtCraft Team. They are
|
||||
not open source and are not covered by the license above; their terms are in
|
||||
docs/brand/LICENSE-brand.txt.
|
||||
|
||||
Third-party material
|
||||
|
||||
This product bundles third-party material under its own licenses, listed below. The per-file
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
<p align="center">
|
||||
<a href="https://getartcraft.com/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="docs/brand/artcraft-logo-white.svg">
|
||||
<img alt="ArtCraft" src="docs/brand/artcraft-logo.svg" width="200">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h1 align="center">GridCraft</h1>
|
||||
|
||||
<p align="center">
|
||||
@@ -15,8 +6,7 @@
|
||||
|
||||
<p align="center">
|
||||
A fast, open-source, clean-room take on the Excel workflow. It runs natively on macOS,
|
||||
Windows, Linux and BSD, and in the browser via WebAssembly.<br>
|
||||
<i>By the ArtCraft team.</i>
|
||||
Windows, Linux and BSD, and in the browser via WebAssembly.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -26,16 +16,6 @@
|
||||
<img alt="Agent-drivable over MCP" src="https://img.shields.io/badge/agents-MCP-1f9d55?style=flat-square">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/artcraft"><img alt="Join the ArtCraft community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://getartcraft.com/apps/gridcraft"><b>GridCraft on getartcraft.com</b></a> ·
|
||||
<a href="https://getartcraft.com/">ArtCraft</a> ·
|
||||
<a href="https://getartcraft.com/apps">All Crafting Apps</a>
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
@@ -43,11 +23,6 @@
|
||||
<br><sub><b>Quarterly Sales Dashboard</b>: a table with a totals row, conditional formatting, sparklines and two live charts — built entirely from GridCraft commands (<code>File → New → Sample</code>).</sub>
|
||||
</p>
|
||||
|
||||
> [!NOTE]
|
||||
> **ArtCraft is a community of artists from all walks of life.** Painters, photographers,
|
||||
> filmmakers, illustrators, designers, animators, hobbyists, and people who picked up a pencil
|
||||
> last week. If you make things, you're one of us. **[Come say hi on Discord](https://discord.gg/artcraft).**
|
||||
|
||||
<p align="center">
|
||||
<a href="#a-look-around">A look around</a> ·
|
||||
<a href="#why-gridcraft">Why GridCraft</a> ·
|
||||
@@ -279,43 +254,19 @@ handling again for troubleshooting.
|
||||
## The Crafting Apps
|
||||
|
||||
GridCraft is one of the **Crafting Apps**: free, open-source creative tools from the
|
||||
[ArtCraft](https://getartcraft.com/) team, each written from scratch in Rust and each able to
|
||||
storytold team, each written from scratch in Rust and each able to
|
||||
stand on its own.
|
||||
|
||||
| | App | What it's for | Code | Learn more |
|
||||
|:-:|---|---|---|---|
|
||||
| <img src="https://raw.githubusercontent.com/storytold/photocraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.photocraft.png" alt="" width="32" height="32"> | **PhotoCraft** | Image editing: layers, masks, type and real PSD files | [GitHub](https://github.com/storytold/photocraft) | [Website](https://getartcraft.com/apps/photocraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/vectorcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.vectorcraft.png" alt="" width="32" height="32"> | **VectorCraft** | Vector illustration | [GitHub](https://github.com/storytold/vectorcraft) | [Website](https://getartcraft.com/apps/vectorcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/filmcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.filmcraft.png" alt="" width="32" height="32"> | **FilmCraft** | Video editing, color and sound | [GitHub](https://github.com/storytold/filmcraft) | [Website](https://getartcraft.com/apps/filmcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/lightcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.lightcraft.png" alt="" width="32" height="32"> | **LightCraft** | Photo library and raw development | [GitHub](https://github.com/storytold/lightcraft) | [Website](https://getartcraft.com/apps/lightcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/pdfcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.pdfcraft.png" alt="" width="32" height="32"> | **PdfCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/pdfcraft) | [Website](https://getartcraft.com/apps/pdfcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/effectcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png" alt="" width="32" height="32"> | **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [Website](https://getartcraft.com/apps/effectcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/designcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.designcraft.png" alt="" width="32" height="32"> | **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) | [Website](https://getartcraft.com/apps/designcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/gridcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.gridcraft.png" alt="" width="32" height="32"> | **GridCraft** | **Spreadsheets and data · you are here** | [GitHub](https://github.com/storytold/gridcraft) | [Website](https://getartcraft.com/apps/gridcraft) |
|
||||
|
||||
And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video studio for artists who want real control.
|
||||
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/artcraft"><img alt="Join the ArtCraft community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
|
||||
</p>
|
||||
|
||||
<h3 align="center">Come make things with us</h3>
|
||||
|
||||
<p align="center">
|
||||
Our Discord is where artists of every kind hang out: people who paint, shoot, draw, cut film,
|
||||
set type, and people still figuring out what they like to make. Share what you're working on,
|
||||
ask for help, tell us what's broken, or tell us what you wish these tools could do.
|
||||
Whatever your medium and however long you've been at it, you're welcome here.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/artcraft"><b>discord.gg/artcraft</b></a> ·
|
||||
<a href="https://getartcraft.com/">getartcraft.com</a> ·
|
||||
<a href="https://getartcraft.com/apps">The Crafting Apps</a> ·
|
||||
<a href="https://getartcraft.com/apps/gridcraft">GridCraft</a>
|
||||
</p>
|
||||
| | App | What it's for | Code |
|
||||
|:-:|---|---|---|
|
||||
| <img src="https://raw.githubusercontent.com/storytold/photocraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.photocraft.png" alt="" width="32" height="32"> | **PhotoCraft** | Image editing: layers, masks, type and real PSD files | [GitHub](https://github.com/storytold/photocraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/vectorcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.vectorcraft.png" alt="" width="32" height="32"> | **VectorCraft** | Vector illustration | [GitHub](https://github.com/storytold/vectorcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/filmcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.filmcraft.png" alt="" width="32" height="32"> | **FilmCraft** | Video editing, color and sound | [GitHub](https://github.com/storytold/filmcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/lightcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.lightcraft.png" alt="" width="32" height="32"> | **LightCraft** | Photo library and raw development | [GitHub](https://github.com/storytold/lightcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/pdfcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.pdfcraft.png" alt="" width="32" height="32"> | **PdfCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/pdfcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/effectcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png" alt="" width="32" height="32"> | **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/designcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.designcraft.png" alt="" width="32" height="32"> | **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/gridcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.gridcraft.png" alt="" width="32" height="32"> | **GridCraft** | **Spreadsheets and data · you are here** | [GitHub](https://github.com/storytold/gridcraft) |
|
||||
|
||||
## License and credits
|
||||
|
||||
@@ -329,16 +280,4 @@ All UI icons are drawn in code and are original; the sample workbooks and every
|
||||
this README were made with GridCraft itself. GridCraft bundles no fonts: it uses the fonts
|
||||
installed on your system.
|
||||
|
||||
The ArtCraft name, wordmark and logos in [`docs/brand/`](docs/brand/) are trademarks of the
|
||||
ArtCraft Team and are not covered by this license. They may be used only unmodified, and only as
|
||||
part of this repository and GridCraft, under [`docs/brand/LICENSE-brand.txt`](docs/brand/LICENSE-brand.txt).
|
||||
Forks and modified versions must remove them.
|
||||
|
||||
<sub>Microsoft, Excel and Microsoft 365 are trademarks or registered trademarks of Microsoft Corporation in the United States and/or other countries. GridCraft is an independent, open-source project and is not affiliated with, sponsored by or endorsed by Microsoft Corporation; these names are used only to describe the workflows it is compatible with.</sub>
|
||||
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://getartcraft.com/"><img alt="ArtCraft" src="docs/brand/artcraft-mark.svg" width="28"></a><br>
|
||||
<sub>Made by the <a href="https://getartcraft.com/">ArtCraft</a> team and community.</sub>
|
||||
</p>
|
||||
|
||||
@@ -49,7 +49,7 @@ pub fn start() {
|
||||
) {
|
||||
app.ui.language = browser;
|
||||
}
|
||||
SheetApp::setup_context_for_language(&cc.egui_ctx, false, app.ui.language);
|
||||
SheetApp::setup_context_for_language(&cc.egui_ctx, false, None, app.ui.language);
|
||||
Ok(Box::new(WebShell { app, inbox }))
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -24,7 +24,7 @@ image = { workspace = true }
|
||||
winresource = "0.1"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
craft-print = { git = "https://github.com/storytold/craft-libs", rev = "d27646a5f7c8b4fc85d9dd3213b8c88b58e0088d" }
|
||||
craft-print = { git = "https://git.marcospaulo.dev.br/masi/craft-libs", rev = "7fcee66b8eef8eac3c7644fd024e1f1b051f5a3e" }
|
||||
rfd = { workspace = true }
|
||||
arboard = { workspace = true }
|
||||
|
||||
|
||||
@@ -336,7 +336,7 @@ fn main() -> eframe::Result<()> {
|
||||
let Some(mut app) = pending.take() else {
|
||||
return Err("the app was already created".into());
|
||||
};
|
||||
SheetApp::setup_context_for_language(&cc.egui_ctx, app.ui.dark, app.ui.language);
|
||||
SheetApp::setup_context_for_language(&cc.egui_ctx, app.ui.dark, app.ui.flavor, app.ui.language);
|
||||
if let Some(port) = control_port {
|
||||
match control_server::start(port, cc.egui_ctx.clone()) {
|
||||
Ok(rx) => app.control_rx = Some(rx),
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::str::FromStr;
|
||||
use std::sync::mpsc::{Receiver, channel};
|
||||
|
||||
use gridcraft_ui_egui::SheetApp;
|
||||
use gridcraft_ui_egui::theme::ThemeMode;
|
||||
use muda::accelerator::Accelerator;
|
||||
use muda::{CheckMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};
|
||||
use serde_json::{Value, json};
|
||||
@@ -173,8 +174,7 @@ fn tree() -> Vec<(&'static str, Vec<Entry>)> {
|
||||
vec![
|
||||
("Agent Control (MCP)…", "dialog:agents", None),
|
||||
("Contributors…", "dialog:about:Contributors", None),
|
||||
("GridCraft on getartcraft.com", "url:https://getartcraft.com/apps/gridcraft", None),
|
||||
("Join the ArtCraft Discord…", "url:https://discord.gg/artcraft", None),
|
||||
("GridCraft on GitHub", "url:https://github.com/storytold/gridcraft", None),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -183,17 +183,12 @@ fn tree() -> Vec<(&'static str, Vec<Entry>)> {
|
||||
pub struct NativeMenu {
|
||||
_menu: Menu,
|
||||
events: Receiver<MenuEvent>,
|
||||
theme_items: Vec<(&'static str, CheckMenuItem)>,
|
||||
theme_items: Vec<(ThemeMode, CheckMenuItem)>,
|
||||
}
|
||||
|
||||
fn select_theme(app: &mut SheetApp, id: &str) -> bool {
|
||||
let mode = match id {
|
||||
"theme:system" => "system",
|
||||
"theme:light" => "light",
|
||||
"theme:dark" => "dark",
|
||||
_ => return false,
|
||||
};
|
||||
app.run_or_alert("view.theme", json!({"mode": mode}));
|
||||
let Some(mode) = id.strip_prefix("theme:").and_then(ThemeMode::parse) else { return false };
|
||||
app.run_or_alert("view.theme", json!({"mode": mode.id()}));
|
||||
true
|
||||
}
|
||||
|
||||
@@ -224,8 +219,8 @@ impl NativeMenu {
|
||||
}
|
||||
if id == "view.theme" {
|
||||
let themes = Submenu::new(label, true);
|
||||
for (mode, label) in [("system", "System"), ("light", "Light"), ("dark", "Dark")] {
|
||||
let item = CheckMenuItem::with_id(format!("theme:{mode}"), label, true, false, None);
|
||||
for mode in ThemeMode::ALL {
|
||||
let item = CheckMenuItem::with_id(format!("theme:{}", mode.id()), mode.label(), true, false, None);
|
||||
let _ = themes.append(&item);
|
||||
theme_items.push((mode, item));
|
||||
}
|
||||
@@ -318,12 +313,12 @@ mod tests {
|
||||
#[test]
|
||||
fn theme_choices_use_the_shared_preference_command() {
|
||||
let mut app = SheetApp::new(gridcraft_engine::Session::new(), Default::default());
|
||||
for mode in ["dark", "system", "light"] {
|
||||
assert!(select_theme(&mut app, &format!("theme:{mode}")));
|
||||
for mode in ThemeMode::ALL.into_iter().rev() {
|
||||
assert!(select_theme(&mut app, &format!("theme:{}", mode.id())));
|
||||
assert_eq!(app.ui.theme_mode(), mode);
|
||||
assert!(app.message.is_none());
|
||||
}
|
||||
assert!(!select_theme(&mut app, "view.formulaBar"));
|
||||
assert_eq!(app.ui.theme_mode(), "light");
|
||||
assert_eq!(app.ui.theme_mode(), ThemeMode::System);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ description = "GridCraft egui frontend (Excel-style UI); swappable — nothing b
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "d27646a5f7c8b4fc85d9dd3213b8c88b58e0088d" }
|
||||
gridcraft-engine = { workspace = true }
|
||||
gridcraft-chart = { workspace = true }
|
||||
craft-ui = { workspace = true }
|
||||
egui = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Renders the whole GridCraft window offscreen (wgpu, no window) to a PNG.
|
||||
//!
|
||||
//! `cargo run --release -p gridcraft-ui-egui --example snapshot -- [--sample sales] [--in file.xlsx]
|
||||
//! [--size 1440x900] [--scale 2] [--cmd 'id={json}']... [--tab Insert] [--dark] [--language en|zh|ja|ko|ru|pt|cs]
|
||||
//! [--size 1440x900] [--scale 2] [--cmd 'id={json}']... [--tab Insert] [--dark] [--theme catppuccinMocha|…] [--language en|zh|ja|ko|ru|pt|cs]
|
||||
//! [--dialog 'name={json}'] [--hf-edit header|footer] out.png`
|
||||
|
||||
use gridcraft_engine::Session;
|
||||
@@ -17,6 +17,7 @@ fn main() {
|
||||
let mut cmds: Vec<String> = vec![];
|
||||
let mut tab = None;
|
||||
let mut dark = false;
|
||||
let mut theme = None;
|
||||
let mut pane: Option<String> = None;
|
||||
let mut chart: Option<u32> = None;
|
||||
let mut dialog: Option<String> = None;
|
||||
@@ -71,6 +72,10 @@ fn main() {
|
||||
i += 1;
|
||||
hf_edit = args.get(i).cloned();
|
||||
}
|
||||
"--theme" => {
|
||||
i += 1;
|
||||
theme = args.get(i).and_then(|s| gridcraft_ui_egui::theme::ThemeMode::parse(s));
|
||||
}
|
||||
"--language" => {
|
||||
i += 1;
|
||||
language = args.get(i).cloned();
|
||||
@@ -103,6 +108,9 @@ fn main() {
|
||||
{
|
||||
let mut app = SheetApp::new(session, Default::default());
|
||||
app.ui.dark = dark;
|
||||
if let Some(mode) = theme {
|
||||
app.ui.set_theme_mode(mode);
|
||||
}
|
||||
if let Some(code) = &language
|
||||
&& let Some(l) = gridcraft_ui_egui::i18n::Language::parse(code)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ use serde_json::{Value as Json, json};
|
||||
use crate::SheetApp;
|
||||
use crate::i18n::{self, Language};
|
||||
use crate::icons::{self, Icon};
|
||||
use crate::theme::{self, Tokens};
|
||||
use crate::theme::{self, ThemeMode, Tokens};
|
||||
|
||||
/// Width of the navigation column.
|
||||
const NAV_W: f32 = 196.0;
|
||||
@@ -795,15 +795,10 @@ fn options(app: &mut SheetApp, ui: &mut Ui) {
|
||||
egui::Grid::new("backstage_options_general").num_columns(2).spacing(vec2(18.0, 10.0)).show(ui, |ui| {
|
||||
ui.label(lang.tr("Display theme"));
|
||||
let mode = app.ui.theme_mode();
|
||||
let current = match mode {
|
||||
"system" => "System",
|
||||
"dark" => "Dark",
|
||||
_ => "Light",
|
||||
};
|
||||
egui::ComboBox::from_id_salt("backstage_theme").width(160.0).selected_text(lang.tr(current)).show_ui(ui, |ui| {
|
||||
for (value, label) in [("system", "System"), ("light", "Light"), ("dark", "Dark")] {
|
||||
if ui.selectable_label(mode == value, lang.tr(label)).clicked() {
|
||||
app.run_or_alert("view.theme", json!({"mode": value}));
|
||||
egui::ComboBox::from_id_salt("backstage_theme").width(160.0).selected_text(lang.tr(mode.label())).show_ui(ui, |ui| {
|
||||
for m in ThemeMode::ALL {
|
||||
if ui.selectable_label(mode == m, lang.tr(m.label())).clicked() {
|
||||
app.run_or_alert("view.theme", json!({"mode": m.id()}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -212,8 +212,7 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) -
|
||||
}
|
||||
"ui.set" => {
|
||||
if let Some(v) = p.get("dark").and_then(Json::as_bool) {
|
||||
app.ui.dark = v;
|
||||
app.ui.system_theme = false;
|
||||
app.ui.set_theme_mode(if v { crate::theme::ThemeMode::Dark } else { crate::theme::ThemeMode::Light });
|
||||
}
|
||||
if let Some(v) = p.get("formulaBar").and_then(Json::as_bool) {
|
||||
let _ = app.run("view.formulaBar", json!({"on": v}));
|
||||
|
||||
@@ -1326,9 +1326,8 @@ fn about(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
ui.heading("GridCraft");
|
||||
ui.label(format!("Version {}", env!("CARGO_PKG_VERSION")));
|
||||
ui.add_space(4.0);
|
||||
ui.label("A clean-room, open-source, Rust-native spreadsheet. Part of ArtCraft.");
|
||||
ui.hyperlink_to("getartcraft.com/apps/gridcraft", "https://getartcraft.com/apps/gridcraft");
|
||||
ui.hyperlink_to("Join the ArtCraft Discord", "https://discord.gg/artcraft");
|
||||
ui.label("A clean-room, open-source, Rust-native spreadsheet.");
|
||||
ui.hyperlink_to("github.com/storytold/gridcraft", "https://github.com/storytold/gridcraft");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ use std::collections::HashMap;
|
||||
|
||||
use gridcraft_engine::core::{CellRef, RangeRef};
|
||||
use gridcraft_engine::{Session, UiRequest};
|
||||
use craft_ui::theme::catppuccin::Flavor;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value as Json, json};
|
||||
|
||||
@@ -51,6 +52,9 @@ pub struct UiState {
|
||||
/// The manual preference, retained when following the system appearance.
|
||||
pub dark: bool,
|
||||
pub system_theme: bool,
|
||||
/// The Catppuccin flavour in use, if any (`dark` then follows the flavour).
|
||||
#[serde(deserialize_with = "lenient_flavor")]
|
||||
pub flavor: Option<Flavor>,
|
||||
pub ribbon_tab: String,
|
||||
pub ribbon_collapsed: bool,
|
||||
pub formula_bar_expanded: bool,
|
||||
@@ -84,6 +88,10 @@ pub fn clamp_interface_scale(percent: f64) -> Option<u16> {
|
||||
percent.is_finite().then(|| percent.round().clamp(INTERFACE_SCALE_MIN as f64, INTERFACE_SCALE_MAX as f64) as u16)
|
||||
}
|
||||
|
||||
fn lenient_flavor<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Option<Flavor>, D::Error> {
|
||||
Ok(serde_json::from_value(Json::deserialize(d)?).unwrap_or_default())
|
||||
}
|
||||
|
||||
fn lenient_interface_scale<'de, D: serde::Deserializer<'de>>(d: D) -> Result<u16, D::Error> {
|
||||
let v = Json::deserialize(d)?;
|
||||
Ok(v.as_f64().and_then(clamp_interface_scale).unwrap_or(100))
|
||||
@@ -94,6 +102,7 @@ impl Default for UiState {
|
||||
UiState {
|
||||
dark: false,
|
||||
system_theme: false,
|
||||
flavor: None,
|
||||
ribbon_tab: "Home".into(),
|
||||
ribbon_collapsed: false,
|
||||
formula_bar_expanded: false,
|
||||
@@ -123,13 +132,28 @@ impl UiState {
|
||||
clamp_interface_scale(self.interface_scale as f64).unwrap_or(100) as f32 / 100.0
|
||||
}
|
||||
|
||||
pub fn theme_mode(&self) -> &'static str {
|
||||
if self.system_theme {
|
||||
"system"
|
||||
} else if self.dark {
|
||||
"dark"
|
||||
} else {
|
||||
"light"
|
||||
pub fn theme_mode(&self) -> theme::ThemeMode {
|
||||
use theme::ThemeMode;
|
||||
match (self.flavor, self.system_theme, self.dark) {
|
||||
(Some(f), ..) => ThemeMode::Catppuccin(f),
|
||||
(None, true, _) => ThemeMode::System,
|
||||
(None, false, true) => ThemeMode::Dark,
|
||||
(None, false, false) => ThemeMode::Light,
|
||||
}
|
||||
}
|
||||
|
||||
/// Chooses a display theme. System keeps the manual light/dark preference.
|
||||
pub fn set_theme_mode(&mut self, mode: theme::ThemeMode) {
|
||||
use theme::ThemeMode;
|
||||
self.flavor = None;
|
||||
self.system_theme = mode == ThemeMode::System;
|
||||
match mode {
|
||||
ThemeMode::System => {}
|
||||
ThemeMode::Light | ThemeMode::Dark => self.dark = mode == ThemeMode::Dark,
|
||||
ThemeMode::Catppuccin(f) => {
|
||||
self.flavor = Some(f);
|
||||
self.dark = f.is_dark();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,13 +321,13 @@ impl SheetApp {
|
||||
/// [`logic`]: SheetApp::logic
|
||||
pub fn setup_context(ctx: &egui::Context, dark: bool) {
|
||||
fonts::start_scan();
|
||||
Self::setup_context_for_language(ctx, dark, i18n::Language::En);
|
||||
Self::setup_context_for_language(ctx, dark, None, i18n::Language::En);
|
||||
}
|
||||
|
||||
/// Sets up fonts and visuals for a specific persisted interface language.
|
||||
pub fn setup_context_for_language(ctx: &egui::Context, dark: bool, language: i18n::Language) {
|
||||
pub fn setup_context_for_language(ctx: &egui::Context, dark: bool, flavor: Option<Flavor>, language: i18n::Language) {
|
||||
ctx.set_fonts(theme::font_definitions_for_language(language));
|
||||
theme::apply(ctx, dark);
|
||||
theme::apply(ctx, dark, flavor);
|
||||
}
|
||||
|
||||
/// The font names the active workbook uses: every style's font plus the theme's heading/body
|
||||
@@ -479,19 +503,15 @@ impl SheetApp {
|
||||
"view.darkMode" => {
|
||||
let current = if self.ui.system_theme { self.effective_dark } else { self.ui.dark };
|
||||
self.ui.dark = p.get("on").and_then(Json::as_bool).unwrap_or(!current);
|
||||
self.ui.system_theme = false;
|
||||
self.ui.set_theme_mode(if self.ui.dark { theme::ThemeMode::Dark } else { theme::ThemeMode::Light });
|
||||
Ok(json!({"on": self.ui.dark}))
|
||||
}
|
||||
"view.theme" => {
|
||||
match p.get("mode").and_then(Json::as_str) {
|
||||
Some("system") => self.ui.system_theme = true,
|
||||
Some(mode @ ("light" | "dark")) => {
|
||||
self.ui.dark = mode == "dark";
|
||||
self.ui.system_theme = false;
|
||||
}
|
||||
_ => return Some(Err("theme mode must be system, light, or dark".into())),
|
||||
}
|
||||
Ok(json!({"mode": self.ui.theme_mode()}))
|
||||
let Some(mode) = p.get("mode").and_then(Json::as_str).and_then(theme::ThemeMode::parse) else {
|
||||
return Some(Err(format!("theme mode must be one of {}", theme::ThemeMode::id_list())));
|
||||
};
|
||||
self.ui.set_theme_mode(mode);
|
||||
Ok(json!({"mode": mode.id()}))
|
||||
}
|
||||
"view.animations" => {
|
||||
self.ui.animations = p.get("on").and_then(Json::as_bool).unwrap_or(!self.ui.animations);
|
||||
@@ -891,7 +911,7 @@ impl SheetApp {
|
||||
if self.fonts_set {
|
||||
self.fonts_ready = true;
|
||||
} else {
|
||||
SheetApp::setup_context_for_language(ctx, self.ui.dark, self.ui.language);
|
||||
SheetApp::setup_context_for_language(ctx, self.ui.dark, self.ui.flavor, self.ui.language);
|
||||
self.fonts_set = true;
|
||||
// The rebuilt set has no workbook fonts yet: bind them again once it is active.
|
||||
self.fonts_bound.clear();
|
||||
@@ -921,6 +941,10 @@ impl SheetApp {
|
||||
if let Some(answer) = self.desktop_theme.take_change() {
|
||||
self.desktop_dark = Some(answer);
|
||||
}
|
||||
if theme::flavor_of(ctx) != self.ui.flavor {
|
||||
theme::apply(ctx, self.ui.dark, self.ui.flavor);
|
||||
ctx.request_repaint();
|
||||
}
|
||||
let preference = match (
|
||||
self.ui.system_theme,
|
||||
ctx.system_theme().or_else(|| self.desktop_dark.map(|d| if d { egui::Theme::Dark } else { egui::Theme::Light })),
|
||||
|
||||
@@ -262,13 +262,13 @@ fn file_menu(app: &mut SheetApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
|
||||
/// System / Light / Dark, as in the View tab's Display theme box (`view.theme`).
|
||||
/// System / Light / Dark / Catppuccin, as in the View tab's Display theme box (`view.theme`).
|
||||
fn theme_choices(app: &mut SheetApp, ui: &mut Ui) {
|
||||
let lang = app.ui.language;
|
||||
let mode = app.ui.theme_mode();
|
||||
for (value, label) in [("system", "System"), ("light", "Light"), ("dark", "Dark")] {
|
||||
if ui.radio(mode == value, lang.tr(label)).clicked() {
|
||||
act(app, "view.theme", json!({"mode": value}));
|
||||
for m in theme::ThemeMode::ALL {
|
||||
if ui.radio(mode == m, lang.tr(m.label())).clicked() {
|
||||
act(app, "view.theme", json!({"mode": m.id()}));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
@@ -2172,15 +2172,10 @@ fn view(app: &mut SheetApp, ui: &mut Ui) {
|
||||
ui.vertical(|ui| {
|
||||
ui.label(lang.tr("Display theme"));
|
||||
let mode = app.ui.theme_mode();
|
||||
let label = match mode {
|
||||
"system" => "System",
|
||||
"dark" => "Dark",
|
||||
_ => "Light",
|
||||
};
|
||||
egui::ComboBox::from_id_salt("display_theme").selected_text(lang.tr(label)).width(88.0).show_ui(ui, |ui| {
|
||||
for (value, label) in [("system", "System"), ("light", "Light"), ("dark", "Dark")] {
|
||||
if ui.selectable_label(mode == value, lang.tr(label)).clicked() {
|
||||
act(app, "view.theme", json!({"mode": value}));
|
||||
egui::ComboBox::from_id_salt("display_theme").selected_text(lang.tr(mode.label())).width(88.0).show_ui(ui, |ui| {
|
||||
for m in theme::ThemeMode::ALL {
|
||||
if ui.selectable_label(mode == m, lang.tr(m.label())).clicked() {
|
||||
act(app, "view.theme", json!({"mode": m.id()}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,8 +7,65 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use craft_ui::theme::catppuccin::Flavor;
|
||||
use egui::{Color32, FontData, FontDefinitions, FontFamily, FontId, Stroke, Visuals};
|
||||
|
||||
/// The Display theme choices: follow the OS, fixed light or dark, or a Catppuccin flavour.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ThemeMode {
|
||||
System,
|
||||
Light,
|
||||
Dark,
|
||||
Catppuccin(Flavor),
|
||||
}
|
||||
|
||||
impl ThemeMode {
|
||||
pub const ALL: [ThemeMode; 7] = [
|
||||
ThemeMode::System,
|
||||
ThemeMode::Light,
|
||||
ThemeMode::Dark,
|
||||
ThemeMode::Catppuccin(Flavor::Latte),
|
||||
ThemeMode::Catppuccin(Flavor::Frappe),
|
||||
ThemeMode::Catppuccin(Flavor::Macchiato),
|
||||
ThemeMode::Catppuccin(Flavor::Mocha),
|
||||
];
|
||||
|
||||
/// The `view.theme` `mode` value.
|
||||
pub const fn id(self) -> &'static str {
|
||||
match self {
|
||||
ThemeMode::System => "system",
|
||||
ThemeMode::Light => "light",
|
||||
ThemeMode::Dark => "dark",
|
||||
ThemeMode::Catppuccin(Flavor::Latte) => "catppuccinLatte",
|
||||
ThemeMode::Catppuccin(Flavor::Frappe) => "catppuccinFrappe",
|
||||
ThemeMode::Catppuccin(Flavor::Macchiato) => "catppuccinMacchiato",
|
||||
ThemeMode::Catppuccin(Flavor::Mocha) => "catppuccinMocha",
|
||||
}
|
||||
}
|
||||
|
||||
/// The English label, the i18n key.
|
||||
pub const fn label(self) -> &'static str {
|
||||
match self {
|
||||
ThemeMode::System => "System",
|
||||
ThemeMode::Light => "Light",
|
||||
ThemeMode::Dark => "Dark",
|
||||
ThemeMode::Catppuccin(Flavor::Latte) => "Catppuccin Latte",
|
||||
ThemeMode::Catppuccin(Flavor::Frappe) => "Catppuccin Frappé",
|
||||
ThemeMode::Catppuccin(Flavor::Macchiato) => "Catppuccin Macchiato",
|
||||
ThemeMode::Catppuccin(Flavor::Mocha) => "Catppuccin Mocha",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(id: &str) -> Option<Self> {
|
||||
Self::ALL.into_iter().find(|m| m.id() == id)
|
||||
}
|
||||
|
||||
/// Every id, `|`-separated, for error messages.
|
||||
pub fn id_list() -> String {
|
||||
Self::ALL.map(ThemeMode::id).join("|")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Tokens {
|
||||
pub dark: bool,
|
||||
@@ -137,8 +194,60 @@ impl Tokens {
|
||||
}
|
||||
}
|
||||
|
||||
/// A Catppuccin flavour on the same layout: base/mantle/crust surfaces, surface0-2 borders and
|
||||
/// hover, mauve accent, blue selection. The grid keeps its white cells.
|
||||
pub fn catppuccin(f: Flavor) -> Tokens {
|
||||
let p = f.palette();
|
||||
let dark = f.is_dark();
|
||||
let base = if dark { Tokens::dark() } else { Tokens::light() };
|
||||
Tokens {
|
||||
dark,
|
||||
window: p.base,
|
||||
ribbon: p.mantle,
|
||||
ribbon_border: p.surface0,
|
||||
text: p.text,
|
||||
text_dim: p.subtext0,
|
||||
text_disabled: p.overlay0,
|
||||
accent: p.mauve,
|
||||
accent_dark: p.mauve,
|
||||
accent_soft: p.surface1,
|
||||
hover: p.surface0,
|
||||
pressed: p.surface1,
|
||||
separator: p.surface0,
|
||||
desk: p.crust,
|
||||
header_bg: p.mantle,
|
||||
header_text: p.subtext1,
|
||||
header_line: p.surface0,
|
||||
header_sel_bg: p.surface1,
|
||||
header_sel_text: p.mauve,
|
||||
header_all_bg: p.surface1,
|
||||
sel_fill: Color32::from_rgba_unmultiplied(p.blue.r(), p.blue.g(), p.blue.b(), 0x28),
|
||||
sel_border: p.blue,
|
||||
input_bg: if dark { p.crust } else { p.base },
|
||||
input_border: p.surface1,
|
||||
tab_bar: p.mantle,
|
||||
tab_active: p.base,
|
||||
status_bar: p.mantle,
|
||||
menu_bg: p.mantle,
|
||||
danger: p.red,
|
||||
notice_warn_bg: p.surface0,
|
||||
notice_warn_border: p.yellow,
|
||||
notice_info_bg: p.surface0,
|
||||
notice_info_border: p.blue,
|
||||
..base
|
||||
}
|
||||
}
|
||||
|
||||
fn for_slot(dark: bool, flavor: Option<Flavor>) -> Tokens {
|
||||
match flavor {
|
||||
Some(f) if f.is_dark() == dark => Tokens::catppuccin(f),
|
||||
_ if dark => Tokens::dark(),
|
||||
_ => Tokens::light(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(ctx: &egui::Context) -> Tokens {
|
||||
if ctx.global_style().visuals.dark_mode { Tokens::dark() } else { Tokens::light() }
|
||||
Tokens::for_slot(ctx.global_style().visuals.dark_mode, flavor_of(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -675,11 +784,17 @@ pub fn symbol_face_installed() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply(ctx: &egui::Context, dark: bool) {
|
||||
/// The Catppuccin flavour [`apply`] last installed on `ctx`.
|
||||
pub fn flavor_of(ctx: &egui::Context) -> Option<Flavor> {
|
||||
ctx.data(|d| d.get_temp::<Option<Flavor>>(egui::Id::new("catppuccin_flavor"))).flatten()
|
||||
}
|
||||
|
||||
pub fn apply(ctx: &egui::Context, dark: bool, flavor: Option<Flavor>) {
|
||||
ctx.data_mut(|d| d.insert_temp(egui::Id::new("catppuccin_flavor"), flavor));
|
||||
// Install both palettes before System can switch between the style slots.
|
||||
for theme in [egui::Theme::Light, egui::Theme::Dark] {
|
||||
let dark = theme == egui::Theme::Dark;
|
||||
let t = if dark { Tokens::dark() } else { Tokens::light() };
|
||||
let t = Tokens::for_slot(dark, flavor);
|
||||
let mut v = if dark { Visuals::dark() } else { Visuals::light() };
|
||||
v.panel_fill = t.window;
|
||||
v.window_fill = t.menu_bg;
|
||||
@@ -935,3 +1050,21 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod theme_mode_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn catppuccin_themes_use_the_flavour_palette() {
|
||||
for f in Flavor::ALL {
|
||||
let t = Tokens::catppuccin(f);
|
||||
assert_eq!((t.window, t.dark), (f.palette().base, f.is_dark()), "{}", f.name());
|
||||
assert_eq!(ThemeMode::parse(ThemeMode::Catppuccin(f).id()), Some(ThemeMode::Catppuccin(f)));
|
||||
}
|
||||
assert_eq!(ThemeMode::id_list().split('|').count(), ThemeMode::ALL.len());
|
||||
let ctx = egui::Context::default();
|
||||
apply(&ctx, true, Some(Flavor::Mocha));
|
||||
assert_eq!(Tokens::get(&ctx).window, Flavor::Mocha.palette().base);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
ArtCraft brand assets: license
|
||||
|
||||
The ArtCraft name, wordmark and mark (the "ArtCraft Marks"), including every file in this
|
||||
directory in every color, size and format, are owned by the ArtCraft Team. They are trademarks
|
||||
and are NOT open source. They are not covered by GridCraft's MIT OR Apache-2.0 license, and nothing in
|
||||
that license, in NOTICE or in any other file of this repository grants any right to them.
|
||||
|
||||
Permission
|
||||
|
||||
The ArtCraft Team grants a limited, non-exclusive, non-transferable, revocable, royalty-free
|
||||
permission to use the ArtCraft Marks only:
|
||||
|
||||
1. as part of copies of this repository; and
|
||||
2. within GridCraft itself, its documentation and its official builds, to identify GridCraft as made
|
||||
by the ArtCraft team and community and to link to getartcraft.com and its community.
|
||||
|
||||
Conditions
|
||||
|
||||
The permission holds only while every one of these is true:
|
||||
|
||||
- The marks are used exactly as supplied in this directory. Do not redraw, recolor, crop,
|
||||
stretch, rotate, animate, outline or add effects to them, extract parts of them, or combine
|
||||
them with any other name, mark or artwork.
|
||||
- They are used only in the context of GridCraft, and only to say that the ArtCraft team makes it.
|
||||
They must never suggest that the ArtCraft Team made, sponsors, endorses, certifies or supports
|
||||
any other product, service, fork, modified version, plug-in, organization or person.
|
||||
- Neither the marks nor the ArtCraft name appear in the name, logo, icon, domain, social media
|
||||
handle, app-store listing, advertising or marketing of anything other than GridCraft.
|
||||
|
||||
Forks and modified versions
|
||||
|
||||
If you distribute or publish a modified version of GridCraft, or any work derived from it, you must
|
||||
remove the ArtCraft Marks from it (or replace them with your own) and must not call it ArtCraft or
|
||||
present it as an ArtCraft product. You may say, in plain text, that your work is based on GridCraft by
|
||||
the ArtCraft team. A fork kept only to propose changes back to this repository may keep the marks
|
||||
unchanged.
|
||||
|
||||
Everything else
|
||||
|
||||
Any use not expressly permitted here needs prior written permission from the ArtCraft Team
|
||||
(https://getartcraft.com/). The permission ends automatically for anyone who breaks these terms.
|
||||
All other rights are reserved.
|
||||
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 26 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -19.42 136.34 136.34" role="img" aria-label="ArtCraft">
|
||||
<title>ArtCraft</title>
|
||||
<path fill="#0b0b0c" d="M104.28,49.49L81.55,0h-31.23l-3.17,4.76L14.75,53.63,0,75.85l21.55,21.55,63.79-36.94,16.99,37.04,14.01-21.74-12.06-26.27ZM32.89,65.66l32.42-48.87,10.91,23.77-43.32,25.09Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 341 B |
|
Before Width: | Height: | Size: 24 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -19.42 136.34 136.34" role="img" aria-label="ArtCraft">
|
||||
<title>ArtCraft</title>
|
||||
<path fill="#4e7bfb" d="M104.28,49.49L81.55,0h-31.23l-3.17,4.76L14.75,53.63,0,75.85l21.55,21.55,63.79-36.94,16.99,37.04,14.01-21.74-12.06-26.27ZM32.89,65.66l32.42-48.87,10.91,23.77-43.32,25.09Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 341 B |
@@ -24,10 +24,10 @@
|
||||
<launchable type="desktop-id">ai.storyteller.gridcraft.desktop</launchable>
|
||||
<icon type="stock">ai.storyteller.gridcraft</icon>
|
||||
<!-- Only url types known to appstream 0.15 (Ubuntu 22.04, where CI validates): no vcs-browser. -->
|
||||
<url type="homepage">https://getartcraft.com/apps/gridcraft</url>
|
||||
<url type="homepage">https://github.com/storytold/gridcraft</url>
|
||||
<url type="bugtracker">https://github.com/storytold/gridcraft/issues</url>
|
||||
<url type="help">https://github.com/storytold/gridcraft</url>
|
||||
<url type="contact">https://discord.gg/artcraft</url>
|
||||
<url type="contact">https://github.com/storytold/gridcraft/issues</url>
|
||||
<categories>
|
||||
<category>Office</category>
|
||||
<category>Spreadsheet</category>
|
||||
|
||||
@@ -14,7 +14,7 @@ section: misc
|
||||
priority: optional
|
||||
maintainer: ${GRIDCRAFT_MAINTAINER}
|
||||
vendor: Learning Machines LLC
|
||||
homepage: https://getartcraft.com/apps/gridcraft
|
||||
homepage: https://github.com/storytold/gridcraft
|
||||
license: MIT OR Apache-2.0
|
||||
description: |-
|
||||
Open-source, native spreadsheet that works the way Excel users expect.
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
|
||||
<Icon Id="GridcraftIcon.ico" SourceFile="$(var.IconPath)" />
|
||||
<Property Id="ARPPRODUCTICON" Value="GridcraftIcon.ico" />
|
||||
<Property Id="ARPURLINFOABOUT" Value="https://getartcraft.com/apps/gridcraft" />
|
||||
<Property Id="ARPURLUPDATEINFO" Value="https://getartcraft.com/apps/gridcraft" />
|
||||
<Property Id="ARPHELPLINK" Value="https://discord.gg/artcraft" />
|
||||
<Property Id="ARPURLINFOABOUT" Value="https://github.com/storytold/gridcraft" />
|
||||
<Property Id="ARPURLUPDATEINFO" Value="https://github.com/storytold/gridcraft" />
|
||||
<Property Id="ARPHELPLINK" Value="https://github.com/storytold/gridcraft/issues" />
|
||||
|
||||
<!-- An explicit command-line value wins. Otherwise retain the previous installation's
|
||||
choice, defaulting to off for a fresh install. Run only in the first sequence so the
|
||||
|
||||