From 8a625dcc78117ef4a7a050d58b17dc0f10572621 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Thu, 14 Apr 2022 12:05:45 +0300 Subject: [PATCH] Added: "focusable" and "user-data" properties, ConstantTags and ColorTags function to Session, ReloadTableViewData function. Bug fixing --- .vscode/launch.json | 4 +- CHANGELOG.md | 6 +++ README-ru.md | 28 +++++++----- README.md | 32 ++++++++------ app_scripts.js | 2 +- app_styles.css | 18 ++++++-- defaultTheme.rui | 2 +- propertyNames.go | 6 +++ propertySet.go | 1 + session.go | 52 ++++++++-------------- sessionTheme.go | 105 +++++++++++++++++++++++++------------------- tableViewUtils.go | 18 ++++++++ tabsLayout.go | 4 +- theme.go | 39 ++++++++++++---- view.go | 9 ++++ viewFactory.go | 9 ++++ viewUtils.go | 14 ++---- 17 files changed, 216 insertions(+), 133 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index e6df305..bf57f4f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,8 +9,8 @@ "type": "go", "request": "launch", "mode": "auto", - "program": "${workspaceRoot}/demo", - //"program": "${workspaceRoot}/editor", + //"program": "${workspaceRoot}/demo", + "program": "${workspaceRoot}/ruiEditor", "env": {}, "args": [] } diff --git a/CHANGELOG.md b/CHANGELOG.md index cc9b323..aa92d48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README-ru.md b/README-ru.md index 7fffa1f..ed0831d 100644 --- a/README-ru.md +++ b/README-ru.md @@ -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) diff --git a/README.md b/README.md index 9946974..e0b9b4f 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/app_scripts.js b/app_scripts.js index 427fa36..a75afd3 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -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) diff --git a/app_styles.css b/app_styles.css index dc9dfa3..8d65893 100644 --- a/app_styles.css +++ b/app_styles.css @@ -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 { diff --git a/defaultTheme.rui b/defaultTheme.rui index 1524a89..23f719f 100644 --- a/defaultTheme.rui +++ b/defaultTheme.rui @@ -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, }, diff --git a/propertyNames.go b/propertyNames.go index 7e97a88..166ed3b 100644 --- a/propertyNames.go +++ b/propertyNames.go @@ -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" ) diff --git a/propertySet.go b/propertySet.go index dafd73d..127571c 100644 --- a/propertySet.go +++ b/propertySet.go @@ -37,6 +37,7 @@ var angleProperties = []string{ var boolProperties = []string{ Disabled, + Focusable, Inset, BackfaceVisible, ReadOnly, diff --git a/session.go b/session.go index a9b7bc6..e2b2f0f 100644 --- a/session.go +++ b/session.go @@ -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") diff --git a/sessionTheme.go b/sessionTheme.go index 3e9d149..002a459 100644 --- a/sessionTheme.go +++ b/sessionTheme.go @@ -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 +} diff --git a/tableViewUtils.go b/tableViewUtils.go index 5ec0570..73663fe 100644 --- a/tableViewUtils.go +++ b/tableViewUtils.go @@ -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 +} diff --git a/tabsLayout.go b/tabsLayout.go index f51795f..8aecb8d 100644 --- a/tabsLayout.go +++ b/tabsLayout.go @@ -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) } diff --git a/theme.go b/theme.go index 919c1c5..e0e942b 100644 --- a/theme.go +++ b/theme.go @@ -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) } } } diff --git a/view.go b/view.go index 3b657c0..e30eeed 100644 --- a/view.go +++ b/view.go @@ -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 { diff --git a/viewFactory.go b/viewFactory.go index e2a25a2..0ee7778 100644 --- a/viewFactory.go +++ b/viewFactory.go @@ -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 } diff --git a/viewUtils.go b/viewUtils.go index b7e9e17..62de339 100644 --- a/viewUtils.go +++ b/viewUtils.go @@ -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 }