Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ac3eb98
refactor(list): expose reusable scheme json generation
bezhermoso Apr 24, 2026
83bfd23
feat(gallery): add static gallery command
bezhermoso Apr 24, 2026
132325f
docs(gallery): document gallery command
bezhermoso Apr 24, 2026
470638c
test(gallery): cover static artifact generation
bezhermoso Apr 24, 2026
961e9d6
feat(gallery): refine branded theme preview sheet
bezhermoso Apr 24, 2026
7b6e06e
refactor(gallery): separate the assets into their own files
bezhermoso Apr 24, 2026
40aea32
feat: paper texture
bezhermoso Apr 24, 2026
1a6a000
refactor: better frontend
bezhermoso Apr 25, 2026
eee9ae9
fix: only filter by id & name
bezhermoso Apr 25, 2026
53b4ac7
feat: add `diff` as previewable language
bezhermoso Apr 25, 2026
54032c4
fix: tinty => tinted
bezhermoso Apr 25, 2026
3b5fec4
feat: humanist modern style
bezhermoso Apr 25, 2026
fdffef3
feat: bundle the fonts to work offline
bezhermoso Apr 25, 2026
b0fc548
feat: updates
bezhermoso Apr 25, 2026
87577ff
feat: updates
bezhermoso Apr 25, 2026
e707ad4
feat: accent color change
bezhermoso Apr 25, 2026
7009771
feat: nicer cards
bezhermoso Apr 25, 2026
d694cb1
feat: object permanence
bezhermoso Apr 25, 2026
7044644
style: rustfmt
bezhermoso Apr 25, 2026
05ebf24
chore: cargo deny cleanup
bezhermoso Apr 25, 2026
1b1948b
feat: improve msmatch contrast
bezhermoso Apr 25, 2026
cf21a76
fix: dont switch apperance filter when switching page mode
bezhermoso Apr 25, 2026
8828adb
feat: card typography
bezhermoso Apr 25, 2026
6250667
fix: tinted8
bezhermoso Apr 25, 2026
b5b1250
feat: terminal output + remove JS & zsh
bezhermoso Apr 25, 2026
adee0f1
feat: modal content view transitions
bezhermoso Apr 25, 2026
aab3be9
feat: transitions
bezhermoso Apr 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ The following is a table of the available subcommands for the CLI tool (Tinty),
|------------|-----------------------------------------------------|----------------------|--------------------------------------------|
| `sync` | Installs and updates schemes and templates defined in `tinty/config.toml` | - | `tinty sync` |
| `list` | Lists all available themes. | Optional argument `--custom-schemes` to list saved custom theme files using `tinty generate-scheme`.<br>Optional argument `--json` to output more info about each scheme in JSON form | `tinty list` |
| `gallery` | Opens an interactive browser gallery for available themes. | Optional argument `--dump <DIR>` to write a static site artifact suitable for GitHub Pages.<br>Optional argument `--custom-schemes` to use saved custom theme files.<br>Optional argument `--no-open` to skip opening a browser. | `tinty gallery` |
| `apply` | Applies a specific theme. | `<scheme_system>-<scheme_name>`: Name of the system and scheme to apply. | `tinty apply base16-mocha` |
| `cycle` | Applies the next theme among your preferred ones. See [Configuration](#configuration). | | `tinty cycle` |
| `init` | Initializes the tool with the last applied theme otherwise `default-scheme` from `config.toml`. | - | `tinty init` |
Expand All @@ -197,7 +198,9 @@ Some subcommands support additional flags and options to modify their behavior:
| `--version` `-V` | Shows the version of tinty. | All | - | `tinty --version` |
| `--config-path` | Shows the config.yml path. | `config` | - | `tinty config --config-path` |
| `--data-dir-path` | Shows the data directory path. | `config` | - | `tinty config --data-dir-path` |
| `--custom-schemes` | Lists saved custom theme files manually created or generated by `tinty generate-scheme` | `list` | - | `tinty list --custom-schemes` |
| `--custom-schemes` | Uses saved custom theme files manually created or generated by `tinty generate-scheme` | `list`, `gallery` | - | `tinty gallery --custom-schemes` |
| `--dump` | Writes the gallery as a static website artifact | `gallery` | `$XDG_DATA_HOME/tinted-theming/tinty/artifacts/gallery` | `tinty gallery --dump ./public` |
| `--no-open` | Generates the gallery without opening a browser | `gallery` | `false` | `tinty gallery --no-open` |
| `--quiet` | Boolean flag which silences stdout prints | `apply`, `build`, `install`, `update`, `sync` | `false` | `tinty build . --quiet` |

## Configuration
Expand Down
20 changes: 20 additions & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,26 @@ Sort themes by background color, from darkest to lightest:
tinty list --json | jq 'sort_by(.lightness.background)' -r
```

## Gallery

`tinty gallery` builds an interactive static gallery from the available
schemes and opens it in your browser:

```sh
tinty gallery
```

To write a hostable static site artifact, use `--dump`:

```sh
tinty gallery --dump ./public
```

The dumped directory contains `index.html` and static assets, so it can
be published with GitHub Pages. Use `--no-open` to generate the files
without launching a browser, and `--custom-schemes` to build the gallery
from saved custom schemes.

## Shell

When Tinty does not have any `[[items]]` set up in `config.toml`, Tinty
Expand Down
Binary file added assets/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/tinted-theming-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 0 additions & 4 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,8 @@ deny = [
]
skip = [
{ name = "bitflags" }, # Related to `tempfile` version lock
{ name = "linux-raw-sys" }, # Related to `tempfile` version lock
{ name = "rustix" }, # Related to `tempfile` version lock
{ name = "thiserror" },
{ name = "thiserror-impl" },
{ name = "zune-core" },
{ name = "zune-jpeg" },
{ name = "nom" },
]

Expand Down
24 changes: 24 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,30 @@ pub fn build_cli() -> Command {
.required(true),
),
)
.subcommand(
Command::new("gallery")
.about("Opens an interactive gallery for available schemes")
.arg(
Arg::new("custom-schemes")
.help("Build gallery from available custom schemes")
.long("custom-schemes")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("dump")
.long("dump")
.help("Write a static gallery site to the provided directory")
.value_name("DIRECTORY")
.value_hint(ValueHint::DirPath)
.action(ArgAction::Set),
)
.arg(
Arg::new("no-open")
.long("no-open")
.help("Do not open the generated gallery in a browser")
.action(ArgAction::SetTrue),
),
)
.subcommand(
Command::new("generate-scheme")
.about("Generates a scheme based on an image")
Expand Down
12 changes: 12 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod operations {
pub mod config;
pub mod current;
pub mod cycle;
pub mod gallery;
pub mod generate_scheme;
pub mod info;
pub mod init;
Expand Down Expand Up @@ -114,6 +115,17 @@ fn main() -> Result<()> {
return Ok(());
}
}
Some(("gallery", sub_matches)) => {
let is_custom = sub_matches
.get_one::<bool>("custom-schemes")
.is_some_and(ToOwned::to_owned);
let should_open = !sub_matches
.get_one::<bool>("no-open")
.is_some_and(ToOwned::to_owned);
let dump_dir = sub_matches.get_one::<String>("dump").map(String::as_str);

operations::gallery::gallery(&data_path, is_custom, dump_dir, should_open)?;
}
Some(("info", sub_matches)) => {
let is_custom = sub_matches
.get_one::<bool>("custom-schemes")
Expand Down
136 changes: 136 additions & 0 deletions src/operations/gallery.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use crate::{
constants::ARTIFACTS_DIR,
operations::list::{scheme_entries_json, schemes_dir_path},
utils::{ensure_directory_exists, write_to_file},
};
use anyhow::{Context, Result};
use std::{
fs::File,
io::Write,
path::{Path, PathBuf},
process::{Command, Stdio},
};

const GALLERY_DIR_NAME: &str = "gallery";
const INDEX_HTML: &str = include_str!("gallery/index.html");
const GALLERY_CSS: &str = include_str!("gallery/gallery.css");
const GALLERY_JS: &str = include_str!("gallery/gallery.js");
const LOGO_BYTES: &[u8] = include_bytes!("../../assets/tinted-theming-logo.png");
const FAVICON_BYTES: &[u8] = include_bytes!("../../assets/favicon.png");
const FONT_DM_SERIF_400: &[u8] = include_bytes!("gallery/fonts/dm-serif-display-400.woff2");
const FONT_DM_SERIF_400_ITALIC: &[u8] =
include_bytes!("gallery/fonts/dm-serif-display-400-italic.woff2");
const FONT_IBM_PLEX_MONO_400: &[u8] = include_bytes!("gallery/fonts/ibm-plex-mono-400.woff2");
const FONT_IBM_PLEX_MONO_500: &[u8] = include_bytes!("gallery/fonts/ibm-plex-mono-500.woff2");

pub fn gallery(
data_path: &Path,
is_custom: bool,
dump_dir: Option<&str>,
should_open: bool,
) -> Result<PathBuf> {
let schemes_path = schemes_dir_path(data_path, is_custom)?;
let schemes_json = scheme_entries_json(&schemes_path)?;
let output_dir = dump_dir.map_or_else(
|| data_path.join(ARTIFACTS_DIR).join(GALLERY_DIR_NAME),
PathBuf::from,
);

write_gallery_files(&output_dir, &schemes_json)?;

let index_path = output_dir.join("index.html");
if should_open {
open_in_browser(&index_path)?;
}

println!("Gallery written to {}", index_path.display());

Ok(index_path)
}

fn write_gallery_files(output_dir: &Path, schemes_json: &str) -> Result<()> {
let assets_dir = output_dir.join("assets");
let fonts_dir = assets_dir.join("fonts");

ensure_directory_exists(output_dir)?;
ensure_directory_exists(&assets_dir)?;
ensure_directory_exists(&fonts_dir)?;

write_to_file(output_dir.join("index.html"), INDEX_HTML)?;
write_to_file(assets_dir.join("gallery.css"), GALLERY_CSS)?;
let gallery_js = GALLERY_JS.replace("__TINTY_SCHEMES__", schemes_json);
write_to_file(assets_dir.join("gallery.js"), &gallery_js)?;
write_binary_file(assets_dir.join("tinted-theming-logo.png"), LOGO_BYTES)?;
write_binary_file(assets_dir.join("favicon.png"), FAVICON_BYTES)?;
write_binary_file(
fonts_dir.join("dm-serif-display-400.woff2"),
FONT_DM_SERIF_400,
)?;
write_binary_file(
fonts_dir.join("dm-serif-display-400-italic.woff2"),
FONT_DM_SERIF_400_ITALIC,
)?;
write_binary_file(
fonts_dir.join("ibm-plex-mono-400.woff2"),
FONT_IBM_PLEX_MONO_400,
)?;
write_binary_file(
fonts_dir.join("ibm-plex-mono-500.woff2"),
FONT_IBM_PLEX_MONO_500,
)?;

Ok(())
}

fn write_binary_file(path: impl AsRef<Path>, contents: &[u8]) -> Result<()> {
let mut file = File::create(path.as_ref())
.map_err(anyhow::Error::new)
.with_context(|| format!("Unable to create file: {}", path.as_ref().display()))?;

file.write_all(contents)?;

Ok(())
}

fn open_in_browser(index_path: &Path) -> Result<()> {
let index_path = index_path
.canonicalize()
.with_context(|| format!("Unable to resolve {}", index_path.display()))?;

let mut command = browser_command(&index_path);
let status = command
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.with_context(|| format!("Unable to open gallery at {}", index_path.display()))?;

if !status.success() {
return Err(anyhow::anyhow!(
"Unable to open gallery at {}",
index_path.display()
));
}

Ok(())
}

#[cfg(target_os = "macos")]
fn browser_command(path: &Path) -> Command {
let mut command = Command::new("open");
command.arg(path);
command
}

#[cfg(target_os = "windows")]
fn browser_command(path: &Path) -> Command {
let mut command = Command::new("cmd");
command.args(["/C", "start", ""]).arg(path);
command
}

#[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
fn browser_command(path: &Path) -> Command {
let mut command = Command::new("xdg-open");
command.arg(path);
command
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Loading