diff --git a/Makefile b/Makefile
index 7637339..f304308 100644
--- a/Makefile
+++ b/Makefile
@@ -5,5 +5,11 @@ install:
cp alacritty_config.py ${INSTALL_DIR}/alacritty-config
chmod u+x ${INSTALL_DIR}/alacritty-config
+gui:
+ pip3 install -Ur requirements.txt
+ cp alacritty_config_gui.py ${INSTALL_DIR}/alacritty-config-gui
+ chmod u+x ${INSTALL_DIR}/alacritty-config-gui
+
uninstall:
- rm ${INSTALL_DIR}/alacritty-config
+ rm -f ${INSTALL_DIR}/alacritty-config
+ rm -f ${INSTALL_DIR}/alacritty-config-gui
diff --git a/README.md b/README.md
index d47b179..fe5b9a6 100644
--- a/README.md
+++ b/README.md
@@ -50,9 +50,8 @@ $ alacritty-config tabspaces '"delete"'
## TODO
-Oh, so much! Ideally, I’d like a GUI at least for all the colorful properties and for
-selecting fonts. For now the tool is simple and usable enough to be useful, but a simple
-GUI would definitely give this a better UX. Another time.
+I’m writing a GUI for the configuration currently, but it’s far from complete. The parts
+that are already wrapped should work, though!
diff --git a/alacritty_config_gui.py b/alacritty_config_gui.py
new file mode 100644
index 0000000..27ef55d
--- /dev/null
+++ b/alacritty_config_gui.py
@@ -0,0 +1,272 @@
+#!/usr/bin/env python3
+import os
+import sys
+
+import yaml
+
+from PyQt5 import QtWidgets, QtGui
+
+
+ALACRITTY_CONFIG = os.path.expanduser("~/.config/alacritty/alacritty.yml")
+
+
+class ColorSelect(QtWidgets.QPushButton):
+ def __init__(self, value):
+ super().__init__()
+ self.set_value(value.replace('0x', '#'))
+ self.pressed.connect(self.handle_pressed)
+
+ def handle_pressed(self):
+ color = QtWidgets.QColorDialog.getColor()
+
+ if color.isValid():
+ self.set_value(color.name())
+
+ def set_value(self, value):
+ self._value = value
+ color = QtGui.QColor(self._value)
+ self.setFlat(True)
+ self.setAutoFillBackground(True)
+ self.setStyleSheet("""
+ QPushButton {{
+ color: {0};
+ background-color: {0};
+ border-style: outset;
+ }}
+ QPushButton:checked{{
+ color: {0};
+ background-color: {0};
+ border-style: outset;
+ }}
+ QPushButton:hover{{
+ background-color: {0};
+ border-style: outset;
+ }}
+ """.format(color.name()))
+ self.update()
+
+ def value(self):
+ return self._value.replace('#', '0x')
+
+
+class ConfigWidget(QtWidgets.QWidget):
+ def __init__(self):
+ super().__init__()
+ self.widgets = {}
+
+ def prettify(self, s):
+ return ' '.join(x.capitalize() for x in s.split('_') if x)
+
+ def render_state(self):
+ self.layout = QtWidgets.QVBoxLayout()
+ for name, widget in self.widgets.items():
+ sub_layout = QtWidgets.QHBoxLayout()
+ sub_layout.addWidget(QtWidgets.QLabel(self.prettify(name)))
+ sub_layout.addWidget(widget)
+ self.layout.addLayout(sub_layout)
+ self.setLayout(self.layout)
+
+ def gather_state(self):
+ state = {}
+ for name, widget in self.widgets.items():
+ path = name.split('__')
+ mut_state = state
+ for elem in path[:-1]:
+ if elem not in mut_state:
+ mut_state[elem] = {}
+ mut_state = mut_state[elem]
+ typ = type(widget)
+ name = path[-1]
+ if typ is QtWidgets.QComboBox:
+ mut_state[name] = widget.currentText()
+ elif typ is QtWidgets.QCheckBox:
+ mut_state[name] = widget.isChecked()
+ elif typ is QtWidgets.QLineEdit:
+ mut_state[name] = widget.text()
+ else:
+ mut_state[name] = widget.value()
+ return state
+
+
+class Debug(ConfigWidget):
+ def __init__(self, config):
+ super().__init__()
+ self.widgets['render_timer'] = QtWidgets.QCheckBox()
+ self.widgets['render_timer'].setCheckState(config.get('render_timer'))
+ self.render_state()
+
+
+class Env(ConfigWidget):
+ def __init__(self, config):
+ super().__init__()
+ self.widgets['TERM'] = QtWidgets.QLineEdit(config.get('TERM'))
+ self.render_state()
+
+
+class Selection(ConfigWidget):
+ def __init__(self, config):
+ super().__init__()
+ self.widgets['semantic_escape_chars'] = QtWidgets.QLineEdit(
+ config.get('semantic_escape_chars')
+ )
+ self.render_state()
+
+
+class Shell(ConfigWidget):
+ def __init__(self, config):
+ super().__init__()
+ self.widgets['program'] = QtWidgets.QLineEdit(config.get('program'))
+ self.render_state()
+
+
+class Font(ConfigWidget):
+ def __init__(self, config):
+ super().__init__()
+ glyph_offset_x = QtWidgets.QSpinBox()
+ glyph_offset_x.setValue(config.get('glyph_offset', {}).get('x'))
+ glyph_offset_y = QtWidgets.QSpinBox()
+ glyph_offset_y.setValue(config.get('glyph_offset', {}).get('y'))
+ scale_with_dpi = QtWidgets.QCheckBox()
+ scale_with_dpi.setChecked(config.get('scale_with_dpi'))
+ size = QtWidgets.QDoubleSpinBox()
+ size.setValue(config.get('size'))
+ use_thin_strokes = QtWidgets.QCheckBox()
+ use_thin_strokes.setChecked(config.get('use_thin_strokes'))
+
+ self.widgets = {
+ 'glyph_offset__x': glyph_offset_x,
+ 'glyph_offset__y': glyph_offset_y,
+ 'scale_with_dpi': scale_with_dpi,
+ 'size': size,
+ 'use_thin_strokes': use_thin_strokes,
+ }
+
+ self.render_state()
+
+
+class Colors(ConfigWidget):
+ def __init__(self, config):
+ super().__init__()
+
+ primary_background = ColorSelect(
+ config.get('primary', {}).get('background')
+ )
+ primary_foreground = ColorSelect(
+ config.get('primary', {}).get('foreground')
+ )
+
+ self.widgets = {
+ 'primary__background': primary_background,
+ 'primary__foreground': primary_foreground,
+ }
+
+ self.render_state()
+
+
+class Window(ConfigWidget):
+ # TODO: transparent and buttonless only on OS X
+ decoration_options = ['full', 'none', 'transparent', 'buttonless']
+
+ # TODO: simplewindowed only on OS X
+ startup_options = ['Windowed', 'FullScreen', 'SimpleWindowed', 'Maximized']
+ def __init__(self, config):
+ super().__init__()
+ decorations = QtWidgets.QComboBox()
+ for option in self.decoration_options:
+ decorations.addItem(option)
+ dec = config.get('decorations')
+ if dec in self.decoration_options:
+ decorations.setCurrentIndex(self.decoration_options.index(dec))
+
+ startup_mode = QtWidgets.QComboBox()
+ for mode in self.startup_options:
+ startup_mode.addItem(mode)
+ dec = config.get('startup_mode')
+ if dec in self.startup_options:
+ startup_mode.setCurrentIndex(self.startup_options.index(dec))
+
+ columns = QtWidgets.QSpinBox()
+ columns.setValue(config.get('dimensions', {}).get('columns'))
+ lines = QtWidgets.QSpinBox()
+ lines.setValue(config.get('dimensions', {}).get('lines'))
+
+ padding_x = QtWidgets.QSpinBox()
+ padding_x.setValue(config.get('padding', {}).get('x'))
+ padding_y = QtWidgets.QSpinBox()
+ padding_y.setValue(config.get('padding', {}).get('y'))
+
+ dynamic_padding = QtWidgets.QCheckBox()
+ dynamic_padding.setChecked(config.get('dynamic_padding', False))
+
+ self.widgets = {
+ 'decorations': decorations,
+ 'startup_mode': startup_mode,
+ 'dimensions__columns': columns,
+ 'dimensions__lines': lines,
+ 'padding__x': padding_x,
+ 'padding__y': padding_y,
+ 'dynamic_padding': dynamic_padding,
+ }
+
+ self.render_state()
+
+
+class Config(QtWidgets.QWidget):
+ def __init__(self, config):
+ super().__init__()
+ self.layout = QtWidgets.QVBoxLayout()
+ self.config = config
+ self.add_tabs(config)
+ self.add_buttons()
+ self.setLayout(self.layout)
+
+ def add_tabs(self, config):
+ self.tabs = QtWidgets.QTabWidget()
+
+ self.tabs.addTab(Window(config.get('window')), "Window")
+ self.tabs.addTab(Font(config.get('font')), "Font")
+ self.tabs.addTab(Debug(config.get('debug')), "Debug")
+ self.tabs.addTab(Env(config.get('env')), "Env")
+ self.tabs.addTab(Selection(config.get('selection')), "Selection")
+ self.tabs.addTab(Shell(config.get('shell')), "Shell")
+ self.tabs.addTab(Colors(config.get('colors')), "Colors")
+
+ self.layout.addWidget(self.tabs)
+
+ def add_buttons(self):
+ self.buttons = QtWidgets.QDialogButtonBox()
+ ok_button = self.buttons.addButton(self.buttons.Ok)
+ self.buttons.addButton(self.buttons.Cancel)
+ self.buttons.accepted.connect(self.save)
+ self.buttons.rejected.connect(sys.exit)
+ self.layout.addWidget(self.buttons)
+
+ def gather_state(self):
+ state = self.config
+
+ for idx in range(self.tabs.count()):
+ tab = self.tabs.widget(idx)
+ title = self.tabs.tabText(idx).lower()
+ state[title] = {**state.get(title, {}), **tab.gather_state()}
+
+ return state
+
+ def save(self):
+ state = self.gather_state()
+ print(yaml.dump(state))
+ with open(ALACRITTY_CONFIG, 'w+') as f:
+ f.write(yaml.dump(state))
+ sys.exit()
+
+ def addDialog(self):
+ dialog = QtWidgets.QColorDialog()
+ dialog.show()
+
+
+if __name__ == '__main__':
+ app = QtWidgets.QApplication([])
+ with open(ALACRITTY_CONFIG) as f:
+ config = yaml.safe_load(f.read())
+ conf = Config(config)
+ conf.show()
+ app.exec_()