Skip to content

Commit

Permalink
Support color themes
Browse files Browse the repository at this point in the history
  • Loading branch information
cspiegel committed Jul 18, 2022
1 parent 3c73537 commit 1a6f0f0
Show file tree
Hide file tree
Showing 21 changed files with 24,720 additions and 23 deletions.
103 changes: 103 additions & 0 deletions THEMES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Gargoyle themes

## Overview

Gargoyle's colors can be configured through its configuration file, but the
syntax is verbose and it's not easy to switch between color configurations.
Gargoyle now has a way to specify entire color themes and switch between them
with a single configuration option.

Color themes are JSON files which specify all possible colors. Gargoyle comes
with some colors themes, and you are able to create your own as well. Where to
place these color themes depends on your operating system. To see all theme
paths, hit control-period (or command-period on Mac) while Gargoyle is running.
The first listed theme path should be your personal path which you can install
new themes into.

## Theme format

Example theme:

{
"name": "Lectrote Slate",
"window": "#4d4d4f",
"border": "#000000",
"caret": "#ffffff",
"link": "#000060",
"more": "#ffddaa",
"text_buffer": {
"default": {"fg": "#ffffff", "bg": "#4d4d4f"},
"input": {"fg": "#ddffdd", "bg": "#4d4d4f"}
},
"text_grid": {
"default": {"fg": "#333333", "bg": "#ffffff"}
}
}

All top-level keys are required.

*name* is the name of the theme, and is
how it is referred to in the configuration file.

The following keys are all colors (described below):

* *window*: The overall color of the window background
* *border*: The color of borders (if any) between windows
* *caret*: The color of the input cursor
* *link*: The color of hyperlinks
* *more*: The color of the MORE prompt
* *text_buffer*: The color of text styles for text buffer windows (see below)
* *text_grid*: The color of text styles for text grid windows (see below)

### Colors

Colors are given in the usual *#RRGGBB* notation. Shorthand is not allowed (for
example *#f0f* is not interpreted as *#ff00ff*).

### Text styles

Glk text can be styled in many different ways (see [Styles in the Glk
specification](https://www.eblong.com/zarf/glk/Glk-Spec-075.html#stream_style)
for more information). For text buffers and text grids, you must specify colors
for all styles, although you can set a default which will be used as a fallback
for styles which are not explicitly set. A style is a pair of foreground and
background colors, and is a simple JSON object:

{"fg": "#333333", "bg": "#ffffff"}

*text_buffer* and *text_grid* are objects mapping a style to a pair of colors.
The key is either *default* to set the default pair, or one of the following,
which correspond to Glk styles as mentioned above:

* *normal*
* *emphasized*
* *preformatted*
* *header*
* *subheader*
* *alert*
* *note*
* *blockquote*
* *input*
* *user1*
* *user2*

These must be spelled exactly as above (which matches the Glk specification);
for example, you cannot use *emphasised* in place of *emphasized*. Case also
matters: it is *emphasized*, not *Emphasized* or *EMPHASIZED*.

## Selecthing themes

Selecting a theme is as easy as putting the following in your configuration file:

theme Lectrote Slate

This must come *after*, or replace, all other color-related configuration
options, or else the colors specified by the theme will be overridden by the
following entries. The theme name is case sensitive.

Gargoyle also includes a pseudo theme called *system* which attempts to follow
the system's color theme, adapting to whatever your current operating system
color theme is. It has built-in light and dark themes that are used, but you can
override these by providing your own themes named *light* and/or *dark*.
Gargoyle will then use your provided theme as the system theme instead of one of
its internal themes.
26 changes: 24 additions & 2 deletions garglk/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ if(INTERFACE STREQUAL "QT")
endif()

add_library(garglk babeldata.c style.c config.cpp draw.cpp event.cpp
garglk.cpp imgload.c imgscale.c winblank.c window.c wingfx.c
wingrid.c winmask.c winpair.c wintext.c ${GARGLKINI_CXX}
garglk.cpp imgload.c imgscale.c theme.cpp winblank.c window.c
wingfx.c wingrid.c winmask.c winpair.c wintext.c ${GARGLKINI_CXX}

# These can't be a library in cheapglk/ because they contain
# references to code in garglk, and garglk contains references to
Expand Down Expand Up @@ -139,6 +139,17 @@ elseif(UNIX OR MINGW)
find_package(Qt${QT_VERSION} COMPONENTS Widgets REQUIRED CONFIG)
target_link_libraries(garglk PRIVATE Qt${QT_VERSION}::Widgets)

# QDBus exists on Windows, but almost nobody will be running DBus
# there, so using QDBus would effectively just require shipping the
# QDBus DLL for no gain.
if(UNIX)
find_package(Qt${QT_VERSION} COMPONENTS DBus CONFIG)
if(Qt${QT_VERSION}DBus_FOUND)
target_compile_definitions(garglk PRIVATE GARGLK_HAS_DBUS)
target_link_libraries(garglk PRIVATE Qt${QT_VERSION}::DBus)
endif()
endif()

if(WITH_LAUNCHER AND ${INTERFACE} STREQUAL "QT")
target_link_libraries(gargoyle PRIVATE Qt${QT_VERSION}::Widgets)
endif()
Expand Down Expand Up @@ -266,6 +277,17 @@ elseif(UNIX)
"${PROJECT_SOURCE_DIR}/fonts/Gargoyle-Serif-Italic.ttf"
"${PROJECT_SOURCE_DIR}/fonts/Gargoyle-Serif.ttf"
DESTINATION "${FONT_DIRECTORY}")

install(FILES
"${PROJECT_SOURCE_DIR}/themes/Blue.json"
"${PROJECT_SOURCE_DIR}/themes/Breeze Darker.json"
"${PROJECT_SOURCE_DIR}/themes/Lectrote Dark.json"
"${PROJECT_SOURCE_DIR}/themes/Lectrote Sepia.json"
"${PROJECT_SOURCE_DIR}/themes/Lectrote Slate.json"
"${PROJECT_SOURCE_DIR}/themes/Pencil.json"
"${PROJECT_SOURCE_DIR}/themes/Zoom.json"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/io.github.garglk/Gargoyle/themes")

endif()

# For users who want to test Gargoyle by running it out of the CMake build
Expand Down
33 changes: 18 additions & 15 deletions garglk/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ style_t gli_gstyles[style_NUMSTYLES] =
style_t gli_tstyles_def[style_NUMSTYLES];
style_t gli_gstyles_def[style_NUMSTYLES];

std::vector<garglk::ConfigFile> garglk::all_configs;

static int font2idx(const std::string &font)
{
if (font == "monor") return MONOR;
Expand Down Expand Up @@ -202,21 +204,13 @@ std::string garglk::downcase(const std::string &string)

static void parsecolor(const std::string &str, unsigned char *rgb)
{
std::string r, g, b;

if (str.size() != 6)
return;

if (!std::all_of(str.begin(), str.end(), [](unsigned char c) { return std::isxdigit(c); }))
return;

r = str.substr(0, 2);
g = str.substr(2, 2);
b = str.substr(4, 2);

rgb[0] = std::stoul(r, nullptr, 16);
rgb[1] = std::stoul(g, nullptr, 16);
rgb[2] = std::stoul(b, nullptr, 16);
try
{
garglk::Color::from(str).to(rgb);
}
catch (const std::runtime_error &)
{
}
}

// Return a vector of all possible config files. This is in order of
Expand Down Expand Up @@ -607,6 +601,8 @@ static void readoneconfig(const std::string &fname, const std::string &argv0, co
gli_conf_stylehint = !!std::stoi(arg);
} else if (cmd == "safeclicks") {
gli_conf_safeclicks = !!std::stoi(arg);
} else if (cmd == "theme") {
garglk::theme::set(arg);
} else if (cmd == "tcolor" || cmd == "gcolor") {
std::istringstream argstream(arg);
std::string style, fg, bg;
Expand Down Expand Up @@ -700,6 +696,11 @@ static void gli_read_config(int argc, char **argv)
if (argc > 1)
gamepath = argv[argc - 1];

/* store so other parts of the code have access to full config information,
* including execdir and gamepath components.
*/
garglk::all_configs = garglk::configs(exedir, gamepath);

/* load from all config files */
auto configs = garglk::configs(exedir, gamepath);
std::reverse(configs.begin(), configs.end());
Expand All @@ -720,6 +721,8 @@ void gli_startup(int argc, char *argv[])
if (argc > 1)
glkunix_set_base_file(argv[argc-1]);

garglk::theme::init();

gli_read_config(argc, argv);

gli_more_prompt = new glui32[1 + base_more_prompt.size()];
Expand Down
28 changes: 28 additions & 0 deletions garglk/garglk.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@
enum FILEFILTERS { FILTER_SAVE, FILTER_TEXT, FILTER_DATA };

#ifdef __cplusplus
#include <array>
#include <functional>
#include <memory>
#include <regex>
#include <sstream>
#include <string>
#include <vector>

Expand All @@ -59,6 +62,22 @@ struct ConfigFile {
bool user;
};

struct Color {
public:
Color(unsigned char red, unsigned char green, unsigned char blue) : m_red(red), m_green(green), m_blue(blue) {
}

void to(unsigned char *rgb) const;
static Color from(const unsigned char *rgb);
static Color from(const std::string &colors);

private:
unsigned char m_red, m_green, m_blue;
static const std::regex m_color_re;
};

extern std::vector<garglk::ConfigFile> all_configs;

std::string winopenfile(const char *prompt, enum FILEFILTERS filter);
std::string winsavefile(const char *prompt, enum FILEFILTERS filter);
void winabort(const std::string &msg);
Expand All @@ -69,6 +88,14 @@ void config_entries(const std::string &fname, bool accept_bare, const std::vecto
std::string user_config();
void set_lcdfilter(const std::string &filter);
std::string winfontpath(const std::string &filename);
std::vector<std::string> winappdata();

namespace theme {
void init();
void set(std::string name);
std::vector<std::string> paths();
std::vector<std::string> names();
}

template <typename T, typename Deleter>
std::unique_ptr<T, Deleter> unique(T *p, Deleter deleter)
Expand Down Expand Up @@ -718,6 +745,7 @@ void winopen(void);
void wintitle(void);
void winmore(void);
void winrepaint(int x0, int y0, int x1, int y1);
bool windark(void);
void winexit(void);
void winclipstore(glui32 *text, int len);

Expand Down
12 changes: 12 additions & 0 deletions garglk/garglk.ini
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,18 @@ gcolor 8 606060 ffffff # Input
gcolor 9 606060 ffffff # User1
gcolor 10 606060 ffffff # User2

# Instead of needing to specify individual colors, Gargoyle supports the concept
# of color themes, which set all possible colors at once. There are two
# hard-coded themes: "light" and "dark". In addition, there is a pseudo theme
# called "system" which attempts to follow the system theme, setting a dark or
# light theme as appropriate.
# Gargoyle also comes with several other themes, and you can provide your own
# themes as well. To see a list of available themes, use Shift-Control-T while
# Gargoyle is running. To see a list of theme paths (where you can add your own
# themes), use Control-period.
# This must come after all other color settings, or they will override the theme.
# See THEMES.md for information on how themes are created.
theme system

#===============================================================================
# Text-to-speech (TTS)
Expand Down
Loading

0 comments on commit 1a6f0f0

Please sign in to comment.