From 26d405a9571010a17082beedd2f75718470bc196 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 22 Mar 2020 19:50:51 +0530 Subject: [PATCH] Work on controls for highlighting UI --- imgsrc/srv/selection-handle.svg | 2 +- imgsrc/srv/swatch.svg | 14 ++++ src/pyj/dom.pyj | 6 +- src/pyj/read_book/create_annotation.pyj | 106 +++++++++++++++++++++++- src/pyj/read_book/iframe.pyj | 4 +- src/pyj/read_book/settings.pyj | 33 ++++++-- src/pyj/read_book/view.pyj | 2 +- src/pyj/session.pyj | 2 + 8 files changed, 156 insertions(+), 13 deletions(-) create mode 100644 imgsrc/srv/swatch.svg diff --git a/imgsrc/srv/selection-handle.svg b/imgsrc/srv/selection-handle.svg index ab4611e539..85395b0670 100644 --- a/imgsrc/srv/selection-handle.svg +++ b/imgsrc/srv/selection-handle.svg @@ -1,6 +1,6 @@ diff --git a/imgsrc/srv/swatch.svg b/imgsrc/srv/swatch.svg new file mode 100644 index 0000000000..84b0207e21 --- /dev/null +++ b/imgsrc/srv/swatch.svg @@ -0,0 +1,14 @@ + + + + diff --git a/src/pyj/dom.pyj b/src/pyj/dom.pyj index e95cf8208d..9d955b6da2 100644 --- a/src/pyj/dom.pyj +++ b/src/pyj/dom.pyj @@ -89,13 +89,17 @@ def change_icon_image(icon_element, new_name): else: icon_element.firstChild.removeAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href') -def svgicon(name, height, width): +def svgicon(name, height, width, tooltip): ans = document.createElementNS('http://www.w3.org/2000/svg', 'svg') ans.setAttribute('style', 'fill: currentColor; height: {}; width: {}; vertical-align: text-top'.format(height ? '2ex', width ? '2ex')) u = document.createElementNS('http://www.w3.org/2000/svg', 'use') ans.appendChild(u) if name: ans.firstChild.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#icon-' + name) + if tooltip: + tt = document.createElementNS('http://www.w3.org/2000/svg', 'title') + tt.textContent = tooltip + ans.appendChild(tt) return ans def element(elem_id, child_selector): diff --git a/src/pyj/read_book/create_annotation.pyj b/src/pyj/read_book/create_annotation.pyj index abf4c903b1..887252e12e 100644 --- a/src/pyj/read_book/create_annotation.pyj +++ b/src/pyj/read_book/create_annotation.pyj @@ -2,7 +2,12 @@ # License: GPL v3 Copyright: 2020, Kovid Goyal from __python__ import bound_methods, hash_literals -from dom import ensure_id, svgicon +from elementmaker import E +from gettext import gettext as _ + +from book_list.globals import get_session_data +from book_list.theme import get_color +from dom import add_extra_css, ensure_id, svgicon, unique_id from read_book.shortcuts import shortcut_for_key_event WAITING_FOR_CLICK = 1 @@ -11,8 +16,35 @@ DRAGGING_LEFT = 3 DRAGGING_RIGHT = 4 +add_extra_css(def(): + ans = '' + ans += '.selection-handle { fill: #3cef3d; stroke: black }' + ans += '.selection-handle:active { fill: #FCE883; }' + return ans +) + + +dark_fg = '#111' +light_fg = '#eee' +highlight_colors = { + '#FCE2AE': dark_fg, + '#B6FFEA': dark_fg, + '#FFB3B3': dark_fg, + '#FFDCF7': dark_fg, + '#cae8d5': dark_fg, + + '#204051': light_fg, + '#3b6978': light_fg, + '#2b580c': light_fg, + '#512b58': light_fg, +} +default_highlight_color = '#fce2ae' + + def selection_handle(invert): ans = svgicon('selection-handle') + use = ans.querySelector('use') + use.classList.add('selection-handle') s = ans.style if invert: s.transform = 'scaleX(-1)' @@ -38,6 +70,14 @@ def map_to_iframe_coords(point): return point +def create_bar(size=32): + ans = E.div( + id=unique_id('annot-bar'), + style=f'height: {size}px; width: 100vw; display: flex; justify-content: space-between;', + ) + return ans + + class CreateAnnotation: container_id = 'create-annotation-overlay' @@ -48,8 +88,40 @@ class CreateAnnotation: self.left_line_height = self.right_line_height = 8 self.in_flow_mode = False container = self.container + container.style.display = 'flex' + container.style.flexDirection = 'column' + container.style.justifyContent = 'space-between' self.position_in_handle = {'x': 0, 'y': 0} + def button(bar, icon, tt, action): + cb = svgicon(icon, bar.style.height, bar.style.height, tt) + document.createElement + cb.setAttribute('title', tt) + cb.classList.add('annot-button') + cb.classList.add(f'annot-button-{icon}') + cb.style.backgroundColor = get_color('window-background') + cb.style.boxSizing = 'border-box' + cb.style.padding = '2px' + cb.classList.add('simple-link') + cb.addEventListener('click', def(ev): + ev.preventDefault(), ev.stopPropagation() + action() + ) + bar.appendChild(cb) + return cb + + tb = create_bar() + container.appendChild(tb) + button(tb, 'close', _('Cancel creation of highlight'), self.hide) + button(tb, 'chevron-up', _('Scroll up'), self.scroll_up) + button(tb, 'check', _('Finish creation of highlight'), self.accept) + + bb = create_bar() + container.appendChild(bb) + button(bb, 'fg', _('Change highlight color'), self.choose_color) + button(bb, 'chevron-down', _('Scroll down'), self.scroll_down) + button(bb, 'pencil', _('Add a note'), self.add_text) + lh = selection_handle(True) self.left_handle_id = ensure_id(lh, 'handle') lh.addEventListener('mousedown', self.mousedown_on_handle, {'passive': False}) @@ -64,6 +136,25 @@ class CreateAnnotation: container.addEventListener('mousemove', self.mousemove_on_container, {'passive': False}) container.addEventListener('keydown', self.on_keydown, {'passive': False}) + sd = get_session_data() + style = sd.get('highlight_style') or { + 'background-color': default_highlight_color, + 'color': highlight_colors[default_highlight_color] + } + self.current_highlight_style = style + + def scroll_up(self): + self.send_message('scroll', backwards=True) + + def scroll_down(self): + self.send_message('scroll', backwards=False) + + def choose_color(self): + pass + + def accept(self): + pass + def on_keydown(self, ev): ev.stopPropagation(), ev.preventDefault() sc_name = shortcut_for_key_event(ev, self.view.keyboard_shortcut_map) @@ -147,15 +238,25 @@ class CreateAnnotation: } } + @property + def current_highlight_style(self): + return JSON.parse(self.container.querySelector('.annot-button-fg').dataset.style) + + @current_highlight_style.setter + def current_highlight_style(self, val): + b = self.container.querySelector('.annot-button-fg') + b.dataset.style = JSON.stringify(val) + def show(self): c = self.container - c.style.display = 'block' + c.style.display = 'flex' c.focus() def hide(self): if self.is_visible: self.container.style.display = 'none' self.view.focus_iframe() + self.send_message('set-highlight-style', style=None) def send_message(self, type, **kw): self.view.iframe_wrapper.send_message('annotations', type=type, **kw) @@ -170,6 +271,7 @@ class CreateAnnotation: if msg.extents.start.x is not None: self.place_handles(msg.extents) self.in_flow_mode = msg.in_flow_mode + self.send_message('set-highlight-style', style=self.current_highlight_style) elif msg.type is 'position-handles': if self.state is WAITING_FOR_CLICK: self.place_handles(msg.extents) diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index 2e5581728d..0f7a627c9b 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -46,7 +46,7 @@ from read_book.referencing import ( from read_book.resources import finalize_resources, unserialize_html from read_book.settings import ( apply_colors, apply_font_size, apply_settings, apply_stylesheet, opts, - set_color_scheme_class, update_settings + set_color_scheme_class, set_selection_style, update_settings ) from read_book.shortcuts import ( create_shortcut_map, keyevent_as_shortcut, shortcut_for_key_event @@ -635,6 +635,8 @@ class IframeBoss: elif data.type is 'perp-scroll': if in_flow_mode and flow_annotation_scroll(data.backwards, True): self.send_message('annotations', type='update-handles', extents=selection_extents(in_flow_mode)) + elif data.type is 'set-highlight-style': + set_selection_style(data.style) else: console.log('Ignoring annotations message to iframe with unknown type: ' + data.type) diff --git a/src/pyj/read_book/settings.pyj b/src/pyj/read_book/settings.pyj index 456ecb9d68..8d4a92222f 100644 --- a/src/pyj/read_book/settings.pyj +++ b/src/pyj/read_book/settings.pyj @@ -36,6 +36,15 @@ def apply_font_size(): document.documentElement.style.fontSize = '{}px'.format(opts.base_font_size) +def default_selection_colors(): + if opts.is_dark_theme: + return dark_link_color, '#111' + return '#3297FD', '#eee' + + +styles_id = 'calibre-color-scheme-style-overrides' + + def apply_colors(): for elem in (document.documentElement, document.body): elem.style.color = opts.color_scheme.foreground @@ -45,7 +54,7 @@ def apply_colors(): document.documentElement.style.backgroundColor = opts.bg_image_fade ss = document.getElementById('calibre-color-scheme-style-overrides') if not ss: - ss = E.style(id='calibre-color-scheme-style-overrides', type='text/css') + ss = E.style(id=styles_id, type='text/css') document.documentElement.appendChild(ss) text = '' if opts.override_book_colors is not 'never': @@ -64,18 +73,28 @@ def apply_colors(): # priority than the override all selectors above text += f'\nhtml > body :link, html > body :link * {{ color: {c} !important }} html > body :visited, html > body :visited * {{ color: {c} !important }}' - if opts.is_dark_theme: - selbg = dark_link_color - selfg = 'black' - else: - selbg = '#3297FD' - selfg = 'white' + selbg, selfg = default_selection_colors() text += f'\n::selection {{ background-color: {selbg}; color: {selfg} }}' text += f'\n::selection:window-inactive {{ background-color: {selbg}; color: {selfg} }}' ss.textContent = text +def set_selection_style(style): + if not style: + selbg, selfg = default_selection_colors() + style = {'color': selfg, 'background-color': selbg} + sheet = document.getElementById(styles_id) + if not sheet: + return + css_text = '' + for prop in Object.keys(style): + css_text += f'{prop}: {style[prop]}; ' + for rule in sheet.sheet.cssRules: + if rule.type is rule.STYLE_RULE and rule.selectorText.indexOf('selection') > -1: + rule.style.cssText = css_text + + def set_color_scheme_class(): if opts.is_dark_theme: document.body.classList.add('calibre-viewer-dark-colors') diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 71c44e7e75..cb76c723c3 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -219,7 +219,7 @@ class View: E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id='book-content-popup-overlay'), # content popup overlay E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; overflow: auto; display:none', id='book-overlay'), # main overlay E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id='controls-help-overlay'), # controls help overlay - E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id=CreateAnnotation.container_id, tabindex='0'), # create annotation overlay + E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none; overflow: hidden', id=CreateAnnotation.container_id, tabindex='0'), # create annotation overlay ) ), E.div( diff --git a/src/pyj/session.pyj b/src/pyj/session.pyj index 486b33737d..6edb6ad6f3 100644 --- a/src/pyj/session.pyj +++ b/src/pyj/session.pyj @@ -59,6 +59,7 @@ defaults = { 'user_color_schemes': {}, 'user_stylesheet': '', 'word_actions': v'[]', + 'highlight_style': None, } is_local_setting = { @@ -85,6 +86,7 @@ is_local_setting = { 'standalone_misc_settings': True, 'standalone_recently_opened': True, 'user_stylesheet': True, + 'highlight_style': True, }