From 15a11dc558cc661e90c9efad00ec09d11a810034 Mon Sep 17 00:00:00 2001 From: anoshenko Date: Wed, 5 Jan 2022 18:33:18 +0300 Subject: [PATCH 01/14] Added TableView properties --- propertySet.go | 5 + tableView.go | 274 +++++++++++++++++++++++++++++++++++++++++++++- tableViewUtils.go | 84 ++++++++++++++ 3 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 tableViewUtils.go diff --git a/propertySet.go b/propertySet.go index 5948b8a..414a0b2 100644 --- a/propertySet.go +++ b/propertySet.go @@ -412,6 +412,11 @@ var enumProperties = map[string]struct { "", []string{"none", "metadata", "auto"}, }, + SelectionMode: { + []string{"none", "cell", "row"}, + "", + []string{"none", "cell", "row"}, + }, } func notCompatibleType(tag string, value interface{}) { diff --git a/tableView.go b/tableView.go index b5d82ba..0c7a90f 100644 --- a/tableView.go +++ b/tableView.go @@ -11,139 +11,217 @@ const ( // The "table-vertical-align" int property sets the vertical alignment of the content inside a table cell. // Valid values are LeftAlign (0), RightAlign (1), CenterAlign (2), and BaselineAlign (3, 4) TableVerticalAlign = "table-vertical-align" + // HeadHeight is the constant for the "head-height" property tag. // The "head-height" int property sets the number of rows in the table header. // The default value is 0 (no header) HeadHeight = "head-height" + // HeadStyle is the constant for the "head-style" property tag. // The "head-style" string property sets the header style name HeadStyle = "head-style" + // FootHeight is the constant for the "foot-height" property tag. // The "foot-height" int property sets the number of rows in the table footer. // The default value is 0 (no footer) FootHeight = "foot-height" + // FootStyle is the constant for the "foot-style" property tag. // The "foot-style" string property sets the footer style name FootStyle = "foot-style" + // RowSpan is the constant for the "row-span" property tag. // The "row-span" int property sets the number of table row to span. // Used only when specifying cell parameters in the implementation of TableCellStyle RowSpan = "row-span" + // ColumnSpan is the constant for the "column-span" property tag. // The "column-span" int property sets the number of table column to span. // Used only when specifying cell parameters in the implementation of TableCellStyle ColumnSpan = "column-span" + // RowStyle is the constant for the "row-style" property tag. // The "row-style" property sets the adapter which specifies styles of each table row. // This property can be assigned or by an implementation of TableRowStyle interface, or by an array of Params. RowStyle = "row-style" + // ColumnStyle is the constant for the "column-style" property tag. // The "column-style" property sets the adapter which specifies styles of each table column. // This property can be assigned or by an implementation of TableColumnStyle interface, or by an array of Params. ColumnStyle = "column-style" + // CellStyle is the constant for the "cell-style" property tag. // The "cell-style" property sets the adapter which specifies styles of each table cell. // This property can be assigned only by an implementation of TableCellStyle interface. CellStyle = "cell-style" + // CellPadding is the constant for the "cell-padding" property tag. // The "cell-padding" Bounds property sets the padding area on all four sides of a table call at once. // An element's padding area is the space between its content and its border. CellPadding = "cell-padding" + // CellPaddingLeft is the constant for the "cell-padding-left" property tag. // The "cell-padding-left" SizeUnit property sets the width of the padding area to the left of a cell content. // An element's padding area is the space between its content and its border. CellPaddingLeft = "cell-padding-left" + // CellPaddingRight is the constant for the "cell-padding-right" property tag. // The "cell-padding-right" SizeUnit property sets the width of the padding area to the left of a cell content. // An element's padding area is the space between its content and its border. CellPaddingRight = "cell-padding-right" + // CellPaddingTop is the constant for the "cell-padding-top" property tag. // The "cell-padding-top" SizeUnit property sets the height of the padding area to the top of a cell content. // An element's padding area is the space between its content and its border. CellPaddingTop = "cell-padding-top" + // CellPaddingBottom is the constant for the "cell-padding-bottom" property tag. // The "cell-padding-bottom" SizeUnit property sets the height of the padding area to the bottom of a cell content. CellPaddingBottom = "cell-padding-bottom" + // CellBorder is the constant for the "cell-border" property tag. // The "cell-border" property sets a table cell's border. It sets the values of a border width, style, and color. // This property can be assigned a value of BorderProperty type, or ViewBorder type, or BorderProperty text representation. CellBorder = "cell-border" + // CellBorderLeft is the constant for the "cell-border-left" property tag. // The "cell-border-left" property sets a view's left border. It sets the values of a border width, style, and color. // This property can be assigned a value of BorderProperty type, or ViewBorder type, or BorderProperty text representation. CellBorderLeft = "cell-border-left" + // CellBorderRight is the constant for the "cell-border-right" property tag. // The "cell-border-right" property sets a view's right border. It sets the values of a border width, style, and color. // This property can be assigned a value of BorderProperty type, or ViewBorder type, or BorderProperty text representation. CellBorderRight = "cell-border-right" + // CellBorderTop is the constant for the "cell-border-top" property tag. // The "cell-border-top" property sets a view's top border. It sets the values of a border width, style, and color. // This property can be assigned a value of BorderProperty type, or ViewBorder type, or BorderProperty text representation. CellBorderTop = "cell-border-top" + // CellBorderBottom is the constant for the "cell-border-bottom" property tag. // The "cell-border-bottom" property sets a view's bottom border. It sets the values of a border width, style, and color. // This property can be assigned a value of BorderProperty type, or ViewBorder type, or BorderProperty text representation. CellBorderBottom = "cell-border-bottom" + // CellBorderStyle is the constant for the "cell-border-style" property tag. // The "cell-border-style" int property sets the line style for all four sides of a table cell's border. // Valid values are NoneLine (0), SolidLine (1), DashedLine (2), DottedLine (3), and DoubleLine (4). CellBorderStyle = "cell-border-style" + // CellBorderLeftStyle is the constant for the "cell-border-left-style" property tag. // The "cell-border-left-style" int property sets the line style of a table cell's left border. // Valid values are NoneLine (0), SolidLine (1), DashedLine (2), DottedLine (3), and DoubleLine (4). CellBorderLeftStyle = "cell-border-left-style" + // CellBorderRightStyle is the constant for the "cell-border-right-style" property tag. // The "cell-border-right-style" int property sets the line style of a table cell's right border. // Valid values are NoneLine (0), SolidLine (1), DashedLine (2), DottedLine (3), and DoubleLine (4). CellBorderRightStyle = "cell-border-right-style" + // CellBorderTopStyle is the constant for the "cell-border-top-style" property tag. // The "cell-border-top-style" int property sets the line style of a table cell's top border. // Valid values are NoneLine (0), SolidLine (1), DashedLine (2), DottedLine (3), and DoubleLine (4). CellBorderTopStyle = "cell-border-top-style" + // CellBorderBottomStyle is the constant for the "cell-border-bottom-style" property tag. // The "cell-border-bottom-style" int property sets the line style of a table cell's bottom border. // Valid values are NoneLine (0), SolidLine (1), DashedLine (2), DottedLine (3), and DoubleLine (4). CellBorderBottomStyle = "cell-border-bottom-style" + // CellBorderWidth is the constant for the "cell-border-width" property tag. // The "cell-border-width" property sets the line width for all four sides of a table cell's border. CellBorderWidth = "cell-border-width" + // CellBorderLeftWidth is the constant for the "cell-border-left-width" property tag. // The "cell-border-left-width" SizeUnit property sets the line width of a table cell's left border. CellBorderLeftWidth = "cell-border-left-width" + // CellBorderRightWidth is the constant for the "cell-border-right-width" property tag. // The "cell-border-right-width" SizeUnit property sets the line width of a table cell's right border. CellBorderRightWidth = "cell-border-right-width" + // CellBorderTopWidth is the constant for the "cell-border-top-width" property tag. // The "cell-border-top-width" SizeUnit property sets the line width of a table cell's top border. CellBorderTopWidth = "cell-border-top-width" + // CellBorderBottomWidth is the constant for the "cell-border-bottom-width" property tag. // The "cell-border-bottom-width" SizeUnit property sets the line width of a table cell's bottom border. CellBorderBottomWidth = "cell-border-bottom-width" + // CellBorderColor is the constant for the "cell-border-color" property tag. // The "cell-border-color" property sets the line color for all four sides of a table cell's border. CellBorderColor = "cell-border-color" + // CellBorderLeftColor is the constant for the "cell-border-left-color" property tag. // The "cell-border-left-color" property sets the line color of a table cell's left border. CellBorderLeftColor = "cell-border-left-color" + // CellBorderRightColor is the constant for the "cell-border-right-color" property tag. // The "cell-border-right-color" property sets the line color of a table cell's right border. CellBorderRightColor = "cell-border-right-color" + // CellBorderTopColor is the constant for the "cell-border-top-color" property tag. // The "cell-border-top-color" property sets the line color of a table cell's top border. CellBorderTopColor = "cell-border-top-color" + // CellBorderBottomColor is the constant for the "cell-border-bottom-color" property tag. // The "cell-border-bottom-color" property sets the line color of a table cell's bottom border. CellBorderBottomColor = "cell-border-bottom-color" + + // SelectionMode is the constant for the "selection-mode" property tag. + // The "selection-mode" int property sets the mode of the table elements selection. + // Valid values are NoneSelection (0), CellSelection (1), and RowSelection (2) + SelectionMode = "selection-mode" + + // TableCellClickedEvent is the constant for "table-cell-clicked" property tag. + // The "table-cell-clicked" event occurs when the user clicks on a table cell. + // The main listener format: func(TableView, int, int), where the second argument is the row number, + // and third argument is the column number. + TableCellClickedEvent = "table-cell-clicked" + + // TableCellSelectedEvent is the constant for "table-cell-selected" property tag. + // The "table-cell-selected" event occurs when a table cell becomes selected. + // The main listener format: func(TableView, int, int), where the second argument is the row number, + // and third argument is the column number. + TableCellSelectedEvent = "table-cell-selected" + + // TableRowClickedEvent is the constant for "table-row-clicked" property tag. + // The "table-row-clicked" event occurs when the user clicks on a table row. + // The main listener format: func(TableView, int), where the second argument is the row number. + TableRowClickedEvent = "table-row-clicked" + + // TableRowSelectedEvent is the constant for "table-row-selected" property tag. + // The "table-row-selected" event occurs when a table row becomes selected. + // The main listener format: func(TableView, int), where the second argument is the row number. + TableRowSelectedEvent = "table-row-selected" + + // NoneSelection is the value of "selection-mode" property: the selection is forbidden. + NoneSelection = 0 + // CellSelection is the value of "selection-mode" property: the selection of a single cell only is enabled. + CellSelection = 1 + // RowSelection is the value of "selection-mode" property: the selection of a table row only is enabled. + RowSelection = 2 ) +// CellIndex defines coordinates of the TableView cell +type CellIndex struct { + Row, Column int +} + // TableView - text View type TableView interface { View + ParanetView ReloadTableData() } type tableViewData struct { viewData + cellViews []View + cellSelectedListener, cellClickedListener []func(TableView, int, int) + rowSelectedListener, rowClickedListener []func(TableView, int) + current CellIndex } type tableCellView struct { @@ -166,6 +244,13 @@ func newTableView(session Session) View { func (table *tableViewData) Init(session Session) { table.viewData.Init(session) table.tag = "TableView" + table.cellViews = []View{} + table.cellSelectedListener = []func(TableView, int, int){} + table.cellClickedListener = []func(TableView, int, int){} + table.rowSelectedListener = []func(TableView, int){} + table.rowClickedListener = []func(TableView, int){} + table.current.Row = -1 + table.current.Column = -1 } func (table *tableViewData) normalizeTag(tag string) string { @@ -206,6 +291,27 @@ func (table *tableViewData) remove(tag string) { table.propertyChanged(tag) } + case TableCellClickedEvent: + table.cellClickedListener = []func(TableView, int, int){} + table.propertyChanged(tag) + + case TableCellSelectedEvent: + table.cellSelectedListener = []func(TableView, int, int){} + table.propertyChanged(tag) + + case TableRowClickedEvent: + table.rowClickedListener = []func(TableView, int){} + table.propertyChanged(tag) + + case TableRowSelectedEvent: + table.rowSelectedListener = []func(TableView, int){} + table.propertyChanged(tag) + + case Current: + table.current.Row = -1 + table.current.Column = -1 + table.propertyChanged(tag) + default: table.viewData.remove(tag) } @@ -238,6 +344,38 @@ func (table *tableViewData) set(tag string, value interface{}) bool { return false } + case TableCellClickedEvent: + listeners := table.valueToCellListeners(value) + if listeners == nil { + notCompatibleType(tag, value) + return false + } + table.cellClickedListener = listeners + + case TableCellSelectedEvent: + listeners := table.valueToCellListeners(value) + if listeners == nil { + notCompatibleType(tag, value) + return false + } + table.cellSelectedListener = listeners + + case TableRowClickedEvent: + listeners := table.valueToRowListeners(value) + if listeners == nil { + notCompatibleType(tag, value) + return false + } + table.rowClickedListener = listeners + + case TableRowSelectedEvent: + listeners := table.valueToRowListeners(value) + if listeners == nil { + notCompatibleType(tag, value) + return false + } + table.rowSelectedListener = []func(TableView, int){} + case CellStyle: if style, ok := value.(TableCellStyle); ok { table.properties[tag] = style @@ -342,6 +480,22 @@ func (table *tableViewData) set(tag string, value interface{}) bool { return false } + case SelectionMode: + if !table.setEnumProperty(SelectionMode, value, enumProperties[SelectionMode].values) { + return false + } + + case Current: + switch GetSelectionMode(table, "") { + case NoneSelection: + + case CellSelection: + // TODO + + case RowSelection: + // TODO + } + default: return table.viewData.set(tag, value) } @@ -355,7 +509,8 @@ func (table *tableViewData) propertyChanged(tag string) { switch tag { case Content, RowStyle, ColumnStyle, CellStyle, CellPadding, CellBorder, HeadHeight, HeadStyle, FootHeight, FootStyle, - CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft: + CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft, + TableCellClickedEvent, TableCellSelectedEvent, TableRowClickedEvent, TableRowSelectedEvent: table.ReloadTableData() case Gap: @@ -374,11 +529,123 @@ func (table *tableViewData) propertyChanged(tag string) { table.propertyChangedEvent(tag) } +func (table *tableViewData) valueToCellListeners(value interface{}) []func(TableView, int, int) { + if value == nil { + return []func(TableView, int, int){} + } + + switch value := value.(type) { + case func(TableView, int, int): + return []func(TableView, int, int){value} + + case func(int, int): + fn := func(view TableView, row, column int) { + value(row, column) + } + return []func(TableView, int, int){fn} + + case []func(TableView, int, int): + return value + + case []func(int, int): + listeners := make([]func(TableView, int, int), len(value)) + for i, val := range value { + if val == nil { + return nil + } + listeners[i] = func(view TableView, row, column int) { + val(row, column) + } + } + return listeners + + case []interface{}: + listeners := make([]func(TableView, int, int), len(value)) + for i, val := range value { + if val == nil { + return nil + } + switch val := val.(type) { + case func(TableView, int, int): + listeners[i] = val + + case func(int, int): + listeners[i] = func(view TableView, row, column int) { + val(row, column) + } + + default: + return nil + } + } + return listeners + } + + return nil +} + +func (table *tableViewData) valueToRowListeners(value interface{}) []func(TableView, int) { + if value == nil { + return []func(TableView, int){} + } + + switch value := value.(type) { + case func(TableView, int): + return []func(TableView, int){value} + + case func(int): + fn := func(view TableView, index int) { + value(index) + } + return []func(TableView, int){fn} + + case []func(TableView, int): + return value + + case []func(int): + listeners := make([]func(TableView, int), len(value)) + for i, val := range value { + if val == nil { + return nil + } + listeners[i] = func(view TableView, index int) { + val(index) + } + } + return listeners + + case []interface{}: + listeners := make([]func(TableView, int), len(value)) + for i, val := range value { + if val == nil { + return nil + } + switch val := val.(type) { + case func(TableView, int): + listeners[i] = val + + case func(int): + listeners[i] = func(view TableView, index int) { + val(index) + } + + default: + return nil + } + } + return listeners + } + + return nil +} + func (table *tableViewData) htmlTag() string { return "table" } func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { + table.cellViews = []View{} + content := table.getRaw(Content) if content == nil { return @@ -556,6 +823,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { case View: viewHTML(value, buffer) + table.cellViews = append(table.cellViews, value) case Color: buffer.WriteString(`
Date: Fri, 14 Jan 2022 17:20:04 -0500 Subject: [PATCH 02/14] Added TableView cell/row selection mode --- CHANGELOG.md | 4 + app_scripts.js | 485 ++++++++++++++++++++++++++++++++++++++++------ customView.go | 6 +- datePicker.go | 4 + defaultTheme.rui | 16 +- demo/tableDemo.go | 6 + dropDownList.go | 4 + editView.go | 4 + filePicker.go | 4 + focusEvents.go | 8 +- listView.go | 23 ++- mediaPlayer.go | 4 + numberPicker.go | 4 + resizeEvent.go | 2 +- session.go | 10 +- tableView.go | 344 ++++++++++++++++++++++++++++---- tableViewUtils.go | 59 ++++++ timePicker.go | 4 + view.go | 18 +- 19 files changed, 888 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecdf41b..910ea22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v0.5.0 + +* Added HasFocus function to the View interface + # v0.4.0 * Added SetTitle and SetTitleColor function to the Session interface diff --git a/app_scripts.js b/app_scripts.js index 86f1742..89f82e0 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -615,12 +615,13 @@ function selectDropDownListItem(elementId, number) { function listItemClickEvent(element, event) { event.stopPropagation(); + var selected = false; if (element.classList) { - selected = (element.classList.contains("ruiListItemFocused") || element.classList.contains("ruiListItemSelected")); - } else { - selected = element.className.indexOf("ruiListItemFocused") >= 0 || element.className.indexOf("ruiListItemSelected") >= 0; - } + const focusStyle = getListFocusedItemStyle(element); + const blurStyle = getListSelectedItemStyle(element); + selected = (element.classList.contains(focusStyle) || element.classList.contains(blurStyle)); + } var list = element.parentNode.parentNode if (list) { @@ -640,18 +641,27 @@ function getListItemNumber(itemId) { } } +function getStyleAttribute(element, attr, defValue) { + var result = element.getAttribute(attr); + if (result) { + return result; + } + return defValue; +} + +function getListFocusedItemStyle(element) { + return getStyleAttribute(element, "data-focusitemstyle", "ruiListItemFocused"); +} + +function getListSelectedItemStyle(element) { + return getStyleAttribute(element, "data-bluritemstyle", "ruiListItemSelected"); +} + function selectListItem(element, item, needSendMessage) { var currentId = element.getAttribute("data-current"); var message; - var focusStyle = element.getAttribute("data-focusitemstyle"); - var blurStyle = element.getAttribute("data-bluritemstyle"); - - if (!focusStyle) { - focusStyle = "ruiListItemFocused" - } - if (!blurStyle) { - blurStyle = "ruiListItemSelected" - } + const focusStyle = getListFocusedItemStyle(element); + const blurStyle = getListSelectedItemStyle(element); if (currentId) { var current = document.getElementById(currentId); @@ -801,24 +811,29 @@ function findBottomListItem(list, x, y) { return result } -function listViewKeyDownEvent(element, event) { - var key; +function getKey(event) { if (event.key) { - key = event.key; - } else if (event.keyCode) { + return event.key; + } + + if (event.keyCode) { switch (event.keyCode) { - case 13: key = "Enter"; break; - case 32: key = " "; break; - case 33: key = "PageUp"; break; - case 34: key = "PageDown"; break; - case 35: key = "End"; break; - case 36: key = "Home"; break; - case 37: key = "ArrowLeft"; break; - case 38: key = "ArrowUp"; break; - case 39: key = "ArrowRight"; break; - case 40: key = "ArrowDown"; break; + case 13: return "Enter"; + case 32: return " "; + case 33: return "PageUp"; + case 34: return "PageDown"; + case 35: return "End"; + case 36: return "Home"; + case 37: return "ArrowLeft"; + case 38: return "ArrowUp"; + case 39: return "ArrowRight"; + case 40: return "ArrowDown"; } } +} + +function listViewKeyDownEvent(element, event) { + const key = getKey(event); if (key) { var currentId = element.getAttribute("data-current"); var current @@ -885,21 +900,10 @@ function listViewFocusEvent(element, event) { if (currentId) { var current = document.getElementById(currentId); if (current) { - var focusStyle = element.getAttribute("data-focusitemstyle"); - var blurStyle = element.getAttribute("data-bluritemstyle"); - if (!focusStyle) { - focusStyle = "ruiListItemFocused" - } - if (!blurStyle) { - blurStyle = "ruiListItemSelected" - } - if (current.classList) { - current.classList.remove(blurStyle); - current.classList.add(focusStyle); - } else { // IE < 10 - current.className = "ruiListItem " + focusStyle; - } + current.classList.remove(getListSelectedItemStyle(element)); + current.classList.add(getListFocusedItemStyle(element)); + } } } } @@ -909,20 +913,9 @@ function listViewBlurEvent(element, event) { if (currentId) { var current = document.getElementById(currentId); if (current) { - var focusStyle = element.getAttribute("data-focusitemstyle"); - var blurStyle = element.getAttribute("data-bluritemstyle"); - if (!focusStyle) { - focusStyle = "ruiListItemFocused" - } - if (!blurStyle) { - blurStyle = "ruiListItemSelected" - } - if (current.classList) { - current.classList.remove(focusStyle); - current.classList.add(blurStyle); - } else { // IE < 10 - current.className = "ruiListItem " + blurStyle; + current.classList.remove(getListFocusedItemStyle(element)); + current.classList.add(getListSelectedItemStyle(element)); } } } @@ -1372,4 +1365,388 @@ function setTitleColor(color) { function detailsEvent(element) { sendMessage("details-open{session=" + sessionID + ",id=" + element.id + ",open=" + (element.open ? "1}" : "0}")); +} + +function getTableFocusedItemStyle(element) { + return getStyleAttribute(element, "data-focusitemstyle", "ruiCurrentTableCellFocused"); +} + +function getTableSelectedItemStyle(element) { + return getStyleAttribute(element, "data-bluritemstyle", "ruiCurrentTableCell"); +} + +function tableViewFocusEvent(element, event) { + var currentId = element.getAttribute("data-current"); + if (currentId) { + var current = document.getElementById(currentId); + if (current) { + if (current.classList) { + current.classList.remove(getTableSelectedItemStyle(element)); + current.classList.add(getTableFocusedItemStyle(element)); + } + } + } +} + +function tableViewBlurEvent(element, event) { + var currentId = element.getAttribute("data-current"); + if (currentId) { + var current = document.getElementById(currentId); + if (current && current.classList) { + current.classList.remove(getTableFocusedItemStyle(element)); + current.classList.add(getTableSelectedItemStyle(element)); + } + } +} + +function setTableCellCursor(element, row, column) { + const cellID = element.id + "-" + row + "-" + column; + var cell = document.getElementById(cellID); + if (!cell) { + return false; + } + + const focusStyle = getTableFocusedItemStyle(element); + const oldCellID = element.getAttribute("data-current"); + if (oldCellID) { + const oldCell = document.getElementById(oldCellID); + if (oldCell && oldCell.classList) { + oldCell.classList.remove(focusStyle); + oldCell.classList.remove(getTableSelectedItemStyle(element)); + } + } + + cell.classList.add(focusStyle); + element.setAttribute("data-current", cellID); + + sendMessage("currentCell{session=" + sessionID + ",id=" + element.id + + ",row=" + row + ",column=" + column + "}"); + return true; +} + +function moveTableCellCursor(element, row, column, dr, dc) { + const rows = element.getAttribute("data-rows"); + if (!rows) { + return; + } + const columns = element.getAttribute("data-columns"); + if (!columns) { + return; + } + + const rowCount = parseInt(rows); + const columnCount = parseInt(columns); + + row += dr; + column += dc; + while (row >= 0 && row < rowCount && column >= 0 && column < columnCount) { + if (setTableCellCursor(element, row, column)) { + return; + } else if (dr == 0) { + var r2 = row - 1; + while (r2 >= 0) { + if (setTableCellCursor(element, r2, column)) { + return; + } + r2--; + } + } else if (dc == 0) { + var c2 = column - 1; + while (c2 >= 0) { + if (setTableCellCursor(element, row, c2)) { + return; + } + c2--; + } + } + row += dr; + column += dc; + } +} + +function tableViewCellKeyDownEvent(element, event) { + const key = getKey(event); + if (key) { + const currentId = element.getAttribute("data-current"); + if (currentId) { + const elements = currentId.split("-"); + if (elements.length >= 3) { + const row = parseInt(elements[1], 10) + const column = parseInt(elements[2], 10) + + switch (key) { + case " ": + case "Enter": + sendMessage("cellClick{session=" + sessionID + ",id=" + element.id + + ",row=" + row + ",column=" + column + "}"); + break; + + case "ArrowLeft": + moveTableCellCursor(element, row, column, 0, -1) + break; + + case "ArrowRight": + moveTableCellCursor(element, row, column, 0, 1) + break; + + case "ArrowDown": + moveTableCellCursor(element, row, column, 1, 0) + break; + + case "ArrowUp": + moveTableCellCursor(element, row, column, -1, 0) + break; + + case "Home": + // TODO + break; + + case "End": + /*var newRow = rowCount-1; + while (newRow > row) { + if (setTableRowCursor(element, newRow)) { + break; + } + newRow--; + }*/ + // TODO + break; + + case "PageUp": + // TODO + break; + + case "PageDown": + // TODO + break; + + default: + return; + } + + event.stopPropagation(); + event.preventDefault(); + return; + } + } + + switch (key) { + case "ArrowLeft": + case "ArrowRight": + case "ArrowDown": + case "ArrowUp": + case "Home": + case "End": + case "PageUp": + case "PageDown": + const rows = element.getAttribute("data-rows"); + const columns = element.getAttribute("data-columns"); + if (rows && columns) { + const rowCount = parseInt(rows); + const columnCount = parseInt(rows); + row = 0; + while (row < rowCount) { + column = 0; + while (columns < columnCount) { + if (setTableCellCursor(element, row, column)) { + event.stopPropagation(); + event.preventDefault(); + return; + } + column++; + } + row++; + } + } + break; + + default: + return; + } + + } + + event.stopPropagation(); + event.preventDefault(); +} + +function setTableRowCursor(element, row) { + const tableRowID = element.id + "-" + row; + var tableRow = document.getElementById(tableRowID); + if (!tableRow) { + return false; + } + + const focusStyle = getTableFocusedItemStyle(element); + const oldRowID = element.getAttribute("data-current"); + if (oldRowID) { + const oldRow = document.getElementById(oldRowID); + if (oldRow && oldRow.classList) { + oldRow.classList.remove(focusStyle); + oldRow.classList.remove(getTableSelectedItemStyle(element)); + + } + } + + tableRow.classList.add(focusStyle); + element.setAttribute("data-current", tableRowID); + + sendMessage("currentRow{session=" + sessionID + ",id=" + element.id + ",row=" + row + "}"); + return true; +} + +function moveTableRowCursor(element, row, dr) { + const rows = element.getAttribute("data-rows"); + if (!rows) { + return; + } + + const rowCount = parseInt(rows); + row += dr; + while (row >= 0 && row < rowCount) { + if (setTableRowCursor(element, row)) { + return; + } + row += dr; + } +} + +function tableViewRowKeyDownEvent(element, event) { + const key = getKey(event); + if (key) { + const currentId = element.getAttribute("data-current"); + if (currentId) { + const elements = currentId.split("-"); + if (elements.length >= 2) { + const row = parseInt(elements[1], 10); + switch (key) { + case " ": + case "Enter": + sendMessage("rowClick{session=" + sessionID + ",id=" + element.id + ",row=" + row + "}"); + break; + + case "ArrowDown": + moveTableRowCursor(element, row, 1) + break; + + case "ArrowUp": + moveTableRowCursor(element, row, -1) + break; + + case "Home": + var newRow = 0; + while (newRow < row) { + if (setTableRowCursor(element, newRow)) { + break; + } + newRow++; + } + break; + + case "End": + var newRow = rowCount-1; + while (newRow > row) { + if (setTableRowCursor(element, newRow)) { + break; + } + newRow--; + } + break; + + case "PageUp": + // TODO + break; + + case "PageDown": + // TODO + break; + + default: + return; + } + event.stopPropagation(); + event.preventDefault(); + return; + } + } + + switch (key) { + case "ArrowLeft": + case "ArrowRight": + case "ArrowDown": + case "ArrowUp": + case "Home": + case "End": + case "PageUp": + case "PageDown": + const rows = element.getAttribute("data-rows"); + if (rows) { + const rowCount = parseInt(rows); + row = 0; + while (row < rowCount) { + if (setTableRowCursor(element, row)) { + break; + } + row++; + } + } + break; + + default: + return; + } + } + + event.stopPropagation(); + event.preventDefault(); +} + +function tableCellClickEvent(element, event) { + event.preventDefault(); + + const elements = element.id.split("-"); + if (elements.length < 3) { + return + } + + const tableID = elements[0]; + const row = parseInt(elements[1], 10); + const column = parseInt(elements[2], 10); + const table = document.getElementById(tableID); + if (table) { + const selection = table.getAttribute("data-selection"); + if (selection == "cell") { + const currentID = table.getAttribute("data-current"); + if (!currentID || currentID != element.ID) { + setTableCellCursor(table, row, column) + } + } + } + + sendMessage("cellClick{session=" + sessionID + ",id=" + tableID + + ",row=" + row + ",column=" + column + "}"); +} + +function tableRowClickEvent(element, event) { + event.preventDefault(); + + const elements = element.id.split("-"); + if (elements.length < 2) { + return + } + + const tableID = elements[0]; + const row = parseInt(elements[1], 10); + const table = document.getElementById(tableID); + if (table) { + const selection = table.getAttribute("data-selection"); + if (selection == "cell") { + const currentID = table.getAttribute("data-current"); + if (!currentID || currentID != element.ID) { + setTableRowCursor(table, row) + } + } + } + + sendMessage("rowClick{session=" + sessionID + ",id=" + tableID + ",row=" + row + "}"); } \ No newline at end of file diff --git a/customView.go b/customView.go index aed1499..3c31d45 100644 --- a/customView.go +++ b/customView.go @@ -148,11 +148,15 @@ func (customView *CustomViewData) Scroll() Frame { return customView.superView.Scroll() } +func (customView *CustomViewData) HasFocus() bool { + return customView.superView.HasFocus() +} + func (customView *CustomViewData) onResize(self View, x, y, width, height float64) { customView.superView.onResize(customView.superView, x, y, width, height) } -func (customView *CustomViewData) onItemResize(self View, index int, x, y, width, height float64) { +func (customView *CustomViewData) onItemResize(self View, index string, x, y, width, height float64) { customView.superView.onItemResize(customView.superView, index, x, y, width, height) } diff --git a/datePicker.go b/datePicker.go index 129b32b..f2e379f 100644 --- a/datePicker.go +++ b/datePicker.go @@ -44,6 +44,10 @@ func (picker *datePickerData) Init(session Session) { picker.dateChangedListeners = []func(DatePicker, time.Time){} } +func (picker *datePickerData) Focusable() bool { + return true +} + func (picker *datePickerData) normalizeTag(tag string) string { tag = strings.ToLower(tag) switch tag { diff --git a/defaultTheme.rui b/defaultTheme.rui index 23fd6f2..1524a89 100644 --- a/defaultTheme.rui +++ b/defaultTheme.rui @@ -213,24 +213,32 @@ theme { text-color = @ruiPopupTextColor, radius = 4px, shadow = _{spread-radius=4px, blur=16px, color=#80808080}, - } + }, ruiPopupTitle { background-color = @ruiPopupTitleColor, text-color = @ruiPopupTitleTextColor, min-height = 24px, - } + }, ruiMessageText { padding-left = 64px, padding-right = 64px, padding-top = 32px, padding-bottom = 32px, - } + }, ruiPopupMenuItem { padding-top = 4px, padding-bottom = 4px, padding-left = 8px, padding-right = 8px, - } + }, + ruiCurrentTableCell { + background-color=@ruiSelectedColor, + text-color=@ruiSelectedTextColor, + }, + ruiCurrentTableCellFocused { + background-color=@ruiHighlightColor, + text-color=@ruiHighlightTextColor, + }, ], } diff --git a/demo/tableDemo.go b/demo/tableDemo.go index 728d797..2e23c1a 100644 --- a/demo/tableDemo.go +++ b/demo/tableDemo.go @@ -32,6 +32,8 @@ GridLayout { DropDownList { row = 5, column = 1, id = tableFootStyle, current = 0, items = ["none", "tableFoot1", "rui.Params"]}, Checkbox { row = 6, column = 0:1, id = tableRowStyle, content = "Row style" }, Checkbox { row = 7, column = 0:1, id = tableColumnStyle, content = "Column style" }, + TextView { row = 8, text = "Selection mode" }, + DropDownList { row = 8, column = 1, id = tableSelectionMode, current = 0, items = ["none", "cell", "row"]}, ] } ] @@ -93,6 +95,10 @@ func createTableViewDemo(session rui.Session) rui.View { } } + rui.Set(view, "tableSelectionMode", rui.DropDownEvent, func(list rui.DropDownList, number int) { + rui.Set(view, "demoTableView1", rui.SelectionMode, number) + }) + rui.Set(view, "tableCellGap", rui.DropDownEvent, func(list rui.DropDownList, number int) { if number == 0 { rui.Set(view, "demoTableView1", rui.Gap, rui.Px(0)) diff --git a/dropDownList.go b/dropDownList.go index 8008226..8b170cf 100644 --- a/dropDownList.go +++ b/dropDownList.go @@ -39,6 +39,10 @@ func (list *dropDownListData) Init(session Session) { list.dropDownListener = []func(DropDownList, int){} } +func (list *dropDownListData) Focusable() bool { + return true +} + func (list *dropDownListData) Remove(tag string) { list.remove(strings.ToLower(tag)) } diff --git a/editView.go b/editView.go index 959a0ff..8c9bd19 100644 --- a/editView.go +++ b/editView.go @@ -63,6 +63,10 @@ func (edit *editViewData) Init(session Session) { edit.tag = "EditView" } +func (edit *editViewData) Focusable() bool { + return true +} + func (edit *editViewData) normalizeTag(tag string) string { tag = strings.ToLower(tag) switch tag { diff --git a/filePicker.go b/filePicker.go index 5faac07..8864d3b 100644 --- a/filePicker.go +++ b/filePicker.go @@ -89,6 +89,10 @@ func (picker *filePickerData) Init(session Session) { picker.fileSelectedListeners = []func(FilePicker, []FileInfo){} } +func (picker *filePickerData) Focusable() bool { + return true +} + func (picker *filePickerData) Files() []FileInfo { return picker.files } diff --git a/focusEvents.go b/focusEvents.go index 9bb041e..6a52c07 100644 --- a/focusEvents.go +++ b/focusEvents.go @@ -140,11 +140,9 @@ func getFocusListeners(view View, subviewID string, tag string) []func(View) { } func focusEventsHtml(view View, buffer *strings.Builder) { - for tag, js := range focusEvents { - if value := view.getRaw(tag); value != nil { - if listeners, ok := value.([]func(View)); ok && len(listeners) > 0 { - buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `) - } + if view.Focusable() { + for _, js := range focusEvents { + buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `) } } } diff --git a/listView.go b/listView.go index 4353526..28ba149 100644 --- a/listView.go +++ b/listView.go @@ -1083,14 +1083,12 @@ func (listView *listViewData) htmlSubviews(self View, buffer *strings.Builder) { func (listView *listViewData) handleCommand(self View, command string, data DataObject) bool { switch command { case "itemSelected": - if text, ok := data.PropertyValue(`number`); ok { - if number, err := strconv.Atoi(text); err == nil { - listView.properties[Current] = number - for _, listener := range listView.selectedListeners { - listener(listView, number) - } - listView.propertyChangedEvent(Current) + if number, ok := dataIntProperty(data, `number`); ok { + listView.properties[Current] = number + for _, listener := range listView.selectedListeners { + listener(listView, number) } + listView.propertyChangedEvent(Current) } case "itemUnselected": @@ -1162,9 +1160,14 @@ func (listView *listViewData) onItemClick() { } } -func (listView *listViewData) onItemResize(self View, index int, x, y, width, height float64) { - if index >= 0 && index < len(listView.itemFrame) { - listView.itemFrame[index] = Frame{Left: x, Top: y, Width: width, Height: height} +func (listView *listViewData) onItemResize(self View, index string, x, y, width, height float64) { + n, err := strconv.Atoi(index) + if err != nil { + ErrorLog(err.Error()) + } else if n >= 0 && n < len(listView.itemFrame) { + listView.itemFrame[n] = Frame{Left: x, Top: y, Width: width, Height: height} + } else { + ErrorLogF(`Invalid ListView item index: %d`, n) } } diff --git a/mediaPlayer.go b/mediaPlayer.go index 4c2534c..7493f93 100644 --- a/mediaPlayer.go +++ b/mediaPlayer.go @@ -168,6 +168,10 @@ func (player *mediaPlayerData) Init(session Session) { player.tag = "MediaPlayer" } +func (player *mediaPlayerData) Focusable() bool { + return true +} + func (player *mediaPlayerData) Remove(tag string) { player.remove(strings.ToLower(tag)) } diff --git a/numberPicker.go b/numberPicker.go index 5f5bf58..c126d99 100644 --- a/numberPicker.go +++ b/numberPicker.go @@ -50,6 +50,10 @@ func (picker *numberPickerData) Init(session Session) { picker.numberChangedListeners = []func(NumberPicker, float64){} } +func (picker *numberPickerData) Focusable() bool { + return true +} + func (picker *numberPickerData) normalizeTag(tag string) string { tag = strings.ToLower(tag) switch tag { diff --git a/resizeEvent.go b/resizeEvent.go index 070dc6a..f00ab9c 100644 --- a/resizeEvent.go +++ b/resizeEvent.go @@ -18,7 +18,7 @@ func (view *viewData) onResize(self View, x, y, width, height float64) { } } -func (view *viewData) onItemResize(self View, index int, x, y, width, height float64) { +func (view *viewData) onItemResize(self View, index string, x, y, width, height float64) { } func (view *viewData) setFrameListener(tag string, value interface{}) bool { diff --git a/session.go b/session.go index e015baf..505a78d 100644 --- a/session.go +++ b/session.go @@ -378,14 +378,10 @@ func (session *sessionData) handleResize(data DataObject) { } if viewID, ok := obj.PropertyValue("id"); ok { if n := strings.IndexRune(viewID, '-'); n > 0 { - if index, err := strconv.Atoi(viewID[n+1:]); err == nil { - if view := session.viewByHTMLID(viewID[:n]); view != nil { - view.onItemResize(view, index, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height")) - } else { - ErrorLogF(`View with id == %s not found`, viewID[:n]) - } + if view := session.viewByHTMLID(viewID[:n]); view != nil { + view.onItemResize(view, viewID[n+1:], getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height")) } else { - ErrorLogF(`Invalid view id == %s not found`, viewID) + ErrorLogF(`View with id == %s not found`, viewID[:n]) } } else if view := session.viewByHTMLID(viewID); view != nil { view.onResize(view, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height")) diff --git a/tableView.go b/tableView.go index 0c7a90f..933739b 100644 --- a/tableView.go +++ b/tableView.go @@ -214,11 +214,14 @@ type TableView interface { View ParanetView ReloadTableData() + CellFrame(row, column int) Frame + getCurrent() CellIndex } type tableViewData struct { viewData cellViews []View + cellFrame []Frame cellSelectedListener, cellClickedListener []func(TableView, int, int) rowSelectedListener, rowClickedListener []func(TableView, int) current CellIndex @@ -245,6 +248,7 @@ func (table *tableViewData) Init(session Session) { table.viewData.Init(session) table.tag = "TableView" table.cellViews = []View{} + table.cellFrame = []Frame{} table.cellSelectedListener = []func(TableView, int, int){} table.cellClickedListener = []func(TableView, int, int){} table.rowSelectedListener = []func(TableView, int){} @@ -270,6 +274,10 @@ func (table *tableViewData) normalizeTag(tag string) string { return tag } +func (table *tableViewData) Focusable() bool { + return GetSelectionMode(table, "") != NoneSelection +} + func (table *tableViewData) Get(tag string) interface{} { return table.get(table.normalizeTag(tag)) } @@ -312,6 +320,10 @@ func (table *tableViewData) remove(tag string) { table.current.Column = -1 table.propertyChanged(tag) + case SelectionMode: + table.viewData.remove(tag) + table.propertyChanged(tag) + default: table.viewData.remove(tag) } @@ -486,14 +498,51 @@ func (table *tableViewData) set(tag string, value interface{}) bool { } case Current: - switch GetSelectionMode(table, "") { - case NoneSelection: + switch value := value.(type) { + case int: + table.current.Row = value + table.current.Column = -1 - case CellSelection: - // TODO + case CellIndex: + table.current = value - case RowSelection: - // TODO + case DataObject: + if row, ok := dataIntProperty(value, "row"); ok { + table.current.Row = row + } + if column, ok := dataIntProperty(value, "column"); ok { + table.current.Column = column + } + + case string: + if strings.Contains(value, ",") { + if values := strings.Split(value, ","); len(values) == 2 { + var n = []int{0, 0} + for i := 0; i < 2; i++ { + var err error + if n[i], err = strconv.Atoi(values[i]); err != nil { + ErrorLog(err.Error()) + return false + } + } + table.current.Row = n[0] + table.current.Column = n[1] + } else { + notCompatibleType(tag, value) + } + } else { + n, err := strconv.Atoi(value) + if err != nil { + ErrorLog(err.Error()) + return false + } + table.current.Row = n + table.current.Column = -1 + } + + default: + notCompatibleType(tag, value) + return false } default: @@ -524,11 +573,75 @@ func (table *tableViewData) propertyChanged(tag string) { updateCSSProperty(htmlID, "border-spacing", gap.cssString("0"), session) updateCSSProperty(htmlID, "border-collapse", "separate", session) } + + case SelectionMode: + htmlID := table.htmlID() + session := table.Session() + + switch GetSelectionMode(table, "") { + case CellSelection: + updateProperty(htmlID, "tabindex", "0", session) + updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)", session) + updateProperty(htmlID, "onblur", "tableViewBlurEvent(this, event)", session) + updateProperty(htmlID, "data-selection", "cell", session) + updateProperty(htmlID, "data-focusitemstyle", table.currentStyle(), session) + updateProperty(htmlID, "data-bluritemstyle", table.currentInactiveStyle(), session) + + if table.current.Row >= 0 && table.current.Column >= 0 { + updateProperty(htmlID, "data-current", table.cellID(table.current.Row, table.current.Column), session) + } else { + removeProperty(htmlID, "data-current", session) + } + updateProperty(htmlID, "onkeydown", "tableViewCellKeyDownEvent(this, event)", session) + + case RowSelection: + updateProperty(htmlID, "tabindex", "0", session) + updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)", session) + updateProperty(htmlID, "onblur", "tableViewBlurEvent(this, event)", session) + updateProperty(htmlID, "data-selection", "cell", session) + updateProperty(htmlID, "data-focusitemstyle", table.currentStyle(), session) + updateProperty(htmlID, "data-bluritemstyle", table.currentInactiveStyle(), session) + + if table.current.Row >= 0 { + updateProperty(htmlID, "data-current", table.rowID(table.current.Row), session) + } else { + removeProperty(htmlID, "data-current", session) + } + updateProperty(htmlID, "onkeydown", "tableViewRowKeyDownEvent(this, event)", session) + + default: // NoneSelection + for _, prop := range []string{"tabindex", "data-current", "onfocus", "onblur", "onkeydown", "data-selection"} { + removeProperty(htmlID, prop, session) + } + } + updateInnerHTML(htmlID, session) } } table.propertyChangedEvent(tag) } +func (table *tableViewData) currentStyle() string { + if value := table.getRaw(CurrentStyle); value != nil { + if style, ok := value.(string); ok { + if style, ok = table.session.resolveConstants(style); ok { + return style + } + } + } + return "ruiCurrentTableCellFocused" +} + +func (table *tableViewData) currentInactiveStyle() string { + if value := table.getRaw(CurrentInactiveStyle); value != nil { + if style, ok := value.(string); ok { + if style, ok = table.session.resolveConstants(style); ok { + return style + } + } + } + return "ruiCurrentTableCell" +} + func (table *tableViewData) valueToCellListeners(value interface{}) []func(TableView, int, int) { if value == nil { return []func(TableView, int, int){} @@ -643,16 +756,69 @@ func (table *tableViewData) htmlTag() string { return "table" } -func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { - table.cellViews = []View{} +func (table *tableViewData) rowID(index int) string { + return fmt.Sprintf("%s-%d", table.htmlID(), index) +} - content := table.getRaw(Content) - if content == nil { - return +func (table *tableViewData) cellID(row, column int) string { + return fmt.Sprintf("%s-%d-%d", table.htmlID(), row, column) +} + +func (table *tableViewData) htmlProperties(self View, buffer *strings.Builder) { + + if content := table.content(); content != nil { + buffer.WriteString(` data-rows="`) + buffer.WriteString(strconv.Itoa(content.RowCount())) + buffer.WriteString(`" data-columns="`) + buffer.WriteString(strconv.Itoa(content.ColumnCount())) + buffer.WriteRune('"') } - adapter, ok := content.(TableAdapter) - if !ok { + if selectionMode := GetSelectionMode(table, ""); selectionMode != NoneSelection { + buffer.WriteString(` onfocus="tableViewFocusEvent(this, event)" onblur="tableViewBlurEvent(this, event)" data-focusitemstyle="`) + buffer.WriteString(table.currentStyle()) + buffer.WriteString(`" data-bluritemstyle="`) + buffer.WriteString(table.currentInactiveStyle()) + buffer.WriteRune('"') + + switch selectionMode { + case RowSelection: + buffer.WriteString(` data-selection="row" onkeydown="tableViewRowKeyDownEvent(this, event)"`) + if table.current.Row >= 0 { + buffer.WriteString(` data-current="`) + buffer.WriteString(table.rowID(table.current.Row)) + buffer.WriteRune('"') + } + + case CellSelection: + buffer.WriteString(` data-selection="cell" onkeydown="tableViewCellKeyDownEvent(this, event)"`) + if table.current.Row >= 0 && table.current.Column >= 0 { + buffer.WriteString(` data-current="`) + buffer.WriteString(table.cellID(table.current.Row, table.current.Column)) + buffer.WriteRune('"') + } + } + } + + table.viewData.htmlProperties(self, buffer) +} + +func (table *tableViewData) content() TableAdapter { + if content := table.getRaw(Content); content != nil { + if adapter, ok := content.(TableAdapter); ok { + return adapter + } + } + + return nil +} + +func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { + table.cellViews = []View{} + table.cellFrame = []Frame{} + + adapter := table.content() + if adapter == nil { return } @@ -662,10 +828,12 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { return } + table.cellFrame = make([]Frame, rowCount*columnCount) + rowStyle := table.getRowStyle() var cellStyle1 TableCellStyle = nil - if style, ok := content.(TableCellStyle); ok { + if style, ok := adapter.(TableCellStyle); ok { cellStyle1 = style } @@ -691,6 +859,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { view.Init(session) ignorCells := []struct{ row, column int }{} + selectionMode := GetSelectionMode(table, "") tableCSS := func(startRow, endRow int, cellTag string, cellBorder BorderProperty, cellPadding BoundsProperty) { for row := startRow; row < endRow; row++ { @@ -706,14 +875,31 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { } } - if cssBuilder.buffer.Len() > 0 { - buffer.WriteString(``) - } else { - buffer.WriteString("") + buffer.WriteString(` 0 { + buffer.WriteString(` style="`) + buffer.WriteString(cssBuilder.buffer.String()) + buffer.WriteString(`"`) + } + buffer.WriteString(">") + for column := 0; column < columnCount; column++ { ignore := false for _, cell := range ignorCells { @@ -748,7 +934,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { return value case string: - if value, ok = session.resolveConstants(value); ok { + if value, ok := session.resolveConstants(value); ok { if n, err := strconv.Atoi(value); err == nil { return n } @@ -780,6 +966,23 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { buffer.WriteRune('<') buffer.WriteString(cellTag) + buffer.WriteString(` id="`) + buffer.WriteString(table.cellID(row, column)) + buffer.WriteString(`" class="ruiView`) + + if selectionMode == CellSelection && row == table.current.Row && column == table.current.Column { + buffer.WriteRune(' ') + if table.HasFocus() { + buffer.WriteString(table.currentStyle()) + } else { + buffer.WriteString(table.currentInactiveStyle()) + } + } + buffer.WriteRune('"') + + if selectionMode == CellSelection { + buffer.WriteString(` onclick="tableCellClickEvent(this, event)"`) + } if columnSpan > 1 { buffer.WriteString(` colspan="`) @@ -1087,6 +1290,10 @@ func (table *tableViewData) getCellBorder() BorderProperty { return nil } +func (table *tableViewData) getCurrent() CellIndex { + return table.current +} + func (table *tableViewData) cssStyle(self View, builder cssBuilder) { table.viewData.cssViewStyle(builder, table.Session()) @@ -1101,30 +1308,93 @@ func (table *tableViewData) cssStyle(self View, builder cssBuilder) { } func (table *tableViewData) ReloadTableData() { + if content := table.content(); content != nil { + updateProperty(table.htmlID(), "data-rows", strconv.Itoa(content.RowCount()), table.Session()) + updateProperty(table.htmlID(), "data-columns", strconv.Itoa(content.ColumnCount()), table.Session()) + } updateInnerHTML(table.htmlID(), table.Session()) } -func (cell *tableCellView) Set(tag string, value interface{}) bool { - return cell.set(strings.ToLower(tag), value) +func (table *tableViewData) onItemResize(self View, index string, x, y, width, height float64) { + if n := strings.IndexRune(index, '-'); n > 0 { + if row, err := strconv.Atoi(index[:n]); err == nil { + if column, err := strconv.Atoi(index[n+1:]); err == nil { + if content := table.content(); content != nil { + i := row*content.ColumnCount() + column + if i < len(table.cellFrame) { + table.cellFrame[i].Left = x + table.cellFrame[i].Top = y + table.cellFrame[i].Width = width + table.cellFrame[i].Height = height + } + } + } else { + ErrorLog(err.Error()) + } + } else { + ErrorLog(err.Error()) + } + } else { + ErrorLogF(`Invalid cell index: %s`, index) + } } -func (cell *tableCellView) set(tag string, value interface{}) bool { - switch tag { - case VerticalAlign: - tag = TableVerticalAlign - } - return cell.viewData.set(tag, value) -} - -func (cell *tableCellView) cssStyle(self View, builder cssBuilder) { - session := cell.Session() - cell.viewData.cssViewStyle(builder, session) - - if value, ok := enumProperty(cell, TableVerticalAlign, session, 0); ok { - builder.add("vertical-align", enumProperties[TableVerticalAlign].values[value]) +func (table *tableViewData) CellFrame(row, column int) Frame { + if content := table.content(); content != nil { + i := row*content.ColumnCount() + column + if i < len(table.cellFrame) { + return table.cellFrame[i] + } } + return Frame{} } func (table *tableViewData) Views() []View { return table.cellViews } + +func (table *tableViewData) handleCommand(self View, command string, data DataObject) bool { + switch command { + case "currentRow": + if row, ok := dataIntProperty(data, "row"); ok && row != table.current.Row { + table.current.Row = row + for _, listener := range table.rowSelectedListener { + listener(table, row) + } + } + + case "currentCell": + if row, ok := dataIntProperty(data, "row"); ok { + if column, ok := dataIntProperty(data, "column"); ok { + if row != table.current.Row || column != table.current.Column { + table.current.Row = row + table.current.Column = column + for _, listener := range table.cellSelectedListener { + listener(table, row, column) + } + } + } + } + + case "rowClick": + if row, ok := dataIntProperty(data, "row"); ok { + for _, listener := range table.rowClickedListener { + listener(table, row) + } + } + + case "cellClick": + if row, ok := dataIntProperty(data, "row"); ok { + if column, ok := dataIntProperty(data, "column"); ok { + for _, listener := range table.cellClickedListener { + listener(table, row, column) + } + } + } + + default: + return table.viewData.handleCommand(self, command, data) + } + + return true +} diff --git a/tableViewUtils.go b/tableViewUtils.go index 6b84aeb..3d079da 100644 --- a/tableViewUtils.go +++ b/tableViewUtils.go @@ -1,5 +1,28 @@ package rui +import "strings" + +func (cell *tableCellView) Set(tag string, value interface{}) bool { + return cell.set(strings.ToLower(tag), value) +} + +func (cell *tableCellView) set(tag string, value interface{}) bool { + switch tag { + case VerticalAlign: + tag = TableVerticalAlign + } + return cell.viewData.set(tag, value) +} + +func (cell *tableCellView) cssStyle(self View, builder cssBuilder) { + session := cell.Session() + cell.viewData.cssViewStyle(builder, session) + + if value, ok := enumProperty(cell, TableVerticalAlign, session, 0); ok { + builder.add("vertical-align", enumProperties[TableVerticalAlign].values[value]) + } +} + // GetSelectionMode returns the mode of the TableView elements selection. // Valid values are NoneSelection (0), CellSelection (1), and RowSelection (2). // If the second argument (subviewID) is "" then a value from the first argument (view) is returned. @@ -15,6 +38,42 @@ func GetSelectionMode(view View, subviewID string) int { return NoneSelection } +// GetSelectionMode returns the index of the TableView selected row. +// If there is no selected row, then a value less than 0 are returned. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetCurrentTableRow(view View, subviewID string) int { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + + if view != nil { + if selectionMode := GetSelectionMode(view, ""); selectionMode != NoneSelection { + if tableView, ok := view.(TableView); ok { + return tableView.getCurrent().Row + } + } + } + return -1 +} + +// GetCurrentTableCell returns the row and column index of the TableView selected cell. +// If there is no selected cell, then a value of the row and column index less than 0 is returned. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetCurrentTableCell(view View, subviewID string) CellIndex { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + + if view != nil { + if selectionMode := GetSelectionMode(view, ""); selectionMode != NoneSelection { + if tableView, ok := view.(TableView); ok { + return tableView.getCurrent() + } + } + } + return CellIndex{Row: -1, Column: -1} +} + // GetTableCellClickedListeners returns listeners of event which occurs when the user clicks on a table cell. // If there are no listeners then the empty list is returned. // If the second argument (subviewID) is "" then a value from the first argument (view) is returned. diff --git a/timePicker.go b/timePicker.go index 42fd616..1c2fa5f 100644 --- a/timePicker.go +++ b/timePicker.go @@ -44,6 +44,10 @@ func (picker *timePickerData) Init(session Session) { picker.timeChangedListeners = []func(TimePicker, time.Time){} } +func (picker *timePickerData) Focusable() bool { + return true +} + func (picker *timePickerData) normalizeTag(tag string) string { tag = strings.ToLower(tag) switch tag { diff --git a/view.go b/view.go index 2c57464..f9ce65e 100644 --- a/view.go +++ b/view.go @@ -58,6 +58,8 @@ type View interface { SetAnimated(tag string, value interface{}, animation Animation) bool // SetChangeListener set the function to track the change of the View property SetChangeListener(tag string, listener func(View, string)) + // HasFocus returns 'true' if the view has focus + HasFocus() bool handleCommand(self View, command string, data DataObject) bool htmlClass(disabled bool) string @@ -73,7 +75,7 @@ type View interface { getTransitions() Params onResize(self View, x, y, width, height float64) - onItemResize(self View, index int, x, y, width, height float64) + onItemResize(self View, index string, x, y, width, height float64) setNoResizeEvent() isNoResizeEvent() bool setScroll(x, y, width, height float64) @@ -95,6 +97,7 @@ type viewData struct { scroll Frame noResizeEvent bool created bool + hasFocus bool //animation map[string]AnimationEndListener } @@ -737,7 +740,14 @@ func (view *viewData) handleCommand(self View, command string, data DataObject) case TouchStart, TouchEnd, TouchMove, TouchCancel: handleTouchEvents(self, command, data) - case FocusEvent, LostFocusEvent: + case FocusEvent: + view.hasFocus = true + for _, listener := range getFocusListeners(view, "", command) { + listener(self) + } + + case LostFocusEvent: + view.hasFocus = false for _, listener := range getFocusListeners(view, "", command) { listener(self) } @@ -838,3 +848,7 @@ func (view *viewData) SetChangeListener(tag string, listener func(View, string)) view.changeListener[tag] = listener } } + +func (view *viewData) HasFocus() bool { + return view.hasFocus +} From 32b6182dbf200d7e18eae3c45212b0f9077e95f6 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Fri, 14 Jan 2022 18:06:10 -0500 Subject: [PATCH 03/14] Added "allow-selection" property to TableView --- app_scripts.js | 4 +-- demo/tableDemo.go | 66 +++++++++++++++++++++++++++++++++++++++++++++++ tableAdapter.go | 8 ++++++ tableView.go | 51 ++++++++++++++++++++++++++++++++++-- 4 files changed, 125 insertions(+), 4 deletions(-) diff --git a/app_scripts.js b/app_scripts.js index 89f82e0..729de78 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -1402,7 +1402,7 @@ function tableViewBlurEvent(element, event) { function setTableCellCursor(element, row, column) { const cellID = element.id + "-" + row + "-" + column; var cell = document.getElementById(cellID); - if (!cell) { + if (!cell || cell.getAttribute("data-disabled")) { return false; } @@ -1573,7 +1573,7 @@ function tableViewCellKeyDownEvent(element, event) { function setTableRowCursor(element, row) { const tableRowID = element.id + "-" + row; var tableRow = document.getElementById(tableRowID); - if (!tableRow) { + if (!tableRow || tableRow.getAttribute("data-disabled")) { return false; } diff --git a/demo/tableDemo.go b/demo/tableDemo.go index 2e23c1a..2da8de3 100644 --- a/demo/tableDemo.go +++ b/demo/tableDemo.go @@ -34,6 +34,8 @@ GridLayout { Checkbox { row = 7, column = 0:1, id = tableColumnStyle, content = "Column style" }, TextView { row = 8, text = "Selection mode" }, DropDownList { row = 8, column = 1, id = tableSelectionMode, current = 0, items = ["none", "cell", "row"]}, + Checkbox { row = 9, column = 0:1, id = tableDisableHead, content = "Disable head selection" }, + Checkbox { row = 10, column = 0:1, id = tableDisableFoot, content = "Disable foot selection" }, ] } ] @@ -42,6 +44,31 @@ GridLayout { } ` +type demoTableAllowSelection struct { + index []int +} + +func (allow *demoTableAllowSelection) AllowCellSelection(row, column int) bool { + return allow.AllowRowSelection(row) +} + +func (allow *demoTableAllowSelection) AllowRowSelection(row int) bool { + if allow.index != nil { + for _, index := range allow.index { + if index == row { + return false + } + } + } + return true +} + +func newDemoTableAllowSelection(index []int) *demoTableAllowSelection { + result := new(demoTableAllowSelection) + result.index = index + return result +} + func createTableViewDemo(session rui.Session) rui.View { view := rui.CreateViewFromText(session, tableViewDemoText) if view == nil { @@ -97,6 +124,45 @@ func createTableViewDemo(session rui.Session) rui.View { rui.Set(view, "tableSelectionMode", rui.DropDownEvent, func(list rui.DropDownList, number int) { rui.Set(view, "demoTableView1", rui.SelectionMode, number) + switch rui.GetCurrent(view, "tableSelectionMode") { + case rui.CellSelection: + // TODO + + case rui.RowSelection: + // TODO + } + }) + + rui.Set(view, "tableDisableHead", rui.CheckboxChangedEvent, func(checked bool) { + if checked { + if rui.IsCheckboxChecked(view, "tableDisableFoot") { + rui.Set(view, "demoTableView1", rui.AllowSelection, newDemoTableAllowSelection([]int{0, 1, 11})) + } else { + rui.Set(view, "demoTableView1", rui.AllowSelection, newDemoTableAllowSelection([]int{0, 1})) + } + } else { + if rui.IsCheckboxChecked(view, "tableDisableFoot") { + rui.Set(view, "demoTableView1", rui.AllowSelection, newDemoTableAllowSelection([]int{11})) + } else { + rui.Set(view, "demoTableView1", rui.AllowSelection, nil) + } + } + }) + + rui.Set(view, "tableDisableFoot", rui.CheckboxChangedEvent, func(checked bool) { + if checked { + if rui.IsCheckboxChecked(view, "tableDisableHead") { + rui.Set(view, "demoTableView1", rui.AllowSelection, newDemoTableAllowSelection([]int{0, 1, 11})) + } else { + rui.Set(view, "demoTableView1", rui.AllowSelection, newDemoTableAllowSelection([]int{11})) + } + } else { + if rui.IsCheckboxChecked(view, "tableDisableHead") { + rui.Set(view, "demoTableView1", rui.AllowSelection, newDemoTableAllowSelection([]int{0, 1})) + } else { + rui.Set(view, "demoTableView1", rui.AllowSelection, nil) + } + } }) rui.Set(view, "tableCellGap", rui.DropDownEvent, func(list rui.DropDownList, number int) { diff --git a/tableAdapter.go b/tableAdapter.go index a32c36a..ee52f20 100644 --- a/tableAdapter.go +++ b/tableAdapter.go @@ -18,6 +18,14 @@ type TableCellStyle interface { CellStyle(row, column int) Params } +type TableAllowCellSelection interface { + AllowCellSelection(row, column int) bool +} + +type TableAllowRowSelection interface { + AllowRowSelection(row int) bool +} + type SimpleTableAdapter interface { TableAdapter TableCellStyle diff --git a/tableView.go b/tableView.go index 933739b..3562fb7 100644 --- a/tableView.go +++ b/tableView.go @@ -196,6 +196,12 @@ const ( // The main listener format: func(TableView, int), where the second argument is the row number. TableRowSelectedEvent = "table-row-selected" + // AllowSelection is the constant for the "allow-selection" property tag. + // The "allow-selection" property sets the adapter which specifies styles of each table row. + // This property can be assigned or by an implementation of TableAllowCellSelection + // or TableAllowRowSelection interface. + AllowSelection = "allow-selection" + // NoneSelection is the value of "selection-mode" property: the selection is forbidden. NoneSelection = 0 // CellSelection is the value of "selection-mode" property: the selection of a single cell only is enabled. @@ -293,7 +299,7 @@ func (table *tableViewData) remove(tag string) { table.propertyChanged(tag) case Gap, CellBorder, CellPadding, RowStyle, ColumnStyle, CellStyle, - HeadHeight, HeadStyle, FootHeight, FootStyle: + HeadHeight, HeadStyle, FootHeight, FootStyle, AllowSelection: if _, ok := table.properties[tag]; ok { delete(table.properties, tag) table.propertyChanged(tag) @@ -497,6 +503,19 @@ func (table *tableViewData) set(tag string, value interface{}) bool { return false } + case AllowSelection: + switch value.(type) { + case TableAllowCellSelection: + table.properties[tag] = value + + case TableAllowRowSelection: + table.properties[tag] = value + + default: + notCompatibleType(tag, value) + return false + } + case Current: switch value := value.(type) { case int: @@ -559,7 +578,8 @@ func (table *tableViewData) propertyChanged(tag string) { case Content, RowStyle, ColumnStyle, CellStyle, CellPadding, CellBorder, HeadHeight, HeadStyle, FootHeight, FootStyle, CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft, - TableCellClickedEvent, TableCellSelectedEvent, TableRowClickedEvent, TableRowSelectedEvent: + TableCellClickedEvent, TableCellSelectedEvent, TableRowClickedEvent, + TableRowSelectedEvent, AllowSelection: table.ReloadTableData() case Gap: @@ -861,6 +881,26 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { ignorCells := []struct{ row, column int }{} selectionMode := GetSelectionMode(table, "") + var allowCellSelection TableAllowCellSelection = nil + if allow, ok := adapter.(TableAllowCellSelection); ok { + allowCellSelection = allow + } + if value := table.getRaw(AllowSelection); value != nil { + if style, ok := value.(TableAllowCellSelection); ok { + allowCellSelection = style + } + } + + var allowRowSelection TableAllowRowSelection = nil + if allow, ok := adapter.(TableAllowRowSelection); ok { + allowRowSelection = allow + } + if value := table.getRaw(AllowSelection); value != nil { + if style, ok := value.(TableAllowRowSelection); ok { + allowRowSelection = style + } + } + tableCSS := func(startRow, endRow int, cellTag string, cellBorder BorderProperty, cellPadding BoundsProperty) { for row := startRow; row < endRow; row++ { @@ -891,6 +931,10 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { } buffer.WriteString(` onclick="tableRowClickEvent(this, event)"`) + + if allowRowSelection != nil && !allowRowSelection.AllowRowSelection(row) { + buffer.WriteString(` data-disabled="1"`) + } } if cssBuilder.buffer.Len() > 0 { @@ -982,6 +1026,9 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { if selectionMode == CellSelection { buffer.WriteString(` onclick="tableCellClickEvent(this, event)"`) + if allowCellSelection != nil && !allowCellSelection.AllowCellSelection(row, column) { + buffer.WriteString(` data-disabled="1"`) + } } if columnSpan > 1 { From 09544fdf252e8c43ea85ecf66e5535c2c4e3108b Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Sun, 16 Jan 2022 12:25:45 -0500 Subject: [PATCH 04/14] Updated TableView Added selection scroll Fixed TableVerticalAlign property ListView bug fixing --- app_scripts.js | 223 ++++++++++++++++++++++++---------------------- demo/tableDemo.go | 6 ++ listView.go | 2 + tableAdapter.go | 55 ++++++++++++ tableView.go | 106 +++++++++++----------- tableViewUtils.go | 96 +++++++++++++++++--- 6 files changed, 315 insertions(+), 173 deletions(-) diff --git a/app_scripts.js b/app_scripts.js index 729de78..526f43b 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -668,8 +668,6 @@ function selectListItem(element, item, needSendMessage) { if (current) { if (current.classList) { current.classList.remove(focusStyle, blurStyle); - } else { // IE < 10 - current.className = "ruiListItem"; } if (sendMessage) { message = "itemUnselected{session=" + sessionID + ",id=" + element.id + "}"; @@ -681,14 +679,10 @@ function selectListItem(element, item, needSendMessage) { if (element === document.activeElement) { if (item.classList) { item.classList.add(focusStyle); - } else { // IE < 10 - item.className = "ruiListItem " + focusStyle } } else { if (item.classList) { item.classList.add(blurStyle); - } else { // IE < 10 - item.className = "ruiListItem " + blurStyle } } @@ -700,6 +694,12 @@ function selectListItem(element, item, needSendMessage) { } } + if (item.scrollIntoViewIfNeeded) { + item.scrollIntoViewIfNeeded() + } else { + item.scrollIntoView({block: "nearest", inline: "nearest"}); + } + /* var left = item.offsetLeft - element.offsetLeft; if (left < element.scrollLeft) { element.scrollLeft = left; @@ -718,7 +718,7 @@ function selectListItem(element, item, needSendMessage) { var bottom = top + item.offsetHeight if (bottom > element.scrollTop + element.clientHeight) { element.scrollTop = bottom - element.clientHeight; - } + }*/ } if (needSendMessage && message != undefined) { @@ -1418,6 +1418,11 @@ function setTableCellCursor(element, row, column) { cell.classList.add(focusStyle); element.setAttribute("data-current", cellID); + if (cell.scrollIntoViewIfNeeded) { + cell.scrollIntoViewIfNeeded() + } else { + cell.scrollIntoView({block: "nearest", inline: "nearest"}); + } sendMessage("currentCell{session=" + sessionID + ",id=" + element.id + ",row=" + row + ",column=" + column + "}"); @@ -1466,108 +1471,107 @@ function moveTableCellCursor(element, row, column, dr, dc) { function tableViewCellKeyDownEvent(element, event) { const key = getKey(event); - if (key) { - const currentId = element.getAttribute("data-current"); - if (currentId) { - const elements = currentId.split("-"); - if (elements.length >= 3) { - const row = parseInt(elements[1], 10) - const column = parseInt(elements[2], 10) - - switch (key) { - case " ": - case "Enter": - sendMessage("cellClick{session=" + sessionID + ",id=" + element.id + - ",row=" + row + ",column=" + column + "}"); - break; - - case "ArrowLeft": - moveTableCellCursor(element, row, column, 0, -1) - break; - - case "ArrowRight": - moveTableCellCursor(element, row, column, 0, 1) - break; - - case "ArrowDown": - moveTableCellCursor(element, row, column, 1, 0) - break; - - case "ArrowUp": - moveTableCellCursor(element, row, column, -1, 0) - break; - - case "Home": - // TODO - break; - - case "End": - /*var newRow = rowCount-1; - while (newRow > row) { - if (setTableRowCursor(element, newRow)) { - break; - } - newRow--; - }*/ - // TODO - break; - - case "PageUp": - // TODO - break; - - case "PageDown": - // TODO - break; - - default: - return; - } - - event.stopPropagation(); - event.preventDefault(); - return; - } - } - - switch (key) { - case "ArrowLeft": - case "ArrowRight": - case "ArrowDown": - case "ArrowUp": - case "Home": - case "End": - case "PageUp": - case "PageDown": - const rows = element.getAttribute("data-rows"); - const columns = element.getAttribute("data-columns"); - if (rows && columns) { - const rowCount = parseInt(rows); - const columnCount = parseInt(rows); - row = 0; - while (row < rowCount) { - column = 0; - while (columns < columnCount) { - if (setTableCellCursor(element, row, column)) { - event.stopPropagation(); - event.preventDefault(); - return; - } - column++; - } - row++; - } - } - break; - - default: - return; - } - + if (!key) { + return; } - event.stopPropagation(); - event.preventDefault(); + const currentId = element.getAttribute("data-current"); + if (!currentId || currentId == "") { + switch (key) { + case "ArrowLeft": + case "ArrowRight": + case "ArrowDown": + case "ArrowUp": + case "Home": + case "End": + case "PageUp": + case "PageDown": + const rows = element.getAttribute("data-rows"); + const columns = element.getAttribute("data-columns"); + if (rows && columns) { + const rowCount = parseInt(rows); + const columnCount = parseInt(rows); + row = 0; + while (row < rowCount) { + column = 0; + while (columns < columnCount) { + if (setTableCellCursor(element, row, column)) { + event.stopPropagation(); + event.preventDefault(); + return; + } + column++; + } + row++; + } + } + event.stopPropagation(); + event.preventDefault(); + break; + } + return; + } + + const elements = currentId.split("-"); + if (elements.length >= 3) { + const row = parseInt(elements[1], 10) + const column = parseInt(elements[2], 10) + + switch (key) { + case " ": + case "Enter": + sendMessage("cellClick{session=" + sessionID + ",id=" + element.id + + ",row=" + row + ",column=" + column + "}"); + break; + + case "ArrowLeft": + moveTableCellCursor(element, row, column, 0, -1) + break; + + case "ArrowRight": + moveTableCellCursor(element, row, column, 0, 1) + break; + + case "ArrowDown": + moveTableCellCursor(element, row, column, 1, 0) + break; + + case "ArrowUp": + moveTableCellCursor(element, row, column, -1, 0) + break; + + case "Home": + // TODO + break; + + case "End": + /*var newRow = rowCount-1; + while (newRow > row) { + if (setTableRowCursor(element, newRow)) { + break; + } + newRow--; + }*/ + // TODO + break; + + case "PageUp": + // TODO + break; + + case "PageDown": + // TODO + break; + + default: + return; + } + + event.stopPropagation(); + event.preventDefault(); + } else { + element.setAttribute("data-current", ""); + } } function setTableRowCursor(element, row) { @@ -1590,6 +1594,11 @@ function setTableRowCursor(element, row) { tableRow.classList.add(focusStyle); element.setAttribute("data-current", tableRowID); + if (tableRow.scrollIntoViewIfNeeded) { + tableRow.scrollIntoViewIfNeeded() + } else { + tableRow.scrollIntoView({block: "nearest", inline: "nearest"}); + } sendMessage("currentRow{session=" + sessionID + ",id=" + element.id + ",row=" + row + "}"); return true; diff --git a/demo/tableDemo.go b/demo/tableDemo.go index 2da8de3..c622c75 100644 --- a/demo/tableDemo.go +++ b/demo/tableDemo.go @@ -36,6 +36,8 @@ GridLayout { DropDownList { row = 8, column = 1, id = tableSelectionMode, current = 0, items = ["none", "cell", "row"]}, Checkbox { row = 9, column = 0:1, id = tableDisableHead, content = "Disable head selection" }, Checkbox { row = 10, column = 0:1, id = tableDisableFoot, content = "Disable foot selection" }, + TextView { row = 11, text = "Vertical align in cells" }, + DropDownList { row = 11, column = 1, id = tableVerticalAlign, current = 0, items = ["top", "bottom", "center", "baseline"]}, ] } ] @@ -274,5 +276,9 @@ func createTableViewDemo(session rui.Session) rui.View { } }) + rui.Set(view, "tableVerticalAlign", rui.DropDownEvent, func(list rui.DropDownList, number int) { + rui.Set(view, "demoTableView1", rui.TableVerticalAlign, number) + }) + return view } diff --git a/listView.go b/listView.go index 28ba149..74bc67a 100644 --- a/listView.go +++ b/listView.go @@ -928,6 +928,8 @@ func (listView *listViewData) htmlProperties(self View, buffer *strings.Builder) buffer.WriteString(strconv.Itoa(current)) buffer.WriteRune('"') } + + listView.viewData.htmlProperties(self, buffer) } /* diff --git a/tableAdapter.go b/tableAdapter.go index ee52f20..4704b20 100644 --- a/tableAdapter.go +++ b/tableAdapter.go @@ -1,31 +1,66 @@ package rui +// TableAdapter describes the TableView content type TableAdapter interface { + // RowCount returns number of rows in the table RowCount() int + + // ColumnCount returns number of columns in the table ColumnCount() int + + // Cell returns the contents of a table cell. The function can return elements of the following types: + // * string + // * rune + // * float32, float64 + // * integer values: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64 + // * bool + // * rui.Color + // * rui.View + // * fmt.Stringer + // * rui.VerticalTableJoin, rui.HorizontalTableJoin Cell(row, column int) interface{} } +// TableColumnStyle describes the style of TableView columns. +// To set column styles, you must either implement the TableColumnStyle interface in the table adapter +// or assign its separate implementation to the "column-style" property. type TableColumnStyle interface { ColumnStyle(column int) Params } +// TableRowStyle describes the style of TableView rows. +// To set row styles, you must either implement the TableRowStyle interface in the table adapter +// or assign its separate implementation to the "row-style" property. type TableRowStyle interface { RowStyle(row int) Params } +// TableCellStyle describes the style of TableView cells. +// To set row cells, you must either implement the TableCellStyle interface in the table adapter +// or assign its separate implementation to the "cell-style" property. type TableCellStyle interface { CellStyle(row, column int) Params } +// TableAllowCellSelection determines whether TableView cell selection is allowed. +// It is only used if the "selection-mode" property is set to CellSelection (1). +// To set cell selection allowing, you must either implement the TableAllowCellSelection interface +// in the table adapter or assign its separate implementation to the "allow-selection" property. type TableAllowCellSelection interface { AllowCellSelection(row, column int) bool } +// TableAllowRowSelection determines whether TableView row selection is allowed. +// It is only used if the "selection-mode" property is set to RowSelection (2). +// To set row selection allowing, you must either implement the TableAllowRowSelection interface +// in the table adapter or assign its separate implementation to the "allow-selection" property. type TableAllowRowSelection interface { AllowRowSelection(row int) bool } +// SimpleTableAdapter is implementation of TableAdapter where the content +// defines as [][]interface{}. +// When you assign [][]interface{} value to the "content" property, it is converted to SimpleTableAdapter type SimpleTableAdapter interface { TableAdapter TableCellStyle @@ -36,6 +71,9 @@ type simpleTableAdapter struct { columnCount int } +// TextTableAdapter is implementation of TableAdapter where the content +// defines as [][]string. +// When you assign [][]string value to the "content" property, it is converted to TextTableAdapter type TextTableAdapter interface { TableAdapter } @@ -45,12 +83,17 @@ type textTableAdapter struct { columnCount int } +// NewTextTableAdapter is an auxiliary structure. It used as cell content and +// specifies that the cell should be merged with the one above it type VerticalTableJoin struct { } +// HorizontalTableJoin is an auxiliary structure. It used as cell content and +// specifies that the cell should be merged with the one before it type HorizontalTableJoin struct { } +// NewSimpleTableAdapter creates the new SimpleTableAdapter func NewSimpleTableAdapter(content [][]interface{}) SimpleTableAdapter { if content == nil { return nil @@ -145,6 +188,7 @@ func (adapter *simpleTableAdapter) CellStyle(row, column int) Params { return params } +// NewTextTableAdapter creates the new TextTableAdapter func NewTextTableAdapter(content [][]string) TextTableAdapter { if content == nil { return nil @@ -337,3 +381,14 @@ func (table *tableViewData) getColumnStyle() TableColumnStyle { } return nil } + +func (table *tableViewData) getCellStyle() TableCellStyle { + for _, tag := range []string{CellStyle, Content} { + if value := table.getRaw(tag); value != nil { + if style, ok := value.(TableCellStyle); ok { + return style + } + } + } + return nil +} diff --git a/tableView.go b/tableView.go index 3562fb7..9000617 100644 --- a/tableView.go +++ b/tableView.go @@ -9,7 +9,7 @@ import ( const ( // TableVerticalAlign is the constant for the "table-vertical-align" property tag. // The "table-vertical-align" int property sets the vertical alignment of the content inside a table cell. - // Valid values are LeftAlign (0), RightAlign (1), CenterAlign (2), and BaselineAlign (3, 4) + // Valid values are TopAlign (0), BottomAlign (1), CenterAlign (2), and BaselineAlign (3, 4) TableVerticalAlign = "table-vertical-align" // HeadHeight is the constant for the "head-height" property tag. @@ -221,7 +221,12 @@ type TableView interface { ParanetView ReloadTableData() CellFrame(row, column int) Frame + + content() TableAdapter getCurrent() CellIndex + getRowStyle() TableRowStyle + getColumnStyle() TableColumnStyle + getCellStyle() TableCellStyle } type tableViewData struct { @@ -298,8 +303,8 @@ func (table *tableViewData) remove(tag string) { table.removeBoundsSide(CellPadding, tag) table.propertyChanged(tag) - case Gap, CellBorder, CellPadding, RowStyle, ColumnStyle, CellStyle, - HeadHeight, HeadStyle, FootHeight, FootStyle, AllowSelection: + case SelectionMode, TableVerticalAlign, Gap, CellBorder, CellPadding, RowStyle, + ColumnStyle, CellStyle, HeadHeight, HeadStyle, FootHeight, FootStyle, AllowSelection: if _, ok := table.properties[tag]; ok { delete(table.properties, tag) table.propertyChanged(tag) @@ -326,10 +331,6 @@ func (table *tableViewData) remove(tag string) { table.current.Column = -1 table.propertyChanged(tag) - case SelectionMode: - table.viewData.remove(tag) - table.propertyChanged(tag) - default: table.viewData.remove(tag) } @@ -489,7 +490,7 @@ func (table *tableViewData) set(tag string, value interface{}) bool { return false } - case CellBorder, CellBorderStyle, CellBorderColor, CellBorderWidth, + case SelectionMode, TableVerticalAlign, CellBorder, CellBorderStyle, CellBorderColor, CellBorderWidth, CellBorderLeft, CellBorderLeftStyle, CellBorderLeftColor, CellBorderLeftWidth, CellBorderRight, CellBorderRightStyle, CellBorderRightColor, CellBorderRightWidth, CellBorderTop, CellBorderTopStyle, CellBorderTopColor, CellBorderTopWidth, @@ -498,11 +499,6 @@ func (table *tableViewData) set(tag string, value interface{}) bool { return false } - case SelectionMode: - if !table.setEnumProperty(SelectionMode, value, enumProperties[SelectionMode].values) { - return false - } - case AllowSelection: switch value.(type) { case TableAllowCellSelection: @@ -575,8 +571,8 @@ func (table *tableViewData) set(tag string, value interface{}) bool { func (table *tableViewData) propertyChanged(tag string) { if table.created { switch tag { - case Content, RowStyle, ColumnStyle, CellStyle, CellPadding, CellBorder, - HeadHeight, HeadStyle, FootHeight, FootStyle, + case Content, TableVerticalAlign, RowStyle, ColumnStyle, CellStyle, CellPadding, + CellBorder, HeadHeight, HeadStyle, FootHeight, FootStyle, CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft, TableCellClickedEvent, TableCellSelectedEvent, TableRowClickedEvent, TableRowSelectedEvent, AllowSelection: @@ -851,18 +847,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { table.cellFrame = make([]Frame, rowCount*columnCount) rowStyle := table.getRowStyle() - - var cellStyle1 TableCellStyle = nil - if style, ok := adapter.(TableCellStyle); ok { - cellStyle1 = style - } - - var cellStyle2 TableCellStyle = nil - if value := table.getRaw(CellStyle); value != nil { - if style, ok := value.(TableCellStyle); ok { - cellStyle2 = style - } - } + cellStyle := table.getCellStyle() session := table.Session() @@ -901,6 +886,14 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { } } + vAlignCss := enumProperties[TableVerticalAlign].cssValues + vAlignValue := GetTableVerticalAlign(table, "") + if vAlignValue < 0 || vAlignValue >= len(vAlignCss) { + vAlignValue = 0 + } + + vAlign := vAlignCss[vAlignValue] + tableCSS := func(startRow, endRow int, cellTag string, cellBorder BorderProperty, cellPadding BoundsProperty) { for row := startRow; row < endRow; row++ { @@ -968,41 +961,37 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { view.set(Padding, cellPadding) } - appendFrom := func(cellStyle TableCellStyle) { - if cellStyle != nil { - if styles := cellStyle.CellStyle(row, column); styles != nil { - for tag, value := range styles { - valueToInt := func() int { - switch value := value.(type) { - case int: - return value + if cellStyle != nil { + if styles := cellStyle.CellStyle(row, column); styles != nil { + for tag, value := range styles { + valueToInt := func() int { + switch value := value.(type) { + case int: + return value - case string: - if value, ok := session.resolveConstants(value); ok { - if n, err := strconv.Atoi(value); err == nil { - return n - } + case string: + if value, ok := session.resolveConstants(value); ok { + if n, err := strconv.Atoi(value); err == nil { + return n } } - return 0 } + return 0 + } - switch tag = strings.ToLower(tag); tag { - case RowSpan: - rowSpan = valueToInt() + switch tag = strings.ToLower(tag); tag { + case RowSpan: + rowSpan = valueToInt() - case ColumnSpan: - columnSpan = valueToInt() + case ColumnSpan: + columnSpan = valueToInt() - default: - view.set(tag, value) - } + default: + view.set(tag, value) } } } } - appendFrom(cellStyle1) - appendFrom(cellStyle2) if len(view.properties) > 0 { view.cssStyle(&view, &cssBuilder) @@ -1162,13 +1151,17 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { if style, ok := session.resolveConstants(value); ok { buffer.WriteString(` class="`) buffer.WriteString(style) - buffer.WriteString(`">`) + buffer.WriteString(`" style="vertical-align: `) + buffer.WriteString(vAlign) + buffer.WriteString(`;">`) + return table.cellBorderFromStyle(style), table.cellPaddingFromStyle(style) } case Params: cssBuilder.buffer.Reset() view.Clear() + view.Set(TableVerticalAlign, vAlignValue) for tag, val := range value { view.Set(tag, val) } @@ -1203,7 +1196,10 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { return border, padding } } - buffer.WriteRune('>') + + buffer.WriteString(` style="vertical-align: `) + buffer.WriteString(vAlign) + buffer.WriteString(`;">`) return nil, nil } @@ -1231,7 +1227,9 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { } if rowCount > footHeight+headHeight { - buffer.WriteString("") + buffer.WriteString(``) tableCSS(headHeight, rowCount-footHeight, "td", cellBorder, cellPadding) buffer.WriteString("") } diff --git a/tableViewUtils.go b/tableViewUtils.go index 3d079da..090dfc9 100644 --- a/tableViewUtils.go +++ b/tableViewUtils.go @@ -23,6 +23,70 @@ func (cell *tableCellView) cssStyle(self View, builder cssBuilder) { } } +// GetTableContent returns a TableAdapter which defines the TableView content. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetTableContent(view View, subviewID string) TableAdapter { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + + if view != nil { + if tableView, ok := view.(TableView); ok { + return tableView.content() + } + } + + return nil +} + +// GetTableRowStyle returns a TableRowStyle which defines styles of TableView rows. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetTableRowStyle(view View, subviewID string) TableRowStyle { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + + if view != nil { + if tableView, ok := view.(TableView); ok { + return tableView.getRowStyle() + } + } + + return nil +} + +// GetTableColumnStyle returns a TableColumnStyle which defines styles of TableView columns. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetTableColumnStyle(view View, subviewID string) TableColumnStyle { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + + if view != nil { + if tableView, ok := view.(TableView); ok { + return tableView.getColumnStyle() + } + } + + return nil +} + +// GetTableCellStyle returns a TableCellStyle which defines styles of TableView cells. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetTableCellStyle(view View, subviewID string) TableCellStyle { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + + if view != nil { + if tableView, ok := view.(TableView); ok { + return tableView.getCellStyle() + } + } + + return nil +} + // GetSelectionMode returns the mode of the TableView elements selection. // Valid values are NoneSelection (0), CellSelection (1), and RowSelection (2). // If the second argument (subviewID) is "" then a value from the first argument (view) is returned. @@ -38,28 +102,36 @@ func GetSelectionMode(view View, subviewID string) int { return NoneSelection } -// GetSelectionMode returns the index of the TableView selected row. -// If there is no selected row, then a value less than 0 are returned. +// GetTableVerticalAlign returns a vertical align in a TavleView cell. Returns one of next values: +// TopAlign (0), BottomAlign (1), CenterAlign (2), and BaselineAlign (3) // If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetCurrentTableRow(view View, subviewID string) int { +func GetTableVerticalAlign(view View, subviewID string) int { if subviewID != "" { view = ViewByID(view, subviewID) } - if view != nil { - if selectionMode := GetSelectionMode(view, ""); selectionMode != NoneSelection { - if tableView, ok := view.(TableView); ok { - return tableView.getCurrent().Row - } + if result, ok := enumStyledProperty(view, TableVerticalAlign, TopAlign); ok { + return result } } - return -1 + return TopAlign } -// GetCurrentTableCell returns the row and column index of the TableView selected cell. -// If there is no selected cell, then a value of the row and column index less than 0 is returned. +// +//HeadHeight = "head-height" + +//HeadStyle = "head-style" + +//FootHeight = "foot-height" + +//FootStyle = "foot-style" + +// GetTableCurrent returns the row and column index of the TableView selected cell/row. +// If there is no selected cell/row or the selection mode is NoneSelection (0), +// then a value of the row and column index less than 0 is returned. +// If the selection mode is RowSelection (2) then the returned column index is less than 0. // If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetCurrentTableCell(view View, subviewID string) CellIndex { +func GetTableCurrent(view View, subviewID string) CellIndex { if subviewID != "" { view = ViewByID(view, subviewID) } From 043778d56b2a7acb923e6a8b28915c36030716aa Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Sun, 16 Jan 2022 13:39:59 -0500 Subject: [PATCH 05/14] Added GetTableFootHeight() and GetTableHeadHeight() --- tableView.go | 4 ++-- tableViewUtils.go | 31 ++++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/tableView.go b/tableView.go index 9000617..442e8c9 100644 --- a/tableView.go +++ b/tableView.go @@ -1130,8 +1130,8 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { buffer.WriteString("") } - headHeight, _ := intProperty(table, HeadHeight, table.Session(), 0) - footHeight, _ := intProperty(table, FootHeight, table.Session(), 0) + headHeight := GetTableHeadHeight(table, "") + footHeight := GetTableFootHeight(table, "") cellBorder := table.getCellBorder() cellPadding := table.boundsProperty(CellPadding) if cellPadding == nil { diff --git a/tableViewUtils.go b/tableViewUtils.go index 090dfc9..01c55fe 100644 --- a/tableViewUtils.go +++ b/tableViewUtils.go @@ -117,14 +117,31 @@ func GetTableVerticalAlign(view View, subviewID string) int { return TopAlign } -// -//HeadHeight = "head-height" +// GetTableHeadHeight returns the number of rows in the table header. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetTableHeadHeight(view View, subviewID string) int { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + if view != nil { + headHeight, _ := intStyledProperty(view, HeadHeight, 0) + return headHeight + } + return 0 +} -//HeadStyle = "head-style" - -//FootHeight = "foot-height" - -//FootStyle = "foot-style" +// GetTableFootHeight returns the number of rows in the table footer. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetTableFootHeight(view View, subviewID string) int { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + if view != nil { + headHeight, _ := intStyledProperty(view, FootHeight, 0) + return headHeight + } + return 0 +} // GetTableCurrent returns the row and column index of the TableView selected cell/row. // If there is no selected cell/row or the selection mode is NoneSelection (0), From d51688b04fee9538a466b7adf4cebe4e5b08f8d7 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Sun, 16 Jan 2022 19:11:23 -0500 Subject: [PATCH 06/14] Updated Readme --- CHANGELOG.md | 3 ++ LICENSE | 2 +- README-ru.md | 116 +++++++++++++++++++++++++++++++++++++--- README.md | 131 ++++++++++++++++++++++++++++++++++++++++------ tableView.go | 8 +-- tableViewUtils.go | 6 +-- 6 files changed, 235 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 910ea22..a338c00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # v0.5.0 * Added HasFocus function to the View interface +* Added the following properties to TableView: "selection-mode", "allow-selection", "current", "current-style", "current-inactive-style" +* Added the following events to TableView: "table-cell-selected", "table-cell-clicked", "table-row-selected", "table-row-clicked", +* Bug fixing # v0.4.0 diff --git a/LICENSE b/LICENSE index 4c53c41..86aba24 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Alexei Anoshenko +Copyright (c) 2021-2022 Alexei Anoshenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README-ru.md b/README-ru.md index 3abf73b..39fd33d 100644 --- a/README-ru.md +++ b/README-ru.md @@ -3260,6 +3260,7 @@ Cell(row, column int) возвращает содержимое ячейки т * rui.Color * rui.View * fmt.Stringer +* rui.VerticalTableJoin, rui.HorizontalTableJoin Свойству "content" можно также присваивать следующие типы данных @@ -3314,11 +3315,11 @@ Cell(row, column int) возвращает содержимое ячейки т В этом случае таблица будет иметь следующий вид -|------|----------------| -| | | -| |-------|--------| -| | | | -|------|-------|--------| + |------|----------------| + | | | + | |-------|--------| + | | | | + |------|-------|--------| Если в качестве значения свойства "content" используется [][]interface{}, то для объединения ячеек используются пустые структуры @@ -3421,7 +3422,110 @@ TableColumnStyle объявлена как | 2 | CenterAlign | "center" | Выравнивание по центру | | 3, 4 | BaselineAlign | "baseline" | Выравнивание по базовой линии | -Для горизонтального выравнивания используется свойство "text-align" +Для горизонтального выравнивания используется свойство "text-align". + +Получить значение данного свойства можно с помощью функции + + func GetTableVerticalAlign(view View, subviewID string) int + +### Свойство "selection-mode" + +Свойство "selection-mode" (константа SelectionMode) типа int определяет режим +выделения (подсвечивания) элементов таблицы. Доступные режимы: + +* NoneSelection (0). Режим по умолчанию. В данном режиме нельзя выделять элементы таблицы. Таблица не может +получить фокус ввода. + +* CellSelection (1). В данном режиме может выделяться (подсвечиваться) одна ячейка таблицы. +Ячейка выделяется интерактивно с помощью мыши или клавиатуры (с использованием клавиш управления курсором). +В данном режиме таблица может получить фокус ввода. В данном режиме таблица генерирует два вида +событий: "table-cell-selected" и "table-cell-clicked" (о них ниже). + +* RowSelection (2). В данном режиме может выделяться (подсвечиваться) только строка таблицы целиком. +В данном режиме таблица похожа на ListView. Строка выделяется интерактивно с помощью мыши или клавиатуры +(с использованием клавиш управления курсором). В данном режиме таблица может получить фокус ввода. +В данном режиме таблица генерирует два вида событий: "table-row-selected" и "table-row-clicked" (о них ниже). + +Получить значение данного свойства можно с помощью функции + + func GetSelectionMode(view View, subviewID string) int + +### Свойство "current" + +Свойство "current" (константа Current) задает координаты выбранной ячейки/строки +в виде структуры + + type CellIndex struct { + Row, Column int + } + +Если ячейка не выбрана, то значения полей Row и Column будут меньше 0. + +В режиме RowSelection значение поля Column игнорируется. Также в данном режиме +свойству "current" можно присваивать значение типа int (индекс строки). + +Получить значение данного свойства можно с помощью функции + + func GetTableCurrent(view View, subviewID string) CellIndex + +### Свойство "allow-selection" + +По умолчанию вы можете выделить любую ячейку/строку таблицы. Однако часто необходимо запретить +выбор определенных элементов. Свойство "selection-mode" (константа SelectionMode) позволяет +задать такое правило. + +В режиме CellSelection данному свойству присваивается реализация интерфейса + + type TableAllowCellSelection interface { + AllowCellSelection(row, column int) bool + } + +а в режиме RowSelection - реализация интерфейса + + type TableAllowRowSelection interface { + AllowRowSelection(row int) bool + } + +Функция AllowCellSelection/AllowRowSelection должна возвращать "true" если ячейка/строка +может быть выделена и "false" если ячейку/строку запрещено выделять. + +### События "table-cell-selected" и "table-cell-clicked" + +Событие "table-cell-selected" генерируется в режиме CellSelection когда пользователь выделил +ячейку таблицы с помощью мыши или клавиатуры. + +Событие "table-cell-clicked" возникает если пользователь кликает мышью по ячейке таблицы +(при этом если она не выделена, то сначала возникает событие "table-cell-selected") или +нажимает клавишу Enter или пробел + +Основной слушатель данных событий имеет следующий формат: + + func(TableView, int, int) + +где второй аргумент это индекс строки ячейки, третий - индекс столбца + +Можно также использовать слушателя следующего формата: + + func(int, int) + +### События "table-row-selected" и "table-row-clicked" + +Событие "table-row-selected" генерируется в режиме RowSelection когда пользователь выделил +строку таблицы с помощью мыши или клавиатуры. + +Событие "table-row-clicked" возникает если пользователь кликает мышью по строке таблицы +(при этом если она не выделена, то сначала возникает событие "table-row-selected") или +нажимает клавишу Enter или пробел + +Основной слушатель данных событий имеет следующий формат: + + func(TableView, int) + +где второй аргумент это индекс строки. + +Можно также использовать слушателя следующего формата: + + func(int) ## Пользовательский View diff --git a/README.md b/README.md index 9ee43b5..e6b7e16 100644 --- a/README.md +++ b/README.md @@ -1301,7 +1301,7 @@ Lines are split on newlines, on "br" elements, and optionally to fill inline box * Lines are wrapped on any spaces, including in the middle of a sequence of spaces. * Spaces take up space and do not hang at the ends of lines, which means they affect the internal dimensions (min-content and max-content). -The table below shows the behavior of various values ​​of the "white-space" property. +The table below shows the behavior of various values of the "white-space" property. | | New lines | Spaces and Tabs | Text wrapping | End of line spaces | End-of-line other space separators | |-----------------------|-----------|-----------------|---------------|--------------------|------------------------------------| @@ -1448,7 +1448,8 @@ You can get the value of this property using the function #### "text-indent" property -The "text-indent" (TextIndent constant) SizeUnit property determines the size of the indent (empty space) before the first line of text. +The "text-indent" (TextIndent constant) SizeUnit property determines the size of the indent (empty space) +before the first line of text. You can get the value of this property using the function @@ -1514,7 +1515,7 @@ You can get the value of this property using the function #### "writing-mode" property The "writing-mode" (WritingMode constant) int property defines how the lines of text are arranged vertically or horizontally, as well as the direction in which the lines are displayed. -Possible values ​​are: +Possible values are: | Value | Constant | Description | |:-----:|-----------------------|------------------------------------------------------------------| @@ -1871,8 +1872,8 @@ The Touch structure describes a single touch and has the following fields | ClientY | float64 | The vertical position of the mouse relative to the upper left corner of the application | | ScreenX | float64 | Horizontal position of the mouse relative to the upper left corner of the screen | | ScreenY | float64 | Vertical position of the mouse relative to the upper left corner of the screen | -| RadiusX | float64 | The x-radius of the ellipse, in pixels, that most closely delimits the area of ​​contact with the screen. | -| RadiusY | float64 | The y-radius of the ellipse, in pixels, that most closely delimits the area of ​​contact with the screen. | +| RadiusX | float64 | The x-radius of the ellipse, in pixels, that most closely delimits the area of contact with the screen. | +| RadiusY | float64 | The y-radius of the ellipse, in pixels, that most closely delimits the area of contact with the screen. | | RotationAngle | float64 | The angle (in degrees) to rotate the ellipse clockwise, described by the radiusX and radiusY parameters, to best cover the contact area between the user and the surface. | | Force | float64 | The amount of pressure from 0.0 (no pressure) to 1.0 (maximum pressure) that the user applies to the surface. | @@ -2038,8 +2039,8 @@ relative to each other. The property can take the following values: | 3 | EndToStartOrientation | Child elements are laid out in a line from end to beginning. | The start and end positions for StartToEndOrientation and EndToStartOrientation depend on the value -of the "text-direction" property. For languages ​​written from right to left (Arabic, Hebrew), -the beginning is on the right, for other languages ​​- on the left. +of the "text-direction" property. For languages written from right to left (Arabic, Hebrew), +the beginning is on the right, for other languages - on the left. ### "wrap" property @@ -2610,7 +2611,7 @@ For a multi-line editor, auto-wrap mode can be enabled. The bool property "wrap" If "wrap" is off (default), then horizontal scrolling is used. If enabled, the text wraps to a new line when the EditView border is reached. -The following functions can be used to get the values ​​of the properties of an EditView: +The following functions can be used to get the values of the properties of an EditView: func GetText(view View, subviewID string) string func GetHint(view View, subviewID string) string @@ -2665,7 +2666,7 @@ The value of the "date-picker-value" property can also be read using the functio func GetNumberPickerValue(view View, subviewID string) float64 -The entered values ​​may be subject to restrictions. For this, the following properties are used: +The entered values may be subject to restrictions. For this, the following properties are used: | Property | Constant | Restriction | |--------------------|------------------|-------------------| @@ -2678,7 +2679,7 @@ Assignments to these properties can be the same value types as "date-picker-valu By default, if "date-picker-type" is equal to NumberSlider, the minimum value is 0, maximum is 1. If "date-picker-type" is equal to NumberEditor, then the entered numbers, by default, are limited only by the range of float64 values. -You can read the values ​​of these properties using the functions: +You can read the values of these properties using the functions: func GetNumberPickerMinMax(view View, subviewID string) (float64, float64) func GetNumberPickerStep(view View, subviewID string) float64 @@ -3027,8 +3028,8 @@ will be positioned relative to each other. The property can take the following v | 3 | EndToStartOrientation | Elements are arranged in a row from end to beginning. | The start and end positions for StartToEndOrientation and EndToStartOrientation depend -on the value of the "text-direction" property. For languages ​​written from right to left -(Arabic, Hebrew), the beginning is on the right, for other languages ​​- on the left. +on the value of the "text-direction" property. For languages written from right to left +(Arabic, Hebrew), the beginning is on the right, for other languages - on the left. You can get the value of this property using the function @@ -3224,6 +3225,7 @@ Cell(row, column int) returns the contents of a table cell. The Cell() function * rui.Color * rui.View * fmt.Stringer +* rui.VerticalTableJoin, rui.HorizontalTableJoin The "content" property can also be assigned the following data types @@ -3277,11 +3279,11 @@ The "row-span" property specifies how many cells to merge vertically, and the "c In this case, the table will look like this -|------|----------------| -| | | -| |-------|--------| -| | | | -|------|-------|--------| + |------+----------------| + | | | + | +-------+--------| + | | | | + |------+-------+--------| If [][]interface{} is used as the value of the "content" property, then empty structures are used to merge cells @@ -3385,6 +3387,101 @@ the vertical alignment of data within a table cell. Valid values: For horizontal alignment, use the "text-align" property +You can get the value of this property using the function + + func GetTableVerticalAlign(view View, subviewID string) int + +### "selection-mode" property + +The "selection-mode" property (SelectionMode constant) of the int type determines the mode of selection (highlighting) of table elements. Available modes: + +* NoneSelection (0). Default mode. In this mode, you cannot select table elements. The table cannot receive input focus. + +* CellSelection (1). In this mode, one table cell can be selected (highlighted). +The cell is selected interactively using the mouse or keyboard (using the cursor keys). +In this mode, the table can receive input focus. In this mode, the table generates two types of events: "table-cell-selected" and "table-cell-clicked" (see below). + +* RowSelection (2). In this mode, only the entire table row can be selected (highlighted). +In this mode, the table is similar to a ListView. The row is selected interactively +with the mouse or keyboard (using the cursor keys). In this mode, the table can receive input focus. +In this mode, the table generates two types of events: "table-row-selected" and "table-row-clicked" (see below). + +You can get the value of this property using the function + + func GetSelectionMode(view View, subviewID string) int + +### "current" property + +The "current" property (Current constant) sets the coordinates of the selected cell/row as a structure + + type CellIndex struct { + Row, Column int + } + +If the cell is not selected, then the values of the Row and Column fields will be less than 0. + +In RowSelection mode, the value of the Column field is ignored. Also in this mode, +the "current" property can be assigned a value of type int (row index). + +You can get the value of this property using the function + + func GetTableCurrent(view View, subviewID string) CellIndex + +### "allow-selection" property + +By default, you can select any cell/row of the table. However, it is often necessary to disable the selection of certain elements. The "selection-mode" property (SelectionMode constant) allows you to set such a rule. + +In CellSelection mode, this property is assigned the implementation of the interface + + type TableAllowCellSelection interface { + AllowCellSelection(row, column int) bool + } + +and in RowSelection mode this property is assigned the implementation of the interface + + type TableAllowRowSelection interface { + AllowRowSelection(row int) bool + } + +The AllowCellSelection/AllowRowSelection function must return "true" +if the cell/row can be selected and "false" if the cell/row cannot be selected. + +### "table-cell-selected" and "table-cell-clicked" events + +The "table-cell-selected" event is fired in CellSelection mode when the user has selected +a table cell with the mouse or keyboard. + +The "table-cell-clicked" event occurs if the user clicks on a table cell (and if it is not selected, +the "table-cell-selected" event occurs first) or presses the Enter or Space key. + +The main listener for these events has the following format: + + func(TableView, int, int) + +where the second argument is the cell row index, the third argument is the column index + +You can also use a listener in the following format: + + func(int, int) + +### "table-row-selected" and "table-row-clicked" events + +The "table-row-selected" event is fired in RowSelection mode when the user has selected +a table row with the mouse or keyboard. + +The "table-row-clicked" event occurs if the user clicks on a table row (if it is not selected, +the "table-row-selected" event fires first) or presses the Enter or Space key. + +The main listener for these events has the following format: + + func(TableView, int) + +where the second argument is the row index. + +You can also use a listener in the following format: + + func(int) + ## Custom View A custom View must implement the CustomView interface, which extends the ViewsContainer and View interfaces. diff --git a/tableView.go b/tableView.go index 442e8c9..45f86da 100644 --- a/tableView.go +++ b/tableView.go @@ -286,7 +286,7 @@ func (table *tableViewData) normalizeTag(tag string) string { } func (table *tableViewData) Focusable() bool { - return GetSelectionMode(table, "") != NoneSelection + return GetTableSelectionMode(table, "") != NoneSelection } func (table *tableViewData) Get(tag string) interface{} { @@ -594,7 +594,7 @@ func (table *tableViewData) propertyChanged(tag string) { htmlID := table.htmlID() session := table.Session() - switch GetSelectionMode(table, "") { + switch GetTableSelectionMode(table, "") { case CellSelection: updateProperty(htmlID, "tabindex", "0", session) updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)", session) @@ -790,7 +790,7 @@ func (table *tableViewData) htmlProperties(self View, buffer *strings.Builder) { buffer.WriteRune('"') } - if selectionMode := GetSelectionMode(table, ""); selectionMode != NoneSelection { + if selectionMode := GetTableSelectionMode(table, ""); selectionMode != NoneSelection { buffer.WriteString(` onfocus="tableViewFocusEvent(this, event)" onblur="tableViewBlurEvent(this, event)" data-focusitemstyle="`) buffer.WriteString(table.currentStyle()) buffer.WriteString(`" data-bluritemstyle="`) @@ -864,7 +864,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { view.Init(session) ignorCells := []struct{ row, column int }{} - selectionMode := GetSelectionMode(table, "") + selectionMode := GetTableSelectionMode(table, "") var allowCellSelection TableAllowCellSelection = nil if allow, ok := adapter.(TableAllowCellSelection); ok { diff --git a/tableViewUtils.go b/tableViewUtils.go index 01c55fe..5ec0570 100644 --- a/tableViewUtils.go +++ b/tableViewUtils.go @@ -87,10 +87,10 @@ func GetTableCellStyle(view View, subviewID string) TableCellStyle { return nil } -// GetSelectionMode returns the mode of the TableView elements selection. +// GetTableSelectionMode returns the mode of the TableView elements selection. // Valid values are NoneSelection (0), CellSelection (1), and RowSelection (2). // If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetSelectionMode(view View, subviewID string) int { +func GetTableSelectionMode(view View, subviewID string) int { if subviewID != "" { view = ViewByID(view, subviewID) } @@ -154,7 +154,7 @@ func GetTableCurrent(view View, subviewID string) CellIndex { } if view != nil { - if selectionMode := GetSelectionMode(view, ""); selectionMode != NoneSelection { + if selectionMode := GetTableSelectionMode(view, ""); selectionMode != NoneSelection { if tableView, ok := view.(TableView); ok { return tableView.getCurrent() } From 1a7a7e9daf7c7177ddff5036ac2fd8c870695f3f Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Mon, 17 Jan 2022 06:59:32 -0500 Subject: [PATCH 07/14] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a338c00..352e342 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ * Added HasFocus function to the View interface * Added the following properties to TableView: "selection-mode", "allow-selection", "current", "current-style", "current-inactive-style" -* Added the following events to TableView: "table-cell-selected", "table-cell-clicked", "table-row-selected", "table-row-clicked", +* Added the following events to TableView: "table-cell-selected", "table-cell-clicked", "table-row-selected", "table-row-clicked" * Bug fixing # v0.4.0 From f989952817b3d348dec7fee752cdd9f5f8fb7984 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Sat, 29 Jan 2022 11:20:05 -0500 Subject: [PATCH 08/14] Added the UserAgent function to the Session interface --- app_scripts.js | 14 ++++++++++++-- demo/canvasDemo.go | 48 +++++++++++++++++++++++++++++++++++----------- session.go | 11 +++++++++++ sessionTheme.go | 4 ++++ 4 files changed, 64 insertions(+), 13 deletions(-) diff --git a/app_scripts.js b/app_scripts.js index 526f43b..427fa36 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -42,9 +42,19 @@ function socketOpen() { } } - const lang = window.navigator.languages; + const lang = window.navigator.language; if (lang) { - message += ",languages=\"" + lang + "\""; + message += ",language=\"" + lang + "\""; + } + + const langs = window.navigator.languages; + if (langs) { + message += ",languages=\"" + langs + "\""; + } + + const userAgent = window.navigator.userAgent + if (userAgent) { + message += ",user-agent=\"" + userAgent + "\""; } const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)"); diff --git a/demo/canvasDemo.go b/demo/canvasDemo.go index e9ad43e..fca814e 100644 --- a/demo/canvasDemo.go +++ b/demo/canvasDemo.go @@ -74,7 +74,13 @@ func textCanvasDemo(canvas rui.Canvas) { canvas.SetTextAlign(rui.LeftAlign) canvas.SetTextBaseline(rui.TopBaseline) - canvas.SetSolidColorFillStyle(0xFF000000) + if canvas.View().Session().DarkTheme() { + canvas.SetSolidColorFillStyle(0xFFFFFFFF) + canvas.SetSolidColorStrokeStyle(0xFFFFFFFF) + } else { + canvas.SetSolidColorFillStyle(0xFF000000) + canvas.SetSolidColorStrokeStyle(0xFF000000) + } canvas.FillText(10, 10, "Default font") canvas.StrokeText(300, 10, "Default font") @@ -136,7 +142,11 @@ func textCanvasDemo(canvas rui.Canvas) { func textAlignCanvasDemo(canvas rui.Canvas) { canvas.Save() canvas.SetFont("sans-serif", rui.Pt(10)) - canvas.SetSolidColorFillStyle(0xFF000000) + if canvas.View().Session().DarkTheme() { + canvas.SetSolidColorFillStyle(0xFFFFFFFF) + } else { + canvas.SetSolidColorFillStyle(0xFF000000) + } canvas.SetSolidColorStrokeStyle(0xFF00FFFF) baseline := []string{"Alphabetic", "Top", "Middle", "Bottom", "Hanging", "Ideographic"} @@ -176,18 +186,28 @@ func lineStyleCanvasDemo(canvas rui.Canvas) { canvas.SetLineWidth(1) y := float64(40 + 20*i) canvas.DrawLine(10, y, 180, y) - canvas.SetSolidColorStrokeStyle(0xFF000000) + if canvas.View().Session().DarkTheme() { + canvas.SetSolidColorStrokeStyle(0xFFFFFFFF) + } else { + canvas.SetSolidColorStrokeStyle(0xFF000000) + } + canvas.SetLineWidth(10) canvas.SetLineCap(i) canvas.DrawLine(20, y, 170, y) canvas.FillText(200, y, cap) } - canvas.SetSolidColorStrokeStyle(0xFF0000FF) + if canvas.View().Session().DarkTheme() { + canvas.SetSolidColorStrokeStyle(0xFFFFFFFF) + canvas.SetSolidColorFillStyle(0xFF00FFFF) + } else { + canvas.SetSolidColorStrokeStyle(0xFF000000) + canvas.SetSolidColorFillStyle(0xFF0000FF) + } canvas.SetFont("courier", rui.Pt(12)) canvas.FillText(80, 115, "SetLineJoin(...)") - canvas.SetSolidColorStrokeStyle(0xFF000000) canvas.SetLineWidth(10) canvas.SetLineCap(rui.ButtCap) @@ -206,14 +226,11 @@ func lineStyleCanvasDemo(canvas rui.Canvas) { canvas.StrokePath(path) canvas.FillText(210, y+20, join) } - - canvas.SetSolidColorStrokeStyle(0xFF0000FF) canvas.SetFont("courier", rui.Pt(12)) canvas.FillText(20, 300, "SetLineDash([]float64{16, 8, 4, 8}, ...)") canvas.SetFont("courier", rui.Pt(10)) canvas.SetLineDash([]float64{16, 8, 4, 8}, 0) - canvas.SetSolidColorStrokeStyle(0xFF000000) canvas.SetLineWidth(4) canvas.SetLineCap(rui.ButtCap) @@ -246,8 +263,13 @@ func transformCanvasDemo(canvas rui.Canvas) { y1 := y0 + float64((ny-1)*20) canvas.SetFont("serif", rui.Pt(10)) - canvas.SetSolidColorFillStyle(rui.Black) - + if canvas.View().Session().DarkTheme() { + canvas.SetSolidColorStrokeStyle(0xFFFFFFFF) + canvas.SetSolidColorFillStyle(0xFFFFFFFF) + } else { + canvas.SetSolidColorStrokeStyle(0xFF000000) + canvas.SetSolidColorFillStyle(0xFF000000) + } canvas.SetTextAlign(rui.CenterAlign) canvas.SetTextBaseline(rui.BottomBaseline) for i := 0; i < nx; i++ { @@ -266,7 +288,11 @@ func transformCanvasDemo(canvas rui.Canvas) { } canvas.SetFont("courier", rui.Pt(14)) - canvas.SetSolidColorFillStyle(rui.Black) + if canvas.View().Session().DarkTheme() { + canvas.SetSolidColorFillStyle(0xFFFFFFFF) + } else { + canvas.SetSolidColorFillStyle(0xFF000000) + } canvas.SetTextAlign(rui.CenterAlign) canvas.SetTextBaseline(rui.TopBaseline) diff --git a/session.go b/session.go index 505a78d..75dcde4 100644 --- a/session.go +++ b/session.go @@ -33,6 +33,8 @@ type Session interface { Color(tag string) (Color, bool) // SetCustomTheme set the custom theme SetCustomTheme(name string) bool + // UserAgent() returns the "user-agent" text of the client browser + UserAgent() string // Language returns the current session language Language() string // SetLanguage set the current session language @@ -106,6 +108,7 @@ type sessionData struct { touchScreen bool textDirection int pixelRatio float64 + userAgent string language string languages []string checkboxOff string @@ -148,12 +151,20 @@ func newSession(app Application, id int, customTheme string, params DataObject) session.touchScreen = (value == "1" || value == "true") } + if value, ok := params.PropertyValue("user-agent"); ok { + session.userAgent = value + } + if value, ok := params.PropertyValue("direction"); ok { if value == "rtl" { session.textDirection = RightToLeftDirection } } + if value, ok := params.PropertyValue("language"); ok { + session.language = value + } + if value, ok := params.PropertyValue("languages"); ok { session.languages = strings.Split(value, ",") } diff --git a/sessionTheme.go b/sessionTheme.go index f6642cf..3e9d149 100644 --- a/sessionTheme.go +++ b/sessionTheme.go @@ -328,6 +328,10 @@ func (session *sessionData) radiobuttonOnImage() string { return session.radiobuttonOn } +func (session *sessionData) UserAgent() string { + return session.userAgent +} + func (session *sessionData) Language() string { if session.language != "" { return session.language From 93abec1bffa7d313d6b324075e1bcd5b2b9b2a08 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Sat, 29 Jan 2022 16:02:38 -0500 Subject: [PATCH 09/14] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 352e342..22742cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # v0.5.0 * Added HasFocus function to the View interface +* Added the UserAgent function to the Session interface * Added the following properties to TableView: "selection-mode", "allow-selection", "current", "current-style", "current-inactive-style" * Added the following events to TableView: "table-cell-selected", "table-cell-clicked", "table-row-selected", "table-row-clicked" * Bug fixing From 4692d0013acb636cd8d5b891df2411d8d0c41618 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Sat, 29 Jan 2022 16:25:46 -0500 Subject: [PATCH 10/14] NewApplication function and Start function of the Application interface were replaced by StartApp function --- CHANGELOG.md | 1 + application.go | 39 ++++++++++++++++++++++++++------------- demo/main.go | 11 +++++------ 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22742cb..cc9b323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # v0.5.0 +* NewApplication function and Start function of the Application interface were replaced by StartApp function * Added HasFocus function to the View interface * Added the UserAgent function to the Session interface * Added the following properties to TableView: "selection-mode", "allow-selection", "current", "current-style", "current-inactive-style" diff --git a/application.go b/application.go index fb0d3db..39df812 100644 --- a/application.go +++ b/application.go @@ -27,19 +27,24 @@ var defaultThemeText string // Application - app interface type Application interface { - // Start - start the application life cycle - Start(addr string) Finish() nextSessionID() int removeSession(id int) } type application struct { - name, icon string + params AppParams createContentFunc func(Session) SessionContent sessions map[int]Session } +// AppParams defines parameters of the app +type AppParams struct { + Title string + TitleColor Color + Icon string +} + func (app *application) getStartPage() string { buffer := allocStringBuilder() defer freeStringBuilder(buffer) @@ -49,12 +54,19 @@ func (app *application) getStartPage() string { `) - buffer.WriteString(app.name) + buffer.WriteString(app.params.Title) buffer.WriteString("") - if app.icon != "" { + if app.params.Icon != "" { buffer.WriteString(` `) + } + + if app.params.TitleColor != 0 { + buffer.WriteString(` + `) } @@ -78,9 +90,8 @@ func (app *application) getStartPage() string { return buffer.String() } -func (app *application) init(name, icon string) { - app.name = name - app.icon = icon +func (app *application) init(params AppParams) { + app.params = params app.sessions = map[int]Session{} } @@ -277,12 +288,14 @@ func (app *application) startSession(params DataObject, events chan DataObject, return session, answerText } -// NewApplication - create the new application of the single view type. -func NewApplication(name, icon string, createContentFunc func(Session) SessionContent) Application { +// NewApplication - create the new application and start it +func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) { app := new(application) - app.init(name, icon) + app.init(params) app.createContentFunc = createContentFunc - return app + + http.Handle("/", app) + log.Fatal(http.ListenAndServe(addr, nil)) } func OpenBrowser(url string) bool { diff --git a/demo/main.go b/demo/main.go index 82bf668..cef30d1 100644 --- a/demo/main.go +++ b/demo/main.go @@ -113,10 +113,6 @@ func (demo *demoSession) CreateRootView(session rui.Session) rui.View { rui.Set(demo.rootView, "rootTitleButton", rui.ClickEvent, demo.clickMenuButton) demo.showPage(0) - if color, ok := rui.StringToColor("#ffc0ded9"); ok { - session.SetTitleColor(color) - } - return demo.rootView } @@ -158,11 +154,14 @@ func (demo *demoSession) showPage(index int) { func main() { rui.ProtocolInDebugLog = true rui.AddEmbedResources(&resources) - app := rui.NewApplication("RUI demo", "icon.svg", createDemo) //addr := rui.GetLocalIP() + ":8080" addr := "localhost:8000" fmt.Print(addr) rui.OpenBrowser("http://" + addr) - app.Start(addr) + rui.StartApp(addr, createDemo, rui.AppParams{ + Title: "RUI demo", + Icon: "icon.svg", + TitleColor: rui.Color(0xffc0ded9), + }) } From d20407cf369b721dada9809d8ccf9b377471174f Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Sun, 30 Jan 2022 11:34:14 -0500 Subject: [PATCH 11/14] Fixed demo --- demo/mouseEventsDemo.go | 2 +- demo/touchEventsDemo.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/mouseEventsDemo.go b/demo/mouseEventsDemo.go index 43103b7..d6868c7 100644 --- a/demo/mouseEventsDemo.go +++ b/demo/mouseEventsDemo.go @@ -17,7 +17,7 @@ GridLayout { content = [ GridLayout { id = mouseEventsTest, cell-horizontal-align = center, cell-vertical-align = center, - height = 150%, + height = 100%, border = _{ style = solid, width = 1px, color = gray}, content = [ TextView { diff --git a/demo/touchEventsDemo.go b/demo/touchEventsDemo.go index 0fec7c3..59066c4 100644 --- a/demo/touchEventsDemo.go +++ b/demo/touchEventsDemo.go @@ -17,7 +17,7 @@ GridLayout { content = [ GridLayout { id = touchEventsTest, cell-horizontal-align = center, cell-vertical-align = center, - height = 150%, + height = 100%, border = _{ style = solid, width = 1px, color = gray}, content = [ TextView { From c968c84e918316504966257a38694ba2ebdb9893 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Sun, 30 Jan 2022 11:48:40 -0500 Subject: [PATCH 12/14] Updated readme --- README-ru.md | 21 +++++++++++++-------- README.md | 18 ++++++++++++------ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/README-ru.md b/README-ru.md index 39fd33d..7fffa1f 100644 --- a/README-ru.md +++ b/README-ru.md @@ -21,28 +21,33 @@ } func main() { - app := rui.NewApplication("Hello world", "icon.svg", createHelloWorldSession) - app.Start("localhost:8000") + rui.StartApp("localhost:8000", createHelloWorldSession, rui.AppParams{ + Title: "Hello world", + Icon: "icon.svg", + }) } -В функции main создается rui приложение и запускается основной цикл. -При создании приложения задаются 3 параметра: имя приложения, имя иконки и функция createHelloWorldSession. -Функция createHelloWorldSession создает структуру реализующую интерфейс SessionContent: +В функции main вызывается функция StartApp. Она создает rui приложение и запускает его основной цикл. +Функция StartApp имеет 3 параметра: +1) IP адрес по которому будет доступно приложение (в нашем примере это "localhost:8000") +2) Фуекция создает структуру реализующую интерфейс SessionContent +3) Дополнительные опциональные параметры (в нашем примере это заголовок и имя файла иконки) + +Интерфейс SessionContent объявлен как: type SessionContent interface { CreateRootView(session rui.Session) rui.View } -Для каждой новой сессии создается свой экземпляр структуры. - Функция CreateRootView интерфейса SessionContent создает корневой элемент. + Когда пользователь обращается к приложению набрав в браузере адрес "localhost:8000", то создается новая сессия, для нее создается новый экземпляр структуры helloWorldSession и в конце вызывается функция CreateRootView. Функция createRootView возвращает представление строки текста, создаваемое с помощью функции NewTextView. Если вы хотите чтобы приложение было видно вне вашего компьютера, то поменяйте адрес в функции Start: - app.Start(rui.GetLocalIP() + ":80") + rui.StartApp(rui.GetLocalIP() + ":80", ... ## Используемые типы данных diff --git a/README.md b/README.md index e6b7e16..7f87584 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,19 @@ and the browser is used as a thin client. WebSocket is used for client-server co } func main() { - app := rui.NewApplication("Hello world", "icon.svg", createHelloWorldSession) - app.Start("localhost:8000") + rui.StartApp("localhost:8000", createHelloWorldSession, rui.AppParams{ + Title: "Hello world", + Icon: "icon.svg", + }) } -In the main function, a rui application is created and the main loop is started. -When creating an application, 3 parameters are set: the name of the application, the name of the icon, and the createHelloWorldSession function. -The createHelloWorldSession function creates a structure that implements the SessionContent interface: +In the main function, the StartApp function is called. It creates a rui app and runs its main loop. +The StartApp function has 3 parameters: +1) IP address where the application will be available (in our example it is "localhost:8000") +2) The function creates a structure that implements the SessionContent interface +3) Additional optional parameters (in our example, this is the title and the icon file name) + +The SessionContent interface is declared as: type SessionContent interface { CreateRootView(session rui.Session) rui.View @@ -42,7 +48,7 @@ The createRootView function returns a representation of a text that is created u If you want the application to be visible outside your computer, then change the address in the Start function: - app.Start(rui.GetLocalIP() + ":80") + rui.StartApp(rui.GetLocalIP() + ":80", ... ## Used data types From 29164a241681e5beb93c324b6cb06a73179e8db3 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Sat, 19 Mar 2022 19:26:06 +0300 Subject: [PATCH 13/14] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7f87584..9946974 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # RUI library -The RUI (Remoute User Interface) library is designed to create web applications in the go language. +The RUI (Remote User Interface) library is designed to create web applications in the go language. The peculiarity of the library is that all data processing is carried out on the server, and the browser is used as a thin client. WebSocket is used for client-server communication. @@ -774,12 +774,12 @@ For this, the following properties of the SizeUnit type are used: If the x- and y-radii are the same, then you can use the auxiliary properties -| Property | Constant | Description | -|----------------|--------------|----------------------------| -| "top-left" | TopLeft | top left corner radius | -| "top-right" | TopRight | top right corner radius | -| "bottom-left" | BottomLeft | bottom left corner radius | -| "bottom-right" | BottomRight | bottom right corner radius | +| Property | Constant | Description | +|----------------|-------------|----------------------------| +| "top-left" | TopLeft | top left corner radius | +| "top-right" | TopRight | top right corner radius | +| "bottom-left" | BottomLeft | bottom left corner radius | +| "bottom-right" | BottomRight | bottom right corner radius | To set all radii to the same values, use the "x" and "y" properties From 7f7dcfb7464b794cfe4bdc50cb1d8832845e9ba7 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Sat, 19 Mar 2022 19:36:13 +0300 Subject: [PATCH 14/14] Added the RemoteAddr function to Session interface --- session.go | 8 +++++++- webBrige.go | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/session.go b/session.go index 75dcde4..a9b7bc6 100644 --- a/session.go +++ b/session.go @@ -33,8 +33,10 @@ type Session interface { Color(tag string) (Color, bool) // SetCustomTheme set the custom theme SetCustomTheme(name string) bool - // UserAgent() returns the "user-agent" text of the client browser + // UserAgent returns the "user-agent" text of the client browser UserAgent() string + // RemoteAddr returns the client address. + RemoteAddr() string // Language returns the current session language Language() string // SetLanguage set the current session language @@ -430,3 +432,7 @@ func (session *sessionData) SetTitle(title string) { func (session *sessionData) SetTitleColor(color Color) { session.runScript(`setTitleColor("` + color.cssString() + `");`) } + +func (session *sessionData) RemoteAddr() string { + return session.brige.remoteAddr() +} diff --git a/webBrige.go b/webBrige.go index 0c45aed..bcfa471 100644 --- a/webBrige.go +++ b/webBrige.go @@ -14,6 +14,7 @@ type WebBrige interface { RunGetterScript(script string) DataObject AnswerReceived(answer DataObject) Close() + remoteAddr() string } type wsBrige struct { @@ -122,3 +123,7 @@ func (brige *wsBrige) AnswerReceived(answer DataObject) { ErrorLog("answerID not found") } } + +func (brige *wsBrige) remoteAddr() string { + return brige.conn.RemoteAddr().String() +}