From a83d634f54063e470a3789d20a5365d55b88b834 Mon Sep 17 00:00:00 2001 From: anoshenko Date: Tue, 25 Apr 2023 17:20:47 +0300 Subject: [PATCH] Added "tooltip" property --- README-ru.md | 9 ++++ README.md | 9 ++++ app_scripts.js | 121 +++++++++++++++++++++++++++++++++++++++++++++++ app_styles.css | 54 +++++++++++++++++++++ application.go | 6 +++ defaultTheme.rui | 6 +++ mouseEvents.go | 7 ++- popup.go | 1 + propertyNames.go | 2 + session.go | 16 +++++++ view.go | 20 +++++++- viewUtils.go | 18 +++++++ 12 files changed, 267 insertions(+), 2 deletions(-) diff --git a/README-ru.md b/README-ru.md index d303853..ea6ac7b 100644 --- a/README-ru.md +++ b/README-ru.md @@ -1472,6 +1472,12 @@ radius необходимо передать nil | 18 | "blockquote" | Цитата. Изменяет стиль текста | | 19 | "code" | Программный код. Изменяет стиль текста | +### Свойство "tooltip" + +Свойство "tooltip" (константа Tooltip) типа string задает текст всплывающей подсказки. +Подсказка всплывает при наведении курсора мыши. При оформление текста подсказки можно +использовать html тэги + ### Свойства текста Все перечисленные в этом разделе свойства являются наследуемыми, т.е. свойство будет применяться не только ко View @@ -5591,6 +5597,9 @@ Safari и Firefox. | ruiPopupTextColor | Цвет текста всплывающего окна | | ruiPopupTitleColor | Цвет фона заголовка всплывающего окна | | ruiPopupTitleTextColor | Цвет текста заголовка всплывающего окна | +| ruiTooltipBackground | Цвет фона всплывающей подсказки | +| ruiTooltipTextColor | Цвет текста всплывающей подсказки | +| ruiTooltipShadowColor | Цвет тени всплывающей подсказки | Константы которые вы можете переопределять: diff --git a/README.md b/README.md index 3881b10..0e91275 100644 --- a/README.md +++ b/README.md @@ -1453,6 +1453,12 @@ It also helps to voice the interface to systems for people with disabilities: | 18 | "blockquote" | Quote. Changes the style of the text | | 19 | "code" | Program code. Changes the style of the text | +### "tooltip" property + +The "tooltip" string property (Tooltip constant) specifies the tooltip text. +Tooltip pops up when hovering the mouse cursor. +You can use html tags when formatting the tooltip text. + ### Text properties All properties listed in this section are inherited, i.e. the property will apply @@ -5542,6 +5548,9 @@ System color constants that you can override: | ruiPopupTextColor | Popup text color | | ruiPopupTitleColor | Popup title background color | | ruiPopupTitleTextColor | Popup Title Text Color | +| ruiTooltipBackground | Tooltip background color | +| ruiTooltipTextColor | Tooltip text color | +| ruiTooltipShadowColor | Tooltip shadow color | Constants that you can override: diff --git a/app_scripts.js b/app_scripts.js index 2b39533..15d5e98 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -1877,3 +1877,124 @@ function localStorageClear() { sendMessage("storageError{session=" + sessionID + ", error=`" + err + "`}") } } + +function showTooltip(element, tooltip) { + const layer = document.getElementById("ruiTooltipLayer"); + if (!layer) { + return; + } + + layer.style.left = "0px"; + layer.style.right = "0px"; + + var tooltipBox = document.getElementById("ruiTooltipText"); + if (tooltipBox) { + tooltipBox.innerHTML = tooltip; + } + + var left = element.offsetLeft; + var top = element.offsetTop; + var width = element.offsetWidth; + var height = element.offsetHeight; + var parent = element.offsetParent; + + while (parent) { + left += parent.offsetLeft; + top += parent.offsetTop; + width = parent.offsetWidth; + height = parent.offsetHeight; + parent = parent.offsetParent; + } + + if (element.offsetWidth >= tooltipBox.offsetWidth) { + layer.style.left = left + "px"; + layer.style.justifyItems = "start"; + } else { + const rightOff = width - (left + element.offsetWidth); + if (left > rightOff) { + if (width - left < tooltipBox.offsetWidth) { + layer.style.right = rightOff + "px"; + layer.style.justifyItems = "end"; + } else { + layer.style.left = (left - rightOff) + "px"; + layer.style.right = "0px"; + layer.style.justifyItems = "center"; + } + } else { + if (width - rightOff < tooltipBox.offsetWidth) { + layer.style.left = left + "px"; + layer.style.justifyItems = "start"; + } else { + layer.style.right = (rightOff - left) + "px"; + layer.style.justifyItems = "center"; + } + } + } + + const bottomOff = height - (top + element.offsetHeight); + var arrow = document.getElementById("ruiTooltipTopArrow"); + + if (bottomOff < arrow.offsetHeight + tooltipBox.offsetHeight) { + if (arrow) { + arrow.style.visibility = "hidden"; + } + + arrow = document.getElementById("ruiTooltipBottomArrow"); + if (arrow) { + arrow.style.visibility = "visible"; + } + + layer.style.top = "0px"; + layer.style.bottom = height - top - arrow.offsetHeight / 2 + "px"; + layer.style.gridTemplateRows = "1fr auto auto" + + } else { + if (arrow) { + arrow.style.visibility = "visible"; + } + + layer.style.top = top + element.offsetHeight - arrow.offsetHeight / 2 + "px"; + layer.style.bottom = "0px"; + layer.style.gridTemplateRows = "auto auto 1fr" + + arrow = document.getElementById("ruiTooltipBottomArrow"); + if (arrow) { + arrow.style.visibility = "hidden"; + } + } + + layer.style.visibility = "visible"; + layer.style.opacity = 1; +} + +function mouseEnterEvent(element, event) { + event.stopPropagation(); + + let tooltip = element.getAttribute("data-tooltip"); + if (tooltip) { + showTooltip(element, tooltip); + } + + sendMessage("mouse-enter{session=" + sessionID + ",id=" + element.id + mouseEventData(element, event) + "}"); +} + +function mouseLeaveEvent(element, event) { + event.stopPropagation(); + + if (element.getAttribute("data-tooltip")) { + const layer = document.getElementById("ruiTooltipLayer"); + if (layer) { + layer.style.opacity = 0; + layer.style.visibility = "hidden"; + } + } + + sendMessage("mouse-leave{session=" + sessionID + ",id=" + element.id + mouseEventData(element, event) + "}"); +} + +function setCssVar(tag, value) { + const root = document.querySelector(':root'); + if (root) { + root.style.setProperty(tag, value); + } +} diff --git a/app_styles.css b/app_styles.css index 79f53e9..2e090a5 100644 --- a/app_styles.css +++ b/app_styles.css @@ -8,6 +8,13 @@ text-overflow: ellipsis; } +:root { + --tooltip-arrow-size: 6px; + --tooltip-background: white; + --tooltip-text-color: black; + --tooltip-shadow-color: gray; +} + body { -webkit-touch-callout: none; -webkit-user-select: none; @@ -76,7 +83,54 @@ ul:focus { left: 0px; } +.ruiTooltipLayer { + display: grid; + grid-template-rows: 1fr auto 1fr; + justify-items: center; + align-items: center; + position: absolute; + top: 0px; + bottom: 0px; + right: 0px; + left: 0px; + transition: opacity 0.5s ease-out; + filter: drop-shadow(0px 0px 2px var(--tooltip-shadow-color)); +} + +.ruiTooltipTopArrow { + grid-row-start: 1; + grid-row-end: 2; + border-width: var(--tooltip-arrow-size); + border-style: solid; + border-color: transparent transparent var(--tooltip-background) transparent; + margin-left: 12px; + margin-right: 12px; +} + +.ruiTooltipBottomArrow { + grid-row-start: 3; + grid-row-end: 4; + border-width: var(--tooltip-arrow-size); + border-style: solid; + border-color: var(--tooltip-background) transparent transparent transparent; + margin-left: 12px; + margin-right: 12px; +} + +.ruiTooltipText { + grid-row-start: 2; + grid-row-end: 3; + padding: 4px 8px 4px 8px; + margin-left: 8px; + margin-right: 8px; + background-color: var(--tooltip-background); + color: var(--tooltip-text-color); + /*box-shadow: 0px 0px 4px 2px #8888;*/ + border-radius: 4px; +} + .ruiView { + box-sizing: border-box; } .ruiAbsoluteLayout { diff --git a/application.go b/application.go index ceafd5e..e5b3046 100644 --- a/application.go +++ b/application.go @@ -76,6 +76,12 @@ func getStartPage(buffer *strings.Builder, params AppParams, addScripts string)
+ + `) } diff --git a/defaultTheme.rui b/defaultTheme.rui index 524481a..a88d6f8 100644 --- a/defaultTheme.rui +++ b/defaultTheme.rui @@ -23,6 +23,9 @@ theme { ruiTabTextColor = #FF404040, ruiCurrentTabColor = #FFFFFFFF, ruiCurrentTabTextColor = #FF000000, + ruiTooltipBackground = #FFFFFFFF, + ruiTooltipTextColor = #FF000000, + ruiTooltipShadowColor = #FF808080, }, colors:dark = _{ ruiTextColor = #FFE0E0E0, @@ -43,6 +46,9 @@ theme { ruiTabTextColor = #FFE0E0E0, ruiCurrentTabColor = #FF000000, ruiCurrentTabTextColor = #FFFFFFFF, + ruiTooltipBackground = #FF303030, + ruiTooltipTextColor = #FFDDDDDD, + ruiTooltipShadowColor = #FFDDDDDD, }, constants = _{ ruiButtonHorizontalPadding = 16px, diff --git a/mouseEvents.go b/mouseEvents.go index e7b3f39..b578147 100644 --- a/mouseEvents.go +++ b/mouseEvents.go @@ -184,7 +184,7 @@ func (view *viewData) removeMouseListener(tag string) { } } -func mouseEventsHtml(view View, buffer *strings.Builder) { +func mouseEventsHtml(view View, buffer *strings.Builder, hasTooltip bool) { for tag, js := range mouseEvents { if value := view.getRaw(tag); value != nil { if listeners, ok := value.([]func(View, MouseEvent)); ok && len(listeners) > 0 { @@ -192,6 +192,11 @@ func mouseEventsHtml(view View, buffer *strings.Builder) { } } } + + if hasTooltip { + buffer.WriteString(`onmouseenter="mouseEnterEvent(this, event)" `) + buffer.WriteString(`onmouseleave="mouseLeaveEvent(this, event)" `) + } } func getTimeStamp(data DataObject) uint64 { diff --git a/popup.go b/popup.go index 7288c5e..87ecb42 100644 --- a/popup.go +++ b/popup.go @@ -637,6 +637,7 @@ func (manager *popupManager) showPopup(popup Popup) { session.callFunc("blurCurrent") manager.updatePopupLayerInnerHTML(session) + session.updateCSSProperty("ruiTooltipLayer", "visibility", "hidden") session.updateCSSProperty("ruiPopupLayer", "visibility", "visible") session.updateCSSProperty("ruiRoot", "pointer-events", "none") } diff --git a/propertyNames.go b/propertyNames.go index 0eebcf1..1469d9e 100644 --- a/propertyNames.go +++ b/propertyNames.go @@ -702,4 +702,6 @@ const ( // * tabindex="0" means that View should be focusable in sequential keyboard navigation, after any positive tabindex values and its order is defined in order of its addition. // * A positive value means View should be focusable in sequential keyboard navigation, with its order defined by the value of the number. TabIndex = "tabindex" + + Tooltip = "tooltip" ) diff --git a/session.go b/session.go index 33cfd37..b625977 100644 --- a/session.go +++ b/session.go @@ -303,6 +303,21 @@ func (session *sessionData) writeInitScript(writer *strings.Builder) { writer.WriteString(text) writer.WriteString("';\nscanElementsSize();") } + + session.updateTooltipConstants() +} + +func (session *sessionData) updateTooltipConstants() { + if color, ok := session.Color("ruiTooltipBackground"); ok { + session.bridge.callFunc("setCssVar", "--tooltip-background", color.cssString()) + } + if color, ok := session.Color("ruiTooltipTextColor"); ok { + session.bridge.callFunc("setCssVar", "--tooltip-text-color", color.cssString()) + } + if color, ok := session.Color("ruiTooltipShadowColor"); ok { + session.bridge.callFunc("setCssVar", "--tooltip-shadow-color", color.cssString()) + } + } func (session *sessionData) reload() { @@ -323,6 +338,7 @@ func (session *sessionData) reload() { } session.bridge.writeMessage(buffer.String()) + session.updateTooltipConstants() } func (session *sessionData) ignoreViewUpdates() bool { diff --git a/view.go b/view.go index ebd7dd7..f8e1c5e 100644 --- a/view.go +++ b/view.go @@ -634,6 +634,16 @@ func viewPropertyChanged(view *viewData, tag string) { session.updateCSSProperty(htmlID, `column-span`, `none`) } return + + case Tooltip: + if tooltip := GetTooltip(view); tooltip == "" { + session.removeProperty(htmlID, "data-tooltip") + } else { + session.updateProperty(htmlID, "data-tooltip", tooltip) + session.updateProperty(htmlID, "onmouseenter", "mouseEnterEvent(this, event)") + session.updateProperty(htmlID, "onmouseleave", "mouseLeaveEvent(this, event)") + } + return } if cssTag, ok := sizeProperties[tag]; ok { @@ -796,10 +806,18 @@ func viewHTML(view View, buffer *strings.Builder) { } } + hasTooltip := false + if tooltip := GetTooltip(view); tooltip != "" { + buffer.WriteString(`data-tooltip=" `) + buffer.WriteString(tooltip) + buffer.WriteString(`" `) + hasTooltip = true + } + buffer.WriteString(`onscroll="scrollEvent(this, event)" `) keyEventsHtml(view, buffer) - mouseEventsHtml(view, buffer) + mouseEventsHtml(view, buffer, hasTooltip) pointerEventsHtml(view, buffer) touchEventsHtml(view, buffer) focusEventsHtml(view, buffer) diff --git a/viewUtils.go b/viewUtils.go index 3322fbb..2c4600f 100644 --- a/viewUtils.go +++ b/viewUtils.go @@ -945,3 +945,21 @@ func GetMixBlendMode(view View, subviewID ...string) int { func GetBackgroundBlendMode(view View, subviewID ...string) int { return enumStyledProperty(view, subviewID, BackgroundBlendMode, BlendNormal, true) } + +// GetTooltip returns a tooltip text of the subview. +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTooltip(view View, subviewID ...string) string { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) + } + + if view != nil { + if value := view.Get(Tooltip); value != nil { + if text, ok := value.(string); ok { + return text + } + } + } + + return "" +}