forked from mbk-lab/rui_orig
Merge branch 'Work'
This commit is contained in:
commit
b0e51e0830
|
@ -1,3 +1,12 @@
|
||||||
|
# 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"
|
||||||
|
* Added the following events to TableView: "table-cell-selected", "table-cell-clicked", "table-row-selected", "table-row-clicked"
|
||||||
|
* Bug fixing
|
||||||
|
|
||||||
# v0.4.0
|
# v0.4.0
|
||||||
|
|
||||||
* Added SetTitle and SetTitleColor function to the Session interface
|
* Added SetTitle and SetTitleColor function to the Session interface
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
137
README-ru.md
137
README-ru.md
|
@ -21,28 +21,33 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := rui.NewApplication("Hello world", "icon.svg", createHelloWorldSession)
|
rui.StartApp("localhost:8000", createHelloWorldSession, rui.AppParams{
|
||||||
app.Start("localhost:8000")
|
Title: "Hello world",
|
||||||
|
Icon: "icon.svg",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
В функции main создается rui приложение и запускается основной цикл.
|
В функции main вызывается функция StartApp. Она создает rui приложение и запускает его основной цикл.
|
||||||
При создании приложения задаются 3 параметра: имя приложения, имя иконки и функция createHelloWorldSession.
|
Функция StartApp имеет 3 параметра:
|
||||||
Функция createHelloWorldSession создает структуру реализующую интерфейс SessionContent:
|
1) IP адрес по которому будет доступно приложение (в нашем примере это "localhost:8000")
|
||||||
|
2) Фуекция создает структуру реализующую интерфейс SessionContent
|
||||||
|
3) Дополнительные опциональные параметры (в нашем примере это заголовок и имя файла иконки)
|
||||||
|
|
||||||
|
Интерфейс SessionContent объявлен как:
|
||||||
|
|
||||||
type SessionContent interface {
|
type SessionContent interface {
|
||||||
CreateRootView(session rui.Session) rui.View
|
CreateRootView(session rui.Session) rui.View
|
||||||
}
|
}
|
||||||
|
|
||||||
Для каждой новой сессии создается свой экземпляр структуры.
|
|
||||||
|
|
||||||
Функция CreateRootView интерфейса SessionContent создает корневой элемент.
|
Функция CreateRootView интерфейса SessionContent создает корневой элемент.
|
||||||
|
|
||||||
Когда пользователь обращается к приложению набрав в браузере адрес "localhost:8000", то создается новая сессия,
|
Когда пользователь обращается к приложению набрав в браузере адрес "localhost:8000", то создается новая сессия,
|
||||||
для нее создается новый экземпляр структуры helloWorldSession и в конце вызывается функция CreateRootView.
|
для нее создается новый экземпляр структуры helloWorldSession и в конце вызывается функция CreateRootView.
|
||||||
Функция createRootView возвращает представление строки текста, создаваемое с помощью функции NewTextView.
|
Функция createRootView возвращает представление строки текста, создаваемое с помощью функции NewTextView.
|
||||||
|
|
||||||
Если вы хотите чтобы приложение было видно вне вашего компьютера, то поменяйте адрес в функции Start:
|
Если вы хотите чтобы приложение было видно вне вашего компьютера, то поменяйте адрес в функции Start:
|
||||||
|
|
||||||
app.Start(rui.GetLocalIP() + ":80")
|
rui.StartApp(rui.GetLocalIP() + ":80", ...
|
||||||
|
|
||||||
## Используемые типы данных
|
## Используемые типы данных
|
||||||
|
|
||||||
|
@ -3260,6 +3265,7 @@ Cell(row, column int) возвращает содержимое ячейки т
|
||||||
* rui.Color
|
* rui.Color
|
||||||
* rui.View
|
* rui.View
|
||||||
* fmt.Stringer
|
* fmt.Stringer
|
||||||
|
* rui.VerticalTableJoin, rui.HorizontalTableJoin
|
||||||
|
|
||||||
Свойству "content" можно также присваивать следующие типы данных
|
Свойству "content" можно также присваивать следующие типы данных
|
||||||
|
|
||||||
|
@ -3314,11 +3320,11 @@ Cell(row, column int) возвращает содержимое ячейки т
|
||||||
|
|
||||||
В этом случае таблица будет иметь следующий вид
|
В этом случае таблица будет иметь следующий вид
|
||||||
|
|
||||||
|------|----------------|
|
|------|----------------|
|
||||||
| | |
|
| | |
|
||||||
| |-------|--------|
|
| |-------|--------|
|
||||||
| | | |
|
| | | |
|
||||||
|------|-------|--------|
|
|------|-------|--------|
|
||||||
|
|
||||||
Если в качестве значения свойства "content" используется [][]interface{}, то для объединения
|
Если в качестве значения свойства "content" используется [][]interface{}, то для объединения
|
||||||
ячеек используются пустые структуры
|
ячеек используются пустые структуры
|
||||||
|
@ -3421,7 +3427,110 @@ TableColumnStyle объявлена как
|
||||||
| 2 | CenterAlign | "center" | Выравнивание по центру |
|
| 2 | CenterAlign | "center" | Выравнивание по центру |
|
||||||
| 3, 4 | BaselineAlign | "baseline" | Выравнивание по базовой линии |
|
| 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
|
## Пользовательский View
|
||||||
|
|
||||||
|
|
163
README.md
163
README.md
|
@ -1,6 +1,6 @@
|
||||||
# RUI library
|
# 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,
|
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.
|
and the browser is used as a thin client. WebSocket is used for client-server communication.
|
||||||
|
@ -21,13 +21,19 @@ and the browser is used as a thin client. WebSocket is used for client-server co
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := rui.NewApplication("Hello world", "icon.svg", createHelloWorldSession)
|
rui.StartApp("localhost:8000", createHelloWorldSession, rui.AppParams{
|
||||||
app.Start("localhost:8000")
|
Title: "Hello world",
|
||||||
|
Icon: "icon.svg",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
In the main function, a rui application is created and the main loop is started.
|
In the main function, the StartApp function is called. It creates a rui app and runs its main loop.
|
||||||
When creating an application, 3 parameters are set: the name of the application, the name of the icon, and the createHelloWorldSession function.
|
The StartApp function has 3 parameters:
|
||||||
The createHelloWorldSession function creates a structure that implements the SessionContent interface:
|
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 {
|
type SessionContent interface {
|
||||||
CreateRootView(session rui.Session) rui.View
|
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:
|
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
|
## Used data types
|
||||||
|
|
||||||
|
@ -768,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
|
If the x- and y-radii are the same, then you can use the auxiliary properties
|
||||||
|
|
||||||
| Property | Constant | Description |
|
| Property | Constant | Description |
|
||||||
|----------------|--------------|----------------------------|
|
|----------------|-------------|----------------------------|
|
||||||
| "top-left" | TopLeft | top left corner radius |
|
| "top-left" | TopLeft | top left corner radius |
|
||||||
| "top-right" | TopRight | top right corner radius |
|
| "top-right" | TopRight | top right corner radius |
|
||||||
| "bottom-left" | BottomLeft | bottom left corner radius |
|
| "bottom-left" | BottomLeft | bottom left corner radius |
|
||||||
| "bottom-right" | BottomRight | bottom right corner radius |
|
| "bottom-right" | BottomRight | bottom right corner radius |
|
||||||
|
|
||||||
To set all radii to the same values, use the "x" and "y" properties
|
To set all radii to the same values, use the "x" and "y" properties
|
||||||
|
|
||||||
|
@ -1301,7 +1307,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.
|
* 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).
|
* 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 |
|
| | New lines | Spaces and Tabs | Text wrapping | End of line spaces | End-of-line other space separators |
|
||||||
|-----------------------|-----------|-----------------|---------------|--------------------|------------------------------------|
|
|-----------------------|-----------|-----------------|---------------|--------------------|------------------------------------|
|
||||||
|
@ -1448,7 +1454,8 @@ You can get the value of this property using the function
|
||||||
|
|
||||||
#### "text-indent" property
|
#### "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
|
You can get the value of this property using the function
|
||||||
|
|
||||||
|
@ -1514,7 +1521,7 @@ You can get the value of this property using the function
|
||||||
#### "writing-mode" property
|
#### "writing-mode" property
|
||||||
The "writing-mode" (WritingMode constant) int property defines how the lines of text are arranged
|
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.
|
vertically or horizontally, as well as the direction in which the lines are displayed.
|
||||||
Possible values are:
|
Possible values are:
|
||||||
|
|
||||||
| Value | Constant | Description |
|
| Value | Constant | Description |
|
||||||
|:-----:|-----------------------|------------------------------------------------------------------|
|
|:-----:|-----------------------|------------------------------------------------------------------|
|
||||||
|
@ -1871,8 +1878,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 |
|
| 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 |
|
| 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 |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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 +2045,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. |
|
| 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
|
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),
|
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.
|
the beginning is on the right, for other languages - on the left.
|
||||||
|
|
||||||
### "wrap" property
|
### "wrap" property
|
||||||
|
|
||||||
|
@ -2610,7 +2617,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 "wrap" is off (default), then horizontal scrolling is used.
|
||||||
If enabled, the text wraps to a new line when the EditView border is reached.
|
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 GetText(view View, subviewID string) string
|
||||||
func GetHint(view View, subviewID string) string
|
func GetHint(view View, subviewID string) string
|
||||||
|
@ -2665,7 +2672,7 @@ The value of the "date-picker-value" property can also be read using the functio
|
||||||
|
|
||||||
func GetNumberPickerValue(view View, subviewID string) float64
|
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 |
|
| Property | Constant | Restriction |
|
||||||
|--------------------|------------------|-------------------|
|
|--------------------|------------------|-------------------|
|
||||||
|
@ -2678,7 +2685,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.
|
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.
|
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 GetNumberPickerMinMax(view View, subviewID string) (float64, float64)
|
||||||
func GetNumberPickerStep(view View, subviewID string) float64
|
func GetNumberPickerStep(view View, subviewID string) float64
|
||||||
|
@ -3027,8 +3034,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. |
|
| 3 | EndToStartOrientation | Elements are arranged in a row from end to beginning. |
|
||||||
|
|
||||||
The start and end positions for StartToEndOrientation and EndToStartOrientation depend
|
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
|
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.
|
(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
|
You can get the value of this property using the function
|
||||||
|
|
||||||
|
@ -3224,6 +3231,7 @@ Cell(row, column int) returns the contents of a table cell. The Cell() function
|
||||||
* rui.Color
|
* rui.Color
|
||||||
* rui.View
|
* rui.View
|
||||||
* fmt.Stringer
|
* fmt.Stringer
|
||||||
|
* rui.VerticalTableJoin, rui.HorizontalTableJoin
|
||||||
|
|
||||||
The "content" property can also be assigned the following data types
|
The "content" property can also be assigned the following data types
|
||||||
|
|
||||||
|
@ -3277,11 +3285,11 @@ The "row-span" property specifies how many cells to merge vertically, and the "c
|
||||||
|
|
||||||
In this case, the table will look like this
|
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
|
If [][]interface{} is used as the value of the "content" property, then empty structures are used to merge cells
|
||||||
|
|
||||||
|
@ -3385,6 +3393,101 @@ the vertical alignment of data within a table cell. Valid values:
|
||||||
|
|
||||||
For horizontal alignment, use the "text-align" property
|
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
|
## Custom View
|
||||||
|
|
||||||
A custom View must implement the CustomView interface, which extends the ViewsContainer and View interfaces.
|
A custom View must implement the CustomView interface, which extends the ViewsContainer and View interfaces.
|
||||||
|
|
518
app_scripts.js
518
app_scripts.js
|
@ -42,9 +42,19 @@ function socketOpen() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const lang = window.navigator.languages;
|
const lang = window.navigator.language;
|
||||||
if (lang) {
|
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)");
|
const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
@ -615,11 +625,12 @@ function selectDropDownListItem(elementId, number) {
|
||||||
|
|
||||||
function listItemClickEvent(element, event) {
|
function listItemClickEvent(element, event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
var selected = false;
|
var selected = false;
|
||||||
if (element.classList) {
|
if (element.classList) {
|
||||||
selected = (element.classList.contains("ruiListItemFocused") || element.classList.contains("ruiListItemSelected"));
|
const focusStyle = getListFocusedItemStyle(element);
|
||||||
} else {
|
const blurStyle = getListSelectedItemStyle(element);
|
||||||
selected = element.className.indexOf("ruiListItemFocused") >= 0 || element.className.indexOf("ruiListItemSelected") >= 0;
|
selected = (element.classList.contains(focusStyle) || element.classList.contains(blurStyle));
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = element.parentNode.parentNode
|
var list = element.parentNode.parentNode
|
||||||
|
@ -640,26 +651,33 @@ 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) {
|
function selectListItem(element, item, needSendMessage) {
|
||||||
var currentId = element.getAttribute("data-current");
|
var currentId = element.getAttribute("data-current");
|
||||||
var message;
|
var message;
|
||||||
var focusStyle = element.getAttribute("data-focusitemstyle");
|
const focusStyle = getListFocusedItemStyle(element);
|
||||||
var blurStyle = element.getAttribute("data-bluritemstyle");
|
const blurStyle = getListSelectedItemStyle(element);
|
||||||
|
|
||||||
if (!focusStyle) {
|
|
||||||
focusStyle = "ruiListItemFocused"
|
|
||||||
}
|
|
||||||
if (!blurStyle) {
|
|
||||||
blurStyle = "ruiListItemSelected"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentId) {
|
if (currentId) {
|
||||||
var current = document.getElementById(currentId);
|
var current = document.getElementById(currentId);
|
||||||
if (current) {
|
if (current) {
|
||||||
if (current.classList) {
|
if (current.classList) {
|
||||||
current.classList.remove(focusStyle, blurStyle);
|
current.classList.remove(focusStyle, blurStyle);
|
||||||
} else { // IE < 10
|
|
||||||
current.className = "ruiListItem";
|
|
||||||
}
|
}
|
||||||
if (sendMessage) {
|
if (sendMessage) {
|
||||||
message = "itemUnselected{session=" + sessionID + ",id=" + element.id + "}";
|
message = "itemUnselected{session=" + sessionID + ",id=" + element.id + "}";
|
||||||
|
@ -671,14 +689,10 @@ function selectListItem(element, item, needSendMessage) {
|
||||||
if (element === document.activeElement) {
|
if (element === document.activeElement) {
|
||||||
if (item.classList) {
|
if (item.classList) {
|
||||||
item.classList.add(focusStyle);
|
item.classList.add(focusStyle);
|
||||||
} else { // IE < 10
|
|
||||||
item.className = "ruiListItem " + focusStyle
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (item.classList) {
|
if (item.classList) {
|
||||||
item.classList.add(blurStyle);
|
item.classList.add(blurStyle);
|
||||||
} else { // IE < 10
|
|
||||||
item.className = "ruiListItem " + blurStyle
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -690,6 +704,12 @@ function selectListItem(element, item, needSendMessage) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.scrollIntoViewIfNeeded) {
|
||||||
|
item.scrollIntoViewIfNeeded()
|
||||||
|
} else {
|
||||||
|
item.scrollIntoView({block: "nearest", inline: "nearest"});
|
||||||
|
}
|
||||||
|
/*
|
||||||
var left = item.offsetLeft - element.offsetLeft;
|
var left = item.offsetLeft - element.offsetLeft;
|
||||||
if (left < element.scrollLeft) {
|
if (left < element.scrollLeft) {
|
||||||
element.scrollLeft = left;
|
element.scrollLeft = left;
|
||||||
|
@ -708,7 +728,7 @@ function selectListItem(element, item, needSendMessage) {
|
||||||
var bottom = top + item.offsetHeight
|
var bottom = top + item.offsetHeight
|
||||||
if (bottom > element.scrollTop + element.clientHeight) {
|
if (bottom > element.scrollTop + element.clientHeight) {
|
||||||
element.scrollTop = bottom - element.clientHeight;
|
element.scrollTop = bottom - element.clientHeight;
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needSendMessage && message != undefined) {
|
if (needSendMessage && message != undefined) {
|
||||||
|
@ -801,24 +821,29 @@ function findBottomListItem(list, x, y) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
function listViewKeyDownEvent(element, event) {
|
function getKey(event) {
|
||||||
var key;
|
|
||||||
if (event.key) {
|
if (event.key) {
|
||||||
key = event.key;
|
return event.key;
|
||||||
} else if (event.keyCode) {
|
}
|
||||||
|
|
||||||
|
if (event.keyCode) {
|
||||||
switch (event.keyCode) {
|
switch (event.keyCode) {
|
||||||
case 13: key = "Enter"; break;
|
case 13: return "Enter";
|
||||||
case 32: key = " "; break;
|
case 32: return " ";
|
||||||
case 33: key = "PageUp"; break;
|
case 33: return "PageUp";
|
||||||
case 34: key = "PageDown"; break;
|
case 34: return "PageDown";
|
||||||
case 35: key = "End"; break;
|
case 35: return "End";
|
||||||
case 36: key = "Home"; break;
|
case 36: return "Home";
|
||||||
case 37: key = "ArrowLeft"; break;
|
case 37: return "ArrowLeft";
|
||||||
case 38: key = "ArrowUp"; break;
|
case 38: return "ArrowUp";
|
||||||
case 39: key = "ArrowRight"; break;
|
case 39: return "ArrowRight";
|
||||||
case 40: key = "ArrowDown"; break;
|
case 40: return "ArrowDown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listViewKeyDownEvent(element, event) {
|
||||||
|
const key = getKey(event);
|
||||||
if (key) {
|
if (key) {
|
||||||
var currentId = element.getAttribute("data-current");
|
var currentId = element.getAttribute("data-current");
|
||||||
var current
|
var current
|
||||||
|
@ -885,20 +910,9 @@ function listViewFocusEvent(element, event) {
|
||||||
if (currentId) {
|
if (currentId) {
|
||||||
var current = document.getElementById(currentId);
|
var current = document.getElementById(currentId);
|
||||||
if (current) {
|
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) {
|
if (current.classList) {
|
||||||
current.classList.remove(blurStyle);
|
current.classList.remove(getListSelectedItemStyle(element));
|
||||||
current.classList.add(focusStyle);
|
current.classList.add(getListFocusedItemStyle(element));
|
||||||
} else { // IE < 10
|
|
||||||
current.className = "ruiListItem " + focusStyle;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -909,20 +923,9 @@ function listViewBlurEvent(element, event) {
|
||||||
if (currentId) {
|
if (currentId) {
|
||||||
var current = document.getElementById(currentId);
|
var current = document.getElementById(currentId);
|
||||||
if (current) {
|
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) {
|
if (current.classList) {
|
||||||
current.classList.remove(focusStyle);
|
current.classList.remove(getListFocusedItemStyle(element));
|
||||||
current.classList.add(blurStyle);
|
current.classList.add(getListSelectedItemStyle(element));
|
||||||
} else { // IE < 10
|
|
||||||
current.className = "ruiListItem " + blurStyle;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1373,3 +1376,396 @@ function setTitleColor(color) {
|
||||||
function detailsEvent(element) {
|
function detailsEvent(element) {
|
||||||
sendMessage("details-open{session=" + sessionID + ",id=" + element.id + ",open=" + (element.open ? "1}" : "0}"));
|
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 || cell.getAttribute("data-disabled")) {
|
||||||
|
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);
|
||||||
|
if (cell.scrollIntoViewIfNeeded) {
|
||||||
|
cell.scrollIntoViewIfNeeded()
|
||||||
|
} else {
|
||||||
|
cell.scrollIntoView({block: "nearest", inline: "nearest"});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const tableRowID = element.id + "-" + row;
|
||||||
|
var tableRow = document.getElementById(tableRowID);
|
||||||
|
if (!tableRow || tableRow.getAttribute("data-disabled")) {
|
||||||
|
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);
|
||||||
|
if (tableRow.scrollIntoViewIfNeeded) {
|
||||||
|
tableRow.scrollIntoViewIfNeeded()
|
||||||
|
} else {
|
||||||
|
tableRow.scrollIntoView({block: "nearest", inline: "nearest"});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 + "}");
|
||||||
|
}
|
|
@ -27,19 +27,24 @@ var defaultThemeText string
|
||||||
|
|
||||||
// Application - app interface
|
// Application - app interface
|
||||||
type Application interface {
|
type Application interface {
|
||||||
// Start - start the application life cycle
|
|
||||||
Start(addr string)
|
|
||||||
Finish()
|
Finish()
|
||||||
nextSessionID() int
|
nextSessionID() int
|
||||||
removeSession(id int)
|
removeSession(id int)
|
||||||
}
|
}
|
||||||
|
|
||||||
type application struct {
|
type application struct {
|
||||||
name, icon string
|
params AppParams
|
||||||
createContentFunc func(Session) SessionContent
|
createContentFunc func(Session) SessionContent
|
||||||
sessions map[int]Session
|
sessions map[int]Session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppParams defines parameters of the app
|
||||||
|
type AppParams struct {
|
||||||
|
Title string
|
||||||
|
TitleColor Color
|
||||||
|
Icon string
|
||||||
|
}
|
||||||
|
|
||||||
func (app *application) getStartPage() string {
|
func (app *application) getStartPage() string {
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
defer freeStringBuilder(buffer)
|
defer freeStringBuilder(buffer)
|
||||||
|
@ -49,12 +54,19 @@ func (app *application) getStartPage() string {
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>`)
|
<title>`)
|
||||||
buffer.WriteString(app.name)
|
buffer.WriteString(app.params.Title)
|
||||||
buffer.WriteString("</title>")
|
buffer.WriteString("</title>")
|
||||||
if app.icon != "" {
|
if app.params.Icon != "" {
|
||||||
buffer.WriteString(`
|
buffer.WriteString(`
|
||||||
<link rel="icon" href="`)
|
<link rel="icon" href="`)
|
||||||
buffer.WriteString(app.icon)
|
buffer.WriteString(app.params.Icon)
|
||||||
|
buffer.WriteString(`">`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if app.params.TitleColor != 0 {
|
||||||
|
buffer.WriteString(`
|
||||||
|
<meta name="theme-color" content="`)
|
||||||
|
buffer.WriteString(app.params.TitleColor.cssString())
|
||||||
buffer.WriteString(`">`)
|
buffer.WriteString(`">`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,9 +90,8 @@ func (app *application) getStartPage() string {
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *application) init(name, icon string) {
|
func (app *application) init(params AppParams) {
|
||||||
app.name = name
|
app.params = params
|
||||||
app.icon = icon
|
|
||||||
app.sessions = map[int]Session{}
|
app.sessions = map[int]Session{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,12 +288,14 @@ func (app *application) startSession(params DataObject, events chan DataObject,
|
||||||
return session, answerText
|
return session, answerText
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApplication - create the new application of the single view type.
|
// NewApplication - create the new application and start it
|
||||||
func NewApplication(name, icon string, createContentFunc func(Session) SessionContent) Application {
|
func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) {
|
||||||
app := new(application)
|
app := new(application)
|
||||||
app.init(name, icon)
|
app.init(params)
|
||||||
app.createContentFunc = createContentFunc
|
app.createContentFunc = createContentFunc
|
||||||
return app
|
|
||||||
|
http.Handle("/", app)
|
||||||
|
log.Fatal(http.ListenAndServe(addr, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func OpenBrowser(url string) bool {
|
func OpenBrowser(url string) bool {
|
||||||
|
|
|
@ -148,11 +148,15 @@ func (customView *CustomViewData) Scroll() Frame {
|
||||||
return customView.superView.Scroll()
|
return customView.superView.Scroll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (customView *CustomViewData) HasFocus() bool {
|
||||||
|
return customView.superView.HasFocus()
|
||||||
|
}
|
||||||
|
|
||||||
func (customView *CustomViewData) onResize(self View, x, y, width, height float64) {
|
func (customView *CustomViewData) onResize(self View, x, y, width, height float64) {
|
||||||
customView.superView.onResize(customView.superView, x, y, width, height)
|
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)
|
customView.superView.onItemResize(customView.superView, index, x, y, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,10 @@ func (picker *datePickerData) Init(session Session) {
|
||||||
picker.dateChangedListeners = []func(DatePicker, time.Time){}
|
picker.dateChangedListeners = []func(DatePicker, time.Time){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (picker *datePickerData) Focusable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (picker *datePickerData) normalizeTag(tag string) string {
|
func (picker *datePickerData) normalizeTag(tag string) string {
|
||||||
tag = strings.ToLower(tag)
|
tag = strings.ToLower(tag)
|
||||||
switch tag {
|
switch tag {
|
||||||
|
|
|
@ -213,24 +213,32 @@ theme {
|
||||||
text-color = @ruiPopupTextColor,
|
text-color = @ruiPopupTextColor,
|
||||||
radius = 4px,
|
radius = 4px,
|
||||||
shadow = _{spread-radius=4px, blur=16px, color=#80808080},
|
shadow = _{spread-radius=4px, blur=16px, color=#80808080},
|
||||||
}
|
},
|
||||||
ruiPopupTitle {
|
ruiPopupTitle {
|
||||||
background-color = @ruiPopupTitleColor,
|
background-color = @ruiPopupTitleColor,
|
||||||
text-color = @ruiPopupTitleTextColor,
|
text-color = @ruiPopupTitleTextColor,
|
||||||
min-height = 24px,
|
min-height = 24px,
|
||||||
}
|
},
|
||||||
ruiMessageText {
|
ruiMessageText {
|
||||||
padding-left = 64px,
|
padding-left = 64px,
|
||||||
padding-right = 64px,
|
padding-right = 64px,
|
||||||
padding-top = 32px,
|
padding-top = 32px,
|
||||||
padding-bottom = 32px,
|
padding-bottom = 32px,
|
||||||
}
|
},
|
||||||
ruiPopupMenuItem {
|
ruiPopupMenuItem {
|
||||||
padding-top = 4px,
|
padding-top = 4px,
|
||||||
padding-bottom = 4px,
|
padding-bottom = 4px,
|
||||||
padding-left = 8px,
|
padding-left = 8px,
|
||||||
padding-right = 8px,
|
padding-right = 8px,
|
||||||
}
|
},
|
||||||
|
ruiCurrentTableCell {
|
||||||
|
background-color=@ruiSelectedColor,
|
||||||
|
text-color=@ruiSelectedTextColor,
|
||||||
|
},
|
||||||
|
ruiCurrentTableCellFocused {
|
||||||
|
background-color=@ruiHighlightColor,
|
||||||
|
text-color=@ruiHighlightTextColor,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,13 @@ func textCanvasDemo(canvas rui.Canvas) {
|
||||||
canvas.SetTextAlign(rui.LeftAlign)
|
canvas.SetTextAlign(rui.LeftAlign)
|
||||||
canvas.SetTextBaseline(rui.TopBaseline)
|
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.FillText(10, 10, "Default font")
|
||||||
canvas.StrokeText(300, 10, "Default font")
|
canvas.StrokeText(300, 10, "Default font")
|
||||||
|
|
||||||
|
@ -136,7 +142,11 @@ func textCanvasDemo(canvas rui.Canvas) {
|
||||||
func textAlignCanvasDemo(canvas rui.Canvas) {
|
func textAlignCanvasDemo(canvas rui.Canvas) {
|
||||||
canvas.Save()
|
canvas.Save()
|
||||||
canvas.SetFont("sans-serif", rui.Pt(10))
|
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)
|
canvas.SetSolidColorStrokeStyle(0xFF00FFFF)
|
||||||
|
|
||||||
baseline := []string{"Alphabetic", "Top", "Middle", "Bottom", "Hanging", "Ideographic"}
|
baseline := []string{"Alphabetic", "Top", "Middle", "Bottom", "Hanging", "Ideographic"}
|
||||||
|
@ -176,18 +186,28 @@ func lineStyleCanvasDemo(canvas rui.Canvas) {
|
||||||
canvas.SetLineWidth(1)
|
canvas.SetLineWidth(1)
|
||||||
y := float64(40 + 20*i)
|
y := float64(40 + 20*i)
|
||||||
canvas.DrawLine(10, y, 180, y)
|
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.SetLineWidth(10)
|
||||||
canvas.SetLineCap(i)
|
canvas.SetLineCap(i)
|
||||||
canvas.DrawLine(20, y, 170, y)
|
canvas.DrawLine(20, y, 170, y)
|
||||||
canvas.FillText(200, y, cap)
|
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.SetFont("courier", rui.Pt(12))
|
||||||
canvas.FillText(80, 115, "SetLineJoin(...)")
|
canvas.FillText(80, 115, "SetLineJoin(...)")
|
||||||
|
|
||||||
canvas.SetSolidColorStrokeStyle(0xFF000000)
|
|
||||||
canvas.SetLineWidth(10)
|
canvas.SetLineWidth(10)
|
||||||
canvas.SetLineCap(rui.ButtCap)
|
canvas.SetLineCap(rui.ButtCap)
|
||||||
|
|
||||||
|
@ -206,14 +226,11 @@ func lineStyleCanvasDemo(canvas rui.Canvas) {
|
||||||
canvas.StrokePath(path)
|
canvas.StrokePath(path)
|
||||||
canvas.FillText(210, y+20, join)
|
canvas.FillText(210, y+20, join)
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.SetSolidColorStrokeStyle(0xFF0000FF)
|
|
||||||
canvas.SetFont("courier", rui.Pt(12))
|
canvas.SetFont("courier", rui.Pt(12))
|
||||||
canvas.FillText(20, 300, "SetLineDash([]float64{16, 8, 4, 8}, ...)")
|
canvas.FillText(20, 300, "SetLineDash([]float64{16, 8, 4, 8}, ...)")
|
||||||
|
|
||||||
canvas.SetFont("courier", rui.Pt(10))
|
canvas.SetFont("courier", rui.Pt(10))
|
||||||
canvas.SetLineDash([]float64{16, 8, 4, 8}, 0)
|
canvas.SetLineDash([]float64{16, 8, 4, 8}, 0)
|
||||||
canvas.SetSolidColorStrokeStyle(0xFF000000)
|
|
||||||
canvas.SetLineWidth(4)
|
canvas.SetLineWidth(4)
|
||||||
|
|
||||||
canvas.SetLineCap(rui.ButtCap)
|
canvas.SetLineCap(rui.ButtCap)
|
||||||
|
@ -246,8 +263,13 @@ func transformCanvasDemo(canvas rui.Canvas) {
|
||||||
y1 := y0 + float64((ny-1)*20)
|
y1 := y0 + float64((ny-1)*20)
|
||||||
|
|
||||||
canvas.SetFont("serif", rui.Pt(10))
|
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.SetTextAlign(rui.CenterAlign)
|
||||||
canvas.SetTextBaseline(rui.BottomBaseline)
|
canvas.SetTextBaseline(rui.BottomBaseline)
|
||||||
for i := 0; i < nx; i++ {
|
for i := 0; i < nx; i++ {
|
||||||
|
@ -266,7 +288,11 @@ func transformCanvasDemo(canvas rui.Canvas) {
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.SetFont("courier", rui.Pt(14))
|
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.SetTextAlign(rui.CenterAlign)
|
||||||
canvas.SetTextBaseline(rui.TopBaseline)
|
canvas.SetTextBaseline(rui.TopBaseline)
|
||||||
|
|
||||||
|
|
11
demo/main.go
11
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)
|
rui.Set(demo.rootView, "rootTitleButton", rui.ClickEvent, demo.clickMenuButton)
|
||||||
demo.showPage(0)
|
demo.showPage(0)
|
||||||
if color, ok := rui.StringToColor("#ffc0ded9"); ok {
|
|
||||||
session.SetTitleColor(color)
|
|
||||||
}
|
|
||||||
|
|
||||||
return demo.rootView
|
return demo.rootView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,11 +154,14 @@ func (demo *demoSession) showPage(index int) {
|
||||||
func main() {
|
func main() {
|
||||||
rui.ProtocolInDebugLog = true
|
rui.ProtocolInDebugLog = true
|
||||||
rui.AddEmbedResources(&resources)
|
rui.AddEmbedResources(&resources)
|
||||||
app := rui.NewApplication("RUI demo", "icon.svg", createDemo)
|
|
||||||
|
|
||||||
//addr := rui.GetLocalIP() + ":8080"
|
//addr := rui.GetLocalIP() + ":8080"
|
||||||
addr := "localhost:8000"
|
addr := "localhost:8000"
|
||||||
fmt.Print(addr)
|
fmt.Print(addr)
|
||||||
rui.OpenBrowser("http://" + addr)
|
rui.OpenBrowser("http://" + addr)
|
||||||
app.Start(addr)
|
rui.StartApp(addr, createDemo, rui.AppParams{
|
||||||
|
Title: "RUI demo",
|
||||||
|
Icon: "icon.svg",
|
||||||
|
TitleColor: rui.Color(0xffc0ded9),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ GridLayout {
|
||||||
content = [
|
content = [
|
||||||
GridLayout {
|
GridLayout {
|
||||||
id = mouseEventsTest, cell-horizontal-align = center, cell-vertical-align = center,
|
id = mouseEventsTest, cell-horizontal-align = center, cell-vertical-align = center,
|
||||||
height = 150%,
|
height = 100%,
|
||||||
border = _{ style = solid, width = 1px, color = gray},
|
border = _{ style = solid, width = 1px, color = gray},
|
||||||
content = [
|
content = [
|
||||||
TextView {
|
TextView {
|
||||||
|
|
|
@ -32,6 +32,12 @@ GridLayout {
|
||||||
DropDownList { row = 5, column = 1, id = tableFootStyle, current = 0, items = ["none", "tableFoot1", "rui.Params"]},
|
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 = 6, column = 0:1, id = tableRowStyle, content = "Row style" },
|
||||||
Checkbox { row = 7, column = 0:1, id = tableColumnStyle, content = "Column 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"]},
|
||||||
|
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"]},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -40,6 +46,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 {
|
func createTableViewDemo(session rui.Session) rui.View {
|
||||||
view := rui.CreateViewFromText(session, tableViewDemoText)
|
view := rui.CreateViewFromText(session, tableViewDemoText)
|
||||||
if view == nil {
|
if view == nil {
|
||||||
|
@ -93,6 +124,49 @@ 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) {
|
rui.Set(view, "tableCellGap", rui.DropDownEvent, func(list rui.DropDownList, number int) {
|
||||||
if number == 0 {
|
if number == 0 {
|
||||||
rui.Set(view, "demoTableView1", rui.Gap, rui.Px(0))
|
rui.Set(view, "demoTableView1", rui.Gap, rui.Px(0))
|
||||||
|
@ -202,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
|
return view
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ GridLayout {
|
||||||
content = [
|
content = [
|
||||||
GridLayout {
|
GridLayout {
|
||||||
id = touchEventsTest, cell-horizontal-align = center, cell-vertical-align = center,
|
id = touchEventsTest, cell-horizontal-align = center, cell-vertical-align = center,
|
||||||
height = 150%,
|
height = 100%,
|
||||||
border = _{ style = solid, width = 1px, color = gray},
|
border = _{ style = solid, width = 1px, color = gray},
|
||||||
content = [
|
content = [
|
||||||
TextView {
|
TextView {
|
||||||
|
|
|
@ -39,6 +39,10 @@ func (list *dropDownListData) Init(session Session) {
|
||||||
list.dropDownListener = []func(DropDownList, int){}
|
list.dropDownListener = []func(DropDownList, int){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (list *dropDownListData) Focusable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (list *dropDownListData) Remove(tag string) {
|
func (list *dropDownListData) Remove(tag string) {
|
||||||
list.remove(strings.ToLower(tag))
|
list.remove(strings.ToLower(tag))
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,10 @@ func (edit *editViewData) Init(session Session) {
|
||||||
edit.tag = "EditView"
|
edit.tag = "EditView"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (edit *editViewData) Focusable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (edit *editViewData) normalizeTag(tag string) string {
|
func (edit *editViewData) normalizeTag(tag string) string {
|
||||||
tag = strings.ToLower(tag)
|
tag = strings.ToLower(tag)
|
||||||
switch tag {
|
switch tag {
|
||||||
|
|
|
@ -89,6 +89,10 @@ func (picker *filePickerData) Init(session Session) {
|
||||||
picker.fileSelectedListeners = []func(FilePicker, []FileInfo){}
|
picker.fileSelectedListeners = []func(FilePicker, []FileInfo){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (picker *filePickerData) Focusable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (picker *filePickerData) Files() []FileInfo {
|
func (picker *filePickerData) Files() []FileInfo {
|
||||||
return picker.files
|
return picker.files
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,11 +140,9 @@ func getFocusListeners(view View, subviewID string, tag string) []func(View) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func focusEventsHtml(view View, buffer *strings.Builder) {
|
func focusEventsHtml(view View, buffer *strings.Builder) {
|
||||||
for tag, js := range focusEvents {
|
if view.Focusable() {
|
||||||
if value := view.getRaw(tag); value != nil {
|
for _, js := range focusEvents {
|
||||||
if listeners, ok := value.([]func(View)); ok && len(listeners) > 0 {
|
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
||||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
25
listView.go
25
listView.go
|
@ -929,6 +929,8 @@ func (listView *listViewData) htmlProperties(self View, buffer *strings.Builder)
|
||||||
buffer.WriteString(strconv.Itoa(current))
|
buffer.WriteString(strconv.Itoa(current))
|
||||||
buffer.WriteRune('"')
|
buffer.WriteRune('"')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listView.viewData.htmlProperties(self, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1084,14 +1086,12 @@ func (listView *listViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
func (listView *listViewData) handleCommand(self View, command string, data DataObject) bool {
|
func (listView *listViewData) handleCommand(self View, command string, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "itemSelected":
|
case "itemSelected":
|
||||||
if text, ok := data.PropertyValue(`number`); ok {
|
if number, ok := dataIntProperty(data, `number`); ok {
|
||||||
if number, err := strconv.Atoi(text); err == nil {
|
listView.properties[Current] = number
|
||||||
listView.properties[Current] = number
|
for _, listener := range listView.selectedListeners {
|
||||||
for _, listener := range listView.selectedListeners {
|
listener(listView, number)
|
||||||
listener(listView, number)
|
|
||||||
}
|
|
||||||
listView.propertyChangedEvent(Current)
|
|
||||||
}
|
}
|
||||||
|
listView.propertyChangedEvent(Current)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "itemUnselected":
|
case "itemUnselected":
|
||||||
|
@ -1163,9 +1163,14 @@ func (listView *listViewData) onItemClick() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) onItemResize(self View, index int, x, y, width, height float64) {
|
func (listView *listViewData) onItemResize(self View, index string, x, y, width, height float64) {
|
||||||
if index >= 0 && index < len(listView.itemFrame) {
|
n, err := strconv.Atoi(index)
|
||||||
listView.itemFrame[index] = Frame{Left: x, Top: y, Width: width, Height: height}
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -168,6 +168,10 @@ func (player *mediaPlayerData) Init(session Session) {
|
||||||
player.tag = "MediaPlayer"
|
player.tag = "MediaPlayer"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (player *mediaPlayerData) Focusable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (player *mediaPlayerData) Remove(tag string) {
|
func (player *mediaPlayerData) Remove(tag string) {
|
||||||
player.remove(strings.ToLower(tag))
|
player.remove(strings.ToLower(tag))
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,10 @@ func (picker *numberPickerData) Init(session Session) {
|
||||||
picker.numberChangedListeners = []func(NumberPicker, float64){}
|
picker.numberChangedListeners = []func(NumberPicker, float64){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (picker *numberPickerData) Focusable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (picker *numberPickerData) normalizeTag(tag string) string {
|
func (picker *numberPickerData) normalizeTag(tag string) string {
|
||||||
tag = strings.ToLower(tag)
|
tag = strings.ToLower(tag)
|
||||||
switch tag {
|
switch tag {
|
||||||
|
|
|
@ -412,6 +412,11 @@ var enumProperties = map[string]struct {
|
||||||
"",
|
"",
|
||||||
[]string{"none", "metadata", "auto"},
|
[]string{"none", "metadata", "auto"},
|
||||||
},
|
},
|
||||||
|
SelectionMode: {
|
||||||
|
[]string{"none", "cell", "row"},
|
||||||
|
"",
|
||||||
|
[]string{"none", "cell", "row"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func notCompatibleType(tag string, value interface{}) {
|
func notCompatibleType(tag string, value interface{}) {
|
||||||
|
|
|
@ -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 {
|
func (view *viewData) setFrameListener(tag string, value interface{}) bool {
|
||||||
|
|
27
session.go
27
session.go
|
@ -33,6 +33,10 @@ type Session interface {
|
||||||
Color(tag string) (Color, bool)
|
Color(tag string) (Color, bool)
|
||||||
// SetCustomTheme set the custom theme
|
// SetCustomTheme set the custom theme
|
||||||
SetCustomTheme(name string) bool
|
SetCustomTheme(name string) bool
|
||||||
|
// 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 returns the current session language
|
||||||
Language() string
|
Language() string
|
||||||
// SetLanguage set the current session language
|
// SetLanguage set the current session language
|
||||||
|
@ -106,6 +110,7 @@ type sessionData struct {
|
||||||
touchScreen bool
|
touchScreen bool
|
||||||
textDirection int
|
textDirection int
|
||||||
pixelRatio float64
|
pixelRatio float64
|
||||||
|
userAgent string
|
||||||
language string
|
language string
|
||||||
languages []string
|
languages []string
|
||||||
checkboxOff string
|
checkboxOff string
|
||||||
|
@ -148,12 +153,20 @@ func newSession(app Application, id int, customTheme string, params DataObject)
|
||||||
session.touchScreen = (value == "1" || value == "true")
|
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, ok := params.PropertyValue("direction"); ok {
|
||||||
if value == "rtl" {
|
if value == "rtl" {
|
||||||
session.textDirection = RightToLeftDirection
|
session.textDirection = RightToLeftDirection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if value, ok := params.PropertyValue("language"); ok {
|
||||||
|
session.language = value
|
||||||
|
}
|
||||||
|
|
||||||
if value, ok := params.PropertyValue("languages"); ok {
|
if value, ok := params.PropertyValue("languages"); ok {
|
||||||
session.languages = strings.Split(value, ",")
|
session.languages = strings.Split(value, ",")
|
||||||
}
|
}
|
||||||
|
@ -378,14 +391,10 @@ func (session *sessionData) handleResize(data DataObject) {
|
||||||
}
|
}
|
||||||
if viewID, ok := obj.PropertyValue("id"); ok {
|
if viewID, ok := obj.PropertyValue("id"); ok {
|
||||||
if n := strings.IndexRune(viewID, '-'); n > 0 {
|
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 {
|
||||||
if view := session.viewByHTMLID(viewID[:n]); view != nil {
|
view.onItemResize(view, viewID[n+1:], getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
|
||||||
view.onItemResize(view, index, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
|
|
||||||
} else {
|
|
||||||
ErrorLogF(`View with id == %s not found`, viewID[:n])
|
|
||||||
}
|
|
||||||
} else {
|
} 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 {
|
} else if view := session.viewByHTMLID(viewID); view != nil {
|
||||||
view.onResize(view, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
|
view.onResize(view, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
|
||||||
|
@ -423,3 +432,7 @@ func (session *sessionData) SetTitle(title string) {
|
||||||
func (session *sessionData) SetTitleColor(color Color) {
|
func (session *sessionData) SetTitleColor(color Color) {
|
||||||
session.runScript(`setTitleColor("` + color.cssString() + `");`)
|
session.runScript(`setTitleColor("` + color.cssString() + `");`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (session *sessionData) RemoteAddr() string {
|
||||||
|
return session.brige.remoteAddr()
|
||||||
|
}
|
||||||
|
|
|
@ -328,6 +328,10 @@ func (session *sessionData) radiobuttonOnImage() string {
|
||||||
return session.radiobuttonOn
|
return session.radiobuttonOn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (session *sessionData) UserAgent() string {
|
||||||
|
return session.userAgent
|
||||||
|
}
|
||||||
|
|
||||||
func (session *sessionData) Language() string {
|
func (session *sessionData) Language() string {
|
||||||
if session.language != "" {
|
if session.language != "" {
|
||||||
return session.language
|
return session.language
|
||||||
|
|
|
@ -1,23 +1,66 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
|
// TableAdapter describes the TableView content
|
||||||
type TableAdapter interface {
|
type TableAdapter interface {
|
||||||
|
// RowCount returns number of rows in the table
|
||||||
RowCount() int
|
RowCount() int
|
||||||
|
|
||||||
|
// ColumnCount returns number of columns in the table
|
||||||
ColumnCount() int
|
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{}
|
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 {
|
type TableColumnStyle interface {
|
||||||
ColumnStyle(column int) Params
|
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 {
|
type TableRowStyle interface {
|
||||||
RowStyle(row int) Params
|
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 {
|
type TableCellStyle interface {
|
||||||
CellStyle(row, column int) Params
|
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 {
|
type SimpleTableAdapter interface {
|
||||||
TableAdapter
|
TableAdapter
|
||||||
TableCellStyle
|
TableCellStyle
|
||||||
|
@ -28,6 +71,9 @@ type simpleTableAdapter struct {
|
||||||
columnCount int
|
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 {
|
type TextTableAdapter interface {
|
||||||
TableAdapter
|
TableAdapter
|
||||||
}
|
}
|
||||||
|
@ -37,12 +83,17 @@ type textTableAdapter struct {
|
||||||
columnCount int
|
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 {
|
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 {
|
type HorizontalTableJoin struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewSimpleTableAdapter creates the new SimpleTableAdapter
|
||||||
func NewSimpleTableAdapter(content [][]interface{}) SimpleTableAdapter {
|
func NewSimpleTableAdapter(content [][]interface{}) SimpleTableAdapter {
|
||||||
if content == nil {
|
if content == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -137,6 +188,7 @@ func (adapter *simpleTableAdapter) CellStyle(row, column int) Params {
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTextTableAdapter creates the new TextTableAdapter
|
||||||
func NewTextTableAdapter(content [][]string) TextTableAdapter {
|
func NewTextTableAdapter(content [][]string) TextTableAdapter {
|
||||||
if content == nil {
|
if content == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -329,3 +381,14 @@ func (table *tableViewData) getColumnStyle() TableColumnStyle {
|
||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
733
tableView.go
733
tableView.go
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,232 @@
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 GetTableSelectionMode(view View, subviewID string) int {
|
||||||
|
if subviewID != "" {
|
||||||
|
view = ViewByID(view, subviewID)
|
||||||
|
}
|
||||||
|
if view != nil {
|
||||||
|
if result, ok := enumStyledProperty(view, SelectionMode, NoneSelection); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NoneSelection
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 GetTableVerticalAlign(view View, subviewID string) int {
|
||||||
|
if subviewID != "" {
|
||||||
|
view = ViewByID(view, subviewID)
|
||||||
|
}
|
||||||
|
if view != nil {
|
||||||
|
if result, ok := enumStyledProperty(view, TableVerticalAlign, TopAlign); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TopAlign
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
// 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 GetTableCurrent(view View, subviewID string) CellIndex {
|
||||||
|
if subviewID != "" {
|
||||||
|
view = ViewByID(view, subviewID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if view != nil {
|
||||||
|
if selectionMode := GetTableSelectionMode(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.
|
||||||
|
func GetTableCellClickedListeners(view View, subviewID string) []func(TableView, int, int) {
|
||||||
|
if subviewID != "" {
|
||||||
|
view = ViewByID(view, subviewID)
|
||||||
|
}
|
||||||
|
if view != nil {
|
||||||
|
if value := view.Get(TableCellClickedEvent); value != nil {
|
||||||
|
if result, ok := value.([]func(TableView, int, int)); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []func(TableView, int, int){}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTableCellSelectedListeners returns listeners of event which occurs when a table cell becomes selected.
|
||||||
|
// 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.
|
||||||
|
func GetTableCellSelectedListeners(view View, subviewID string) []func(TableView, int, int) {
|
||||||
|
if subviewID != "" {
|
||||||
|
view = ViewByID(view, subviewID)
|
||||||
|
}
|
||||||
|
if view != nil {
|
||||||
|
if value := view.Get(TableCellSelectedEvent); value != nil {
|
||||||
|
if result, ok := value.([]func(TableView, int, int)); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []func(TableView, int, int){}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTableRowClickedListeners returns listeners of event which occurs when the user clicks on a table row.
|
||||||
|
// 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.
|
||||||
|
func GetTableRowClickedListeners(view View, subviewID string) []func(TableView, int) {
|
||||||
|
if subviewID != "" {
|
||||||
|
view = ViewByID(view, subviewID)
|
||||||
|
}
|
||||||
|
if view != nil {
|
||||||
|
if value := view.Get(TableRowClickedEvent); value != nil {
|
||||||
|
if result, ok := value.([]func(TableView, int)); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []func(TableView, int){}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTableRowSelectedListeners returns listeners of event which occurs when a table row becomes selected.
|
||||||
|
// 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.
|
||||||
|
func GetTableRowSelectedListeners(view View, subviewID string) []func(TableView, int) {
|
||||||
|
if subviewID != "" {
|
||||||
|
view = ViewByID(view, subviewID)
|
||||||
|
}
|
||||||
|
if view != nil {
|
||||||
|
if value := view.Get(TableRowSelectedEvent); value != nil {
|
||||||
|
if result, ok := value.([]func(TableView, int)); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []func(TableView, int){}
|
||||||
|
}
|
|
@ -44,6 +44,10 @@ func (picker *timePickerData) Init(session Session) {
|
||||||
picker.timeChangedListeners = []func(TimePicker, time.Time){}
|
picker.timeChangedListeners = []func(TimePicker, time.Time){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (picker *timePickerData) Focusable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (picker *timePickerData) normalizeTag(tag string) string {
|
func (picker *timePickerData) normalizeTag(tag string) string {
|
||||||
tag = strings.ToLower(tag)
|
tag = strings.ToLower(tag)
|
||||||
switch tag {
|
switch tag {
|
||||||
|
|
18
view.go
18
view.go
|
@ -58,6 +58,8 @@ type View interface {
|
||||||
SetAnimated(tag string, value interface{}, animation Animation) bool
|
SetAnimated(tag string, value interface{}, animation Animation) bool
|
||||||
// SetChangeListener set the function to track the change of the View property
|
// SetChangeListener set the function to track the change of the View property
|
||||||
SetChangeListener(tag string, listener func(View, string))
|
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
|
handleCommand(self View, command string, data DataObject) bool
|
||||||
htmlClass(disabled bool) string
|
htmlClass(disabled bool) string
|
||||||
|
@ -73,7 +75,7 @@ type View interface {
|
||||||
getTransitions() Params
|
getTransitions() Params
|
||||||
|
|
||||||
onResize(self View, x, y, width, height float64)
|
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()
|
setNoResizeEvent()
|
||||||
isNoResizeEvent() bool
|
isNoResizeEvent() bool
|
||||||
setScroll(x, y, width, height float64)
|
setScroll(x, y, width, height float64)
|
||||||
|
@ -95,6 +97,7 @@ type viewData struct {
|
||||||
scroll Frame
|
scroll Frame
|
||||||
noResizeEvent bool
|
noResizeEvent bool
|
||||||
created bool
|
created bool
|
||||||
|
hasFocus bool
|
||||||
//animation map[string]AnimationEndListener
|
//animation map[string]AnimationEndListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -737,7 +740,14 @@ func (view *viewData) handleCommand(self View, command string, data DataObject)
|
||||||
case TouchStart, TouchEnd, TouchMove, TouchCancel:
|
case TouchStart, TouchEnd, TouchMove, TouchCancel:
|
||||||
handleTouchEvents(self, command, data)
|
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) {
|
for _, listener := range getFocusListeners(view, "", command) {
|
||||||
listener(self)
|
listener(self)
|
||||||
}
|
}
|
||||||
|
@ -838,3 +848,7 @@ func (view *viewData) SetChangeListener(tag string, listener func(View, string))
|
||||||
view.changeListener[tag] = listener
|
view.changeListener[tag] = listener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (view *viewData) HasFocus() bool {
|
||||||
|
return view.hasFocus
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ type WebBrige interface {
|
||||||
RunGetterScript(script string) DataObject
|
RunGetterScript(script string) DataObject
|
||||||
AnswerReceived(answer DataObject)
|
AnswerReceived(answer DataObject)
|
||||||
Close()
|
Close()
|
||||||
|
remoteAddr() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type wsBrige struct {
|
type wsBrige struct {
|
||||||
|
@ -122,3 +123,7 @@ func (brige *wsBrige) AnswerReceived(answer DataObject) {
|
||||||
ErrorLog("answerID not found")
|
ErrorLog("answerID not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (brige *wsBrige) remoteAddr() string {
|
||||||
|
return brige.conn.RemoteAddr().String()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue