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
|
||||
|
||||
* Added SetTitle and SetTitleColor function to the Session interface
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Alexei Anoshenko
|
||||
Copyright (c) 2021-2022 Alexei Anoshenko
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
137
README-ru.md
137
README-ru.md
|
@ -21,28 +21,33 @@
|
|||
}
|
||||
|
||||
func main() {
|
||||
app := rui.NewApplication("Hello world", "icon.svg", createHelloWorldSession)
|
||||
app.Start("localhost:8000")
|
||||
rui.StartApp("localhost:8000", createHelloWorldSession, rui.AppParams{
|
||||
Title: "Hello world",
|
||||
Icon: "icon.svg",
|
||||
})
|
||||
}
|
||||
|
||||
В функции main создается rui приложение и запускается основной цикл.
|
||||
При создании приложения задаются 3 параметра: имя приложения, имя иконки и функция createHelloWorldSession.
|
||||
Функция createHelloWorldSession создает структуру реализующую интерфейс SessionContent:
|
||||
В функции main вызывается функция StartApp. Она создает rui приложение и запускает его основной цикл.
|
||||
Функция StartApp имеет 3 параметра:
|
||||
1) IP адрес по которому будет доступно приложение (в нашем примере это "localhost:8000")
|
||||
2) Фуекция создает структуру реализующую интерфейс SessionContent
|
||||
3) Дополнительные опциональные параметры (в нашем примере это заголовок и имя файла иконки)
|
||||
|
||||
Интерфейс SessionContent объявлен как:
|
||||
|
||||
type SessionContent interface {
|
||||
CreateRootView(session rui.Session) rui.View
|
||||
}
|
||||
|
||||
Для каждой новой сессии создается свой экземпляр структуры.
|
||||
|
||||
Функция CreateRootView интерфейса SessionContent создает корневой элемент.
|
||||
|
||||
Когда пользователь обращается к приложению набрав в браузере адрес "localhost:8000", то создается новая сессия,
|
||||
для нее создается новый экземпляр структуры helloWorldSession и в конце вызывается функция CreateRootView.
|
||||
Функция createRootView возвращает представление строки текста, создаваемое с помощью функции NewTextView.
|
||||
|
||||
Если вы хотите чтобы приложение было видно вне вашего компьютера, то поменяйте адрес в функции Start:
|
||||
|
||||
app.Start(rui.GetLocalIP() + ":80")
|
||||
rui.StartApp(rui.GetLocalIP() + ":80", ...
|
||||
|
||||
## Используемые типы данных
|
||||
|
||||
|
@ -3260,6 +3265,7 @@ Cell(row, column int) возвращает содержимое ячейки т
|
|||
* rui.Color
|
||||
* rui.View
|
||||
* fmt.Stringer
|
||||
* rui.VerticalTableJoin, rui.HorizontalTableJoin
|
||||
|
||||
Свойству "content" можно также присваивать следующие типы данных
|
||||
|
||||
|
@ -3314,11 +3320,11 @@ Cell(row, column int) возвращает содержимое ячейки т
|
|||
|
||||
В этом случае таблица будет иметь следующий вид
|
||||
|
||||
|------|----------------|
|
||||
| | |
|
||||
| |-------|--------|
|
||||
| | | |
|
||||
|------|-------|--------|
|
||||
|------|----------------|
|
||||
| | |
|
||||
| |-------|--------|
|
||||
| | | |
|
||||
|------|-------|--------|
|
||||
|
||||
Если в качестве значения свойства "content" используется [][]interface{}, то для объединения
|
||||
ячеек используются пустые структуры
|
||||
|
@ -3421,7 +3427,110 @@ TableColumnStyle объявлена как
|
|||
| 2 | CenterAlign | "center" | Выравнивание по центру |
|
||||
| 3, 4 | BaselineAlign | "baseline" | Выравнивание по базовой линии |
|
||||
|
||||
Для горизонтального выравнивания используется свойство "text-align"
|
||||
Для горизонтального выравнивания используется свойство "text-align".
|
||||
|
||||
Получить значение данного свойства можно с помощью функции
|
||||
|
||||
func GetTableVerticalAlign(view View, subviewID string) int
|
||||
|
||||
### Свойство "selection-mode"
|
||||
|
||||
Свойство "selection-mode" (константа SelectionMode) типа int определяет режим
|
||||
выделения (подсвечивания) элементов таблицы. Доступные режимы:
|
||||
|
||||
* NoneSelection (0). Режим по умолчанию. В данном режиме нельзя выделять элементы таблицы. Таблица не может
|
||||
получить фокус ввода.
|
||||
|
||||
* CellSelection (1). В данном режиме может выделяться (подсвечиваться) одна ячейка таблицы.
|
||||
Ячейка выделяется интерактивно с помощью мыши или клавиатуры (с использованием клавиш управления курсором).
|
||||
В данном режиме таблица может получить фокус ввода. В данном режиме таблица генерирует два вида
|
||||
событий: "table-cell-selected" и "table-cell-clicked" (о них ниже).
|
||||
|
||||
* RowSelection (2). В данном режиме может выделяться (подсвечиваться) только строка таблицы целиком.
|
||||
В данном режиме таблица похожа на ListView. Строка выделяется интерактивно с помощью мыши или клавиатуры
|
||||
(с использованием клавиш управления курсором). В данном режиме таблица может получить фокус ввода.
|
||||
В данном режиме таблица генерирует два вида событий: "table-row-selected" и "table-row-clicked" (о них ниже).
|
||||
|
||||
Получить значение данного свойства можно с помощью функции
|
||||
|
||||
func GetSelectionMode(view View, subviewID string) int
|
||||
|
||||
### Свойство "current"
|
||||
|
||||
Свойство "current" (константа Current) задает координаты выбранной ячейки/строки
|
||||
в виде структуры
|
||||
|
||||
type CellIndex struct {
|
||||
Row, Column int
|
||||
}
|
||||
|
||||
Если ячейка не выбрана, то значения полей Row и Column будут меньше 0.
|
||||
|
||||
В режиме RowSelection значение поля Column игнорируется. Также в данном режиме
|
||||
свойству "current" можно присваивать значение типа int (индекс строки).
|
||||
|
||||
Получить значение данного свойства можно с помощью функции
|
||||
|
||||
func GetTableCurrent(view View, subviewID string) CellIndex
|
||||
|
||||
### Свойство "allow-selection"
|
||||
|
||||
По умолчанию вы можете выделить любую ячейку/строку таблицы. Однако часто необходимо запретить
|
||||
выбор определенных элементов. Свойство "selection-mode" (константа SelectionMode) позволяет
|
||||
задать такое правило.
|
||||
|
||||
В режиме CellSelection данному свойству присваивается реализация интерфейса
|
||||
|
||||
type TableAllowCellSelection interface {
|
||||
AllowCellSelection(row, column int) bool
|
||||
}
|
||||
|
||||
а в режиме RowSelection - реализация интерфейса
|
||||
|
||||
type TableAllowRowSelection interface {
|
||||
AllowRowSelection(row int) bool
|
||||
}
|
||||
|
||||
Функция AllowCellSelection/AllowRowSelection должна возвращать "true" если ячейка/строка
|
||||
может быть выделена и "false" если ячейку/строку запрещено выделять.
|
||||
|
||||
### События "table-cell-selected" и "table-cell-clicked"
|
||||
|
||||
Событие "table-cell-selected" генерируется в режиме CellSelection когда пользователь выделил
|
||||
ячейку таблицы с помощью мыши или клавиатуры.
|
||||
|
||||
Событие "table-cell-clicked" возникает если пользователь кликает мышью по ячейке таблицы
|
||||
(при этом если она не выделена, то сначала возникает событие "table-cell-selected") или
|
||||
нажимает клавишу Enter или пробел
|
||||
|
||||
Основной слушатель данных событий имеет следующий формат:
|
||||
|
||||
func(TableView, int, int)
|
||||
|
||||
где второй аргумент это индекс строки ячейки, третий - индекс столбца
|
||||
|
||||
Можно также использовать слушателя следующего формата:
|
||||
|
||||
func(int, int)
|
||||
|
||||
### События "table-row-selected" и "table-row-clicked"
|
||||
|
||||
Событие "table-row-selected" генерируется в режиме RowSelection когда пользователь выделил
|
||||
строку таблицы с помощью мыши или клавиатуры.
|
||||
|
||||
Событие "table-row-clicked" возникает если пользователь кликает мышью по строке таблицы
|
||||
(при этом если она не выделена, то сначала возникает событие "table-row-selected") или
|
||||
нажимает клавишу Enter или пробел
|
||||
|
||||
Основной слушатель данных событий имеет следующий формат:
|
||||
|
||||
func(TableView, int)
|
||||
|
||||
где второй аргумент это индекс строки.
|
||||
|
||||
Можно также использовать слушателя следующего формата:
|
||||
|
||||
func(int)
|
||||
|
||||
## Пользовательский View
|
||||
|
||||
|
|
163
README.md
163
README.md
|
@ -1,6 +1,6 @@
|
|||
# RUI library
|
||||
|
||||
The RUI (Remoute User Interface) library is designed to create web applications in the go language.
|
||||
The RUI (Remote User Interface) library is designed to create web applications in the go language.
|
||||
|
||||
The peculiarity of the library is that all data processing is carried out on the server,
|
||||
and the browser is used as a thin client. WebSocket is used for client-server communication.
|
||||
|
@ -21,13 +21,19 @@ and the browser is used as a thin client. WebSocket is used for client-server co
|
|||
}
|
||||
|
||||
func main() {
|
||||
app := rui.NewApplication("Hello world", "icon.svg", createHelloWorldSession)
|
||||
app.Start("localhost:8000")
|
||||
rui.StartApp("localhost:8000", createHelloWorldSession, rui.AppParams{
|
||||
Title: "Hello world",
|
||||
Icon: "icon.svg",
|
||||
})
|
||||
}
|
||||
|
||||
In the main function, a rui application is created and the main loop is started.
|
||||
When creating an application, 3 parameters are set: the name of the application, the name of the icon, and the createHelloWorldSession function.
|
||||
The createHelloWorldSession function creates a structure that implements the SessionContent interface:
|
||||
In the main function, the StartApp function is called. It creates a rui app and runs its main loop.
|
||||
The StartApp function has 3 parameters:
|
||||
1) IP address where the application will be available (in our example it is "localhost:8000")
|
||||
2) The function creates a structure that implements the SessionContent interface
|
||||
3) Additional optional parameters (in our example, this is the title and the icon file name)
|
||||
|
||||
The SessionContent interface is declared as:
|
||||
|
||||
type SessionContent interface {
|
||||
CreateRootView(session rui.Session) rui.View
|
||||
|
@ -42,7 +48,7 @@ The createRootView function returns a representation of a text that is created u
|
|||
|
||||
If you want the application to be visible outside your computer, then change the address in the Start function:
|
||||
|
||||
app.Start(rui.GetLocalIP() + ":80")
|
||||
rui.StartApp(rui.GetLocalIP() + ":80", ...
|
||||
|
||||
## Used data types
|
||||
|
||||
|
@ -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
|
||||
|
||||
| Property | Constant | Description |
|
||||
|----------------|--------------|----------------------------|
|
||||
| "top-left" | TopLeft | top left corner radius |
|
||||
| "top-right" | TopRight | top right corner radius |
|
||||
| "bottom-left" | BottomLeft | bottom left corner radius |
|
||||
| "bottom-right" | BottomRight | bottom right corner radius |
|
||||
| Property | Constant | Description |
|
||||
|----------------|-------------|----------------------------|
|
||||
| "top-left" | TopLeft | top left corner radius |
|
||||
| "top-right" | TopRight | top right corner radius |
|
||||
| "bottom-left" | BottomLeft | bottom left corner radius |
|
||||
| "bottom-right" | BottomRight | bottom right corner radius |
|
||||
|
||||
To set all radii to the same values, use the "x" and "y" properties
|
||||
|
||||
|
@ -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.
|
||||
* Spaces take up space and do not hang at the ends of lines, which means they affect the internal dimensions (min-content and max-content).
|
||||
|
||||
The table below shows the behavior of various values of the "white-space" property.
|
||||
The table below shows the behavior of various values of the "white-space" property.
|
||||
|
||||
| | New lines | Spaces and Tabs | Text wrapping | End of line spaces | End-of-line other space separators |
|
||||
|-----------------------|-----------|-----------------|---------------|--------------------|------------------------------------|
|
||||
|
@ -1448,7 +1454,8 @@ You can get the value of this property using the function
|
|||
|
||||
#### "text-indent" property
|
||||
|
||||
The "text-indent" (TextIndent constant) SizeUnit property determines the size of the indent (empty space) before the first line of text.
|
||||
The "text-indent" (TextIndent constant) SizeUnit property determines the size of the indent (empty space)
|
||||
before the first line of text.
|
||||
|
||||
You can get the value of this property using the function
|
||||
|
||||
|
@ -1514,7 +1521,7 @@ You can get the value of this property using the function
|
|||
#### "writing-mode" property
|
||||
The "writing-mode" (WritingMode constant) int property defines how the lines of text are arranged
|
||||
vertically or horizontally, as well as the direction in which the lines are displayed.
|
||||
Possible values are:
|
||||
Possible values are:
|
||||
|
||||
| Value | Constant | Description |
|
||||
|:-----:|-----------------------|------------------------------------------------------------------|
|
||||
|
@ -1871,8 +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 |
|
||||
| ScreenX | float64 | Horizontal position of the mouse relative to the upper left corner of the screen |
|
||||
| ScreenY | float64 | Vertical position of the mouse relative to the upper left corner of the screen |
|
||||
| RadiusX | float64 | The x-radius of the ellipse, in pixels, that most closely delimits the area of contact with the screen. |
|
||||
| RadiusY | float64 | The y-radius of the ellipse, in pixels, that most closely delimits the area of contact with the screen. |
|
||||
| RadiusX | float64 | The x-radius of the ellipse, in pixels, that most closely delimits the area of contact with the screen. |
|
||||
| RadiusY | float64 | The y-radius of the ellipse, in pixels, that most closely delimits the area of contact with the screen. |
|
||||
| RotationAngle | float64 | The angle (in degrees) to rotate the ellipse clockwise, described by the radiusX and radiusY parameters, to best cover the contact area between the user and the surface. |
|
||||
| Force | float64 | The amount of pressure from 0.0 (no pressure) to 1.0 (maximum pressure) that the user applies to the surface. |
|
||||
|
||||
|
@ -2038,8 +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. |
|
||||
|
||||
The start and end positions for StartToEndOrientation and EndToStartOrientation depend on the value
|
||||
of the "text-direction" property. For languages written from right to left (Arabic, Hebrew),
|
||||
the beginning is on the right, for other languages - on the left.
|
||||
of the "text-direction" property. For languages written from right to left (Arabic, Hebrew),
|
||||
the beginning is on the right, for other languages - on the left.
|
||||
|
||||
### "wrap" property
|
||||
|
||||
|
@ -2610,7 +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 enabled, the text wraps to a new line when the EditView border is reached.
|
||||
|
||||
The following functions can be used to get the values of the properties of an EditView:
|
||||
The following functions can be used to get the values of the properties of an EditView:
|
||||
|
||||
func GetText(view View, subviewID string) string
|
||||
func GetHint(view View, subviewID string) string
|
||||
|
@ -2665,7 +2672,7 @@ The value of the "date-picker-value" property can also be read using the functio
|
|||
|
||||
func GetNumberPickerValue(view View, subviewID string) float64
|
||||
|
||||
The entered values may be subject to restrictions. For this, the following properties are used:
|
||||
The entered values may be subject to restrictions. For this, the following properties are used:
|
||||
|
||||
| Property | Constant | Restriction |
|
||||
|--------------------|------------------|-------------------|
|
||||
|
@ -2678,7 +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.
|
||||
If "date-picker-type" is equal to NumberEditor, then the entered numbers, by default, are limited only by the range of float64 values.
|
||||
|
||||
You can read the values of these properties using the functions:
|
||||
You can read the values of these properties using the functions:
|
||||
|
||||
func GetNumberPickerMinMax(view View, subviewID string) (float64, float64)
|
||||
func GetNumberPickerStep(view View, subviewID string) float64
|
||||
|
@ -3027,8 +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. |
|
||||
|
||||
The start and end positions for StartToEndOrientation and EndToStartOrientation depend
|
||||
on the value of the "text-direction" property. For languages written from right to left
|
||||
(Arabic, Hebrew), the beginning is on the right, for other languages - on the left.
|
||||
on the value of the "text-direction" property. For languages written from right to left
|
||||
(Arabic, Hebrew), the beginning is on the right, for other languages - on the left.
|
||||
|
||||
You can get the value of this property using the function
|
||||
|
||||
|
@ -3224,6 +3231,7 @@ Cell(row, column int) returns the contents of a table cell. The Cell() function
|
|||
* rui.Color
|
||||
* rui.View
|
||||
* fmt.Stringer
|
||||
* rui.VerticalTableJoin, rui.HorizontalTableJoin
|
||||
|
||||
The "content" property can also be assigned the following data types
|
||||
|
||||
|
@ -3277,11 +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
|
||||
|
||||
|------|----------------|
|
||||
| | |
|
||||
| |-------|--------|
|
||||
| | | |
|
||||
|------|-------|--------|
|
||||
|------+----------------|
|
||||
| | |
|
||||
| +-------+--------|
|
||||
| | | |
|
||||
|------+-------+--------|
|
||||
|
||||
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
|
||||
|
||||
You can get the value of this property using the function
|
||||
|
||||
func GetTableVerticalAlign(view View, subviewID string) int
|
||||
|
||||
### "selection-mode" property
|
||||
|
||||
The "selection-mode" property (SelectionMode constant) of the int type determines the mode of selection (highlighting) of table elements. Available modes:
|
||||
|
||||
* NoneSelection (0). Default mode. In this mode, you cannot select table elements. The table cannot receive input focus.
|
||||
|
||||
* CellSelection (1). In this mode, one table cell can be selected (highlighted).
|
||||
The cell is selected interactively using the mouse or keyboard (using the cursor keys).
|
||||
In this mode, the table can receive input focus. In this mode, the table generates two types of events: "table-cell-selected" and "table-cell-clicked" (see below).
|
||||
|
||||
* RowSelection (2). In this mode, only the entire table row can be selected (highlighted).
|
||||
In this mode, the table is similar to a ListView. The row is selected interactively
|
||||
with the mouse or keyboard (using the cursor keys). In this mode, the table can receive input focus.
|
||||
In this mode, the table generates two types of events: "table-row-selected" and "table-row-clicked" (see below).
|
||||
|
||||
You can get the value of this property using the function
|
||||
|
||||
func GetSelectionMode(view View, subviewID string) int
|
||||
|
||||
### "current" property
|
||||
|
||||
The "current" property (Current constant) sets the coordinates of the selected cell/row as a structure
|
||||
|
||||
type CellIndex struct {
|
||||
Row, Column int
|
||||
}
|
||||
|
||||
If the cell is not selected, then the values of the Row and Column fields will be less than 0.
|
||||
|
||||
In RowSelection mode, the value of the Column field is ignored. Also in this mode,
|
||||
the "current" property can be assigned a value of type int (row index).
|
||||
|
||||
You can get the value of this property using the function
|
||||
|
||||
func GetTableCurrent(view View, subviewID string) CellIndex
|
||||
|
||||
### "allow-selection" property
|
||||
|
||||
By default, you can select any cell/row of the table. However, it is often necessary to disable the selection of certain elements. The "selection-mode" property (SelectionMode constant) allows you to set such a rule.
|
||||
|
||||
In CellSelection mode, this property is assigned the implementation of the interface
|
||||
|
||||
type TableAllowCellSelection interface {
|
||||
AllowCellSelection(row, column int) bool
|
||||
}
|
||||
|
||||
and in RowSelection mode this property is assigned the implementation of the interface
|
||||
|
||||
type TableAllowRowSelection interface {
|
||||
AllowRowSelection(row int) bool
|
||||
}
|
||||
|
||||
The AllowCellSelection/AllowRowSelection function must return "true"
|
||||
if the cell/row can be selected and "false" if the cell/row cannot be selected.
|
||||
|
||||
### "table-cell-selected" and "table-cell-clicked" events
|
||||
|
||||
The "table-cell-selected" event is fired in CellSelection mode when the user has selected
|
||||
a table cell with the mouse or keyboard.
|
||||
|
||||
The "table-cell-clicked" event occurs if the user clicks on a table cell (and if it is not selected,
|
||||
the "table-cell-selected" event occurs first) or presses the Enter or Space key.
|
||||
|
||||
The main listener for these events has the following format:
|
||||
|
||||
func(TableView, int, int)
|
||||
|
||||
where the second argument is the cell row index, the third argument is the column index
|
||||
|
||||
You can also use a listener in the following format:
|
||||
|
||||
func(int, int)
|
||||
|
||||
### "table-row-selected" and "table-row-clicked" events
|
||||
|
||||
The "table-row-selected" event is fired in RowSelection mode when the user has selected
|
||||
a table row with the mouse or keyboard.
|
||||
|
||||
The "table-row-clicked" event occurs if the user clicks on a table row (if it is not selected,
|
||||
the "table-row-selected" event fires first) or presses the Enter or Space key.
|
||||
|
||||
The main listener for these events has the following format:
|
||||
|
||||
func(TableView, int)
|
||||
|
||||
where the second argument is the row index.
|
||||
|
||||
You can also use a listener in the following format:
|
||||
|
||||
func(int)
|
||||
|
||||
## Custom View
|
||||
|
||||
A custom View must implement the CustomView interface, which extends the ViewsContainer and View interfaces.
|
||||
|
|
522
app_scripts.js
522
app_scripts.js
|
@ -42,9 +42,19 @@ function socketOpen() {
|
|||
}
|
||||
}
|
||||
|
||||
const lang = window.navigator.languages;
|
||||
const lang = window.navigator.language;
|
||||
if (lang) {
|
||||
message += ",languages=\"" + lang + "\"";
|
||||
message += ",language=\"" + lang + "\"";
|
||||
}
|
||||
|
||||
const langs = window.navigator.languages;
|
||||
if (langs) {
|
||||
message += ",languages=\"" + langs + "\"";
|
||||
}
|
||||
|
||||
const userAgent = window.navigator.userAgent
|
||||
if (userAgent) {
|
||||
message += ",user-agent=\"" + userAgent + "\"";
|
||||
}
|
||||
|
||||
const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
|
@ -615,12 +625,13 @@ function selectDropDownListItem(elementId, number) {
|
|||
|
||||
function listItemClickEvent(element, event) {
|
||||
event.stopPropagation();
|
||||
|
||||
var selected = false;
|
||||
if (element.classList) {
|
||||
selected = (element.classList.contains("ruiListItemFocused") || element.classList.contains("ruiListItemSelected"));
|
||||
} else {
|
||||
selected = element.className.indexOf("ruiListItemFocused") >= 0 || element.className.indexOf("ruiListItemSelected") >= 0;
|
||||
}
|
||||
const focusStyle = getListFocusedItemStyle(element);
|
||||
const blurStyle = getListSelectedItemStyle(element);
|
||||
selected = (element.classList.contains(focusStyle) || element.classList.contains(blurStyle));
|
||||
}
|
||||
|
||||
var list = element.parentNode.parentNode
|
||||
if (list) {
|
||||
|
@ -640,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) {
|
||||
var currentId = element.getAttribute("data-current");
|
||||
var message;
|
||||
var focusStyle = element.getAttribute("data-focusitemstyle");
|
||||
var blurStyle = element.getAttribute("data-bluritemstyle");
|
||||
|
||||
if (!focusStyle) {
|
||||
focusStyle = "ruiListItemFocused"
|
||||
}
|
||||
if (!blurStyle) {
|
||||
blurStyle = "ruiListItemSelected"
|
||||
}
|
||||
const focusStyle = getListFocusedItemStyle(element);
|
||||
const blurStyle = getListSelectedItemStyle(element);
|
||||
|
||||
if (currentId) {
|
||||
var current = document.getElementById(currentId);
|
||||
if (current) {
|
||||
if (current.classList) {
|
||||
current.classList.remove(focusStyle, blurStyle);
|
||||
} else { // IE < 10
|
||||
current.className = "ruiListItem";
|
||||
}
|
||||
if (sendMessage) {
|
||||
message = "itemUnselected{session=" + sessionID + ",id=" + element.id + "}";
|
||||
|
@ -671,14 +689,10 @@ function selectListItem(element, item, needSendMessage) {
|
|||
if (element === document.activeElement) {
|
||||
if (item.classList) {
|
||||
item.classList.add(focusStyle);
|
||||
} else { // IE < 10
|
||||
item.className = "ruiListItem " + focusStyle
|
||||
}
|
||||
} else {
|
||||
if (item.classList) {
|
||||
item.classList.add(blurStyle);
|
||||
} else { // IE < 10
|
||||
item.className = "ruiListItem " + blurStyle
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
if (left < element.scrollLeft) {
|
||||
element.scrollLeft = left;
|
||||
|
@ -708,7 +728,7 @@ function selectListItem(element, item, needSendMessage) {
|
|||
var bottom = top + item.offsetHeight
|
||||
if (bottom > element.scrollTop + element.clientHeight) {
|
||||
element.scrollTop = bottom - element.clientHeight;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
if (needSendMessage && message != undefined) {
|
||||
|
@ -801,24 +821,29 @@ function findBottomListItem(list, x, y) {
|
|||
return result
|
||||
}
|
||||
|
||||
function listViewKeyDownEvent(element, event) {
|
||||
var key;
|
||||
function getKey(event) {
|
||||
if (event.key) {
|
||||
key = event.key;
|
||||
} else if (event.keyCode) {
|
||||
return event.key;
|
||||
}
|
||||
|
||||
if (event.keyCode) {
|
||||
switch (event.keyCode) {
|
||||
case 13: key = "Enter"; break;
|
||||
case 32: key = " "; break;
|
||||
case 33: key = "PageUp"; break;
|
||||
case 34: key = "PageDown"; break;
|
||||
case 35: key = "End"; break;
|
||||
case 36: key = "Home"; break;
|
||||
case 37: key = "ArrowLeft"; break;
|
||||
case 38: key = "ArrowUp"; break;
|
||||
case 39: key = "ArrowRight"; break;
|
||||
case 40: key = "ArrowDown"; break;
|
||||
case 13: return "Enter";
|
||||
case 32: return " ";
|
||||
case 33: return "PageUp";
|
||||
case 34: return "PageDown";
|
||||
case 35: return "End";
|
||||
case 36: return "Home";
|
||||
case 37: return "ArrowLeft";
|
||||
case 38: return "ArrowUp";
|
||||
case 39: return "ArrowRight";
|
||||
case 40: return "ArrowDown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function listViewKeyDownEvent(element, event) {
|
||||
const key = getKey(event);
|
||||
if (key) {
|
||||
var currentId = element.getAttribute("data-current");
|
||||
var current
|
||||
|
@ -885,21 +910,10 @@ function listViewFocusEvent(element, event) {
|
|||
if (currentId) {
|
||||
var current = document.getElementById(currentId);
|
||||
if (current) {
|
||||
var focusStyle = element.getAttribute("data-focusitemstyle");
|
||||
var blurStyle = element.getAttribute("data-bluritemstyle");
|
||||
if (!focusStyle) {
|
||||
focusStyle = "ruiListItemFocused"
|
||||
}
|
||||
if (!blurStyle) {
|
||||
blurStyle = "ruiListItemSelected"
|
||||
}
|
||||
|
||||
if (current.classList) {
|
||||
current.classList.remove(blurStyle);
|
||||
current.classList.add(focusStyle);
|
||||
} else { // IE < 10
|
||||
current.className = "ruiListItem " + focusStyle;
|
||||
}
|
||||
current.classList.remove(getListSelectedItemStyle(element));
|
||||
current.classList.add(getListFocusedItemStyle(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -909,20 +923,9 @@ function listViewBlurEvent(element, event) {
|
|||
if (currentId) {
|
||||
var current = document.getElementById(currentId);
|
||||
if (current) {
|
||||
var focusStyle = element.getAttribute("data-focusitemstyle");
|
||||
var blurStyle = element.getAttribute("data-bluritemstyle");
|
||||
if (!focusStyle) {
|
||||
focusStyle = "ruiListItemFocused"
|
||||
}
|
||||
if (!blurStyle) {
|
||||
blurStyle = "ruiListItemSelected"
|
||||
}
|
||||
|
||||
if (current.classList) {
|
||||
current.classList.remove(focusStyle);
|
||||
current.classList.add(blurStyle);
|
||||
} else { // IE < 10
|
||||
current.className = "ruiListItem " + blurStyle;
|
||||
current.classList.remove(getListFocusedItemStyle(element));
|
||||
current.classList.add(getListSelectedItemStyle(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1372,4 +1375,397 @@ function setTitleColor(color) {
|
|||
|
||||
function detailsEvent(element) {
|
||||
sendMessage("details-open{session=" + sessionID + ",id=" + element.id + ",open=" + (element.open ? "1}" : "0}"));
|
||||
}
|
||||
|
||||
function getTableFocusedItemStyle(element) {
|
||||
return getStyleAttribute(element, "data-focusitemstyle", "ruiCurrentTableCellFocused");
|
||||
}
|
||||
|
||||
function getTableSelectedItemStyle(element) {
|
||||
return getStyleAttribute(element, "data-bluritemstyle", "ruiCurrentTableCell");
|
||||
}
|
||||
|
||||
function tableViewFocusEvent(element, event) {
|
||||
var currentId = element.getAttribute("data-current");
|
||||
if (currentId) {
|
||||
var current = document.getElementById(currentId);
|
||||
if (current) {
|
||||
if (current.classList) {
|
||||
current.classList.remove(getTableSelectedItemStyle(element));
|
||||
current.classList.add(getTableFocusedItemStyle(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tableViewBlurEvent(element, event) {
|
||||
var currentId = element.getAttribute("data-current");
|
||||
if (currentId) {
|
||||
var current = document.getElementById(currentId);
|
||||
if (current && current.classList) {
|
||||
current.classList.remove(getTableFocusedItemStyle(element));
|
||||
current.classList.add(getTableSelectedItemStyle(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setTableCellCursor(element, row, column) {
|
||||
const cellID = element.id + "-" + row + "-" + column;
|
||||
var cell = document.getElementById(cellID);
|
||||
if (!cell || 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
|
||||
type Application interface {
|
||||
// Start - start the application life cycle
|
||||
Start(addr string)
|
||||
Finish()
|
||||
nextSessionID() int
|
||||
removeSession(id int)
|
||||
}
|
||||
|
||||
type application struct {
|
||||
name, icon string
|
||||
params AppParams
|
||||
createContentFunc func(Session) SessionContent
|
||||
sessions map[int]Session
|
||||
}
|
||||
|
||||
// AppParams defines parameters of the app
|
||||
type AppParams struct {
|
||||
Title string
|
||||
TitleColor Color
|
||||
Icon string
|
||||
}
|
||||
|
||||
func (app *application) getStartPage() string {
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
@ -49,12 +54,19 @@ func (app *application) getStartPage() string {
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>`)
|
||||
buffer.WriteString(app.name)
|
||||
buffer.WriteString(app.params.Title)
|
||||
buffer.WriteString("</title>")
|
||||
if app.icon != "" {
|
||||
if app.params.Icon != "" {
|
||||
buffer.WriteString(`
|
||||
<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(`">`)
|
||||
}
|
||||
|
||||
|
@ -78,9 +90,8 @@ func (app *application) getStartPage() string {
|
|||
return buffer.String()
|
||||
}
|
||||
|
||||
func (app *application) init(name, icon string) {
|
||||
app.name = name
|
||||
app.icon = icon
|
||||
func (app *application) init(params AppParams) {
|
||||
app.params = params
|
||||
app.sessions = map[int]Session{}
|
||||
}
|
||||
|
||||
|
@ -277,12 +288,14 @@ func (app *application) startSession(params DataObject, events chan DataObject,
|
|||
return session, answerText
|
||||
}
|
||||
|
||||
// NewApplication - create the new application of the single view type.
|
||||
func NewApplication(name, icon string, createContentFunc func(Session) SessionContent) Application {
|
||||
// NewApplication - create the new application and start it
|
||||
func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) {
|
||||
app := new(application)
|
||||
app.init(name, icon)
|
||||
app.init(params)
|
||||
app.createContentFunc = createContentFunc
|
||||
return app
|
||||
|
||||
http.Handle("/", app)
|
||||
log.Fatal(http.ListenAndServe(addr, nil))
|
||||
}
|
||||
|
||||
func OpenBrowser(url string) bool {
|
||||
|
|
|
@ -148,11 +148,15 @@ func (customView *CustomViewData) Scroll() Frame {
|
|||
return customView.superView.Scroll()
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) HasFocus() bool {
|
||||
return customView.superView.HasFocus()
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) onResize(self View, x, y, width, height float64) {
|
||||
customView.superView.onResize(customView.superView, x, y, width, height)
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) onItemResize(self View, index int, x, y, width, height float64) {
|
||||
func (customView *CustomViewData) onItemResize(self View, index string, x, y, width, height float64) {
|
||||
customView.superView.onItemResize(customView.superView, index, x, y, width, height)
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,10 @@ func (picker *datePickerData) Init(session Session) {
|
|||
picker.dateChangedListeners = []func(DatePicker, time.Time){}
|
||||
}
|
||||
|
||||
func (picker *datePickerData) Focusable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (picker *datePickerData) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
|
|
|
@ -213,24 +213,32 @@ theme {
|
|||
text-color = @ruiPopupTextColor,
|
||||
radius = 4px,
|
||||
shadow = _{spread-radius=4px, blur=16px, color=#80808080},
|
||||
}
|
||||
},
|
||||
ruiPopupTitle {
|
||||
background-color = @ruiPopupTitleColor,
|
||||
text-color = @ruiPopupTitleTextColor,
|
||||
min-height = 24px,
|
||||
}
|
||||
},
|
||||
ruiMessageText {
|
||||
padding-left = 64px,
|
||||
padding-right = 64px,
|
||||
padding-top = 32px,
|
||||
padding-bottom = 32px,
|
||||
}
|
||||
},
|
||||
ruiPopupMenuItem {
|
||||
padding-top = 4px,
|
||||
padding-bottom = 4px,
|
||||
padding-left = 8px,
|
||||
padding-right = 8px,
|
||||
}
|
||||
},
|
||||
ruiCurrentTableCell {
|
||||
background-color=@ruiSelectedColor,
|
||||
text-color=@ruiSelectedTextColor,
|
||||
},
|
||||
ruiCurrentTableCellFocused {
|
||||
background-color=@ruiHighlightColor,
|
||||
text-color=@ruiHighlightTextColor,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
|
|
@ -74,7 +74,13 @@ func textCanvasDemo(canvas rui.Canvas) {
|
|||
canvas.SetTextAlign(rui.LeftAlign)
|
||||
canvas.SetTextBaseline(rui.TopBaseline)
|
||||
|
||||
canvas.SetSolidColorFillStyle(0xFF000000)
|
||||
if canvas.View().Session().DarkTheme() {
|
||||
canvas.SetSolidColorFillStyle(0xFFFFFFFF)
|
||||
canvas.SetSolidColorStrokeStyle(0xFFFFFFFF)
|
||||
} else {
|
||||
canvas.SetSolidColorFillStyle(0xFF000000)
|
||||
canvas.SetSolidColorStrokeStyle(0xFF000000)
|
||||
}
|
||||
canvas.FillText(10, 10, "Default font")
|
||||
canvas.StrokeText(300, 10, "Default font")
|
||||
|
||||
|
@ -136,7 +142,11 @@ func textCanvasDemo(canvas rui.Canvas) {
|
|||
func textAlignCanvasDemo(canvas rui.Canvas) {
|
||||
canvas.Save()
|
||||
canvas.SetFont("sans-serif", rui.Pt(10))
|
||||
canvas.SetSolidColorFillStyle(0xFF000000)
|
||||
if canvas.View().Session().DarkTheme() {
|
||||
canvas.SetSolidColorFillStyle(0xFFFFFFFF)
|
||||
} else {
|
||||
canvas.SetSolidColorFillStyle(0xFF000000)
|
||||
}
|
||||
canvas.SetSolidColorStrokeStyle(0xFF00FFFF)
|
||||
|
||||
baseline := []string{"Alphabetic", "Top", "Middle", "Bottom", "Hanging", "Ideographic"}
|
||||
|
@ -176,18 +186,28 @@ func lineStyleCanvasDemo(canvas rui.Canvas) {
|
|||
canvas.SetLineWidth(1)
|
||||
y := float64(40 + 20*i)
|
||||
canvas.DrawLine(10, y, 180, y)
|
||||
canvas.SetSolidColorStrokeStyle(0xFF000000)
|
||||
if canvas.View().Session().DarkTheme() {
|
||||
canvas.SetSolidColorStrokeStyle(0xFFFFFFFF)
|
||||
} else {
|
||||
canvas.SetSolidColorStrokeStyle(0xFF000000)
|
||||
}
|
||||
|
||||
canvas.SetLineWidth(10)
|
||||
canvas.SetLineCap(i)
|
||||
canvas.DrawLine(20, y, 170, y)
|
||||
canvas.FillText(200, y, cap)
|
||||
}
|
||||
|
||||
canvas.SetSolidColorStrokeStyle(0xFF0000FF)
|
||||
if canvas.View().Session().DarkTheme() {
|
||||
canvas.SetSolidColorStrokeStyle(0xFFFFFFFF)
|
||||
canvas.SetSolidColorFillStyle(0xFF00FFFF)
|
||||
} else {
|
||||
canvas.SetSolidColorStrokeStyle(0xFF000000)
|
||||
canvas.SetSolidColorFillStyle(0xFF0000FF)
|
||||
}
|
||||
canvas.SetFont("courier", rui.Pt(12))
|
||||
canvas.FillText(80, 115, "SetLineJoin(...)")
|
||||
|
||||
canvas.SetSolidColorStrokeStyle(0xFF000000)
|
||||
canvas.SetLineWidth(10)
|
||||
canvas.SetLineCap(rui.ButtCap)
|
||||
|
||||
|
@ -206,14 +226,11 @@ func lineStyleCanvasDemo(canvas rui.Canvas) {
|
|||
canvas.StrokePath(path)
|
||||
canvas.FillText(210, y+20, join)
|
||||
}
|
||||
|
||||
canvas.SetSolidColorStrokeStyle(0xFF0000FF)
|
||||
canvas.SetFont("courier", rui.Pt(12))
|
||||
canvas.FillText(20, 300, "SetLineDash([]float64{16, 8, 4, 8}, ...)")
|
||||
|
||||
canvas.SetFont("courier", rui.Pt(10))
|
||||
canvas.SetLineDash([]float64{16, 8, 4, 8}, 0)
|
||||
canvas.SetSolidColorStrokeStyle(0xFF000000)
|
||||
canvas.SetLineWidth(4)
|
||||
|
||||
canvas.SetLineCap(rui.ButtCap)
|
||||
|
@ -246,8 +263,13 @@ func transformCanvasDemo(canvas rui.Canvas) {
|
|||
y1 := y0 + float64((ny-1)*20)
|
||||
|
||||
canvas.SetFont("serif", rui.Pt(10))
|
||||
canvas.SetSolidColorFillStyle(rui.Black)
|
||||
|
||||
if canvas.View().Session().DarkTheme() {
|
||||
canvas.SetSolidColorStrokeStyle(0xFFFFFFFF)
|
||||
canvas.SetSolidColorFillStyle(0xFFFFFFFF)
|
||||
} else {
|
||||
canvas.SetSolidColorStrokeStyle(0xFF000000)
|
||||
canvas.SetSolidColorFillStyle(0xFF000000)
|
||||
}
|
||||
canvas.SetTextAlign(rui.CenterAlign)
|
||||
canvas.SetTextBaseline(rui.BottomBaseline)
|
||||
for i := 0; i < nx; i++ {
|
||||
|
@ -266,7 +288,11 @@ func transformCanvasDemo(canvas rui.Canvas) {
|
|||
}
|
||||
|
||||
canvas.SetFont("courier", rui.Pt(14))
|
||||
canvas.SetSolidColorFillStyle(rui.Black)
|
||||
if canvas.View().Session().DarkTheme() {
|
||||
canvas.SetSolidColorFillStyle(0xFFFFFFFF)
|
||||
} else {
|
||||
canvas.SetSolidColorFillStyle(0xFF000000)
|
||||
}
|
||||
canvas.SetTextAlign(rui.CenterAlign)
|
||||
canvas.SetTextBaseline(rui.TopBaseline)
|
||||
|
||||
|
|
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)
|
||||
demo.showPage(0)
|
||||
if color, ok := rui.StringToColor("#ffc0ded9"); ok {
|
||||
session.SetTitleColor(color)
|
||||
}
|
||||
|
||||
return demo.rootView
|
||||
}
|
||||
|
||||
|
@ -158,11 +154,14 @@ func (demo *demoSession) showPage(index int) {
|
|||
func main() {
|
||||
rui.ProtocolInDebugLog = true
|
||||
rui.AddEmbedResources(&resources)
|
||||
app := rui.NewApplication("RUI demo", "icon.svg", createDemo)
|
||||
|
||||
//addr := rui.GetLocalIP() + ":8080"
|
||||
addr := "localhost:8000"
|
||||
fmt.Print(addr)
|
||||
rui.OpenBrowser("http://" + addr)
|
||||
app.Start(addr)
|
||||
rui.StartApp(addr, createDemo, rui.AppParams{
|
||||
Title: "RUI demo",
|
||||
Icon: "icon.svg",
|
||||
TitleColor: rui.Color(0xffc0ded9),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ GridLayout {
|
|||
content = [
|
||||
GridLayout {
|
||||
id = mouseEventsTest, cell-horizontal-align = center, cell-vertical-align = center,
|
||||
height = 150%,
|
||||
height = 100%,
|
||||
border = _{ style = solid, width = 1px, color = gray},
|
||||
content = [
|
||||
TextView {
|
||||
|
|
|
@ -32,6 +32,12 @@ GridLayout {
|
|||
DropDownList { row = 5, column = 1, id = tableFootStyle, current = 0, items = ["none", "tableFoot1", "rui.Params"]},
|
||||
Checkbox { row = 6, column = 0:1, id = tableRowStyle, content = "Row style" },
|
||||
Checkbox { row = 7, column = 0:1, id = tableColumnStyle, content = "Column style" },
|
||||
TextView { row = 8, text = "Selection mode" },
|
||||
DropDownList { row = 8, column = 1, id = tableSelectionMode, current = 0, items = ["none", "cell", "row"]},
|
||||
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 {
|
||||
view := rui.CreateViewFromText(session, tableViewDemoText)
|
||||
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) {
|
||||
if number == 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
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ GridLayout {
|
|||
content = [
|
||||
GridLayout {
|
||||
id = touchEventsTest, cell-horizontal-align = center, cell-vertical-align = center,
|
||||
height = 150%,
|
||||
height = 100%,
|
||||
border = _{ style = solid, width = 1px, color = gray},
|
||||
content = [
|
||||
TextView {
|
||||
|
|
|
@ -39,6 +39,10 @@ func (list *dropDownListData) Init(session Session) {
|
|||
list.dropDownListener = []func(DropDownList, int){}
|
||||
}
|
||||
|
||||
func (list *dropDownListData) Focusable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (list *dropDownListData) Remove(tag string) {
|
||||
list.remove(strings.ToLower(tag))
|
||||
}
|
||||
|
|
|
@ -63,6 +63,10 @@ func (edit *editViewData) Init(session Session) {
|
|||
edit.tag = "EditView"
|
||||
}
|
||||
|
||||
func (edit *editViewData) Focusable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (edit *editViewData) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
|
|
|
@ -89,6 +89,10 @@ func (picker *filePickerData) Init(session Session) {
|
|||
picker.fileSelectedListeners = []func(FilePicker, []FileInfo){}
|
||||
}
|
||||
|
||||
func (picker *filePickerData) Focusable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (picker *filePickerData) Files() []FileInfo {
|
||||
return picker.files
|
||||
}
|
||||
|
|
|
@ -140,11 +140,9 @@ func getFocusListeners(view View, subviewID string, tag string) []func(View) {
|
|||
}
|
||||
|
||||
func focusEventsHtml(view View, buffer *strings.Builder) {
|
||||
for tag, js := range focusEvents {
|
||||
if value := view.getRaw(tag); value != nil {
|
||||
if listeners, ok := value.([]func(View)); ok && len(listeners) > 0 {
|
||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
||||
}
|
||||
if view.Focusable() {
|
||||
for _, js := range focusEvents {
|
||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
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.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 {
|
||||
switch command {
|
||||
case "itemSelected":
|
||||
if text, ok := data.PropertyValue(`number`); ok {
|
||||
if number, err := strconv.Atoi(text); err == nil {
|
||||
listView.properties[Current] = number
|
||||
for _, listener := range listView.selectedListeners {
|
||||
listener(listView, number)
|
||||
}
|
||||
listView.propertyChangedEvent(Current)
|
||||
if number, ok := dataIntProperty(data, `number`); ok {
|
||||
listView.properties[Current] = number
|
||||
for _, listener := range listView.selectedListeners {
|
||||
listener(listView, number)
|
||||
}
|
||||
listView.propertyChangedEvent(Current)
|
||||
}
|
||||
|
||||
case "itemUnselected":
|
||||
|
@ -1163,9 +1163,14 @@ func (listView *listViewData) onItemClick() {
|
|||
}
|
||||
}
|
||||
|
||||
func (listView *listViewData) onItemResize(self View, index int, x, y, width, height float64) {
|
||||
if index >= 0 && index < len(listView.itemFrame) {
|
||||
listView.itemFrame[index] = Frame{Left: x, Top: y, Width: width, Height: height}
|
||||
func (listView *listViewData) onItemResize(self View, index string, x, y, width, height float64) {
|
||||
n, err := strconv.Atoi(index)
|
||||
if err != nil {
|
||||
ErrorLog(err.Error())
|
||||
} else if n >= 0 && n < len(listView.itemFrame) {
|
||||
listView.itemFrame[n] = Frame{Left: x, Top: y, Width: width, Height: height}
|
||||
} else {
|
||||
ErrorLogF(`Invalid ListView item index: %d`, n)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -168,6 +168,10 @@ func (player *mediaPlayerData) Init(session Session) {
|
|||
player.tag = "MediaPlayer"
|
||||
}
|
||||
|
||||
func (player *mediaPlayerData) Focusable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (player *mediaPlayerData) Remove(tag string) {
|
||||
player.remove(strings.ToLower(tag))
|
||||
}
|
||||
|
|
|
@ -50,6 +50,10 @@ func (picker *numberPickerData) Init(session Session) {
|
|||
picker.numberChangedListeners = []func(NumberPicker, float64){}
|
||||
}
|
||||
|
||||
func (picker *numberPickerData) Focusable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (picker *numberPickerData) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
|
|
|
@ -412,6 +412,11 @@ var enumProperties = map[string]struct {
|
|||
"",
|
||||
[]string{"none", "metadata", "auto"},
|
||||
},
|
||||
SelectionMode: {
|
||||
[]string{"none", "cell", "row"},
|
||||
"",
|
||||
[]string{"none", "cell", "row"},
|
||||
},
|
||||
}
|
||||
|
||||
func notCompatibleType(tag string, value interface{}) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
27
session.go
27
session.go
|
@ -33,6 +33,10 @@ type Session interface {
|
|||
Color(tag string) (Color, bool)
|
||||
// SetCustomTheme set the custom theme
|
||||
SetCustomTheme(name string) bool
|
||||
// UserAgent returns the "user-agent" text of the client browser
|
||||
UserAgent() string
|
||||
// RemoteAddr returns the client address.
|
||||
RemoteAddr() string
|
||||
// Language returns the current session language
|
||||
Language() string
|
||||
// SetLanguage set the current session language
|
||||
|
@ -106,6 +110,7 @@ type sessionData struct {
|
|||
touchScreen bool
|
||||
textDirection int
|
||||
pixelRatio float64
|
||||
userAgent string
|
||||
language string
|
||||
languages []string
|
||||
checkboxOff string
|
||||
|
@ -148,12 +153,20 @@ func newSession(app Application, id int, customTheme string, params DataObject)
|
|||
session.touchScreen = (value == "1" || value == "true")
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("user-agent"); ok {
|
||||
session.userAgent = value
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("direction"); ok {
|
||||
if value == "rtl" {
|
||||
session.textDirection = RightToLeftDirection
|
||||
}
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("language"); ok {
|
||||
session.language = value
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("languages"); ok {
|
||||
session.languages = strings.Split(value, ",")
|
||||
}
|
||||
|
@ -378,14 +391,10 @@ func (session *sessionData) handleResize(data DataObject) {
|
|||
}
|
||||
if viewID, ok := obj.PropertyValue("id"); ok {
|
||||
if n := strings.IndexRune(viewID, '-'); n > 0 {
|
||||
if index, err := strconv.Atoi(viewID[n+1:]); err == nil {
|
||||
if view := session.viewByHTMLID(viewID[:n]); view != nil {
|
||||
view.onItemResize(view, index, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
|
||||
} else {
|
||||
ErrorLogF(`View with id == %s not found`, viewID[:n])
|
||||
}
|
||||
if view := session.viewByHTMLID(viewID[:n]); view != nil {
|
||||
view.onItemResize(view, viewID[n+1:], getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
|
||||
} else {
|
||||
ErrorLogF(`Invalid view id == %s not found`, viewID)
|
||||
ErrorLogF(`View with id == %s not found`, viewID[:n])
|
||||
}
|
||||
} else if view := session.viewByHTMLID(viewID); view != nil {
|
||||
view.onResize(view, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
|
||||
|
@ -423,3 +432,7 @@ func (session *sessionData) SetTitle(title string) {
|
|||
func (session *sessionData) SetTitleColor(color Color) {
|
||||
session.runScript(`setTitleColor("` + color.cssString() + `");`)
|
||||
}
|
||||
|
||||
func (session *sessionData) RemoteAddr() string {
|
||||
return session.brige.remoteAddr()
|
||||
}
|
||||
|
|
|
@ -328,6 +328,10 @@ func (session *sessionData) radiobuttonOnImage() string {
|
|||
return session.radiobuttonOn
|
||||
}
|
||||
|
||||
func (session *sessionData) UserAgent() string {
|
||||
return session.userAgent
|
||||
}
|
||||
|
||||
func (session *sessionData) Language() string {
|
||||
if session.language != "" {
|
||||
return session.language
|
||||
|
|
|
@ -1,23 +1,66 @@
|
|||
package rui
|
||||
|
||||
// TableAdapter describes the TableView content
|
||||
type TableAdapter interface {
|
||||
// RowCount returns number of rows in the table
|
||||
RowCount() int
|
||||
|
||||
// ColumnCount returns number of columns in the table
|
||||
ColumnCount() int
|
||||
|
||||
// Cell returns the contents of a table cell. The function can return elements of the following types:
|
||||
// * string
|
||||
// * rune
|
||||
// * float32, float64
|
||||
// * integer values: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
|
||||
// * bool
|
||||
// * rui.Color
|
||||
// * rui.View
|
||||
// * fmt.Stringer
|
||||
// * rui.VerticalTableJoin, rui.HorizontalTableJoin
|
||||
Cell(row, column int) interface{}
|
||||
}
|
||||
|
||||
// TableColumnStyle describes the style of TableView columns.
|
||||
// To set column styles, you must either implement the TableColumnStyle interface in the table adapter
|
||||
// or assign its separate implementation to the "column-style" property.
|
||||
type TableColumnStyle interface {
|
||||
ColumnStyle(column int) Params
|
||||
}
|
||||
|
||||
// TableRowStyle describes the style of TableView rows.
|
||||
// To set row styles, you must either implement the TableRowStyle interface in the table adapter
|
||||
// or assign its separate implementation to the "row-style" property.
|
||||
type TableRowStyle interface {
|
||||
RowStyle(row int) Params
|
||||
}
|
||||
|
||||
// TableCellStyle describes the style of TableView cells.
|
||||
// To set row cells, you must either implement the TableCellStyle interface in the table adapter
|
||||
// or assign its separate implementation to the "cell-style" property.
|
||||
type TableCellStyle interface {
|
||||
CellStyle(row, column int) Params
|
||||
}
|
||||
|
||||
// TableAllowCellSelection determines whether TableView cell selection is allowed.
|
||||
// It is only used if the "selection-mode" property is set to CellSelection (1).
|
||||
// To set cell selection allowing, you must either implement the TableAllowCellSelection interface
|
||||
// in the table adapter or assign its separate implementation to the "allow-selection" property.
|
||||
type TableAllowCellSelection interface {
|
||||
AllowCellSelection(row, column int) bool
|
||||
}
|
||||
|
||||
// TableAllowRowSelection determines whether TableView row selection is allowed.
|
||||
// It is only used if the "selection-mode" property is set to RowSelection (2).
|
||||
// To set row selection allowing, you must either implement the TableAllowRowSelection interface
|
||||
// in the table adapter or assign its separate implementation to the "allow-selection" property.
|
||||
type TableAllowRowSelection interface {
|
||||
AllowRowSelection(row int) bool
|
||||
}
|
||||
|
||||
// SimpleTableAdapter is implementation of TableAdapter where the content
|
||||
// defines as [][]interface{}.
|
||||
// When you assign [][]interface{} value to the "content" property, it is converted to SimpleTableAdapter
|
||||
type SimpleTableAdapter interface {
|
||||
TableAdapter
|
||||
TableCellStyle
|
||||
|
@ -28,6 +71,9 @@ type simpleTableAdapter struct {
|
|||
columnCount int
|
||||
}
|
||||
|
||||
// TextTableAdapter is implementation of TableAdapter where the content
|
||||
// defines as [][]string.
|
||||
// When you assign [][]string value to the "content" property, it is converted to TextTableAdapter
|
||||
type TextTableAdapter interface {
|
||||
TableAdapter
|
||||
}
|
||||
|
@ -37,12 +83,17 @@ type textTableAdapter struct {
|
|||
columnCount int
|
||||
}
|
||||
|
||||
// NewTextTableAdapter is an auxiliary structure. It used as cell content and
|
||||
// specifies that the cell should be merged with the one above it
|
||||
type VerticalTableJoin struct {
|
||||
}
|
||||
|
||||
// HorizontalTableJoin is an auxiliary structure. It used as cell content and
|
||||
// specifies that the cell should be merged with the one before it
|
||||
type HorizontalTableJoin struct {
|
||||
}
|
||||
|
||||
// NewSimpleTableAdapter creates the new SimpleTableAdapter
|
||||
func NewSimpleTableAdapter(content [][]interface{}) SimpleTableAdapter {
|
||||
if content == nil {
|
||||
return nil
|
||||
|
@ -137,6 +188,7 @@ func (adapter *simpleTableAdapter) CellStyle(row, column int) Params {
|
|||
return params
|
||||
}
|
||||
|
||||
// NewTextTableAdapter creates the new TextTableAdapter
|
||||
func NewTextTableAdapter(content [][]string) TextTableAdapter {
|
||||
if content == nil {
|
||||
return nil
|
||||
|
@ -329,3 +381,14 @@ func (table *tableViewData) getColumnStyle() TableColumnStyle {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (table *tableViewData) getCellStyle() TableCellStyle {
|
||||
for _, tag := range []string{CellStyle, Content} {
|
||||
if value := table.getRaw(tag); value != nil {
|
||||
if style, ok := value.(TableCellStyle); ok {
|
||||
return style
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
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){}
|
||||
}
|
||||
|
||||
func (picker *timePickerData) Focusable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (picker *timePickerData) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
|
|
18
view.go
18
view.go
|
@ -58,6 +58,8 @@ type View interface {
|
|||
SetAnimated(tag string, value interface{}, animation Animation) bool
|
||||
// SetChangeListener set the function to track the change of the View property
|
||||
SetChangeListener(tag string, listener func(View, string))
|
||||
// HasFocus returns 'true' if the view has focus
|
||||
HasFocus() bool
|
||||
|
||||
handleCommand(self View, command string, data DataObject) bool
|
||||
htmlClass(disabled bool) string
|
||||
|
@ -73,7 +75,7 @@ type View interface {
|
|||
getTransitions() Params
|
||||
|
||||
onResize(self View, x, y, width, height float64)
|
||||
onItemResize(self View, index int, x, y, width, height float64)
|
||||
onItemResize(self View, index string, x, y, width, height float64)
|
||||
setNoResizeEvent()
|
||||
isNoResizeEvent() bool
|
||||
setScroll(x, y, width, height float64)
|
||||
|
@ -95,6 +97,7 @@ type viewData struct {
|
|||
scroll Frame
|
||||
noResizeEvent bool
|
||||
created bool
|
||||
hasFocus bool
|
||||
//animation map[string]AnimationEndListener
|
||||
}
|
||||
|
||||
|
@ -737,7 +740,14 @@ func (view *viewData) handleCommand(self View, command string, data DataObject)
|
|||
case TouchStart, TouchEnd, TouchMove, TouchCancel:
|
||||
handleTouchEvents(self, command, data)
|
||||
|
||||
case FocusEvent, LostFocusEvent:
|
||||
case FocusEvent:
|
||||
view.hasFocus = true
|
||||
for _, listener := range getFocusListeners(view, "", command) {
|
||||
listener(self)
|
||||
}
|
||||
|
||||
case LostFocusEvent:
|
||||
view.hasFocus = false
|
||||
for _, listener := range getFocusListeners(view, "", command) {
|
||||
listener(self)
|
||||
}
|
||||
|
@ -838,3 +848,7 @@ func (view *viewData) SetChangeListener(tag string, listener func(View, string))
|
|||
view.changeListener[tag] = listener
|
||||
}
|
||||
}
|
||||
|
||||
func (view *viewData) HasFocus() bool {
|
||||
return view.hasFocus
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ type WebBrige interface {
|
|||
RunGetterScript(script string) DataObject
|
||||
AnswerReceived(answer DataObject)
|
||||
Close()
|
||||
remoteAddr() string
|
||||
}
|
||||
|
||||
type wsBrige struct {
|
||||
|
@ -122,3 +123,7 @@ func (brige *wsBrige) AnswerReceived(answer DataObject) {
|
|||
ErrorLog("answerID not found")
|
||||
}
|
||||
}
|
||||
|
||||
func (brige *wsBrige) remoteAddr() string {
|
||||
return brige.conn.RemoteAddr().String()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue