# 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 @@
this.box_bbox = this.box_elt.getBBox()
+ this.highlight_bbox = this.highlight_elt.getBBox()
+
+ this.highlight_elt.style.visibility = "hidden";
+
// Compute margins
@@ -3228,7 +3232,7 @@
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");
@@ -3306,6 +3310,8 @@
this.reset_box();
+ this.reset_highlight();
+
// Put the button back in place
this.element.appendChild(this.button_elt);
@@ -3326,289 +3332,359 @@
let txt = this.text_elt;
+ let original_text_y = this.text_bbox.y;
+
+ let highlight = this.highlight_elt;
+
+ let original_h_y = this.highlight_bbox.y;
+
+ let clickable = highlight.cloneNode();
+
+ let yoffset = span.getBBox().y - original_text_y;
+
+ clickable.y.baseVal.value = original_h_y + yoffset;
+
+ clickable.style.pointerEvents = "bounding-box";
+
+ //clickable.style.visibility = "hidden";
+
+ //clickable.onclick = () => alert("love JS");
+
+ clickable.onclick = func;
+
+ this.element.appendChild(clickable);
+
+ this.clickables.push(clickable)
+
+ }
+
+ reset_clickables() {
+
+ while(this.clickables.length){
+
+ this.element.removeChild(this.clickables.pop());
+
+ }
+
+ }
+
+ // Set text content when content is smaller than menu (no scrolling)
+
+ set_complete_text(){
+
+ let spans = this.text_elt.children;
+
+ let c = 0;
+
+ for(let item of this.content){
+
+ let span=spans[c];
+
+ span.textContent = item;
+
+ let sel = c;
+
+ this.make_clickable(span, (evt) => this.bound_on_selection_click(sel));
+
+ c++;
+
+ }
+
+ }
+
+ // Move partial view :
+
+ // false : upward, lower value
+
+ // true : downward, higher value
+
+ scroll(forward){
+
+ let contentlength = this.content.length;
+
+ let spans = this.text_elt.children;
+
+ let spanslength = spans.length;
+
+ // 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
+
+ if(this.menu_offset != 0) spanslength--;
+
+ this.menu_offset = Math.min(
+
+ contentlength - spans.length + 1,
+
+ this.menu_offset + spanslength);
+
+ }else{
+
+ 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
+
+ set_partial_text(){
+
+ let spans = this.text_elt.children;
+
+ let contentlength = this.content.length;
+
+ let spanslength = spans.length;
+
+ let i = this.menu_offset, c = 0;
+
+ let m = this.box_bbox;
+
+ while(c < spanslength){
+
+ let span=spans[c];
+
+ let onclickfunc;
+
+ // backward jump only present if not exactly at start
+
+ if(c == 0 && i != 0){
+
+ span.textContent = "▲";
+
+ onclickfunc = this.bound_on_backward_click;
+
+ let o = span.getBBox();
+
+ span.setAttribute("dx", (m.width - o.width)/2);
+
+ // presence of forward jump when not right at the end
+
+ }else if(c == spanslength-1 && i < contentlength - 1){
+
+ span.textContent = "▼";
+
+ onclickfunc = this.bound_on_forward_click;
+
+ let o = span.getBBox();
+
+ span.setAttribute("dx", (m.width - o.width)/2);
+
+ // otherwise normal content
+
+ }else{
+
+ span.textContent = this.content[i];
+
+ let sel = i;
+
+ onclickfunc = (evt) => this.bound_on_selection_click(sel);
+
+ span.removeAttribute("dx");
+
+ i++;
+
+ }
+
+ this.make_clickable(span, onclickfunc);
+
+ c++;
+
+ }
+
+ }
+
+ open(){
+
+ let length = this.content.length;
+
+ // systematically reset text, to strip eventual whitespace spans
+
+ this.reset_text();
+
+ // grow as much as needed or possible
+
+ let slots = this.grow_text(length);
+
+ // Depending on final size
+
+ if(slots == length) {
+
+ // show all at once
+
+ this.set_complete_text();
+
+ } else {
+
+ // eventualy align menu to current selection, compensating for lift
+
+ let offset = this.last_selection - this.lift;
+
+ if(offset > 0)
+
+ this.menu_offset = Math.min(offset + 1, length - slots + 1);
+
+ else
+
+ this.menu_offset = 0;
+
+ // show surrounding values
+
+ this.set_partial_text();
+
+ }
+
+ // Now that text size is known, we can set the box around it
+
+ this.adjust_box_to_text();
+
+ // Take button out until menu closed
+
+ this.element.removeChild(this.button_elt);
+
+ // Rise widget to top by moving it to last position among siblings
+
+ this.element.parentNode.appendChild(this.element.parentNode.removeChild(this.element));
+
+ // disable interaction with background
+
+ 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;
+
+ }
+
+ // Put text element in normalized state
+
+ reset_text(){
+
+ let txt = this.text_elt;
+
let first = txt.firstElementChild;
+ // remove attribute eventually added to first text line while opening
+
+ first.onclick = null;
+
+ first.removeAttribute("dy");
+
+ first.removeAttribute("dx");
+
+ // keep only the first line of text
+
+ for(let span of Array.from(txt.children).slice(1)){
+
+ txt.removeChild(span)
+
+ }
+
+ }
+
+ // Put rectangle element in saved original state
+
+ reset_box(){
+
+ let m = this.box_bbox;
+
+ let b = this.box_elt;
+
+ b.x.baseVal.value = m.x;
+
+ b.y.baseVal.value = m.y;
+
+ 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 original_h_y = highlight.getBBox().y;
-
- let clickable = highlight.cloneNode();
+ let span = spans[highlighted_row];
let yoffset = span.getBBox().y - original_text_y;
- clickable.setAttribute("y", original_h_y + yoffset);
-
- clickable.style.pointerEvents = "bounding-box";
-
- clickable.style.visibility = "hidden";
-
- //clickable.onclick = () => alert("love JS");
-
- clickable.onclick = func;
-
- this.element.appendChild(clickable);
-
- this.clickables.push(clickable)
-
- }
-
- reset_clickables() {
-
- while(this.clickables.length){
-
- this.element.removeChild(this.clickables.pop());
-
- }
-
- }
-
- // Set text content when content is smaller than menu (no scrolling)
-
- set_complete_text(){
-
- let spans = this.text_elt.children;
-
- let c = 0;
-
- for(let item of this.content){
-
- let span=spans[c];
-
- span.textContent = item;
-
- let sel = c;
-
- this.make_clickable(span, (evt) => this.bound_on_selection_click(sel));
-
- c++;
-
- }
-
- }
-
- // Move partial view :
-
- // false : upward, lower value
-
- // true : downward, higher value
-
- scroll(forward){
-
- 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--;
-
- if(forward){
-
- this.menu_offset = Math.min(
-
- contentlength - spans.length + 1,
-
- this.menu_offset + spanslength);
-
- }else{
-
- this.menu_offset = Math.max(
-
- 0,
-
- this.menu_offset - spanslength);
-
- }
-
- this.reset_clickables();
-
- this.set_partial_text();
-
- }
-
- // Setup partial view text content
-
- // with jumps at first and last entry when appropriate
-
- set_partial_text(){
-
- let spans = this.text_elt.children;
-
- let contentlength = this.content.length;
-
- let spanslength = spans.length;
-
- let i = this.menu_offset, c = 0;
-
- let m = this.box_bbox;
-
- while(c < spanslength){
-
- let span=spans[c];
-
- let onclickfunc;
-
- // backward jump only present if not exactly at start
-
- if(c == 0 && i != 0){
-
- span.textContent = "▲";
-
- onclickfunc = this.bound_on_backward_click;
-
- let o = span.getBBox();
-
- span.setAttribute("dx", (m.width - o.width)/2);
-
- // presence of forward jump when not right at the end
-
- }else if(c == spanslength-1 && i < contentlength - 1){
-
- span.textContent = "▼";
-
- onclickfunc = this.bound_on_forward_click;
-
- let o = span.getBBox();
-
- span.setAttribute("dx", (m.width - o.width)/2);
-
- // otherwise normal content
-
- }else{
-
- span.textContent = this.content[i];
-
- let sel = i;
-
- onclickfunc = (evt) => this.bound_on_selection_click(sel);
-
- span.removeAttribute("dx");
-
- i++;
-
- }
-
- this.make_clickable(span, onclickfunc);
-
- c++;
-
- }
-
- }
-
- open(){
-
- let length = this.content.length;
-
- // systematically reset text, to strip eventual whitespace spans
-
- this.reset_text();
-
- // grow as much as needed or possible
-
- let slots = this.grow_text(length);
-
- // Depending on final size
-
- if(slots == length) {
-
- // show all at once
-
- this.set_complete_text();
-
- } else {
-
- // eventualy align menu to current selection, compensating for lift
-
- let offset = this.last_selection - this.lift;
-
- if(offset > 0)
-
- this.menu_offset = Math.min(offset + 1, length - slots + 1);
-
- else
-
- this.menu_offset = 0;
-
- // show surrounding values
-
- this.set_partial_text();
-
- }
-
- // Now that text size is known, we can set the box around it
-
- this.adjust_box_to_text();
-
- // Take button out until menu closed
-
- this.element.removeChild(this.button_elt);
-
- // Rise widget to top by moving it to last position among siblings
-
- this.element.parentNode.appendChild(this.element.parentNode.removeChild(this.element));
-
- // disable interaction with background
-
- 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);
-
- // mark as open
-
- this.opened = true;
-
- }
-
- // Put text element in normalized state
-
- reset_text(){
-
- let txt = this.text_elt;
-
- let first = txt.firstElementChild;
-
- // remove attribute eventually added to first text line while opening
-
- first.onclick = null;
-
- first.removeAttribute("dy");
-
- first.removeAttribute("dx");
-
- // keep only the first line of text
-
- for(let span of Array.from(txt.children).slice(1)){
-
- txt.removeChild(span)
-
- }
-
- }
-
- // Put rectangle element in saved original state
-
- reset_box(){
-
- let m = this.box_bbox;
-
- let b = this.box_elt;
-
- b.x.baseVal.value = m.x;
-
- b.y.baseVal.value = m.y;
-
- b.width.baseVal.value = m.width;
-
- b.height.baseVal.value = m.height;
+ 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";
}
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 @@