Added: "focusable" and "user-data" properties, ConstantTags and ColorTags function to Session, ReloadTableViewData function. Bug fixing

This commit is contained in:
Alexei Anoshenko 2022-04-14 12:05:45 +03:00
parent 1e3c820c61
commit 8a625dcc78
17 changed files with 216 additions and 133 deletions

4
.vscode/launch.json vendored
View File

@ -9,8 +9,8 @@
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceRoot}/demo",
//"program": "${workspaceRoot}/editor",
//"program": "${workspaceRoot}/demo",
"program": "${workspaceRoot}/ruiEditor",
"env": {},
"args": []
}

View File

@ -1,3 +1,9 @@
# v0.6.0
* Added "user-data" property
* Added "focusable" property
* Added ReloadTableViewData function
# v0.5.0
* NewApplication function and Start function of the Application interface were replaced by StartApp function

View File

@ -1679,6 +1679,10 @@ radius необходимо передать nil
func GetSkew(view View, subviewID string) (AngleUnit, AngleUnit)
### Пользовательские данные
Вы можете сохранить любые ваши данные в виде свойства "user-data" (константа UserData)
### События клавиатуры
Для View получившего фокус ввода могут генерироваться два вида событий клавиатуры
@ -2682,16 +2686,16 @@ EditTextChangedEvent). Основной слушатель события име
func NewNumberPicker(session Session, params Params) NumberPicker
NumberPicker может работать в двух режимах: редактор текста и слайдер.
Режим устанавливает int свойство "date-picker-type" (константа NumberPickerType).
Свойство "date-picker-type" может принимать следующие значения:
Режим устанавливает int свойство "number-picker-type" (константа NumberPickerType).
Свойство "number-picker-type" может принимать следующие значения:
| Значение | Константа | Имя | Тип редактора |
|:--------:|--------------|----------|----------------------------------------|
| 0 | NumberEditor | "editor" | Редактор текста. Значение по умолчанию |
| 1 | NumberSlider | "slider" | Слайдер |
Установить/прочитать текущее значение можно с помощью свойства "date-picker-value"
(константа NumberPickerValue). В качестве значения свойству "date-picker-value" могут быть переданы:
Установить/прочитать текущее значение можно с помощью свойства "number-picker-value"
(константа NumberPickerValue). В качестве значения свойству "number-picker-value" могут быть переданы:
* float64
* float32
@ -2702,7 +2706,7 @@ NumberPicker может работать в двух режимах: редак
* текстовое представление любых из выше перечисленых типов
Все эти типы приводятся к float64. Соответственно функция Get всегда возвращает float64 значение.
Прочитано значение свойства "date-picker-value" может быть также с помощью функции:
Прочитано значение свойства "number-picker-value" может быть также с помощью функции:
func GetNumberPickerValue(view View, subviewID string) float64
@ -2710,14 +2714,14 @@ NumberPicker может работать в двух режимах: редак
| Свойство | Константа | Ограничение |
|----------------------|------------------|------------------------|
| "date-picker-min" | NumberPickerMin | Минимальное значение |
| "date-picker-max" | NumberPickerMax | Максимальное значение |
| "date-picker-step" | NumberPickerStep | Шаг изменения значения |
| "number-picker-min" | NumberPickerMin | Минимальное значение |
| "number-picker-max" | NumberPickerMax | Максимальное значение |
| "number-picker-step" | NumberPickerStep | Шаг изменения значения |
Присвоины данным свойствам могут те же типы значений, что и "date-picker-value".
Присвоины данным свойствам могут те же типы значений, что и "number-picker-value".
По умолчанию, в случае если "date-picker-type" равно NumberSlider, минимальное значение равно 0,
максимальное - 1. Если же "date-picker-type" равно NumberEditor то вводимые числа, по умолчанию,
По умолчанию, в случае если "number-picker-type" равно NumberSlider, минимальное значение равно 0,
максимальное - 1. Если же "number-picker-type" равно NumberEditor то вводимые числа, по умолчанию,
ограничены лишь диапазоном значений float64.
Прочитать значения данных свойств можно с помощью функций:
@ -2725,7 +2729,7 @@ NumberPicker может работать в двух режимах: редак
func GetNumberPickerMinMax(view View, subviewID string) (float64, float64)
func GetNumberPickerStep(view View, subviewID string) float64
Для отслеживания изменения вводимого значения используется событие "date-changed" (константа
Для отслеживания изменения вводимого значения используется событие "number-changed" (константа
NumberChangedEvent). Основной слушатель события имеет следующий формат:
func(picker NumberPicker, newValue float64)

View File

@ -1654,6 +1654,10 @@ You can get the value of these properties using the function
func GetSkew(view View, subviewID string) (AngleUnit, AngleUnit)
### User data
You can save any of your data as "user-data" property (UserData constant)
### Keyboard events
Two kinds of keyboard events can be generated for a View that has received input focus.
@ -2648,16 +2652,16 @@ To create a NumberPicker, the function is used:
func NewNumberPicker(session Session, params Params) NumberPicker
NumberPicker can work in two modes: text editor and slider.
The mode sets the int property "date-picker-type" (NumberPickerType constant).
The "date-picker-type" property can take the following values:
The mode sets the int property "number-picker-type" (NumberPickerType constant).
The "number-picker-type" property can take the following values:
| Value | Constant | Name | Editor type |
|:-----:|--------------|----------|----------------------------|
| 0 | NumberEditor | "editor" | Text editor. Default value |
| 1 | NumberSlider | "slider" | Slider |
You can set/get the current value using the "date-picker-value" property (NumberPickerValue constant).
The following can be passed as a value to the "date-picker-value" property:
You can set/get the current value using the "number-picker-value" property (NumberPickerValue constant).
The following can be passed as a value to the "number-picker-value" property:
* float64
* float32
@ -2668,29 +2672,29 @@ The following can be passed as a value to the "date-picker-value" property:
* textual representation of any of the above types
All of these types are cast to float64. Accordingly, the Get function always returns a float64 value.
The value of the "date-picker-value" property can also be read using the function:
The value of the "number-picker-value" property can also be read using the function:
func GetNumberPickerValue(view View, subviewID string) float64
The entered values may be subject to restrictions. For this, the following properties are used:
| Property | Constant | Restriction |
|--------------------|------------------|-------------------|
| "date-picker-min" | NumberPickerMin | Minimum value |
| "date-picker-max" | NumberPickerMax | Maximum value |
| "date-picker-step" | NumberPickerStep | Value change step |
| Property | Constant | Restriction |
|----------------------|------------------|-------------------|
| "number-picker-min" | NumberPickerMin | Minimum value |
| "number-picker-max" | NumberPickerMax | Maximum value |
| "number-picker-step" | NumberPickerStep | Value change step |
Assignments to these properties can be the same value types as "date-picker-value".
Assignments to these properties can be the same value types as "number-picker-value".
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.
By default, if "number-picker-type" is equal to NumberSlider, the minimum value is 0, maximum is 1.
If "number-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:
func GetNumberPickerMinMax(view View, subviewID string) (float64, float64)
func GetNumberPickerStep(view View, subviewID string) float64
The "date-changed" event (NumberChangedEvent constant) is used to track the change in the entered value.
The "number-changed" event (NumberChangedEvent constant) is used to track the change in the entered value.
The main event listener has the following format:
func(picker NumberPicker, newValue float64)

View File

@ -1759,7 +1759,7 @@ function tableRowClickEvent(element, event) {
const table = document.getElementById(tableID);
if (table) {
const selection = table.getAttribute("data-selection");
if (selection == "cell") {
if (selection == "row") {
const currentID = table.getAttribute("data-current");
if (!currentID || currentID != element.ID) {
setTableRowCursor(table, row)

View File

@ -21,13 +21,25 @@ div:focus {
}
input {
padding: 4px;
overflow: auto;
margin: 2px;
padding: 1px;
font-size: inherit;
}
select {
margin: 2px;
font-size: inherit;
}
button {
font-size: inherit;
}
textarea {
padding: 4px;
margin: 2px;
padding: 1px;
overflow: auto;
font-size: inherit;
}
ul:focus {

View File

@ -65,7 +65,7 @@ theme {
styles = [
ruiApp {
font-name = "Arial, Helvetica, sans-serif",
text-size = 12pt,
text-size = 10pt,
text-color = @ruiTextColor,
background-color = @ruiBackgroundColor,
},

View File

@ -9,6 +9,9 @@ const (
StyleDisabled = "style-disabled"
// Disabled is the constant for the "disabled" property tag.
Disabled = "disabled"
// Focusable is the constant for the "disabled" property tag.
// The bool "focusable" determines whether the view will receive focus.
Focusable = "focusable"
// Semantics is the constant for the "semantics" property tag.
Semantics = "semantics"
// Visibility is the constant for the "visibility" property tag.
@ -440,4 +443,7 @@ const (
// The "float" property places a View on the left or right side of its container,
// allowing text and inline Views to wrap around it.
Float = "float"
// UsetData is the constant for the "user-data" property tag.
// This property can contain any user data
UserData = "user-data"
)

View File

@ -37,6 +37,7 @@ var angleProperties = []string{
var boolProperties = []string{
Disabled,
Focusable,
Inset,
BackfaceVisible,
ReadOnly,

View File

@ -29,8 +29,12 @@ type Session interface {
TextDirection() int
// Constant returns the constant with "tag" name or "" if it is not exists
Constant(tag string) (string, bool)
// ConstantTags returns the list of all available constants
ConstantTags() []string
// Color returns the color with "tag" name or 0 if it is not exists
Color(tag string) (Color, bool)
// ColorTags returns the list of all available color constants
ColorTags() []string
// SetCustomTheme set the custom theme
SetCustomTheme(name string) bool
// UserAgent returns the "user-agent" text of the client browser
@ -106,6 +110,7 @@ type Session interface {
type sessionData struct {
customTheme *theme
currentTheme *theme
darkTheme bool
touchScreen bool
textDirection int
@ -146,6 +151,7 @@ func newSession(app Application, id int, customTheme string, params DataObject)
if customTheme != "" {
if theme, ok := newTheme(customTheme); ok {
session.customTheme = theme
session.currentTheme = nil
}
}
@ -206,18 +212,11 @@ func (session *sessionData) close() {
}
func (session *sessionData) styleProperty(styleTag, propertyTag string) (string, bool) {
var style DataObject
ok := false
if session.customTheme != nil {
style, ok = session.customTheme.styles[styleTag]
}
if !ok {
style, ok = defaultTheme.styles[styleTag]
}
if ok {
if node := style.PropertyWithTag(propertyTag); node != nil && node.Type() == TextNode {
return session.resolveConstants(node.Text())
if style, ok := session.getCurrentTheme().styles[styleTag]; ok {
if value, ok := style[propertyTag]; ok {
if text, ok := value.(string); ok {
return session.resolveConstants(text)
}
}
}
@ -226,17 +225,12 @@ func (session *sessionData) styleProperty(styleTag, propertyTag string) (string,
}
func (session *sessionData) stylePropertyNode(styleTag, propertyTag string) DataNode {
var style DataObject
ok := false
if session.customTheme != nil {
style, ok = session.customTheme.styles[styleTag]
}
if !ok {
style, ok = defaultTheme.styles[styleTag]
}
if ok {
return style.PropertyWithTag(propertyTag)
if style, ok := session.getCurrentTheme().styles[styleTag]; ok {
if value, ok := style[propertyTag]; ok {
if node, ok := value.(DataNode); ok {
return node
}
}
}
return nil
@ -280,17 +274,7 @@ func (session *sessionData) RootView() View {
}
func (session *sessionData) writeInitScript(writer *strings.Builder) {
var workTheme *theme
if session.customTheme == nil {
workTheme = defaultTheme
} else {
workTheme = new(theme)
workTheme.init()
workTheme.concat(defaultTheme)
workTheme.concat(session.customTheme)
}
if css := workTheme.cssText(session); css != "" {
if css := session.getCurrentTheme().cssText(session); css != "" {
writer.WriteString(`document.querySelector('style').textContent += "`)
writer.WriteString(css)
writer.WriteString("\";\n")

View File

@ -2,24 +2,10 @@ package rui
import (
"fmt"
"sort"
"strings"
)
/*
type Session struct {
customTheme *theme
darkTheme bool
touchScreen bool
textDirection int
pixelRatio float64
language string
languages []string
checkboxOff string
checkboxOn string
radiobuttonOff string
radiobuttonOn string
}
*/
func (session *sessionData) DarkTheme() bool {
return session.darkTheme
}
@ -39,27 +25,17 @@ func (session *sessionData) TextDirection() int {
func (session *sessionData) constant(tag string, prevTags []string) (string, bool) {
tags := append(prevTags, tag)
result := ""
themes := session.themes()
theme := session.getCurrentTheme()
for {
ok := false
if session.touchScreen {
for _, theme := range themes {
if theme.touchConstants != nil {
if result, ok = theme.touchConstants[tag]; ok {
break
}
}
if theme.touchConstants != nil {
result, ok = theme.touchConstants[tag]
}
}
if !ok {
for _, theme := range themes {
if theme.constants != nil {
if result, ok = theme.constants[tag]; ok {
break
}
}
}
result, ok = theme.constants[tag]
}
if !ok {
@ -149,38 +125,38 @@ func (session *sessionData) Constant(tag string) (string, bool) {
return session.constant(tag, []string{})
}
func (session *sessionData) themes() []*theme {
if session.customTheme != nil {
return []*theme{session.customTheme, defaultTheme}
func (session *sessionData) getCurrentTheme() *theme {
if session.currentTheme != nil {
return session.currentTheme
}
return []*theme{defaultTheme}
if session.customTheme != nil {
session.currentTheme = new(theme)
session.currentTheme.init()
session.currentTheme.concat(defaultTheme)
session.currentTheme.concat(session.customTheme)
return session.currentTheme
}
return defaultTheme
}
// Color return the color with "tag" name or 0 if it is not exists
func (session *sessionData) Color(tag string) (Color, bool) {
tags := []string{tag}
result := ""
themes := session.themes()
theme := session.getCurrentTheme()
for {
ok := false
if session.darkTheme {
for _, theme := range themes {
if theme.darkColors != nil {
if result, ok = theme.darkColors[tag]; ok {
break
}
}
if theme.darkColors != nil {
result, ok = theme.darkColors[tag]
}
}
if !ok {
for _, theme := range themes {
if theme.colors != nil {
if result, ok = theme.colors[tag]; ok {
break
}
}
if theme.colors != nil {
result, ok = theme.colors[tag]
}
}
@ -217,6 +193,7 @@ func (session *sessionData) SetCustomTheme(name string) bool {
}
} else if theme, ok := resources.themes[name]; ok {
session.customTheme = theme
session.currentTheme = nil
} else {
return false
}
@ -361,3 +338,39 @@ func (session *sessionData) SetLanguage(lang string) {
}
}
}
func (session *sessionData) ConstantTags() []string {
theme := session.getCurrentTheme()
keys := make([]string, 0, len(theme.constants))
for k := range theme.constants {
keys = append(keys, k)
}
for tag := range theme.touchConstants {
if _, ok := theme.constants[tag]; !ok {
keys = append(keys, tag)
}
}
sort.Strings(keys)
return keys
}
func (session *sessionData) ColorTags() []string {
theme := session.getCurrentTheme()
keys := make([]string, 0, len(theme.colors))
for k := range theme.colors {
keys = append(keys, k)
}
for tag := range theme.darkColors {
if _, ok := theme.colors[tag]; !ok {
keys = append(keys, tag)
}
}
sort.Strings(keys)
return keys
}

View File

@ -230,3 +230,21 @@ func GetTableRowSelectedListeners(view View, subviewID string) []func(TableView,
}
return []func(TableView, int){}
}
// ReloadTableViewData updates TableView
func ReloadTableViewData(view View, subviewID string) bool {
var tableView TableView
if subviewID != "" {
if tableView = TableViewByID(view, subviewID); tableView == nil {
return false
}
} else {
var ok bool
if tableView, ok = view.(TableView); !ok {
return false
}
}
tableView.ReloadTableData()
return true
}

View File

@ -147,7 +147,7 @@ func (tabsLayout *tabsLayoutData) remove(tag string) {
return
}
if tabsLayout.created {
tabsLayout.session.runScript(fmt.Sprintf("activateTab(%v, %d);", tabsLayout.htmlID(), 0))
tabsLayout.session.runScript(fmt.Sprintf("activateTab('%v', %d);", tabsLayout.htmlID(), 0))
for _, listener := range tabsLayout.tabListener {
listener(tabsLayout, 0, oldCurrent)
}
@ -224,7 +224,7 @@ func (tabsLayout *tabsLayoutData) set(tag string, value interface{}) bool {
return true
}
if tabsLayout.created {
tabsLayout.session.runScript(fmt.Sprintf("activateTab(%v, %d);", tabsLayout.htmlID(), current))
tabsLayout.session.runScript(fmt.Sprintf("activateTab('%v', %d);", tabsLayout.htmlID(), current))
for _, listener := range tabsLayout.tabListener {
listener(tabsLayout, current, oldCurrent)
}

View File

@ -16,7 +16,7 @@ type mediaStyle struct {
orientation int
width int
height int
styles map[string]DataObject
styles map[string]Params
}
func (rule mediaStyle) cssText() string {
@ -47,7 +47,7 @@ func (rule mediaStyle) cssText() string {
}
func parseMediaRule(text string) (mediaStyle, bool) {
rule := mediaStyle{orientation: defaultMedia, width: 0, height: 0, styles: map[string]DataObject{}}
rule := mediaStyle{orientation: defaultMedia, width: 0, height: 0, styles: map[string]Params{}}
elements := strings.Split(text, ":")
for i := 1; i < len(elements); i++ {
switch element := elements[i]; element {
@ -111,7 +111,7 @@ type theme struct {
touchConstants map[string]string
colors map[string]string
darkColors map[string]string
styles map[string]DataObject
styles map[string]Params
mediaStyles []mediaStyle
}
@ -129,7 +129,7 @@ func (theme *theme) init() {
theme.touchConstants = map[string]string{}
theme.colors = map[string]string{}
theme.darkColors = map[string]string{}
theme.styles = map[string]DataObject{}
theme.styles = map[string]Params{}
theme.mediaStyles = []mediaStyle{}
}
@ -189,7 +189,9 @@ func (theme *theme) cssText(session Session) string {
for tag, obj := range theme.styles {
var style viewStyle
style.init()
parseProperties(&style, obj)
for tag, value := range obj {
style.Set(tag, value)
}
builder.startStyle(tag)
style.cssViewStyle(&builder, session)
builder.endStyle()
@ -200,7 +202,9 @@ func (theme *theme) cssText(session Session) string {
for tag, obj := range media.styles {
var style viewStyle
style.init()
parseProperties(&style, obj)
for tag, value := range obj {
style.Set(tag, value)
}
builder.startStyle(tag)
style.cssViewStyle(&builder, session)
builder.endStyle()
@ -234,6 +238,25 @@ func (theme *theme) addData(data DataObject) {
func (theme *theme) parseThemeData(data DataObject) {
count := data.PropertyCount()
objToParams := func(obj DataObject) Params {
params := Params{}
for i := 0; i < obj.PropertyCount(); i++ {
if node := obj.Property(i); node != nil {
switch node.Type() {
case ArrayNode:
params[node.Tag()] = node.ArrayElements()
case ObjectNode:
params[node.Tag()] = node.Object()
default:
params[node.Tag()] = node.Text()
}
}
}
return params
}
for i := 0; i < count; i++ {
if d := data.Property(i); d != nil {
switch tag := d.Tag(); tag {
@ -291,7 +314,7 @@ func (theme *theme) parseThemeData(data DataObject) {
for k := 0; k < arraySize; k++ {
if element := d.ArrayElement(k); element != nil && element.IsObject() {
if obj := element.Object(); obj != nil {
theme.styles[obj.Tag()] = obj
theme.styles[obj.Tag()] = objToParams(obj)
}
}
}
@ -304,7 +327,7 @@ func (theme *theme) parseThemeData(data DataObject) {
for k := 0; k < arraySize; k++ {
if element := d.ArrayElement(k); element != nil && element.IsObject() {
if obj := element.Object(); obj != nil {
rule.styles[obj.Tag()] = obj
rule.styles[obj.Tag()] = objToParams(obj)
}
}
}

View File

@ -177,6 +177,9 @@ func (view *viewData) ViewByID(id string) View {
}
func (view *viewData) Focusable() bool {
if focus, ok := boolProperty(view, Focusable, view.session); ok {
return focus
}
return false
}
@ -189,6 +192,9 @@ func (view *viewData) remove(tag string) {
case ID:
view.viewID = ""
case UserData:
delete(view.properties, tag)
case Style, StyleDisabled:
if _, ok := view.properties[tag]; ok {
delete(view.properties, tag)
@ -311,6 +317,9 @@ func (view *viewData) set(tag string, value interface{}) bool {
}
view.viewID = text
case UserData:
view.properties[tag] = value
case Style, StyleDisabled:
text, ok := value.(string)
if !ok {

View File

@ -1,6 +1,7 @@
package rui
import (
"os"
"path/filepath"
"strings"
)
@ -135,5 +136,13 @@ func CreateViewFromResources(session Session, name string) View {
}
}
if resources.path != "" {
if data, err := os.ReadFile(resources.path + viewDir + "/" + name); err == nil {
if data := ParseDataText(string(data)); data != nil {
return CreateViewFromObject(session, data)
}
}
}
return nil
}

View File

@ -1053,19 +1053,13 @@ func GetCurrent(view View, subviewID string) int {
view = ViewByID(view, subviewID)
}
var defaultValue int
switch view.Tag() {
case "ListView":
defaultValue = -1
default:
defaultValue = 0
}
defaultValue := -1
if view != nil {
if result, ok := intProperty(view, Current, view.Session(), defaultValue); ok {
return result
} else if view.Tag() != "ListView" {
defaultValue = 0
}
}
return -1
return defaultValue
}