emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/svg-lib f2cc9615ef 3/4: Added interactive buttons (icon


From: ELPA Syncer
Subject: [elpa] externals/svg-lib f2cc9615ef 3/4: Added interactive buttons (icon+tag)
Date: Fri, 29 Dec 2023 06:58:45 -0500 (EST)

branch: externals/svg-lib
commit f2cc9615ef3a052747135d34f31c423a26592f14
Author: Nicolas P. Rougier <Nicolas.Rougier@inria.fr>
Commit: Nicolas P. Rougier <Nicolas.Rougier@inria.fr>

    Added interactive buttons (icon+tag)
---
 README.org      | 134 ++++++++++++++++++++++++++--
 svg-lib-demo.el |  10 ++-
 svg-lib.el      | 271 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 402 insertions(+), 13 deletions(-)

diff --git a/README.org b/README.org
index 912603eba1..c640e8c071 100644
--- a/README.org
+++ b/README.org
@@ -1,18 +1,138 @@
 
-*** SVG Library
+* SVG Library
 
-A small Emacs library to create and display various SVG objects,
-namely tags, progress bars, progress pies and icons. Each object is
-guaranteed to fit nicely in a text buffer ensuring width is an integer
-multiple of character width.
+svg-lib is an Emacs library that allows to create and display various
+SVG objects, namely tags, icons, buttons, progress bars, progress pies
+and dates. Each object is guaranteed to fit nicely in a text buffer
+ensuring width is a multiple of character width and height a multiple
+of character height.
 
-*** Installation
+* Installation
 
 #+begin_src elisp
 M-x package-install RET svg-lib RET
 #+end_src
 
-*** Usage example
+* Quick start
+
+For the impatient, evaluate the following expression:
+
+#+begin_src emacs-lisp
+M-: (insert-image (svg-lib-tag "TODO"))
+#+end_src
+
+This should insert a SVG tag displaying "TODO" in a rounded box whose
+size is exactly 5 characters wide (because of padding).
+
+* Usage
+
+** Objects
+
+- =svg-lib-tag LABEL=
+- =svg-lib-icon ICON=
+- =svg-lib-icon+tag ICON LABEL=
+- =svg-lib-button LABEL HOOK=
+- =svg-lib-progress-bar VALUE=
+- =svg-lib-progress-pie VALUE=
+- =svg-lib-date DATE=
+  
+** Styling
+
+Each library object can be styled using a style property list that
+defines:
+
+- foreground color
+- background color
+- internal padding (tag and icon)
+- external margin (in char)
+- stroke width (in pixels)
+- corner radius (in pixels) for the rounded box
+- horizontal alignment (0 to 1) inside margins
+- width (in characters)
+- height as a scale of line height
+- scale (for icon)
+- ascent (for text)
+- crop-left to crop object on the left
+- crop-right to  crop object on the right
+- collection to use for icon 
+- font family
+- font size
+- font weight
+
+* Icon repositories
+
+Icons can be created by parsing remote collections whose license are
+compatibles with GNU Emacs. The default size of an icon is exactly 2x1
+characters such that it can be inserted inside a text without
+disturbing alignment.
+
+Each icon is cached locally to speed-up loading the next time you use
+it. If for some reason the cache is corrupted you can force reload
+using the svg-icon-get-data function. If you want to add new
+collections (i.e. URL), make sure the icons are monochrome, that their
+size is consistent and that they include a 'viewBox' node.
+
+** [[https://pictogrammers.com/library/mdi/][Material Design]] (7447 icons)
+
+Open-source iconography for designers and developers.
+
+- Version: 7.4.47 (December 2023)
+- Licence: Apache 2.0 
+- Number of icons: 7447
+- Sources: https://github.com/Templarian/MaterialDesign
+- Collection: =material=
+
+** [[https://simpleicons.org/][Simple icons]] (2926 icons)
+
+Over 2900 Free SVG icons for popular brands.
+
+- Version: 10.4.0 (December 2023)
+- Licence: CC0-1.0 license
+- Number of icons: 2926
+- Sources: https://github.com/twbs/icons
+- Collection: =simple=
+
+** [[https://icons.getbootstrap.com/][Bootstrap]] (> 2000 icons)
+
+Official open source SVG icon library for Bootstrap.
+ 
+- Version: 1.11.2 (December 2023)
+- Licence: MIT License
+- Number of icons: > 2000
+- Sources: https://github.com/twbs/icons
+- Collection: =bootstrap=
+
+** [[https://boxicons.com/][Boxicons]] (1634 icons)
+
+Simple Open Source icons carefully crafted for designers & developers.
+
+- Version: 2.1.4 (September 2022)
+- Licence: MIT License
+- Number of icons: 1634
+- Sources: https://github.com/atisawd/boxicons.
+- Collection: =boxicons=
+  
+** [[https://primer.style/octicons][Octicons]] (322 icons)
+
+Octicons are a set of SVG icons built by GitHub for GitHub.
+
+- Version: 19.8.0 (September 2023)
+- Licence: MIT License
+- Number of icons: 322
+- Sources: https://github.com/primer/octicons
+- Collection: =octicons=
+
+** 
[[https://www.figma.com/community/file/768673354734944365/visual-studio-code-icons][VSCode]]
 (209 icons)
+
+Icons used in Visual Studio Code.
+
+- Version: (September 2020)
+- Licence: CC-BY 4.0
+- Number of icons: 209
+- Sources: https://github.com/microsoft/vscode-icons
+- Collection: =vscode=
+   
+* Screenshots
 
 [[file:screenshot.png]]
 
diff --git a/svg-lib-demo.el b/svg-lib-demo.el
index f1350fca80..f611f41766 100644
--- a/svg-lib-demo.el
+++ b/svg-lib-demo.el
@@ -52,10 +52,10 @@
   (insert-image (svg-lib-icon "star" nil :scale (/ (+ i 1) 10.0))))
           
 
-(insert-image (svg-lib-button "check-bold" "DONE" nil
+(insert-image (svg-lib-icon+tag "check-bold" "DONE" nil
                               :font-family "Roboto Mono"
                               :font-weight 500
-                         :stroke 0 :background "#673AB7" :foreground "white"))
+                              :stroke 0 :background "#673AB7" :foreground 
"white"))
  
 
 (insert-image (svg-lib-icon "gnuemacs" nil :collection "simple"
@@ -65,5 +65,9 @@
 (insert-image (svg-lib-date nil nil :font-family "Roboto" :radius 5
                                     :foreground "#673AB7"))
  
- 
+(svg-lib-button-mode 1)
+(insert (svg-lib-button "[check-bold] OK"
+                        (lambda () (interactive) (message "OK"))))
+
+
 
diff --git a/svg-lib.el b/svg-lib.el
index a53f23dfdc..79928d6d42 100644
--- a/svg-lib.el
+++ b/svg-lib.el
@@ -4,7 +4,7 @@
 
 ;; Maintainer: Nicolas P. Rougier <Nicolas.Rougier@inria.fr>
 ;; URL: https://github.com/rougier/svg-lib
-;; Version: 0.2.8
+;; Version: 0.3
 ;; Package-Requires: ((emacs "27.1"))
 ;; Keywords: svg, icons, tags, convenience
 
@@ -67,6 +67,11 @@
 
 ;;; NEWS:
 
+;; Version 0.3
+;; - Renamed 'svg-lib-button' to 'svg-lib-icon+tag'
+;; - Added interactive 'svg-lib-button' with associtated 'svg-lib-button-mode'
+;; - Added proper documentation in the README
+
 ;; Version 0.2.8
 ;; - No background for icon when background color is nil
 ;; - Refactored date icons
@@ -114,6 +119,33 @@
   :group 'convenience
   :prefix "svg-lib-")
 
+(defface svg-lib-button-active-face
+  `((t :foreground ,(face-foreground 'default)
+       :background ,(face-background 'default)
+       :family "RobotoMono Nerd Font"
+       :weight regular
+       :box (:line-width 2 :style nil)))
+  "Default face for active button"
+  :group 'svg-lib)
+
+(defface svg-lib-button-hover-face
+  `((t :foreground ,(face-background 'font-lock-comment-face nil 'default)
+       :background ,(face-foreground 'font-lock-comment-face nil 'default)
+       :family "RobotoMono Nerd Font"
+       :weight semibold
+       :box nil))
+  "default face for when mouse is over the button"
+  :group 'svg-lib)
+
+(defface svg-lib-button-press-face
+  `((t :foreground ,(face-background 'default)
+       :background ,(face-foreground 'default)
+       :family "RobotoMono Nerd Font"
+       :weight semibold
+       :box nil))
+  "Default face for when button is prssed (mouse click or keyboard)"
+  :group 'svg-lib)
+
 ;; Default icon collections
 ;; ---------------------------------------------------------------------
 (defcustom  svg-lib-icon-collections
@@ -485,7 +517,6 @@ given STYLE and style elements ARGS."
 
          (collection  (plist-get style :collection))
          (root (svg-lib--icon-get-data collection icon))
-
          
          (foreground  (plist-get style :foreground))
          (background  (plist-get style :background))
@@ -551,7 +582,7 @@ given STYLE and style elements ARGS."
 
 
 ;; Create an image displaying LABEL in a rounded box.
-(defun svg-lib-button (icon label &optional style &rest args)
+(defun svg-lib-icon+tag (icon label &optional style &rest args)
   "Create an image displaying LABEL in a rounded box using given STYLE
 and style elements ARGS."
 
@@ -771,6 +802,240 @@ given STYLE and style elements ARGS."
      (dom-append-child svg child))
    svg))
 
+
+(defvar svg-lib-button--id-counter 0
+  "SVG button unique id counter")
+
+(defun svg-lib-button--search (id)
+  "Return region for the button with given ID"
+
+  (save-excursion
+    (goto-char (point-min))
+    (save-match-data
+      (when-let* ((match (text-property-search-forward 'button-id id t)))
+        (cons (prop-match-beginning match)
+              (prop-match-end match))))))
+
+(defun svg-lib-button--at-point (&optional pos)
+  "Return the button at point"
+  (get-text-property (or pos (point)) 'button-id))
+
+(defun svg-lib-button--get-state (id &optional region)
+  "Return the state of button ID"
+  
+  (when-let* ((region (or region (svg-lib-button--search id))))
+    (get-text-property (car region) 'button-state)))
+
+(defun svg-lib-button--set-state (id state &optional no-reset)
+  "Set the state of button ID to STATE, reset the state of any
+hovered button unless NO-RESET is t"
+
+  ;; Reset previous hover button state (if any)
+  (when (and (boundp 'svg-lib-button--hover-id) svg-lib-button--hover-id (not 
no-reset))
+    (let ((prev-id svg-lib-button--hover-id))
+      (setq-local svg-lib-button--hover-id nil)
+      (svg-lib-button--set-state prev-id 'active)))
+
+  ;; Set new state
+  (when-let* ((region (svg-lib-button--search id))
+              (cur-state (svg-lib-button--get-state id region))
+              (button-list (get-text-property (car region) 'button-list))
+              (display (cdr (assoc state button-list))))
+        (put-text-property (car region) (cdr region) 'display display)
+        (put-text-property (car region) (cdr region) 'button-state state)
+        (cond ((eq state 'hover)
+               (setq-local svg-lib-button--hover-id id))
+              ((eq state 'press)
+               (setq-local svg-lib-button--press-id id)))))
+
+(defun svg-lib-button--tooltip-hide (&rest args)
+  "Set currently press or hightlighted button to default
+state (active) and hover button at point if any."
+
+  (when (boundp 'svg-lib-button--press-id)
+    (svg-lib-button--set-state svg-lib-button--press-id 'active))
+  (when (boundp 'svg-lib-button--hover-id)
+    (svg-lib-button--set-state svg-lib-button--hover-id 'active))
+
+  ;; Hover button at point (if any)
+  (svg-lib-button--set-state (svg-lib-button--at-point) 'hover)
+  (advice-remove 'tooltip-hide #'svg-lib-button--tooltip-hide))
+
+(defun svg-lib-button--tooltip-show (pos)
+  "Set button under mouse state to hover or press depending
+on whether mouse button 1 is down (press) or up (hover)"
+
+  (if (and (consp last-input-event)
+           (string-match-p "down-mouse-1" (format "%s" (car 
last-input-event))))
+      (svg-lib-button--set-state (svg-lib-button--at-point pos) 'press)
+    (svg-lib-button--set-state (svg-lib-button--at-point pos) 'hover))
+  (advice-add 'tooltip-hide :before #'svg-lib-button--tooltip-hide))
+
+(defun svg-lib-button--mouse-down ()
+  "Set button under mouse state to press."
+
+  (interactive)
+  (save-excursion
+    (mouse-set-point last-input-event)
+    (svg-lib-button--set-state (svg-lib-button--at-point) 'press))
+  (advice-add 'tooltip-hide :before #'svg-lib-button--tooltip-hide))
+
+(defun svg-lib-button--mouse-press ()
+    "Set button under mouse state to default state (active) and call
+button hook. If current buffer is minibuffer, it aborts it. It
+would be better to simply exit minibuffer but this leads to focus
+problem if the hook creates a frame."
+
+  (interactive)
+
+  ;; Here we check if mouse is still over the button
+  (let ((mouse-point (save-excursion
+                       (mouse-set-point last-input-event)
+                       (point))))
+    (if-let ((id (svg-lib-button--at-point mouse-point)))
+        (svg-lib-button--set-state svg-lib-button--press-id 'hover)
+      (svg-lib-button--set-state svg-lib-button--press-id 'active)))
+  
+  (when-let* ((region (svg-lib-button--search svg-lib-button--press-id))
+              (hook (get-text-property (car region) 'button-hook)))
+    (if (minibufferp nil t)
+        (unwind-protect
+            (abort-minibuffers)
+          (funcall hook))
+      (funcall hook))))
+ 
+(defun svg-lib-button--mouse-drag ()
+  "Update the state of the button under mouse"
+  
+  (interactive)
+  (save-excursion
+    (mouse-set-point last-input-event)
+    (svg-lib-button--set-state (svg-lib-button--at-point) 'press)))
+
+(defun svg-lib-button--make (label &optional face)
+  "Return a svg tag with given LABEL and FACE. LABEL can be composed
+as \"[collection:icon] label\" resulting in an icon+tag button."
+
+  (save-match-data
+    (let* ((face (or face 'default))
+           (label-regex "\\[\\([a-zA-Z0-9]+:\\)?\\([a-zA-Z0-9 _-]+\\)\\] 
*\\(.+\\)"))
+      (if (string-match label-regex label)
+          (let* ((collection (match-string 1 label))
+                 (collection (if (stringp collection)
+                                 (substring collection 0 -1)
+                               (plist-get svg-lib-style-default ':collection)))
+                 (icon       (match-string 2 label))
+                 (label      (match-string 3 label)))
+            (svg-lib-icon+tag icon label nil
+                              :collection collection
+                              :stroke (or (plist-get (face-attribute face 
:box) ':line-width) 0)
+                              :font-family (face-attribute face :family nil t)
+                              :font-weight (face-attribute face :weight nil t)
+                              :foreground (face-foreground face nil 'default)
+                              :background (face-background face nil 'default)))
+        (svg-lib-tag label nil
+                     :stroke (or (plist-get (face-attribute face :box) 
':line-width) 0)
+                     :font-family (face-attribute face :family nil t)
+                     :font-weight (face-attribute face :weight nil t)
+                     :foreground (face-foreground face nil 'default)
+                     :background (face-background face nil 'default))))))
+
+
+(defun svg-lib-button (label &optional hook help active-face hover-face 
press-face)
+  "Make a button with given LABEL that will call HOOK when
+pressed. The HELP text is displatyed when mouse is hovering the
+button. ACTIVE-FACE, HOVER-FACE and PRESS-FACE correspond to the
+different states of the button. LABEL can be composed as
+\"[collection:icon] label\" resulting in an icon+tag button.
+
+For proper highlighting, `svg-lib-button-mode' needs to be
+activated before inserting a button into a buffer."
+
+  ;; Having a SVG button highlighted when mouse cursor is hovering is
+  ;; not totally straightforward because Emacs lacks the proper
+  ;; machinery to do so. There is actually a mouse-face property but it
+  ;; only changes the face and cannot change the display property (that
+  ;; is needed for SVG). To solve the problem, we can take advantage of
+  ;; the tooltip machinery because it offers a hackable enter/exit event
+  ;; that can be used to update the display at the proper time.
+  ;;
+  ;; To make this works, a few properties needs to be removed from
+  ;; `font-lock-extra-managed-props' and `yank-excluded-properties'.
+  ;; For org-mode, another hack is necessary because when org-mode
+  ;; unfontifies a region (see `org-unfontify-region'), it removes the
+  ;; local keymap that is used. You thus need to activate the
+  ;; svg-lib-button-mode to have this set for you.
+  
+  (let* ((active (svg-lib-button--make label
+                          (or active-face 'svg-lib-button-active-face)))
+         (hover (svg-lib-button--make label
+                          (or hover-face 'svg-lib-button-hover-face)))
+         (press (svg-lib-button--make label
+                          (or press-face 'svg-lib-button-press-face)))
+         (buttons `((active . ,active)
+                    (hover . ,hover)
+                    (press . ,press)))
+         (state 'active))
+    (setq svg-lib-button--id-counter (1+ svg-lib-button--id-counter))
+    (propertize (concat label " ")
+                'display (cdr (assoc state buttons))
+                'svg-lib-button t
+                'button-id svg-lib-button--id-counter
+                'button-state state
+                'button-list buttons
+                'button-hook hook
+                'front-sticky nil
+                'rear-nonsticky t
+                'keymap (define-keymap :suppress t
+                          "<down-mouse-1>" #'svg-lib-button--mouse-down
+                          "<mouse-1>"      #'svg-lib-button--mouse-press
+                          "<drag-mouse-1>" #'svg-lib-button--mouse-drag)
+                'help-echo `(lambda (_window _object pos)
+                              (svg-lib-button--tooltip-show pos)
+                               ,help)
+                'pointer 'hand)))
+
+(defun svg-lib-button--remove-text-properties (orig-fun beg end properties 
&optional object)
+  "This advice function ensures keymap is not removed when in 
svg-lib-button-mode"
+
+  (let ((properties (if (and svg-lib-button-mode (derived-mode-p 'org-mode))
+                        (org-plist-delete properties 'keymap)
+                       properties)))
+    (apply orig-fun (list beg end properties object))))
+
+(define-minor-mode svg-lib-button-mode
+  "Activate svg-lib-button-mode that takes care of activating tooltip
+mode and removing some properties from `yank-excluded-properties'
+and `font-lock-extra-managed-props' in order for highlight to
+work properly. This mode also installs an advice on
+`remove-text-properties' in org-mode in order to not delete the
+`keymap' property that is necessary to detect mouse press events."
+
+  :lighter "B"
+
+  (when svg-lib-button-mode
+    ;; This is necessary for detecting when mouse cursor enter or
+    ;; leave a button
+    (require 'tooltip)
+    (tooltip-mode 1)
+
+    ;; This is necessary for preventing org-mode to remove keymap when
+    ;; unfontiying a region
+    (advice-add #'remove-text-properties :around 
#'svg-lib-button--remove-text-properties)
+
+    ;; This prevents help-echo to be removed.when button is copied/yanked
+    (dolist (property '(help-echo keymap))
+      (setq-local yank-excluded-properties
+                  (remove property yank-excluded-properties)))
+
+    ;; This prevents help-echo to be removed.by font-lock
+    (dolist (property '(help-echo keymap display))
+      (setq-local font-lock-extra-managed-props
+                  (remove property font-lock-extra-managed-props))))
+  
+  (unless svg-lib-button-mode
+    (advice-remove #'remove-text-properties 
#'svg-lib-button--remove-text-properties)))
+
 (provide 'svg-lib)
 ;;; svg-lib.el ends here
 



reply via email to

[Prev in Thread] Current Thread [Next in Thread]