# HG changeset patch # User Edouard Tisserant # Date 1608229860 -3600 # Node ID 96ffd8b1b0169f6b0c763cb4281c226859a754d0 # Parent f475f39713aa94007f688a5f753be40262aac5d1 SVGHMI: added dropdown selection highlighting and fixed scrolling so that it doesn't miss any entry while jumping from one page to the other. diff -r f475f39713aa -r 96ffd8b1b016 svghmi/gen_index_xhtml.xslt --- a/svghmi/gen_index_xhtml.xslt Wed Dec 16 15:44:24 2020 +0100 +++ b/svghmi/gen_index_xhtml.xslt Thu Dec 17 19:31:00 2020 +0100 @@ -3084,6 +3084,10 @@ </xsl:text> <xsl:text> this.box_bbox = this.box_elt.getBBox() </xsl:text> + <xsl:text> this.highlight_bbox = this.highlight_elt.getBBox() +</xsl:text> + <xsl:text> this.highlight_elt.style.visibility = "hidden"; +</xsl:text> <xsl:text> </xsl:text> <xsl:text> // Compute margins @@ -3228,7 +3232,7 @@ </xsl:text> <xsl:text> let backup = first.getAttribute("dy"); </xsl:text> - <xsl:text> // apply lift asr a dy added too first span (y attrib stays) + <xsl:text> // apply lift as a dy added too first span (y attrib stays) </xsl:text> <xsl:text> first.setAttribute("dy", "-"+String((this.lift+1)*1.1)+"em"); </xsl:text> @@ -3306,6 +3310,8 @@ </xsl:text> <xsl:text> this.reset_box(); </xsl:text> + <xsl:text> this.reset_highlight(); +</xsl:text> <xsl:text> // Put the button back in place </xsl:text> <xsl:text> this.element.appendChild(this.button_elt); @@ -3326,289 +3332,359 @@ </xsl:text> <xsl:text> let txt = this.text_elt; </xsl:text> + <xsl:text> let original_text_y = this.text_bbox.y; +</xsl:text> + <xsl:text> let highlight = this.highlight_elt; +</xsl:text> + <xsl:text> let original_h_y = this.highlight_bbox.y; +</xsl:text> + <xsl:text> let clickable = highlight.cloneNode(); +</xsl:text> + <xsl:text> let yoffset = span.getBBox().y - original_text_y; +</xsl:text> + <xsl:text> clickable.y.baseVal.value = original_h_y + yoffset; +</xsl:text> + <xsl:text> clickable.style.pointerEvents = "bounding-box"; +</xsl:text> + <xsl:text> //clickable.style.visibility = "hidden"; +</xsl:text> + <xsl:text> //clickable.onclick = () => alert("love JS"); +</xsl:text> + <xsl:text> clickable.onclick = func; +</xsl:text> + <xsl:text> this.element.appendChild(clickable); +</xsl:text> + <xsl:text> this.clickables.push(clickable) +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> reset_clickables() { +</xsl:text> + <xsl:text> while(this.clickables.length){ +</xsl:text> + <xsl:text> this.element.removeChild(this.clickables.pop()); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> // Set text content when content is smaller than menu (no scrolling) +</xsl:text> + <xsl:text> set_complete_text(){ +</xsl:text> + <xsl:text> let spans = this.text_elt.children; +</xsl:text> + <xsl:text> let c = 0; +</xsl:text> + <xsl:text> for(let item of this.content){ +</xsl:text> + <xsl:text> let span=spans[c]; +</xsl:text> + <xsl:text> span.textContent = item; +</xsl:text> + <xsl:text> let sel = c; +</xsl:text> + <xsl:text> this.make_clickable(span, (evt) => this.bound_on_selection_click(sel)); +</xsl:text> + <xsl:text> c++; +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> // Move partial view : +</xsl:text> + <xsl:text> // false : upward, lower value +</xsl:text> + <xsl:text> // true : downward, higher value +</xsl:text> + <xsl:text> scroll(forward){ +</xsl:text> + <xsl:text> let contentlength = this.content.length; +</xsl:text> + <xsl:text> let spans = this.text_elt.children; +</xsl:text> + <xsl:text> let spanslength = spans.length; +</xsl:text> + <xsl:text> // reduce accounted menu size according to prsence of scroll buttons +</xsl:text> + <xsl:text> // since we scroll there is necessarly one button +</xsl:text> + <xsl:text> spanslength--; +</xsl:text> + <xsl:text> if(forward){ +</xsl:text> + <xsl:text> // reduce accounted menu size because of back button +</xsl:text> + <xsl:text> if(this.menu_offset != 0) spanslength--; +</xsl:text> + <xsl:text> this.menu_offset = Math.min( +</xsl:text> + <xsl:text> contentlength - spans.length + 1, +</xsl:text> + <xsl:text> this.menu_offset + spanslength); +</xsl:text> + <xsl:text> }else{ +</xsl:text> + <xsl:text> if(this.menu_offset - spanslength > 0) spanslength--; +</xsl:text> + <xsl:text> this.menu_offset = Math.max( +</xsl:text> + <xsl:text> 0, +</xsl:text> + <xsl:text> this.menu_offset - spanslength); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> if(this.menu_offset == 1) +</xsl:text> + <xsl:text> this.menu_offset = 0; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> this.reset_highlight(); +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> this.reset_clickables(); +</xsl:text> + <xsl:text> this.set_partial_text(); +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> this.highlight_selection(); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> // Setup partial view text content +</xsl:text> + <xsl:text> // with jumps at first and last entry when appropriate +</xsl:text> + <xsl:text> set_partial_text(){ +</xsl:text> + <xsl:text> let spans = this.text_elt.children; +</xsl:text> + <xsl:text> let contentlength = this.content.length; +</xsl:text> + <xsl:text> let spanslength = spans.length; +</xsl:text> + <xsl:text> let i = this.menu_offset, c = 0; +</xsl:text> + <xsl:text> let m = this.box_bbox; +</xsl:text> + <xsl:text> while(c < spanslength){ +</xsl:text> + <xsl:text> let span=spans[c]; +</xsl:text> + <xsl:text> let onclickfunc; +</xsl:text> + <xsl:text> // backward jump only present if not exactly at start +</xsl:text> + <xsl:text> if(c == 0 && i != 0){ +</xsl:text> + <xsl:text> span.textContent = "▲"; +</xsl:text> + <xsl:text> onclickfunc = this.bound_on_backward_click; +</xsl:text> + <xsl:text> let o = span.getBBox(); +</xsl:text> + <xsl:text> span.setAttribute("dx", (m.width - o.width)/2); +</xsl:text> + <xsl:text> // presence of forward jump when not right at the end +</xsl:text> + <xsl:text> }else if(c == spanslength-1 && i < contentlength - 1){ +</xsl:text> + <xsl:text> span.textContent = "▼"; +</xsl:text> + <xsl:text> onclickfunc = this.bound_on_forward_click; +</xsl:text> + <xsl:text> let o = span.getBBox(); +</xsl:text> + <xsl:text> span.setAttribute("dx", (m.width - o.width)/2); +</xsl:text> + <xsl:text> // otherwise normal content +</xsl:text> + <xsl:text> }else{ +</xsl:text> + <xsl:text> span.textContent = this.content[i]; +</xsl:text> + <xsl:text> let sel = i; +</xsl:text> + <xsl:text> onclickfunc = (evt) => this.bound_on_selection_click(sel); +</xsl:text> + <xsl:text> span.removeAttribute("dx"); +</xsl:text> + <xsl:text> i++; +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> this.make_clickable(span, onclickfunc); +</xsl:text> + <xsl:text> c++; +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> open(){ +</xsl:text> + <xsl:text> let length = this.content.length; +</xsl:text> + <xsl:text> // systematically reset text, to strip eventual whitespace spans +</xsl:text> + <xsl:text> this.reset_text(); +</xsl:text> + <xsl:text> // grow as much as needed or possible +</xsl:text> + <xsl:text> let slots = this.grow_text(length); +</xsl:text> + <xsl:text> // Depending on final size +</xsl:text> + <xsl:text> if(slots == length) { +</xsl:text> + <xsl:text> // show all at once +</xsl:text> + <xsl:text> this.set_complete_text(); +</xsl:text> + <xsl:text> } else { +</xsl:text> + <xsl:text> // eventualy align menu to current selection, compensating for lift +</xsl:text> + <xsl:text> let offset = this.last_selection - this.lift; +</xsl:text> + <xsl:text> if(offset > 0) +</xsl:text> + <xsl:text> this.menu_offset = Math.min(offset + 1, length - slots + 1); +</xsl:text> + <xsl:text> else +</xsl:text> + <xsl:text> this.menu_offset = 0; +</xsl:text> + <xsl:text> // show surrounding values +</xsl:text> + <xsl:text> this.set_partial_text(); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> // Now that text size is known, we can set the box around it +</xsl:text> + <xsl:text> this.adjust_box_to_text(); +</xsl:text> + <xsl:text> // Take button out until menu closed +</xsl:text> + <xsl:text> this.element.removeChild(this.button_elt); +</xsl:text> + <xsl:text> // Rise widget to top by moving it to last position among siblings +</xsl:text> + <xsl:text> this.element.parentNode.appendChild(this.element.parentNode.removeChild(this.element)); +</xsl:text> + <xsl:text> // disable interaction with background +</xsl:text> + <xsl:text> svg_root.addEventListener("pointerdown", numb_event, true); +</xsl:text> + <xsl:text> svg_root.addEventListener("pointerup", numb_event, true); +</xsl:text> + <xsl:text> svg_root.addEventListener("click", this.bound_close_on_click_elsewhere, true); +</xsl:text> + <xsl:text> this.highlight_selection(); +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> // mark as open +</xsl:text> + <xsl:text> this.opened = true; +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> // Put text element in normalized state +</xsl:text> + <xsl:text> reset_text(){ +</xsl:text> + <xsl:text> let txt = this.text_elt; +</xsl:text> <xsl:text> let first = txt.firstElementChild; </xsl:text> + <xsl:text> // remove attribute eventually added to first text line while opening +</xsl:text> + <xsl:text> first.onclick = null; +</xsl:text> + <xsl:text> first.removeAttribute("dy"); +</xsl:text> + <xsl:text> first.removeAttribute("dx"); +</xsl:text> + <xsl:text> // keep only the first line of text +</xsl:text> + <xsl:text> for(let span of Array.from(txt.children).slice(1)){ +</xsl:text> + <xsl:text> txt.removeChild(span) +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> // Put rectangle element in saved original state +</xsl:text> + <xsl:text> reset_box(){ +</xsl:text> + <xsl:text> let m = this.box_bbox; +</xsl:text> + <xsl:text> let b = this.box_elt; +</xsl:text> + <xsl:text> b.x.baseVal.value = m.x; +</xsl:text> + <xsl:text> b.y.baseVal.value = m.y; +</xsl:text> + <xsl:text> b.width.baseVal.value = m.width; +</xsl:text> + <xsl:text> b.height.baseVal.value = m.height; +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> highlight_selection(){ +</xsl:text> + <xsl:text> let highlighted_row = this.last_selection - this.menu_offset; +</xsl:text> + <xsl:text> if(highlighted_row < 0) return; +</xsl:text> + <xsl:text> let spans = this.text_elt.children; +</xsl:text> + <xsl:text> let spanslength = spans.length; +</xsl:text> + <xsl:text> let contentlength = this.content.length; +</xsl:text> + <xsl:text> if(this.menu_offset != 0) { +</xsl:text> + <xsl:text> spanslength--; +</xsl:text> + <xsl:text> highlighted_row++; +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> if(this.menu_offset + spanslength < contentlength - 1) spanslength--; +</xsl:text> + <xsl:text> if(highlighted_row > spanslength) return; +</xsl:text> <xsl:text> let original_text_y = this.text_bbox.y; </xsl:text> <xsl:text> let highlight = this.highlight_elt; </xsl:text> - <xsl:text> let original_h_y = highlight.getBBox().y; -</xsl:text> - <xsl:text> let clickable = highlight.cloneNode(); + <xsl:text> let span = spans[highlighted_row]; </xsl:text> <xsl:text> let yoffset = span.getBBox().y - original_text_y; </xsl:text> - <xsl:text> clickable.setAttribute("y", original_h_y + yoffset); -</xsl:text> - <xsl:text> clickable.style.pointerEvents = "bounding-box"; -</xsl:text> - <xsl:text> clickable.style.visibility = "hidden"; -</xsl:text> - <xsl:text> //clickable.onclick = () => alert("love JS"); -</xsl:text> - <xsl:text> clickable.onclick = func; -</xsl:text> - <xsl:text> this.element.appendChild(clickable); -</xsl:text> - <xsl:text> this.clickables.push(clickable) -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> reset_clickables() { -</xsl:text> - <xsl:text> while(this.clickables.length){ -</xsl:text> - <xsl:text> this.element.removeChild(this.clickables.pop()); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> // Set text content when content is smaller than menu (no scrolling) -</xsl:text> - <xsl:text> set_complete_text(){ -</xsl:text> - <xsl:text> let spans = this.text_elt.children; -</xsl:text> - <xsl:text> let c = 0; -</xsl:text> - <xsl:text> for(let item of this.content){ -</xsl:text> - <xsl:text> let span=spans[c]; -</xsl:text> - <xsl:text> span.textContent = item; -</xsl:text> - <xsl:text> let sel = c; -</xsl:text> - <xsl:text> this.make_clickable(span, (evt) => this.bound_on_selection_click(sel)); -</xsl:text> - <xsl:text> c++; -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> // Move partial view : -</xsl:text> - <xsl:text> // false : upward, lower value -</xsl:text> - <xsl:text> // true : downward, higher value -</xsl:text> - <xsl:text> scroll(forward){ -</xsl:text> - <xsl:text> let contentlength = this.content.length; -</xsl:text> - <xsl:text> let spans = this.text_elt.children; -</xsl:text> - <xsl:text> let spanslength = spans.length; -</xsl:text> - <xsl:text> // reduce accounted menu size according to jumps -</xsl:text> - <xsl:text> if(this.menu_offset != 0) spanslength--; -</xsl:text> - <xsl:text> if(this.menu_offset < contentlength - 1) spanslength--; -</xsl:text> - <xsl:text> if(forward){ -</xsl:text> - <xsl:text> this.menu_offset = Math.min( -</xsl:text> - <xsl:text> contentlength - spans.length + 1, -</xsl:text> - <xsl:text> this.menu_offset + spanslength); -</xsl:text> - <xsl:text> }else{ -</xsl:text> - <xsl:text> this.menu_offset = Math.max( -</xsl:text> - <xsl:text> 0, -</xsl:text> - <xsl:text> this.menu_offset - spanslength); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> this.reset_clickables(); -</xsl:text> - <xsl:text> this.set_partial_text(); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> // Setup partial view text content -</xsl:text> - <xsl:text> // with jumps at first and last entry when appropriate -</xsl:text> - <xsl:text> set_partial_text(){ -</xsl:text> - <xsl:text> let spans = this.text_elt.children; -</xsl:text> - <xsl:text> let contentlength = this.content.length; -</xsl:text> - <xsl:text> let spanslength = spans.length; -</xsl:text> - <xsl:text> let i = this.menu_offset, c = 0; -</xsl:text> - <xsl:text> let m = this.box_bbox; -</xsl:text> - <xsl:text> while(c < spanslength){ -</xsl:text> - <xsl:text> let span=spans[c]; -</xsl:text> - <xsl:text> let onclickfunc; -</xsl:text> - <xsl:text> // backward jump only present if not exactly at start -</xsl:text> - <xsl:text> if(c == 0 && i != 0){ -</xsl:text> - <xsl:text> span.textContent = "▲"; -</xsl:text> - <xsl:text> onclickfunc = this.bound_on_backward_click; -</xsl:text> - <xsl:text> let o = span.getBBox(); -</xsl:text> - <xsl:text> span.setAttribute("dx", (m.width - o.width)/2); -</xsl:text> - <xsl:text> // presence of forward jump when not right at the end -</xsl:text> - <xsl:text> }else if(c == spanslength-1 && i < contentlength - 1){ -</xsl:text> - <xsl:text> span.textContent = "▼"; -</xsl:text> - <xsl:text> onclickfunc = this.bound_on_forward_click; -</xsl:text> - <xsl:text> let o = span.getBBox(); -</xsl:text> - <xsl:text> span.setAttribute("dx", (m.width - o.width)/2); -</xsl:text> - <xsl:text> // otherwise normal content -</xsl:text> - <xsl:text> }else{ -</xsl:text> - <xsl:text> span.textContent = this.content[i]; -</xsl:text> - <xsl:text> let sel = i; -</xsl:text> - <xsl:text> onclickfunc = (evt) => this.bound_on_selection_click(sel); -</xsl:text> - <xsl:text> span.removeAttribute("dx"); -</xsl:text> - <xsl:text> i++; -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> this.make_clickable(span, onclickfunc); -</xsl:text> - <xsl:text> c++; -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> open(){ -</xsl:text> - <xsl:text> let length = this.content.length; -</xsl:text> - <xsl:text> // systematically reset text, to strip eventual whitespace spans -</xsl:text> - <xsl:text> this.reset_text(); -</xsl:text> - <xsl:text> // grow as much as needed or possible -</xsl:text> - <xsl:text> let slots = this.grow_text(length); -</xsl:text> - <xsl:text> // Depending on final size -</xsl:text> - <xsl:text> if(slots == length) { -</xsl:text> - <xsl:text> // show all at once -</xsl:text> - <xsl:text> this.set_complete_text(); -</xsl:text> - <xsl:text> } else { -</xsl:text> - <xsl:text> // eventualy align menu to current selection, compensating for lift -</xsl:text> - <xsl:text> let offset = this.last_selection - this.lift; -</xsl:text> - <xsl:text> if(offset > 0) -</xsl:text> - <xsl:text> this.menu_offset = Math.min(offset + 1, length - slots + 1); -</xsl:text> - <xsl:text> else -</xsl:text> - <xsl:text> this.menu_offset = 0; -</xsl:text> - <xsl:text> // show surrounding values -</xsl:text> - <xsl:text> this.set_partial_text(); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> // Now that text size is known, we can set the box around it -</xsl:text> - <xsl:text> this.adjust_box_to_text(); -</xsl:text> - <xsl:text> // Take button out until menu closed -</xsl:text> - <xsl:text> this.element.removeChild(this.button_elt); -</xsl:text> - <xsl:text> // Rise widget to top by moving it to last position among siblings -</xsl:text> - <xsl:text> this.element.parentNode.appendChild(this.element.parentNode.removeChild(this.element)); -</xsl:text> - <xsl:text> // disable interaction with background -</xsl:text> - <xsl:text> svg_root.addEventListener("pointerdown", numb_event, true); -</xsl:text> - <xsl:text> svg_root.addEventListener("pointerup", numb_event, true); -</xsl:text> - <xsl:text> svg_root.addEventListener("click", this.bound_close_on_click_elsewhere, true); -</xsl:text> - <xsl:text> // mark as open -</xsl:text> - <xsl:text> this.opened = true; -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> // Put text element in normalized state -</xsl:text> - <xsl:text> reset_text(){ -</xsl:text> - <xsl:text> let txt = this.text_elt; -</xsl:text> - <xsl:text> let first = txt.firstElementChild; -</xsl:text> - <xsl:text> // remove attribute eventually added to first text line while opening -</xsl:text> - <xsl:text> first.onclick = null; -</xsl:text> - <xsl:text> first.removeAttribute("dy"); -</xsl:text> - <xsl:text> first.removeAttribute("dx"); -</xsl:text> - <xsl:text> // keep only the first line of text -</xsl:text> - <xsl:text> for(let span of Array.from(txt.children).slice(1)){ -</xsl:text> - <xsl:text> txt.removeChild(span) -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> // Put rectangle element in saved original state -</xsl:text> - <xsl:text> reset_box(){ -</xsl:text> - <xsl:text> let m = this.box_bbox; -</xsl:text> - <xsl:text> let b = this.box_elt; -</xsl:text> - <xsl:text> b.x.baseVal.value = m.x; -</xsl:text> - <xsl:text> b.y.baseVal.value = m.y; -</xsl:text> - <xsl:text> b.width.baseVal.value = m.width; -</xsl:text> - <xsl:text> b.height.baseVal.value = m.height; + <xsl:text> highlight.y.baseVal.value = this.highlight_bbox.y + yoffset; +</xsl:text> + <xsl:text> highlight.style.visibility = "visible"; +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> reset_highlight(){ +</xsl:text> + <xsl:text> let highlight = this.highlight_elt; +</xsl:text> + <xsl:text> highlight.y.baseVal.value = this.highlight_bbox.y; +</xsl:text> + <xsl:text> highlight.style.visibility = "hidden"; </xsl:text> <xsl:text> } </xsl:text> diff -r f475f39713aa -r 96ffd8b1b016 svghmi/widget_dropdown.ysl2 --- a/svghmi/widget_dropdown.ysl2 Wed Dec 16 15:44:24 2020 +0100 +++ b/svghmi/widget_dropdown.ysl2 Thu Dec 17 19:31:00 2020 +0100 @@ -13,6 +13,8 @@ this.button_elt.onclick = this.on_button_click.bind(this); // Save original size of rectangle this.box_bbox = this.box_elt.getBBox() + this.highlight_bbox = this.highlight_elt.getBBox() + this.highlight_elt.style.visibility = "hidden"; // Compute margins this.text_bbox = this.text_elt.getBBox(); @@ -85,7 +87,7 @@ if(rect.bottom > bounds.bottom){ // in case of overflow at the bottom, lift up one row let backup = first.getAttribute("dy"); - // apply lift asr a dy added too first span (y attrib stays) + // apply lift as a dy added too first span (y attrib stays) first.setAttribute("dy", "-"+String((this.lift+1)*1.1)+"em"); rect = txt.getBoundingClientRect(); if(rect.top > bounds.top){ @@ -124,6 +126,7 @@ this.reset_text(); this.reset_clickables(); this.reset_box(); + this.reset_highlight(); // Put the button back in place this.element.appendChild(this.button_elt); // Mark as closed (to allow dispatch) @@ -134,15 +137,14 @@ // Make item (text span) clickable by overlaying a rectangle on top of it make_clickable(span, func) { let txt = this.text_elt; - let first = txt.firstElementChild; let original_text_y = this.text_bbox.y; let highlight = this.highlight_elt; - let original_h_y = highlight.getBBox().y; + let original_h_y = this.highlight_bbox.y; let clickable = highlight.cloneNode(); let yoffset = span.getBBox().y - original_text_y; - clickable.setAttribute("y", original_h_y + yoffset); + clickable.y.baseVal.value = original_h_y + yoffset; clickable.style.pointerEvents = "bounding-box"; - clickable.style.visibility = "hidden"; + //clickable.style.visibility = "hidden"; //clickable.onclick = () => alert("love JS"); clickable.onclick = func; this.element.appendChild(clickable); @@ -172,20 +174,33 @@ let contentlength = this.content.length; let spans = this.text_elt.children; let spanslength = spans.length; - // reduce accounted menu size according to jumps - if(this.menu_offset != 0) spanslength--; - if(this.menu_offset < contentlength - 1) spanslength--; + // reduce accounted menu size according to prsence of scroll buttons + // since we scroll there is necessarly one button + spanslength--; if(forward){ + // reduce accounted menu size because of back button + // in current view + if(this.menu_offset > 0) spanslength--; this.menu_offset = Math.min( contentlength - spans.length + 1, this.menu_offset + spanslength); }else{ + // reduce accounted menu size because of back button + // in view once scrolled + if(this.menu_offset - spanslength > 0) spanslength--; this.menu_offset = Math.max( 0, this.menu_offset - spanslength); } + if(this.menu_offset == 1) + this.menu_offset = 0; + + this.reset_highlight(); + this.reset_clickables(); this.set_partial_text(); + + this.highlight_selection(); } // Setup partial view text content // with jumps at first and last entry when appropriate @@ -252,6 +267,8 @@ svg_root.addEventListener("pointerdown", numb_event, true); svg_root.addEventListener("pointerup", numb_event, true); svg_root.addEventListener("click", this.bound_close_on_click_elsewhere, true); + this.highlight_selection(); + // mark as open this.opened = true; } @@ -277,6 +294,30 @@ b.width.baseVal.value = m.width; b.height.baseVal.value = m.height; } + highlight_selection(){ + let highlighted_row = this.last_selection - this.menu_offset; + if(highlighted_row < 0) return; + let spans = this.text_elt.children; + let spanslength = spans.length; + let contentlength = this.content.length; + if(this.menu_offset != 0) { + spanslength--; + highlighted_row++; + } + if(this.menu_offset + spanslength < contentlength - 1) spanslength--; + if(highlighted_row > spanslength) return; + let original_text_y = this.text_bbox.y; + let highlight = this.highlight_elt; + let span = spans[highlighted_row]; + let yoffset = span.getBBox().y - original_text_y; + highlight.y.baseVal.value = this.highlight_bbox.y + yoffset; + highlight.style.visibility = "visible"; + } + reset_highlight(){ + let highlight = this.highlight_elt; + highlight.y.baseVal.value = this.highlight_bbox.y; + highlight.style.visibility = "hidden"; + } // Use margin and text size to compute box size adjust_box_to_text(){ let [lmargin, tmargin] = this.margins; diff -r f475f39713aa -r 96ffd8b1b016 tests/svghmi/svghmi_0@svghmi/svghmi.svg --- a/tests/svghmi/svghmi_0@svghmi/svghmi.svg Wed Dec 16 15:44:24 2020 +0100 +++ b/tests/svghmi/svghmi_0@svghmi/svghmi.svg Thu Dec 17 19:31:00 2020 +0100 @@ -197,12 +197,12 @@ inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:document-units="px" - inkscape:current-layer="g14237" + inkscape:current-layer="hmi0" showgrid="false" units="px" inkscape:zoom="0.54565796" - inkscape:cx="147.6698" - inkscape:cy="180.09341" + inkscape:cx="-623.8758" + inkscape:cy="172.76281" inkscape:window-width="1600" inkscape:window-height="836" inkscape:window-x="0" @@ -2394,7 +2394,7 @@ </g> <g id="g14237" - inkscape:label="HMI:DropDown:1:2:3:4:5:6:7:8:9:10@/SELECTION" + inkscape:label="HMI:DropDown: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@/SELECTION" transform="matrix(0.81491208,0,0,0.81491208,243.6641,-510.30491)" style="stroke-width:0.35083869"> <rect