patx/twig

Add editor font zoom and dark titlebar

Commit 4ff36e5 · patx · 2026-06-13T20:42:47-04:00

Changeset
4ff36e5a49af01dd32df8d28bedb0b66b48c20c5
Parents
9f64c2a5d65336ae5e606ed9adbc4ed0478950de

View source at this commit

Comments

No comments yet.

Log in to comment

Diff

diff --git a/README.md b/README.md
index 8f6cf07..476104f 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,8 @@ Twig intentionally has no toolbar or menu bar. Use these keyboard shortcuts:
 | `Ctrl+Shift+G` or `Shift+F3` | Find previous match |
 | `Ctrl+H` or `Ctrl+R` | Open Find and Replace with the replace field focused |
 | `Ctrl+J` | Jump to line |
+| `Ctrl+Shift++` | Increase editor font size |
+| `Ctrl+Shift+-` | Decrease editor font size |
 | `Tab` with selected lines | Indent selected lines with spaces |
 | `Shift+Tab` with selected lines | Unindent selected lines |
 | `Enter` in Find | Find next match |
diff --git a/docs/index.html b/docs/index.html
index 792e542..f787234 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -77,6 +77,7 @@ sudo make install</pre>
   <tr><td><code>Ctrl+G</code> / <code>Ctrl+Shift+G</code></td><td>Find next / previous</td></tr>
   <tr><td><code>Ctrl+H</code> / <code>Ctrl+R</code></td><td>Find &amp; replace</td></tr>
   <tr><td><code>Ctrl+J</code></td><td>Jump to line</td></tr>
+  <tr><td><code>Ctrl+Shift++</code> / <code>Ctrl+Shift+-</code></td><td>Increase / decrease editor font size</td></tr>
   <tr><td><code>Tab</code> with selection</td><td>Indent</td></tr>
   <tr><td><code>Shift+Tab</code> with selection</td><td>Unindent</td></tr>
   <tr><td><code>Enter</code> in Find</td><td>Find next</td></tr>
@@ -86,4 +87,4 @@ sudo make install</pre>
 <footer><a href="https://gitman.io/patx/twig">GitMan</a> &nbsp;· MIT license</footer>
 
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/twig.py b/twig.py
index 53db57d..a66cfc3 100755
--- a/twig.py
+++ b/twig.py
@@ -31,6 +31,10 @@ APP_ID = "io.github.patx.twig"
 APP_ICON = "twig"
 UNTITLED = "Untitled"
 BASE_DIR = Path(__file__).resolve().parent
+DEFAULT_EDITOR_FONT_SIZE = 10
+MIN_EDITOR_FONT_SIZE = 6
+MAX_EDITOR_FONT_SIZE = 24
+EDITOR_FONT_STEP = 1
 
 
 def read_text_file(path):
@@ -69,6 +73,29 @@ def install_css():
         background: #181b20;
         color: #7f8a99;
     }
+
+    headerbar.twig-titlebar,
+    headerbar.twig-titlebar:backdrop {
+        background: #181b20;
+        background-image: none;
+        border-color: #111318;
+        box-shadow: none;
+        color: #d8dee9;
+    }
+
+    headerbar.twig-titlebar button.titlebutton,
+    headerbar.twig-titlebar button.titlebutton:backdrop {
+        background: transparent;
+        background-image: none;
+        border-color: transparent;
+        box-shadow: none;
+        color: #d8dee9;
+    }
+
+    headerbar.twig-titlebar button.titlebutton:hover {
+        background: #2a3038;
+        background-image: none;
+    }
     """
     provider = Gtk.CssProvider()
     provider.load_from_data(css)
@@ -96,16 +123,23 @@ class TwigWindow(Gtk.ApplicationWindow):
         self.path = Path(path).resolve() if path else None
         self.encoding = "utf-8"
         self.find_dialog = None
+        self.editor_font_size = DEFAULT_EDITOR_FONT_SIZE
+        self.editor_css_name = f"twig-editor-{id(self):x}"
+        self.editor_css_provider = Gtk.CssProvider()
         self.buffer = GtkSource.Buffer()
         self.view = GtkSource.View.new_with_buffer(self.buffer)
+        self.view.set_name(self.editor_css_name)
 
         self.set_default_size(920, 640)
+        self._build_titlebar()
         set_window_icon(self)
         self._build_actions()
         self._build_ui()
         self._configure_editor()
+        self._install_editor_css()
         self.buffer.connect("modified-changed", lambda _buffer: self.update_title())
         self.connect("delete-event", self._on_delete_event)
+        self.connect("destroy", self._on_destroy)
 
         if self.path:
             self.load()
@@ -143,6 +177,13 @@ class TwigWindow(Gtk.ApplicationWindow):
             action.connect("activate", callback)
             self.add_action(action)
 
+    def _build_titlebar(self):
+        self.headerbar = Gtk.HeaderBar()
+        self.headerbar.set_show_close_button(True)
+        self.headerbar.set_has_subtitle(False)
+        self.headerbar.get_style_context().add_class("twig-titlebar")
+        self.set_titlebar(self.headerbar)
+
     def _build_ui(self):
         root = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
 
@@ -176,6 +217,42 @@ class TwigWindow(Gtk.ApplicationWindow):
                 self.buffer.set_style_scheme(scheme)
                 break
 
+    def _install_editor_css(self):
+        self._apply_editor_font_size()
+        Gtk.StyleContext.add_provider_for_screen(
+            Gdk.Screen.get_default(),
+            self.editor_css_provider,
+            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + 1,
+        )
+
+    def _apply_editor_font_size(self):
+        css = f"""
+        #{self.editor_css_name},
+        #{self.editor_css_name} text,
+        #{self.editor_css_name} border {{
+            font: {self.editor_font_size}pt monospace;
+        }}
+        """.encode()
+        self.editor_css_provider.load_from_data(css)
+        self.view.queue_resize()
+        self.view.queue_draw()
+
+    def _change_editor_font_size(self, delta):
+        new_size = min(
+            MAX_EDITOR_FONT_SIZE,
+            max(MIN_EDITOR_FONT_SIZE, self.editor_font_size + delta),
+        )
+        if new_size != self.editor_font_size:
+            self.editor_font_size = new_size
+            self._apply_editor_font_size()
+        return True
+
+    def _on_destroy(self, *_args):
+        Gtk.StyleContext.remove_provider_for_screen(
+            Gdk.Screen.get_default(),
+            self.editor_css_provider,
+        )
+
     def apply_language(self):
         manager = GtkSource.LanguageManager.get_default()
         if not self.path:
@@ -413,7 +490,9 @@ class TwigWindow(Gtk.ApplicationWindow):
     def update_title(self):
         dirty = "*" if self.buffer.get_modified() else ""
         name = str(self.path) if self.path else self.title_name
-        self.set_title(f"{dirty}{name} - Twig")
+        title = f"{dirty}{name} - Twig"
+        self.set_title(title)
+        self.headerbar.set_title(title)
 
     def show_error(self, title, detail):
         dialog = Gtk.MessageDialog(
@@ -523,7 +602,25 @@ class TwigWindow(Gtk.ApplicationWindow):
     def on_select_all(self, *_args):
         self.buffer.select_range(self.buffer.get_start_iter(), self.buffer.get_end_iter())
 
+    def _is_editor_font_shortcut(self, event):
+        state = event.state & Gtk.accelerator_get_default_mod_mask()
+        required = Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK
+        if (state & required) != required:
+            return False
+        return event.keyval in (
+            Gdk.KEY_plus,
+            Gdk.KEY_equal,
+            Gdk.KEY_KP_Add,
+            Gdk.KEY_minus,
+            Gdk.KEY_underscore,
+            Gdk.KEY_KP_Subtract,
+        )
+
     def on_key_press(self, _view, event):
+        if self._is_editor_font_shortcut(event):
+            if event.keyval in (Gdk.KEY_minus, Gdk.KEY_underscore, Gdk.KEY_KP_Subtract):
+                return self._change_editor_font_size(-EDITOR_FONT_STEP)
+            return self._change_editor_font_size(EDITOR_FONT_STEP)
         if event.keyval == Gdk.KEY_Tab and self.buffer.get_has_selection():
             return self.indent_selection()
         if event.keyval in (Gdk.KEY_ISO_Left_Tab, Gdk.KEY_Tab) and event.state & Gdk.ModifierType.SHIFT_MASK: