├── .gitignore
├── LICENSE
├── README.org
├── client
├── package.json
├── rollup.config.js
├── src
│ └── index.js
├── static
│ └── index.html
└── yarn.lock
├── color-picker.html
├── screenshots
└── webkit-color-picker.gif
├── webkit-color-picker.el
└── webkit-color-picker.org
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled
2 | *.elc
3 |
4 | # Packaging
5 | .cask
6 |
7 | # Backup files
8 | *~
9 |
10 | # Undo-tree save-files
11 | *.~undo-tree
12 |
13 | /client/node_modules
14 | /client/build
15 | /client/yarn-error.log
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Ozan Sener
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.org:
--------------------------------------------------------------------------------
1 | #+AUTHOR: Ozan Sener
2 | * webkit-color-picker
3 |
4 | Small experiment with embedded a Webkit widgets in a childframe. Requires Emacs 26 compiled with [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Embedded-WebKit-Widgets.html][embedded Webkit Widget support]].
5 |
6 | webkit-color-picker is available on [[https://melpa.org/][MELPA]]. Example configuration using [[https://github.com/jwiegley/use-package][use-package]]:
7 |
8 | #+BEGIN_SRC emacs-lisp
9 | (use-package webkit-color-picker
10 | :ensure t
11 | :bind (("C-c C-p" . webkit-color-picker-show)))
12 | #+END_SRC
13 |
14 | ** Screenshot
15 | [[file:./screenshots/webkit-color-picker.gif]]
16 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "emacs-webkit-color-picker",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "browse": "browser-sync start --s --ss static --ss build --index static/index-dev.html --files static/*.html,build/*.js --no-notify",
8 | "build:js": "rollup -c",
9 | "build:html": "sed -e 's/app\\.js/app.min.js/' static/index.html | html-inline -b build -o ../color-picker.html",
10 | "build": "npm run build:js && npm run build:html",
11 | "start": "rollup -c -w"
12 | },
13 | "devDependencies": {
14 | "babel-core": "^6.26.0",
15 | "babel-plugin-external-helpers": "^6.22.0",
16 | "babel-preset-env": "^1.6.1",
17 | "babel-preset-react": "^6.24.1",
18 | "babel-preset-stage-0": "^6.24.1",
19 | "browser-sync": "^2.23.6",
20 | "html-inline": "^1.2.0",
21 | "rollup": "0.52.0",
22 | "rollup-plugin-alias": "^1.4.0",
23 | "rollup-plugin-babel": "^3.0.3",
24 | "rollup-plugin-browsersync": "^0.2.6",
25 | "rollup-plugin-commonjs": "8.2.6",
26 | "rollup-plugin-node-globals": "1.1.0",
27 | "rollup-plugin-node-resolve": "3.0.0",
28 | "rollup-plugin-replace": "2.0.0",
29 | "rollup-plugin-uglify": "2.0.1",
30 | "rollup-watch": "4.3.1"
31 | },
32 | "dependencies": {
33 | "preact": "^8.2.7",
34 | "preact-compat": "^3.18.0",
35 | "react-color": "https://github.com/casesandberg/react-color",
36 | "tinycolor2": "^1.4.1"
37 | },
38 | "resolutions": {
39 | "prop-types": "15.6.0",
40 | "loose-envify": "1.3.1",
41 | "object-assign": "4.1.1"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/client/rollup.config.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 |
3 | import alias from "rollup-plugin-alias";
4 | import babel from "rollup-plugin-babel";
5 | import cjs from "rollup-plugin-commonjs";
6 | import globals from "rollup-plugin-node-globals";
7 | import replace from "rollup-plugin-replace";
8 | import resolve from "rollup-plugin-node-resolve";
9 | import uglify from "rollup-plugin-uglify";
10 | import browsersync from "rollup-plugin-browsersync";
11 |
12 | const production = !process.env.ROLLUP_WATCH;
13 |
14 | const devPlugins = !production ? [browsersync({ server: "build" })] : [];
15 | const prodPlugins = production ? [uglify()] : [];
16 |
17 | export default {
18 | input: "src/index.js",
19 | output: {
20 | name: "colorpicker",
21 | file: production ? "build/app.min.js" : "build/app.js",
22 | format: "iife",
23 | sourcemap: true
24 | },
25 | plugins: [
26 | alias({
27 | react: path.resolve(
28 | __dirname,
29 | "node_modules",
30 | "preact-compat",
31 | "dist",
32 | "preact-compat.es.js"
33 | ),
34 | "react-dom": path.resolve(
35 | __dirname,
36 | "node_modules",
37 | "preact-compat",
38 | "dist",
39 | "preact-compat.es.js"
40 | ),
41 | "create-react-class": path.resolve(
42 | __dirname,
43 | "node_modules",
44 | "preact-compat",
45 | "lib",
46 | "create-react-class.js"
47 | )
48 | }),
49 | resolve({
50 | browser: true,
51 | main: true
52 | }),
53 | cjs({
54 | exclude: ["node_modules/process-es6/**", "node_modules/react-color/**"],
55 | include: [
56 | "node_modules/fbjs/**",
57 | "node_modules/object-assign/**",
58 | "node_modules/reactcss/**",
59 | "node_modules/lodash/**",
60 | "node_modules/tinycolor2/**",
61 | "node_modules/prop-types/**"
62 | ],
63 | namedExports: {
64 | "node_modules/react/index.js": [
65 | "Component",
66 | "PureComponent",
67 | "Children",
68 | "createElement"
69 | ]
70 | }
71 | }),
72 | globals(),
73 | replace({
74 | "process.env.NODE_ENV": JSON.stringify(
75 | production ? "production" : "development"
76 | )
77 | }),
78 | babel({
79 | babelrc: false,
80 | presets: [
81 | [
82 | "env",
83 | {
84 | modules: false,
85 | loose: true
86 | // targets: { safari: 11 }
87 | }
88 | ],
89 | "stage-0",
90 | "react"
91 | ],
92 | plugins: ["external-helpers"]
93 | }),
94 | ...devPlugins,
95 | ...prodPlugins
96 | ]
97 | };
98 |
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import ReactDOM from "react-dom";
3 | import SketchPicker from "react-color/src/components/sketch/Sketch";
4 | import tinycolor from "tinycolor2";
5 |
6 | function toState(input) {
7 | const color = tinycolor(input);
8 |
9 | return {
10 | format: color.getFormat() || "hex",
11 | color: color.toRgb()
12 | };
13 | }
14 |
15 | export default class ColorPicker extends Component {
16 | state = toState(window.selectedColor);
17 |
18 | componentDidMount() {
19 | Object.defineProperty(window, "selectedColor", {
20 | get: () => {
21 | const color = tinycolor(this.state.color);
22 |
23 | switch (this.state.format) {
24 | case "hsl":
25 | return color.toHslString();
26 | case "hex8":
27 | return color.toHex8String();
28 | case "prgb":
29 | return color.toPercentageRgbString();
30 | case "rgb":
31 | return color.toRgbString();
32 | default:
33 | return color.getAlpha() === 1
34 | ? color.toHexString()
35 | : color.toRgbString();
36 | }
37 | },
38 | set: color => this.setState(toState(color))
39 | });
40 | }
41 |
42 | setColor = color => this.setState({ color: color.rgb });
43 |
44 | render() {
45 | return (
46 |
53 | );
54 | }
55 | }
56 |
57 | ReactDOM.render(, document.getElementById("root"));
58 |
--------------------------------------------------------------------------------
/client/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/color-picker.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/screenshots/webkit-color-picker.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozanmakes/emacs-webkit-color-picker/765cac80144cad4bc0bf59025ea0199f0486f737/screenshots/webkit-color-picker.gif
--------------------------------------------------------------------------------
/webkit-color-picker.el:
--------------------------------------------------------------------------------
1 | ;;; webkit-color-picker.el --- Insert and adjust colors using Webkit Widgets -*- lexical-binding: t -*-
2 |
3 | ;; Copyright (C) 2018 Ozan Sener
4 |
5 | ;; Author: Ozan Sener
6 | ;; URL: https://github.com/osener/emacs-webkit-color-picker
7 | ;; Maintainer: Ozan Sener
8 | ;; Version: 0.1.0
9 | ;; Keywords: tools
10 | ;; Package-Requires: ((emacs "26.0") (posframe "0.1.0"))
11 |
12 | ;; This file is NOT part of GNU Emacs.
13 |
14 | ;; The MIT License (MIT)
15 |
16 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy
17 | ;; of this software and associated documentation files (the "Software"), to deal
18 | ;; in the Software without restriction, including without limitation the rights
19 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20 | ;; copies of the Software, and to permit persons to whom the Software is
21 | ;; furnished to do so, subject to the following conditions:
22 |
23 | ;; The above copyright notice and this permission notice shall be included in all
24 | ;; copies or substantial portions of the Software.
25 |
26 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32 | ;; SOFTWARE.
33 |
34 | ;;; Commentary:
35 |
36 | ;; #+OPTIONS: toc:nil title:nil timestamp:nil
37 | ;; * webkit-color-picker :README:
38 |
39 | ;; Small experiment with embedded a Webkit widgets in a childframe. Requires Emacs 26 compiled with [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Embedded-WebKit-Widgets.html][embedded Webkit Widget support]].
40 |
41 | ;; webkit-color-picker is available on [[https://melpa.org/][MELPA]]. Example configuration using [[https://github.com/jwiegley/use-package][use-package]]:
42 |
43 | ;; #+BEGIN_SRC emacs-lisp
44 | ;; (use-package webkit-color-picker
45 | ;; :ensure t
46 | ;; :bind (("C-c C-p" . webkit-color-picker-show)))
47 | ;; #+END_SRC
48 |
49 | ;; ** Screenshot
50 | ;; [[./screenshots/webkit-color-picker.gif]]
51 |
52 | ;;; Code:
53 | ;; * webkit-color-picker's code
54 |
55 | (require 'xwidget)
56 | (require 'posframe)
57 | (require 'css-mode)
58 | (eval-when-compile (require 'subr-x))
59 | (eval-when-compile (require 'cl-lib))
60 |
61 | (defvar webkit-color-picker--client-path
62 | (concat "file://"
63 | (file-name-directory (or load-file-name buffer-file-name))
64 | "color-picker.html"))
65 |
66 | (defun webkit-color-picker--run-xwidget ()
67 | "Launch embedded Webkit instance."
68 | (with-current-buffer " *webkit-color-picker*"
69 | (let ((inhibit-read-only t))
70 | (goto-char 1)
71 |
72 | (let ((id (make-xwidget
73 | 'webkit
74 | nil
75 | (window-pixel-width)
76 | (window-pixel-height)
77 | nil
78 | "*webkit-color-picker*")))
79 | (put-text-property (point) (+ 1 (point))
80 | 'display (list 'xwidget ':xwidget id))
81 | (xwidget-webkit-mode)
82 | (xwidget-webkit-goto-uri (xwidget-at 1)
83 | webkit-color-picker--client-path)))))
84 |
85 | (defun webkit-color-picker--show ()
86 | "Make color picker childframe visible."
87 | (when-let* ((current-frame (selected-frame))
88 | (buffer (webkit-color-picker--get-buffer))
89 | (frame (webkit-color-picker--get-frame)))
90 | (progn
91 | (select-frame frame t)
92 | (switch-to-buffer buffer t t)
93 | (select-frame current-frame t)
94 | (make-frame-visible frame)
95 | (redraw-frame frame)
96 |
97 | (let*
98 | ((position (point))
99 | (parent-window (selected-window))
100 | (parent-frame (window-frame parent-window))
101 | (x-pixel-offset 0)
102 | (y-pixel-offset 0)
103 | (font-width (default-font-width))
104 | (font-height (posframe--get-font-height position))
105 | (frame-resize-pixelwise t)
106 | (position (posframe-poshandler-point-bottom-left-corner
107 | `(;All poshandlers will get info from this plist.
108 | :position ,position
109 | :font-height ,font-height
110 | :font-width ,font-width
111 | :posframe ,frame
112 | :posframe-buffer ,buffer
113 | :parent-frame ,parent-frame
114 | :parent-window ,parent-window
115 | :x-pixel-offset ,x-pixel-offset
116 | :y-pixel-offset ,y-pixel-offset))))
117 |
118 | (set-frame-position frame (car position) (cdr position))))))
119 |
120 | (defun webkit-color-picker--create ()
121 | "Create a new posframe and launch Webkit."
122 | (let ((x-pointer-shape x-pointer-top-left-arrow))
123 | (posframe-show " *webkit-color-picker*"
124 | :string " "
125 | :position (point)))
126 |
127 | (define-key (current-global-map) [xwidget-event]
128 | (lambda ()
129 | (interactive)
130 |
131 | (let ((xwidget-event-type (nth 1 last-input-event)))
132 | (when (eq xwidget-event-type 'load-changed)
133 | (webkit-color-picker--resize)
134 | (webkit-color-picker--set-background))
135 |
136 | (when (eq xwidget-event-type 'javascript-callback)
137 | (let ((proc (nth 3 last-input-event))
138 | (arg (nth 4 last-input-event)))
139 | (funcall proc arg))))))
140 |
141 | (webkit-color-picker--run-xwidget))
142 |
143 | (defun webkit-color-picker--get-buffer ()
144 | "Return color picker buffer."
145 | (get-buffer " *webkit-color-picker*"))
146 |
147 | (defun webkit-color-picker--get-frame ()
148 | "Return color picker frame."
149 | (when-let* ((buffer (webkit-color-picker--get-buffer)))
150 | (seq-find
151 | (lambda (frame)
152 | (let ((buffer-info (frame-parameter frame 'posframe-buffer)))
153 | (or (eq buffer (car buffer-info))
154 | (eq buffer (cdr buffer-info)))))
155 | (frame-list))))
156 |
157 | (defun webkit-color-picker--set-background ()
158 | "Evaluate JS code in color picker Webkit instance."
159 | (webkit-color-picker--execute-script
160 | (format "document.body.style.background = '%s';"
161 | (face-attribute 'default :background))))
162 |
163 | (defun webkit-color-picker--insert-color ()
164 | "Get the selected color from the widget and insert in the current buffer."
165 | (webkit-color-picker--execute-script
166 | "window.selectedColor;"
167 | `(lambda (color)
168 | (let ((color (kill-new (or color "")))
169 | (start (or (car webkit-color-picker--last-position) (point)))
170 | (end (or (cdr webkit-color-picker--last-position) (point))))
171 | (when (> (length color) 0)
172 | (delete-region start end)
173 | (goto-char start)
174 | (insert color)
175 | (webkit-color-picker-hide))))))
176 |
177 | (defvar webkit-color-picker--emulation-alist '((t . nil)))
178 |
179 | (defvar-local webkit-color-picker--my-keymap nil)
180 | (defvar-local webkit-color-picker--last-position nil)
181 |
182 | (defsubst webkit-color-picker--enable-overriding-keymap (keymap)
183 | "Enable color picker overriding KEYMAP."
184 | (webkit-color-picker--uninstall-map)
185 | (setq webkit-color-picker--my-keymap keymap))
186 |
187 | (defun webkit-color-picker--ensure-emulation-alist ()
188 | "Append color picker emulation alist."
189 | (unless (eq 'webkit-color-picker--emulation-alist (car emulation-mode-map-alists))
190 | (setq emulation-mode-map-alists
191 | (cons 'webkit-color-picker--emulation-alist
192 | (delq 'webkit-color-picker--emulation-alist emulation-mode-map-alists)))))
193 |
194 | ;; TODO: Find a better way of preventing accidental keystrokes whether the
195 | ;; childframe is in focus or not
196 | (defun webkit-color-picker--install-map ()
197 | "Install temporary color picker keymap."
198 | (unless (or (cdar webkit-color-picker--emulation-alist)
199 | (null webkit-color-picker--my-keymap))
200 | (setf (cdar webkit-color-picker--emulation-alist) webkit-color-picker--my-keymap)))
201 |
202 | (defun webkit-color-picker--uninstall-map ()
203 | "Uninstall temporary color picker keymap."
204 | (setf (cdar webkit-color-picker--emulation-alist) nil))
205 |
206 | (defvar webkit-color-picker--active-map
207 | (let ((keymap (make-sparse-keymap)))
208 | (define-key keymap "\e\e\e" 'webkit-color-picker-hide)
209 | (define-key keymap "\C-g" 'webkit-color-picker-hide)
210 | (define-key keymap [mouse-1] (lambda () (interactive) (webkit-color-picker--insert-color)))
211 | (define-key keymap (kbd "RET") (lambda () (interactive) (webkit-color-picker--insert-color)))
212 | keymap)
213 | "Keymap that is enabled during an active completion.")
214 |
215 | (defvar webkit-color-picker--hex-color-regexp
216 | (concat
217 | ;; Short hex. css-color-4 adds alpha.
218 | "\\(#[0-9a-fA-F]\\{3,4\\}\\b\\)"
219 | "\\|"
220 | ;; Long hex. css-color-4 adds alpha.
221 | "\\(#\\(?:[0-9a-fA-F][0-9a-fA-F]\\)\\{3,4\\}\\b\\)"))
222 |
223 | (defun webkit-color-picker--get-hex-color-at-point ()
224 | "Return hex color at point."
225 | (with-syntax-table (copy-syntax-table (syntax-table))
226 | (modify-syntax-entry ?# "w") ; Make `#' a word constituent.
227 | (when-let* ((word (thing-at-point 'word t))
228 | (bounds (bounds-of-thing-at-point 'word)))
229 | (when (string-match webkit-color-picker--hex-color-regexp word)
230 | (cons word bounds)))))
231 |
232 | (defun webkit-color-picker--get-named-color-at-point ()
233 | "Return color name at point."
234 | (when-let* ((word (word-at-point))
235 | (color (assoc (downcase word) css--color-map)))
236 | (cons word (bounds-of-thing-at-point 'word))))
237 |
238 | (defun webkit-color-picker--get-rgb-or-hsl-color-at-point ()
239 | "Return RGB or HSL formatted color at point."
240 | (save-excursion
241 | (when-let* ((open-paren-pos (nth 1 (syntax-ppss))))
242 | (when (save-excursion
243 | (goto-char open-paren-pos)
244 | (looking-back "\\(?:hsl\\|rgb\\)a?" (- (point) 4)))
245 | (goto-char (nth 1 (syntax-ppss)))))
246 | (when (eq (char-before) ?\))
247 | (backward-sexp))
248 | (skip-chars-backward "rgbhslaRGBHSLA")
249 | (when (looking-at "\\(\\_<\\(?:hsl\\|rgb\\)a?(\\)")
250 | (when-let* ((start (point))
251 | (end (search-forward ")" nil t)))
252 | (cons (buffer-substring-no-properties start end) (cons start end))))))
253 |
254 | (defun webkit-color-picker--color-at-point ()
255 | "Return recognized color at point."
256 | (or
257 | (webkit-color-picker--get-rgb-or-hsl-color-at-point)
258 | (webkit-color-picker--get-named-color-at-point)
259 | (webkit-color-picker--get-hex-color-at-point)))
260 |
261 | (defun webkit-color-picker--get-xwidget ()
262 | "Return Xwidget instance."
263 | (with-current-buffer " *webkit-color-picker*"
264 | (xwidget-at 1)))
265 |
266 | (defun webkit-color-picker--execute-script (script &optional fn)
267 | "Execute SCRIPT in embedded Xwidget and run optional callback FN."
268 | (when-let* ((xw (webkit-color-picker--get-xwidget)))
269 | (xwidget-webkit-execute-script xw script fn)))
270 |
271 | (defun webkit-color-picker--resize ()
272 | "Resize color picker frame to widget boundaries."
273 | (webkit-color-picker--execute-script
274 | "[document.querySelector('.picker').offsetWidth, document.querySelector('.picker').offsetHeight];"
275 | (lambda (size)
276 | (when-let* ((frame (webkit-color-picker--get-frame)))
277 | (modify-frame-parameters
278 | frame
279 | `((width . (text-pixels . ,(+ 30 (aref size 0))))
280 | (height . (text-pixels . ,(+ 30 (aref size 1))))
281 | (inhibit-double-buffering . t)))))))
282 |
283 | (defun webkit-color-picker--set-color (color)
284 | "Update color picker widget state with COLOR."
285 | (webkit-color-picker--execute-script
286 | (format
287 | "window.selectedColor = '%s';"
288 | (if (stringp color) color "#000000"))))
289 |
290 | ;;;###autoload
291 | (defun webkit-color-picker-show ()
292 | "Activate color picker."
293 | (interactive)
294 | (unless (featurep 'xwidget-internal)
295 | (user-error "Your Emacs was not compiled with xwidgets support"))
296 | (unless (display-graphic-p)
297 | (user-error "webkit-color-picker only works in graphical displays"))
298 | (let ((color-at-point (webkit-color-picker--color-at-point)))
299 | (if (buffer-live-p (webkit-color-picker--get-buffer))
300 | (webkit-color-picker--show)
301 | (webkit-color-picker--create))
302 |
303 | (webkit-color-picker--set-color (car color-at-point))
304 | (webkit-color-picker--set-background)
305 |
306 | (setq-local webkit-color-picker--last-position
307 | (or (cdr color-at-point)
308 | (cons (point) (point))))
309 |
310 | (webkit-color-picker--ensure-emulation-alist)
311 | (webkit-color-picker--enable-overriding-keymap webkit-color-picker--active-map)
312 | (webkit-color-picker--install-map)
313 |
314 | t))
315 |
316 | ;;;###autoload
317 | (defun webkit-color-picker-hide ()
318 | "Hide color picker frame."
319 | (interactive)
320 | (when-let* ((frame (webkit-color-picker--get-frame)))
321 | (make-frame-invisible frame))
322 | (webkit-color-picker--enable-overriding-keymap nil))
323 |
324 | ;;;###autoload
325 | (defun webkit-color-picker-cleanup ()
326 | "Destroy color picker buffer and frame."
327 | (interactive)
328 | (dolist (xwidget-view xwidget-view-list)
329 | (delete-xwidget-view xwidget-view))
330 | (posframe-delete-all)
331 | (kill-buffer " *webkit-color-picker*"))
332 |
333 | (provide 'webkit-color-picker)
334 |
335 | ;; Local Variables:
336 | ;; coding: utf-8-unix
337 | ;; End:
338 |
339 | ;;; webkit-color-picker.el ends here
340 |
--------------------------------------------------------------------------------
/webkit-color-picker.org:
--------------------------------------------------------------------------------
1 | # Created 2018-03-25 Sun 08:55
2 | #+OPTIONS: toc:nil
3 | #+TITLE: Insert and adjust colors using Webkit Widgets
4 | #+AUTHOR: Ozan Sener
5 | Copyright (C) 2018 Ozan Sener
6 |
7 | Author: Ozan Sener
8 | URL: https://github.com/osener/emacs-webkit-color-picker
9 | Maintainer: Ozan Sener
10 | Version: 0.1.0
11 | Keywords: tools
12 | Package-Requires: ((emacs "26.0") (posframe "0.1.0"))
13 |
14 | This file is NOT part of GNU Emacs.
15 |
16 | The MIT License (MIT)
17 |
18 | Permission is hereby granted, free of charge, to any person obtaining a copy
19 | of this software and associated documentation files (the "Software"), to deal
20 | in the Software without restriction, including without limitation the rights
21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22 | copies of the Software, and to permit persons to whom the Software is
23 | furnished to do so, subject to the following conditions:
24 |
25 | The above copyright notice and this permission notice shall be included in all
26 | copies or substantial portions of the Software.
27 |
28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
34 | SOFTWARE.
35 | * webkit-color-picker :README:
36 |
37 | Small experiment with embedded a Webkit widgets in a childframe. Requires Emacs 26 compiled with [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Embedded-WebKit-Widgets.html][embedded Webkit Widget support]].
38 |
39 | webkit-color-picker is available on [[https://melpa.org/][MELPA]].
40 |
41 | #+BEGIN_EXAMPLE
42 | (use-package webkit-color-picker
43 | :ensure t
44 | :bind (("C-c C-p" . webkit-color-picker-show)))
45 | #+END_EXAMPLE
46 |
47 | ** Screenshot
48 | [[file:./screenshots/webkit-color-picker.gif]]
49 | * webkit-color-picker's code
50 |
51 | #+BEGIN_SRC emacs-lisp
52 | (require 'xwidget)
53 | (require 'posframe)
54 | (require 'css-mode)
55 | (eval-when-compile (require 'subr-x))
56 | (eval-when-compile (require 'cl-lib))
57 | #+END_SRC
58 |
59 | #+BEGIN_SRC emacs-lisp
60 | (defvar webkit-color-picker--client-path
61 | (concat "file://"
62 | (file-name-directory (or load-file-name buffer-file-name))
63 | "color-picker.html"))
64 | #+END_SRC
65 |
66 | #+BEGIN_SRC emacs-lisp
67 | (defun webkit-color-picker--run-xwidget ()
68 | "Launch embedded Webkit instance."
69 | (with-current-buffer " *webkit-color-picker*"
70 | (let ((inhibit-read-only t))
71 | (goto-char 1)
72 |
73 | (let ((id (make-xwidget
74 | 'webkit
75 | nil
76 | (window-pixel-width)
77 | (window-pixel-height)
78 | nil
79 | "*webkit-color-picker*")))
80 | (put-text-property (point) (+ 1 (point))
81 | 'display (list 'xwidget ':xwidget id))
82 | (xwidget-webkit-mode)
83 | (xwidget-webkit-goto-uri (xwidget-at 1)
84 | webkit-color-picker--client-path)))))
85 | #+END_SRC
86 |
87 | #+BEGIN_SRC emacs-lisp
88 | (defun webkit-color-picker--show ()
89 | "Make color picker childframe visible."
90 | (when-let* ((current-frame (selected-frame))
91 | (buffer (webkit-color-picker--get-buffer))
92 | (frame (webkit-color-picker--get-frame)))
93 | (progn
94 | (select-frame frame t)
95 | (switch-to-buffer buffer t t)
96 | (select-frame current-frame t)
97 | (make-frame-visible frame)
98 | (redraw-frame frame)
99 |
100 | (let*
101 | ((position (point))
102 | (parent-window (selected-window))
103 | (parent-frame (window-frame parent-window))
104 | (x-pixel-offset 0)
105 | (y-pixel-offset 0)
106 | (font-width (default-font-width))
107 | (font-height (posframe--get-font-height position))
108 | (frame-resize-pixelwise t)
109 | (position (posframe-poshandler-point-bottom-left-corner
110 | `(;All poshandlers will get info from this plist.
111 | :position ,position
112 | :font-height ,font-height
113 | :font-width ,font-width
114 | :posframe ,frame
115 | :posframe-buffer ,buffer
116 | :parent-frame ,parent-frame
117 | :parent-window ,parent-window
118 | :x-pixel-offset ,x-pixel-offset
119 | :y-pixel-offset ,y-pixel-offset))))
120 |
121 | (set-frame-position frame (car position) (cdr position))))))
122 | #+END_SRC
123 |
124 | #+BEGIN_SRC emacs-lisp
125 | (defun webkit-color-picker--create ()
126 | "Create a new posframe and launch Webkit."
127 | (let ((x-pointer-shape x-pointer-top-left-arrow))
128 | (posframe-show " *webkit-color-picker*"
129 | :string " "
130 | :position (point)))
131 |
132 | (define-key (current-global-map) [xwidget-event]
133 | (lambda ()
134 | (interactive)
135 |
136 | (let ((xwidget-event-type (nth 1 last-input-event)))
137 | (when (eq xwidget-event-type 'load-changed)
138 | (webkit-color-picker--resize)
139 | (webkit-color-picker--set-background))
140 |
141 | (when (eq xwidget-event-type 'javascript-callback)
142 | (let ((proc (nth 3 last-input-event))
143 | (arg (nth 4 last-input-event)))
144 | (funcall proc arg))))))
145 |
146 | (webkit-color-picker--run-xwidget))
147 | #+END_SRC
148 |
149 | #+BEGIN_SRC emacs-lisp
150 | (defun webkit-color-picker--get-buffer ()
151 | "Return color picker buffer."
152 | (get-buffer " *webkit-color-picker*"))
153 | #+END_SRC
154 |
155 | #+BEGIN_SRC emacs-lisp
156 | (defun webkit-color-picker--get-frame ()
157 | "Return color picker frame."
158 | (when-let* ((buffer (webkit-color-picker--get-buffer)))
159 | (seq-find
160 | (lambda (frame)
161 | (let ((buffer-info (frame-parameter frame 'posframe-buffer)))
162 | (or (eq buffer (car buffer-info))
163 | (eq buffer (cdr buffer-info)))))
164 | (frame-list))))
165 | #+END_SRC
166 |
167 | #+BEGIN_SRC emacs-lisp
168 | (defun webkit-color-picker--set-background ()
169 | "Evaluate JS code in color picker Webkit instance."
170 | (webkit-color-picker--execute-script
171 | (format "document.body.style.background = '%s';"
172 | (face-attribute 'default :background))))
173 | #+END_SRC
174 |
175 | #+BEGIN_SRC emacs-lisp
176 | (defun webkit-color-picker--insert-color ()
177 | "Get the selected color from the widget and insert in the current buffer."
178 | (webkit-color-picker--execute-script
179 | "window.selectedColor;"
180 | `(lambda (color)
181 | (let ((color (kill-new (or color "")))
182 | (start (or (car webkit-color-picker--last-position) (point)))
183 | (end (or (cdr webkit-color-picker--last-position) (point))))
184 | (when (> (length color) 0)
185 | (delete-region start end)
186 | (goto-char start)
187 | (insert color)
188 | (webkit-color-picker-hide))))))
189 | #+END_SRC
190 |
191 | #+BEGIN_SRC emacs-lisp
192 | (defvar webkit-color-picker--emulation-alist '((t . nil)))
193 | #+END_SRC
194 |
195 | #+BEGIN_SRC emacs-lisp
196 | (defvar-local webkit-color-picker--my-keymap nil)
197 | (defvar-local webkit-color-picker--last-position nil)
198 | #+END_SRC
199 |
200 | #+BEGIN_SRC emacs-lisp
201 | (defsubst webkit-color-picker--enable-overriding-keymap (keymap)
202 | "Enable color picker overriding KEYMAP."
203 | (webkit-color-picker--uninstall-map)
204 | (setq webkit-color-picker--my-keymap keymap))
205 | #+END_SRC
206 |
207 | #+BEGIN_SRC emacs-lisp
208 | (defun webkit-color-picker--ensure-emulation-alist ()
209 | "Append color picker emulation alist."
210 | (unless (eq 'webkit-color-picker--emulation-alist (car emulation-mode-map-alists))
211 | (setq emulation-mode-map-alists
212 | (cons 'webkit-color-picker--emulation-alist
213 | (delq 'webkit-color-picker--emulation-alist emulation-mode-map-alists)))))
214 | #+END_SRC
215 |
216 | TODO: Find a better way of preventing accidental keystrokes whether the
217 | childframe is in focus or not
218 | #+BEGIN_SRC emacs-lisp
219 | (defun webkit-color-picker--install-map ()
220 | "Install temporary color picker keymap."
221 | (unless (or (cdar webkit-color-picker--emulation-alist)
222 | (null webkit-color-picker--my-keymap))
223 | (setf (cdar webkit-color-picker--emulation-alist) webkit-color-picker--my-keymap)))
224 | #+END_SRC
225 |
226 | #+BEGIN_SRC emacs-lisp
227 | (defun webkit-color-picker--uninstall-map ()
228 | "Uninstall temporary color picker keymap."
229 | (setf (cdar webkit-color-picker--emulation-alist) nil))
230 | #+END_SRC
231 |
232 | #+BEGIN_SRC emacs-lisp
233 | (defvar webkit-color-picker--active-map
234 | (let ((keymap (make-sparse-keymap)))
235 | (define-key keymap "\e\e\e" 'webkit-color-picker-hide)
236 | (define-key keymap "\C-g" 'webkit-color-picker-hide)
237 | (define-key keymap [mouse-1] (lambda () (interactive) (webkit-color-picker--insert-color)))
238 | (define-key keymap (kbd "RET") (lambda () (interactive) (webkit-color-picker--insert-color)))
239 | keymap)
240 | "Keymap that is enabled during an active completion.")
241 | #+END_SRC
242 |
243 | #+BEGIN_SRC emacs-lisp
244 | (defvar webkit-color-picker--hex-color-regexp
245 | (concat
246 | ;; Short hex. css-color-4 adds alpha.
247 | "\\(#[0-9a-fA-F]\\{3,4\\}\\b\\)"
248 | "\\|"
249 | ;; Long hex. css-color-4 adds alpha.
250 | "\\(#\\(?:[0-9a-fA-F][0-9a-fA-F]\\)\\{3,4\\}\\b\\)"))
251 | #+END_SRC
252 |
253 | #+BEGIN_SRC emacs-lisp
254 | (defun webkit-color-picker--get-hex-color-at-point ()
255 | "Return hex color at point."
256 | (with-syntax-table (copy-syntax-table (syntax-table))
257 | (modify-syntax-entry ?# "w") ; Make `#' a word constituent.
258 | (when-let* ((word (thing-at-point 'word t))
259 | (bounds (bounds-of-thing-at-point 'word)))
260 | (when (string-match webkit-color-picker--hex-color-regexp word)
261 | (cons word bounds)))))
262 | #+END_SRC
263 |
264 | #+BEGIN_SRC emacs-lisp
265 | (defun webkit-color-picker--get-named-color-at-point ()
266 | "Return color name at point."
267 | (when-let* ((word (word-at-point))
268 | (color (assoc (downcase word) css--color-map)))
269 | (cons word (bounds-of-thing-at-point 'word))))
270 | #+END_SRC
271 |
272 | #+BEGIN_SRC emacs-lisp
273 | (defun webkit-color-picker--get-rgb-or-hsl-color-at-point ()
274 | "Return RGB or HSL formatted color at point."
275 | (save-excursion
276 | (when-let* ((open-paren-pos (nth 1 (syntax-ppss))))
277 | (when (save-excursion
278 | (goto-char open-paren-pos)
279 | (looking-back "\\(?:hsl\\|rgb\\)a?" (- (point) 4)))
280 | (goto-char (nth 1 (syntax-ppss)))))
281 | (when (eq (char-before) ?\))
282 | (backward-sexp))
283 | (skip-chars-backward "rgbhslaRGBHSLA")
284 | (when (looking-at "\\(\\_<\\(?:hsl\\|rgb\\)a?(\\)")
285 | (when-let* ((start (point))
286 | (end (search-forward ")" nil t)))
287 | (cons (buffer-substring-no-properties start end) (cons start end))))))
288 | #+END_SRC
289 |
290 | #+BEGIN_SRC emacs-lisp
291 | (defun webkit-color-picker--color-at-point ()
292 | "Return recognized color at point."
293 | (or
294 | (webkit-color-picker--get-rgb-or-hsl-color-at-point)
295 | (webkit-color-picker--get-named-color-at-point)
296 | (webkit-color-picker--get-hex-color-at-point)))
297 | #+END_SRC
298 |
299 | #+BEGIN_SRC emacs-lisp
300 | (defun webkit-color-picker--get-xwidget ()
301 | "Return Xwidget instance."
302 | (with-current-buffer " *webkit-color-picker*"
303 | (xwidget-at 1)))
304 | #+END_SRC
305 |
306 | #+BEGIN_SRC emacs-lisp
307 | (defun webkit-color-picker--execute-script (script &optional fn)
308 | "Execute SCRIPT in embedded Xwidget and run optional callback FN."
309 | (when-let* ((xw (webkit-color-picker--get-xwidget)))
310 | (xwidget-webkit-execute-script xw script fn)))
311 | #+END_SRC
312 |
313 | #+BEGIN_SRC emacs-lisp
314 | (defun webkit-color-picker--resize ()
315 | "Resize color picker frame to widget boundaries."
316 | (webkit-color-picker--execute-script
317 | "[document.querySelector('.picker').offsetWidth, document.querySelector('.picker').offsetHeight];"
318 | (lambda (size)
319 | (when-let* ((frame (webkit-color-picker--get-frame)))
320 | (modify-frame-parameters
321 | frame
322 | `((width . (text-pixels . ,(+ 30 (aref size 0))))
323 | (height . (text-pixels . ,(+ 30 (aref size 1))))
324 | (inhibit-double-buffering . t)))))))
325 | #+END_SRC
326 |
327 | #+BEGIN_SRC emacs-lisp
328 | (defun webkit-color-picker--set-color (color)
329 | "Update color picker widget state with COLOR."
330 | (webkit-color-picker--execute-script
331 | (format
332 | "window.selectedColor = '%s';"
333 | (if (stringp color) color "#000000"))))
334 | #+END_SRC
335 |
336 | #+BEGIN_SRC emacs-lisp
337 | (defun webkit-color-picker-show ()
338 | "Activate color picker."
339 | (interactive)
340 | (or (featurep 'xwidget-internal)
341 | (user-error "Your Emacs was not compiled with xwidgets support"))
342 | (let ((color-at-point (webkit-color-picker--color-at-point)))
343 | (if (buffer-live-p (webkit-color-picker--get-buffer))
344 | (webkit-color-picker--show)
345 | (webkit-color-picker--create))
346 |
347 | (webkit-color-picker--set-color (car color-at-point))
348 | (webkit-color-picker--set-background)
349 |
350 | (setq-local webkit-color-picker--last-position
351 | (or (cdr color-at-point)
352 | (cons (point) (point))))
353 |
354 | (webkit-color-picker--ensure-emulation-alist)
355 | (webkit-color-picker--enable-overriding-keymap webkit-color-picker--active-map)
356 | (webkit-color-picker--install-map)
357 |
358 | t))
359 | #+END_SRC
360 |
361 | #+BEGIN_SRC emacs-lisp
362 | (defun webkit-color-picker-hide ()
363 | "Hide color picker frame."
364 | (interactive)
365 | (when-let* ((frame (webkit-color-picker--get-frame)))
366 | (make-frame-invisible frame))
367 | (webkit-color-picker--enable-overriding-keymap nil))
368 | #+END_SRC
369 |
370 | #+BEGIN_SRC emacs-lisp
371 | (defun webkit-color-picker-cleanup ()
372 | "Destroy color picker buffer and frame."
373 | (interactive)
374 | (dolist (xwidget-view xwidget-view-list)
375 | (delete-xwidget-view xwidget-view))
376 | (posframe-delete-all)
377 | (kill-buffer " *webkit-color-picker*"))
378 | #+END_SRC
379 |
380 | #+BEGIN_SRC emacs-lisp
381 | (provide 'webkit-color-picker)
382 | #+END_SRC
383 |
384 | #+BEGIN_EXAMPLE
385 | Local Variables:
386 | coding: utf-8-unix
387 | End:
388 | #+END_EXAMPLE
389 |
--------------------------------------------------------------------------------