aboutsummaryrefslogtreecommitdiff
path: root/modules/ui-config.el
blob: 7afe528b213c71af26024ddb6db3c0ec5fcf8edc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
;;; ui-config --- User Interface Preferences -*- lexical-binding: t; coding: utf-8; -*-
;; author: Craig Jennings <c@cjennings.net>

;;; Commentary:
;;
;; Layer: 2 (Core UX).
;; Category: C/S.
;; Load shape: eager.
;; Eager reason: UI preferences that should be visible in the first frame.
;; Top-level side effects: UI defaults, a post-command hook, and
;;   display-buffer-alist entries.
;; Runtime requires: user-constants.
;; Direct test load: yes.
;;
;; This file centralizes user interface preferences, including:

;; • Frame and window behavior
;;   – Start all frames maximized
;;   – Disable file‐ and dialog‐boxes
;;   – Pixel scroll precision
;;   – Show column numbers in the mode-line

;; • Transparency controls
;;   – Customizable variables 'cj/enable-transparency' and 'cj/transparency-level'
;;   – Interactive 'cj/toggle-transparency' command

;; • Cursor appearance
;;   – Cursor color changes on the buffer's write and insertion state
;;     (i.e., read-only, overwrite, normal)
;;   – Option to customize cursor shape with 'cj/set-cursor-type'

;; • Icons
;;   – Load and enable 'nerd-icons' for UI glyphs

;; Customize the transparency and cursor color options at the top of this file.

;;; Code:

;; -------------------------------- UI Constants -------------------------------

(defvar cj/enable-transparency nil
  "Non-nil means use `cj/transparency-level' for frame transparency.")

(defvar cj/transparency-level 84
  "Opacity level for Emacs frames when `cj/enable-transparency' is non-nil.
100 = fully opaque, 0 = fully transparent.")

;; Use buffer status colors from user-constants
(require 'user-constants)

;; ----------------------------- System UI Settings ----------------------------

(add-to-list 'initial-frame-alist '(fullscreen . maximized)) ;; start the initial frame maximized
(add-to-list 'default-frame-alist '(fullscreen . maximized)) ;; start every frame maximized
(setq pixel-scroll-precision-mode nil)						 ;; disabled for performance

(setq-default frame-inhibit-implied-resize t)				 ;; don't resize frames when setting ui-elements
(setq frame-title-format '("Emacs " emacs-version " : %b"))	 ;; the title is emacs with version and buffer name

(setq use-file-dialog nil)									 ;; no file dialog
(setq use-dialog-box nil)									 ;; no dialog boxes either
(line-number-mode 1)                                         ;; show line number in the modeline (cached)
(column-number-mode 1)										 ;; show column number in the modeline (cached)
(setq switch-to-buffer-obey-display-actions t)               ;; manual buffer switching obeys display action rules

;; -------------------------------- Transparency -------------------------------

(defun cj/apply-transparency ()
  "Apply `cj/transparency-level' to the selected frame and future frames.

When `cj/enable-transparency' is nil, reset alpha to fully opaque."
  (let ((alpha (if cj/enable-transparency
				   (cons cj/transparency-level cj/transparency-level)
				 '(100 . 100))))
	;; apply to current frame (skip if terminal frame)
	(when (display-graphic-p)
	  (condition-case err
		  (set-frame-parameter nil 'alpha alpha)
		(error (message "Failed to set transparency: %s" (error-message-string err)))))
	;; update default for new frames
	(setq default-frame-alist
		  (assq-delete-all 'alpha default-frame-alist))
	(add-to-list 'default-frame-alist `(alpha . ,alpha))))

;; apply once at startup
(cj/apply-transparency)

(defun cj/toggle-transparency ()
  "Toggle `cj/enable-transparency' and re-apply."
  (interactive)
  (setq cj/enable-transparency (not cj/enable-transparency))
  (cj/apply-transparency)
  (message "Transparency %s"
		   (if cj/enable-transparency "enabled" "disabled")))

;; ----------------------------------- Cursor ----------------------------------
;; set cursor color according to mode
;;
;;    #f06a3f indicates a read-only document
;;    #c48702 indicates overwrite mode
;;    #64aa0f indicates insert and read/write mode

(defvar cj/-cursor-last-color nil
  "Last color applied by `cj/set-cursor-color-according-to-mode'.")
(defvar cj/-cursor-last-buffer nil
  "Last buffer name where cursor color was applied.")

(defun cj/--buffer-cursor-state ()
  "Return the buffer-state symbol used to choose the cursor color.

One of `read-only', `overwrite', `modified', or `unmodified' — keys
of `cj/buffer-status-colors'.

A live ghostel terminal (in `ghostel-mode' and an input mode that
forwards keys — semi-char / char / line) reports `unmodified' even
though the buffer is read-only: keystrokes go to the terminal process,
so from the user's side the buffer is writeable and the read-only
(orange) cursor would be misleading.  ghostel's `copy' and `emacs'
input modes are the exception — there the buffer really is a read-only
Emacs buffer the user navigates, so it falls through to `read-only'
and keeps the orange cursor."
  (cond
   ((and (eq major-mode 'ghostel-mode)
		 (not (memq (bound-and-true-p ghostel--input-mode) '(copy emacs))))
	'unmodified)
   (buffer-read-only       'read-only)
   (overwrite-mode         'overwrite)
   ((buffer-modified-p)    'modified)
   (t                      'unmodified)))

(defun cj/set-cursor-color-according-to-mode ()
  "Change cursor color according to buffer state (modified, read-only, overwrite).
Only updates for real user buffers, not internal/temporary buffers.
A no-op on non-graphical frames -- TTY/batch sessions have no cursor color
to set."
  (when (display-graphic-p)
    ;; Only update cursor for real buffers (not internal ones like *temp*, *Echo Area*, etc.)
    (unless (string-prefix-p " " (buffer-name))  ; Internal buffers start with space
      (let ((color (alist-get (cj/--buffer-cursor-state) cj/buffer-status-colors)))
        ;; Only skip if BOTH color AND buffer are the same (optimization)
        ;; This allows color to update when buffer state changes
        (unless (and (string= color cj/-cursor-last-color)
                     (string= (buffer-name) cj/-cursor-last-buffer))
          (set-cursor-color color)
          (setq cj/-cursor-last-color color
                cj/-cursor-last-buffer (buffer-name)))))))

;; Use post-command-hook to update cursor color after every command
;; This ensures cursor color always matches the current buffer's state.
;; The hook only registers under a graphical session so batch / TTY runs
;; don't pay per-command overhead for a no-op.
(when (display-graphic-p)
  (add-hook 'post-command-hook #'cj/set-cursor-color-according-to-mode))
;; Daemon mode: the first frame may be created after this module loads.
;; Re-attempt the hook install once a GUI frame appears.
(add-hook 'server-after-make-frame-hook
          (lambda ()
            (when (and (display-graphic-p)
                       (not (memq #'cj/set-cursor-color-according-to-mode
                                  post-command-hook)))
              (add-hook 'post-command-hook
                        #'cj/set-cursor-color-according-to-mode))))

;; Don’t show a cursor in non-selected windows:
(setq cursor-in-non-selected-windows nil)

;; Initialize to box cursor (or any type you prefer)
(defun cj/set-cursor-type (new-cursor-type)
  "Set the cursor type of the selected frame to NEW-CURSOR-TYPE."
  (interactive
   (list (intern (completing-read
				  "Cursor type: "
				  (mapcar #'list '("box" "hollow" "bar" "hbar" nil))))))
  (modify-frame-parameters nil `((cursor-type . ,new-cursor-type))))

(cj/set-cursor-type 'box)

;; Keep the cursor a solid block -- no blinking (including the initial blink
;; burst when entering read-only buffers like EPUBs or vterm).
(blink-cursor-mode -1)

;; --------------------------------- Nerd Icons --------------------------------
;; use icons from nerd fonts in the Emacs UI

(use-package nerd-icons
  :defer t)

(provide 'ui-config)
;;; ui-config.el ends here