Improved ImageView and added "user-select" property

* Added "loaded-event" and "error-event" events to ImageView
* Added NaturalSize and CurrentSource methods to ImageView
* Added "user-select" property and IsUserSelect function
* Renamed "LightGoldenrodYellow" color constant to "LightGoldenRodYellow"
This commit is contained in:
Alexei Anoshenko 2022-07-08 13:16:42 +03:00
parent ab1b406278
commit 1285dc5c04
15 changed files with 847 additions and 37 deletions

View File

@ -1,3 +1,10 @@
# v0.8.0
* Added "loaded-event" and "error-event" events to ImageView
* Added NaturalSize and CurrentSource methods to ImageView
* Added "user-select" property and IsUserSelect function
* Renamed "LightGoldenrodYellow" color constant to "LightGoldenRodYellow"
# v0.7.0
* Added "resize", "grid-auto-flow", "caret-color", and "backdrop-filter" properties

View File

@ -1630,7 +1630,7 @@ radius необходимо передать nil
#### Свойство "vertical-text-orientation"
Свойство "vertical-text-orientation" (константа VerticalTextOrientation) - свойство типа int используется, только
Свойство "vertical-text-orientation" (константа VerticalTextOrientation) типа int используется, только
если "writing-mode" установлено в VerticalRightToLeft (2) или VerticalLeftToRight (3) и определяет положение
символов вертикальной строки. Возможны следующие значения:
@ -1643,6 +1643,22 @@ radius необходимо передать nil
func GetVerticalTextOrientation(view View, subviewID string) int
#### Свойство "user-select"
Свойство "user-select" (константа UserSelect) типа bool определяет может ли пользователь выделять текст.
Соответственно если свойство установлено в true, то пользователь может выделять текст. Если в false, то не может.
Значение по умолчанию зависит, от значения свойства "semantics". Если "semantics" установлено в "p", "h1"..."h6",
"blockquote" или "code", то значение по умолчанию равно "true", в остальных случаях значение по умолчанию равно "false".
Исключением является TableView. Для него значение по умолчанию равно "true".
Как и все свойства текста свойство "user-select" наследуемое, т.е. если вы установите его для контейнера,
то оно также примениться ко всем дочерним элементам
Получить значение данного свойства можно с помощью функции
func IsUserSelect(view View, subviewID string) bool
### Свойства трансформации
Данные свойства используются для трансформации (наклон, масштабирование и т.п.) содержимого View.
@ -2693,6 +2709,84 @@ TextView наследует от View все свойства параметро
| 0 | TextOverflowClip | "clip" | Текст обрезается по границе (по умолчанию) |
| 1 | TextOverflowEllipsis | "ellipsis" | В конце видимой части текста выводится '…' |
## ImageView
Элемент ImageView расширяющий интерфейс View предназначен для вывода изображений.
Для создания ImageView используется функция:
func NewImageView(session Session, params Params) ImageView
Выводимое изображение задается string свойством "src" (константа Source).
В качестве значения данному свойству присваивается либо имя изображения в папке images ресурсов,
либо url изобрадения.
ImageView позволяет выдодить разные изображения в зависимости от плотности экрана
(см. раздел "Изображения для экранов с разной плотностью пикселей").
В связи с этим интерфейс ImageView имеет два дополнительных метода, которые позволяют узнать какое именно изображение отображается:
CurrentSource() string
NaturalSize() (float64, float64)
Метод CurrentSource возвращает url выводимого изображения. Пока изображение не загрузилось данный метод возвращает пустую строку.
NaturalSize() возвращает исходную ширину и высоту выводимого изображения в экранных пикселях.
Т.е. если исходное изображение имеет размер 100x200, а плотность экрана равна 2, то метод NaturalSize вернет значение (50, 100).
Пока изображение не загрузилось данный метод возвращает значение (0, 0).
Для отслеживания загрузки изображения используются два события:
* "loaded-event" (константа LoadedEvent). Данное событие возникает сразу после загрузки изображения.
* "error-event" (константа ErrorEvent). Данное событие возникает если при загрузке изображения возникла ошибка.
Основной слушатель этих событий имеет следующий формат:
func(ImageView)
Свойство "alt-text" (константа AltText) типа string позволяет задать описание изображения.
Данный текст отображается если браузер не смог загрузить изображение.
Также данный текст используется в системе озвучивания для незрячих.
Свойство "fit" (константа Fit) типа int определяет параметры масштабирования изображения.
Допустимые значения:
| Значение | Константа | Имя | Значение |
|:--------:|--------------|--------------|-----------------------------------|
| 0 | NoneFit | "none" | Размер изображения не изменяется |
| 1 | ContainFit | "contain" | Изображение масштабируется так чтобы сохранить соотношение сторон и вписаться в размеры ImageView |
| 2 | CoverFit | "cover" | Изображение масштабируется так чтобы полность заполнить область ImageView. При этом сохраняется соотношение сторон изображения. Если после масштабтрования изображение выходит за границы по высоте или ширине, то оно обрезается |
| 3 | FillFit | "fill" | Изображение масштабируется так чтобы полность заполнить область ImageView. При этом соотношение сторон изображения может не сохраняться |
| 4 | ScaleDownFit | "scale-down" | Изображение масштабируется так как если бы были указаны NoneFit или ContainFit, в зависимости от того, что приведет к меньшему размеру изображения. Т.е. масштабирование может выполняться только в сторону уменьшения изображения |
Свойство "image-horizontal-align" (константа ImageHorizontalAlign) типа int устанавливет
горизонтальное выравнивание изображения относительно границ ImageView. Допустимые значения:
| Значение | Константа | Имя | Значение |
|:--------:|--------------|-----------|------------------------------|
| 0 | LeftAlign | "left" | Выравнивание по левому краю |
| 1 | RightAlign | "right" | Выравнивание по правому краю |
| 2 | CenterAlign | "center" | Выравнивание по центру |
| 3 | StretchAlign | "stretch" | Выравнивание по ширине |
Свойство "image-vertical-align" (константа ImageVerticalAlign) типа int устанавливает вертикальное
выравнивание изображения относительно границ ImageView. Допустимые значения:
| Значение | Константа | Имя | Значение |
|:--------:|--------------|-----------|-------------------------------|
| 0 | TopAlign | "top" | Выравнивание по верхнему краю |
| 1 | BottomAlign | "bottom" | Выравнивание по нижнему краю |
| 2 | CenterAlign | "center" | Выравнивание по центру |
| 3 | StretchAlign | "stretch" | Выравнивание по высоте |
Для получения значений свойств ImageView могут использоваться следующие функции:
func GetImageViewSource(view View, subviewID string) string
func GetImageViewAltText(view View, subviewID string) string
func GetImageViewFit(view View, subviewID string) int
func GetImageViewVerticalAlign(view View, subviewID string) int
func GetImageViewHorizontalAlign(view View, subviewID string) int
## EditView
Элемент EditView является редактором теста и расширяет интерфейс View.
@ -4211,6 +4305,172 @@ MediaPlayer имеет ряд методов для управления пар
где view - корневой View, playerID - id of AudioPlayer or VideoPlayer
## Popup
Popup это интерфейс позволяющий отобразить произвольный View в виде всплывающего окна.
Для создания интерфейса Popup используется функция
NewPopup(view View, param Params) Popup
где view - View содержимого всплывающего окна (не может быть nil);
params - параметры всплавающего окна (может быть nil). В качестве параметров всплавающего окна, могут
использоваться как любые свойства View, так и ряд дополнительных свойств (они будут описаны ниже)
После создания Popup его необходимо отобразить. Для этого используется метод Show() интерфейса Popup.
Для упрощения кода можно использовать функцию ShowPopup, которая опредена как
func ShowPopup(view View, param Params) Popup {
popup := NewPopup(view, param)
if popup != nil {
popup.Show()
}
return popup
}
Для закрытия всплавающего окна используется метод Dismiss() интерфейса Popup.
Помимо медодов Show() и Dismiss() интерфейс Popup имеет следующие методы:
* Session() Session - возвращает текущую сессию;
* View() View - возвращает содержимое всплывающего окна.
### Заголовок Popup
Всплывающее окно может иметь заголовок. Для того чтобы добавить заголовок необходимо добавить текст заголовка.
Для этого используется свойство "title" (константа Title) которое может принимать два типа значений:
* string
* View
Для установления стиля заголовка используется свойство "title-style" (константа TitleStyle) типа string.
Стиль заголовка по умолчанию "ruiPopupTitle". Если вы хотите чтобы все ваши всплывающие окна имели одинаковый стиль,
для этого лучше не использовать свойство "title-style", а переопределить стиль "ruiPopupTitle".
Заголовок также может иметь кнопку закрытия окна. Для ее добавления к заголовку используется свойство "close-button" типа bool.
Установка этого свойства в "true" добавляет к заголовку кнопку закрытия окна (значение по умолчанию равно "false").
### Закрытие Popup
Как было сказано выше, для закрытия всплавающего окна используется метод Dismiss() интерфейса Popup.
Если к заголовку окна добавлена кнопка закрытия, то нажатие на нее автоматически вызывает метод Dismiss().
Переопределить поведение кнопки закрытия окна нельзя.
Если все же необходимо переопределить поведение этой кнопки, то это можно сделать создав кастомный заголовок и
создав в нем свою кнопку закрытия.
Существует еще один способ автоматического вызова метода Dismiss(). Это свойство "outside-close" (константа OutsideClose) типа bool.
Если это свойство установлено в "true", то клик мышью вне пределов всплывающего окна автоматически вызывает метод Dismiss().
Для отслеживания закрытия всплывающего окна используются событие "dismiss-event" (константа DismissEvent).
Оно возникает после того как Popup исчезнет с экрана.
Основной слушатель этого событя имеет следующий формат:
func(Popup)
### Область кнопок
Часто во всплывающее окно необходимо добавить кнопки, такие как "OK", "Cancel" и т.п.
С помощью свойства "buttons" (константа Buttons) вы можете добавлять кнопки, которые будут располагаться внизу окна.
Свойству "buttons" можно присваивать следующие типы данных:
* PopupButton
* []PopupButton
Где структура PopupButton объявлена как
type PopupButton struct {
Title string
OnClick func(Popup)
}
где Title - текст кнопки, OnClick - функция вызываемая при нажатии на кнопку
По умолчанию кнопки выравниваются по правому краю окна. Однако это поведение можно переопределить.
Для этого используется свойство "buttons-align" (константа ButtonsAlign) типа int, которое может принимать следующие значения:
| Значение | Константа | Имя | Выравнивание |
|:--------:|--------------|-----------|------------------------------|
| 0 | LeftAlign | "left" | Выравнивание по левому краю |
| 1 | RightAlign | "right" | Выравнивание по правому краю |
| 2 | CenterAlign | "center" | Выравнивание по центру |
| 3 | StretchAlign | "stretch" | Выравнивание по ширине |
Расстояние между кнопками задается с помощью константы "ruiPopupButtonGap" типа SizeUnit. Вы можете переопределить ее в своей теме.
### Выравнивание Popup
По умолчанию всплывающее окно располагается по центру окна браузера. Изменить это поведение можно с помощью свойств
"vertical-align" (константа VerticalAlign) и "horizontal-align" (константа HorizontalAlign) типа int.
Свойство "vertical-align" может принимать следующие значения:
| Значение | Константа | Имя | Значение |
|:--------:|--------------|-----------|-------------------------------|
| 0 | TopAlign | "top" | Выравнивание по верхнему краю |
| 1 | BottomAlign | "bottom" | Выравнивание по нижнему краю |
| 2 | CenterAlign | "center" | Выравнивание по центру |
| 3 | StretchAlign | "stretch" | Выравнивание по высоте |
Свойство "horizontal-align" может принимать следующие значения:
| Значение | Константа | Имя | Значение |
|:--------:|--------------|-----------|------------------------------|
| 0 | LeftAlign | "left" | Выравнивание по левому краю |
| 1 | RightAlign | "right" | Выравнивание по правому краю |
| 2 | CenterAlign | "center" | Выравнивание по центру |
| 3 | StretchAlign | "stretch" | Выравнивание по ширине |
Для сдвига окна может использоваться свойство "margin".
Например, организовать выподающее окно привязанное к кнопке можно так
rui.ShowPopup(myPopupView, rui.Params{
rui.HorizontalAlign: rui.LeftAlign,
rui.VerticalAlign: rui.TopAlign,
rui.MarginLeft: rui.Px(myButton.Frame().Left),
rui.MarginTop: rui.Px(myButton.Frame().Bottom()),
})
### Стандартные Popup
В библеотеке rui уже реализованы некотороые стандартные всплывающие окна.
Для их отображения используются следующие функции
func ShowMessage(title, text string, session Session)
Данная функция выводит на экран сообщение с заголовком заданным в аргументе title и текстом сообщения заданном в аргументе text.
func ShowQuestion(title, text string, session Session, onYes func(), onNo func())
Данная функция выводит на экран сообщение с заданным заголовком и текстом и двумя кнопками "Yes" и "No".
При нажатии кнопки "Yes" сообщение закрывается и вызывается функция onYes (если она не nil).
При нажатии кнопки "No" сообщение закрывается и вызывается функция onNo (если она не nil).
func ShowCancellableQuestion(title, text string, session Session, onYes func(), onNo func(), onCancel func())
Данная функция выводит на экран сообщение с заданным заголовком и текстом и тремя кнопками "Yes", "No" и "Cancel".
При нажатии кнопки "Yes", "No" или "Cancel" сообщение закрывается и вызывается, соотвественно, функция onYes,
onNo или onCancel (если она не nil).
func ShowMenu(session Session, params Params) Popup
Данная функция выводит на экран меню. Пункты меню задаются с помощью свойства Items.
Свойство идентично Items идентично одноименному свойству ListView.
С помощью свойства "popup-menu-result" (константа PopupMenuResult) задается функция вызываемая при выборе пункта меню.
Ее формат
func(int)
Пример меню
rui.ShowMenu(session, rui.Params{
rui.OutsideClose: true,
rui.Items: []string{"Item 1", "Item 2", "Item 3"},
rui.PopupMenuResult: func(index int) {
// ...
},
})
## Анимация
Библиотека поддерживает два вида анимации:

256
README.md
View File

@ -1622,6 +1622,22 @@ You can get the value of this property using the function
func GetVerticalTextOrientation(view View, subviewID string) int
#### "user-select" property
The "user-select" property (UserSelect constant) of type bool determines whether the user can select text.
Accordingly, if the property is set to true, then the user can select text. If it's false, then it can't.
The default value depends on the value of the "semantics" property. If "semantics" is set to "p", "h1"..."h6",
"blockquote" or "code", then the default value is "true", otherwise the default value is "false".
The exception is TableView. Its default value is "true".
Like all text properties, the "user-select" property is inherited, i.e. if you set it for a container,
it will also apply to all child elements
You can get the value of this property using the function
func IsUserSelect(view View, subviewID string) bool
### Transformation properties
These properties are used to transform (skew, scale, etc.) the content of the View.
@ -2667,6 +2683,81 @@ This property of type int can take the following values
| 0 | TextOverflowClip | "clip" | Text is clipped at the border (default) |
| 1 | TextOverflowEllipsis | "ellipsis" | At the end of the visible part of the text '…' is displayed |
## ImageView
The ImageView element extending the View interface is designed to display images.
To create an ImageView function is used:
func NewImageView(session Session, params Params) ImageView
The displayed image is specified by the string property "src" (Source constant).
As a value, this property is assigned either the name of the image in the "images" folder of the resources, or the url of the image.
ImageView allows you to display different images depending on screen density
(See section "Images for screens with different pixel densities").
In this regard, the ImageView interface has two additional methods that allow you to find out which image is displayed:
CurrentSource() string
NaturalSize() (float64, float64)
CurrentSource returns the url of the rendered image. Until the image is loaded, this method returns an empty string.
NaturalSize() returns the original width and height of the rendered image, in screen pixels.
Those if the original image is 100x200 and the screen density is 2, then the NaturalSize method will return (50, 100).
Until the image is loaded, this method returns the value (0, 0).
Two events are used to track the loading of an image:
* "loaded-event" (LoadedEvent constant). This event fires right after the image is loaded.
* "error-event" (ErrorEvent constant). This event occurs when an error occurs while loading an image.
The main listener for these events has the following format:
func(ImageView)
The "alt-text" property (AltText constant) of the string type allows you to set a description of the image.
This text is displayed if the browser was unable to load the image.
Also, this text is used in the sound system for the blind.
The "fit" property (Fit constant) of type int defines the image scaling parameters.
Valid values:
| Value | Constant | Name | Resizing |
|:-----:|--------------|--------------|------------------------------|
| 0 | NoneFit | "none" | The image is not resized |
| 1 | ContainFit | "contain" | The image is scaled to maintain its aspect ratio while fitting within the elements content box. The entire object is made to fill the box, while preserving its aspect ratio, so the object will be "letterboxed" if its aspect ratio does not match the aspect ratio of the box |
| 2 | CoverFit | "cover" | The image is sized to maintain its aspect ratio while filling the elements entire content box. If the object's aspect ratio does not match the aspect ratio of its box, then the object will be clipped to fit |
| 3 | FillFit | "fill" | The image to fill the elements content box. The entire object will completely fill the box. If the object's aspect ratio does not match the aspect ratio of its box, then the object will be stretched to fit |
| 4 | ScaleDownFit | "scale-down" | The image is sized as if NoneFit or ContainFit were specified, whichever would result in a smaller concrete object size |
The "image-vertical-align" int property (ImageVerticalAlign constant) sets the vertical alignment of the image
relative to the bounds of the ImageView. Valid values:
| Value | Constant | Name | Alignment |
|:-----:|--------------|-----------|------------------|
| 0 | TopAlign | "top" | Top alignment |
| 1 | BottomAlign | "bottom" | Bottom alignment |
| 2 | CenterAlign | "center" | Center alignment |
The "image-horizontal-align" int property (ImageHorizontalAlign constant) sets the horizontal alignment of the image
relative to the bounds of the ImageView. Valid values:
| Value | Constant | Name | Alignment |
|:-----:|--------------|-----------|------------------|
| 0 | LeftAlign | "left" | Left alignment |
| 1 | RightAlign | "right" | Right alignment |
| 2 | CenterAlign | "center" | Center alignment |
The following functions can be used to retrieve ImageView property values:
func GetImageViewSource(view View, subviewID string) string
func GetImageViewAltText(view View, subviewID string) string
func GetImageViewFit(view View, subviewID string) int
func GetImageViewVerticalAlign(view View, subviewID string) int
func GetImageViewHorizontalAlign(view View, subviewID string) int
## EditView
The EditView element is a test editor and extends the View interface.
@ -4177,6 +4268,171 @@ For quick access to these methods, there are global functions:
where view is the root View, playerID is the id of AudioPlayer or VideoPlayer
## Popup
Popup is an interface that allows you to display an arbitrary View as a popup window.
To create the Popup interface, use the function
NewPopup(view View, param Params) Popup
where view - View of popup content (cannot be nil);
params - parameters of the popup window (may be nil). As parameters of the pop-up window, either any View properties
or a number of additional properties (they will be described below) can be used.
Once a Popup has been created, it needs to be displayed. To do this, use the Show() method of the Popup interface.
To simplify the code, you can use the ShowPopup function, which is defined as
func ShowPopup(view View, param Params) Popup {
popup := NewPopup(view, param)
if popup != nil {
popup.Show()
}
return popup
}
To close a popup window, use the Dismiss() method of the Popup interface.
In addition to the Show() and Dismiss() methods, the Popup interface has the following methods:
* Session() Session - returns the current session;
* View() View - returns the contents of the popup window.
### Popup header
The popup window can have a title. In order to add a title, you need to add title text.
For this, the "title" property (Title constant) is used, which can take two types of values:
* string
* view
The "title-style" string property (TitleStyle constant) is used to set the title style.
The default title style is "ruiPopupTitle". If you want all your popups to have the same style, it's better
not to use the "title-style" property, but to override the "ruiPopupTitle" style.
The header can also have a window close button. To add it to the header, use the "close-button" bool property.
Setting this property to "true" adds a window close button to the title bar (the default value is "false").
### Close Popup
As it was said above, the Dismiss() method of the Popup interface is used to close the popup window.
If a close button is added to the window title, clicking on it automatically calls the Dismiss() method.
You cannot override the behavior of the window's close button.
If you still need to redefine the behavior of this button, then this can be done by creating a custom header and creating your own close button in it.
There is another way to automatically call the Dismiss() method. This is the "outside-close" bool property (OutsideClose constant).
If this property is set to "true", then clicking outside the popup window automatically calls the Dismiss() method.
The "dismiss-event" event (DismissEvent constant) is used to track the closing of the popup.
It occurs after the Popup disappears from the screen.
The main listener for this event has the following format:
func(Popup)
### Button area
It is often necessary to add buttons such as "OK", "Cancel", etc. to the popup window.
Using the "buttons" property (Buttons constant) you can add buttons that will be placed at the bottom of the window.
The "buttons" property can be assigned the following data types:
* PopupButton
* []PopupButton
Where the PopupButton structure is declared as
type PopupButton struct {
Title string
OnClick func(Popup)
}
where Title is the text of the button, OnClick is the function called when the button is clicked.
By default, buttons are aligned to the right edge of the popup window. However, this behavior can be overridden.
For this, the "buttons-align" int property (ButtonsAlign constant) is used, which can take the following values:
| Value | Constant | Name | Alignment |
|:-----:|--------------|-----------|------------------|
| 0 | LeftAlign | "left" | Left alignment |
| 1 | RightAlign | "right" | Right alignment |
| 2 | CenterAlign | "center" | Center alignment |
| 3 | StretchAlign | "stretch" | Width alignment |
The distance between the buttons is set using the "ruiPopupButtonGap" constant of the SizeUnit type. You can override it in your theme.
### Popup alignment
By default, the popup is positioned in the center of the browser window. You can change this behavior using
the "vertical-align" (VerticalAlign constant) and "horizontal-align" (HorizontalAlign constant) int properties.
The "vertical-align" property can take the following values:
| Value | Constant | Name | Alignment |
|:-----:|--------------|-----------|------------------|
| 0 | TopAlign | "top" | Top alignment |
| 1 | BottomAlign | "bottom" | Bottom alignment |
| 2 | CenterAlign | "center" | Center alignment |
| 3 | StretchAlign | "stretch" | Height alignment |
The "horizontal-align" property can take the following values:
| Value | Constant | Name | Alignment |
|:-----:|--------------|-----------|------------------|
| 0 | LeftAlign | "left" | Left alignment |
| 1 | RightAlign | "right" | Right alignment |
| 2 | CenterAlign | "center" | Center alignment |
| 3 | StretchAlign | "stretch" | Width alignment |
The "margin" property can be used to move the window.
For example, you can organize a popup window attached to a button like this
rui.ShowPopup(myPopupView, rui.Params{
rui.HorizontalAlign: rui.LeftAlign,
rui.VerticalAlign: rui.TopAlign,
rui.MarginLeft: rui.Px(myButton.Frame().Left),
rui.MarginTop: rui.Px(myButton.Frame().Bottom()),
})
### Standard Popup
The rui library already implements some standard popups.
The following functions are used to display them.
func ShowMessage(title, text string, session Session)
This function displays a message with the title given in the "title" argument and the message text given in the "text" argument.
func ShowQuestion(title, text string, session Session, onYes func(), onNo func())
This function displays a message with the given title and text and two buttons "Yes" and "No".
When the "Yes" button is clicked, the message is closed and the onYes function is called (if it is not nil).
When the "No" button is pressed, the message is closed and the onNo function is called (if it is not nil).
func ShowCancellableQuestion(title, text string, session Session, onYes func(), onNo func(), onCancel func())
This function displays a message with the given title and text and three buttons "Yes", "No" and "Cancel".
When the "Yes", "No" or "Cancel" button is pressed, the message is closed and the onYes, onNo or onCancel function
(if it is not nil) is called, respectively.
func ShowMenu(session Session, params Params) Popup
This function displays the menu. Menu items are set using the Items property.
The property is identical to Items and is identical to the ListView property of the same name.
The "popup-menu-result" property (PopupMenuResult constant) sets the function to be called when a menu item is selected.
Its format:
func(int)
Menu example:
rui.ShowMenu(session, rui.Params{
rui.OutsideClose: true,
rui.Items: []string{"Item 1", "Item 2", "Item 3"},
rui.PopupMenuResult: func(index int) {
// ...
},
})
## Animation
The library supports two types of animation:

View File

@ -1787,6 +1787,15 @@ function tableRowClickEvent(element, event) {
sendMessage("rowClick{session=" + sessionID + ",id=" + tableID + ",row=" + row + "}");
}
function stopEventPropagation(element, event) {
event.stopPropagation()
}
function imageLoaded(element, event) {
var message = "imageViewLoaded{session=" + sessionID + ",id=" + element.id +
",natural-width=" + element.naturalWidth +
",natural-height=" + element.naturalHeight +
",current-src=\"" + element.currentSrc + "\"}";
sendMessage(message);
}
function imageError(element, event) {
var message = "imageViewError{session=" + sessionID + ",id=" + element.id + "}";
sendMessage(message);
}

View File

@ -8,27 +8,31 @@
text-overflow: ellipsis;
}
div {
body {
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
margin: 0 auto;
width: 100%;
height: 100vh;
}
p, h1, h2, h3, h4, h5, h6, blockquote, code {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
div {
cursor: default;
}
p, h1, h2, h3, h4, h5, h6, blockquote, code, table {
cursor: text;
-webkit-user-select: auto;
user-select: auto;
}
/*
div:focus {
outline: none;
}
*/
input {
margin: 2px;
padding: 1px;
@ -55,11 +59,6 @@ ul:focus {
outline: none;
}
body {
margin: 0 auto;
width: 100%;
height: 100vh;
}
.ruiRoot {
position: absolute;

View File

@ -166,7 +166,7 @@ const (
// LightCyan color constant
LightCyan Color = 0xffe0ffff
// LightGoldenrodYellow color constant
LightGoldenrodYellow Color = 0xfffafad2
LightGoldenRodYellow Color = 0xfffafad2
// LightGray color constant
LightGray Color = 0xffd3d3d3
// LightGreen color constant

View File

@ -1,6 +1,8 @@
package main
import (
"fmt"
"github.com/anoshenko/rui"
)
@ -8,9 +10,17 @@ const imageViewDemoText = `
GridLayout {
style = demoPage,
content = [
ImageView {
id = imageView1, width = 100%, height = 100%, src = "cat.jpg",
border = _{ style = solid, width = 1px, color = #FF008800 }
GridLayout {
cell-height = "auto, 1fr",
content = [
TextView {
id = imageViewInfo,
},
ImageView {
id = imageView1, row = 1, width = 100%, height = 100%, src = "cat.jpg",
border = _{ style = solid, width = 1px, color = #FF008800 }
},
],
},
ListLayout {
style = optionsPanel,
@ -21,7 +31,7 @@ GridLayout {
TextView { row = 0, text = "Image" },
DropDownList { row = 0, column = 1, id = imageViewImage, current = 0, items = ["cat.jpg", "winds.png", "gifsInEmail.gif", "mountain.svg"]},
TextView { row = 1, text = "Fit" },
DropDownList { row = 1, column = 1, id = imageViewFit, current = 0, items = ["none", "fill", "contain", "cover", "scale-down"]},
DropDownList { row = 1, column = 1, id = imageViewFit, current = 0, items = ["none", "contain", "cover", "fill", "scale-down"]},
TextView { row = 2, text = "Horizontal align" },
DropDownList { row = 2, column = 1, id = imageViewHAlign, current = 2, items = ["left", "right", "center"]},
TextView { row = 3, text = "Vertical align" },
@ -40,6 +50,15 @@ func createImageViewDemo(session rui.Session) rui.View {
return nil
}
rui.Set(view, "imageView1", rui.LoadedEvent, func(imageView rui.ImageView) {
w, h := imageView.NaturalSize()
rui.Set(view, "imageViewInfo", rui.Text, fmt.Sprintf("Natural size: (%g, %g). Current URL: %s", w, h, imageView.CurrentSource()))
})
rui.Set(view, "imageView1", rui.ErrorEvent, func(imageView rui.ImageView) {
rui.Set(view, "imageViewInfo", rui.Text, "Image loading error")
})
rui.Set(view, "imageViewImage", rui.DropDownEvent, func(list rui.DropDownList, number int) {
images := []string{"cat.jpg", "winds.png", "gifsInEmail.gif", "mountain.svg"}
if number < len(images) {

View File

@ -6,6 +6,13 @@ import (
)
const (
// LoadedEvent is the constant for the "loaded-event" property tag.
// The "loaded-event" event occurs event occurs when the image has been loaded.
LoadedEvent = "loaded-event"
// ErrorEvent is the constant for the "error-event" property tag.
// The "error-event" event occurs event occurs when the image loading failed.
ErrorEvent = "error-event"
// NoneFit - value of the "object-fit" property of an ImageView. The replaced content is not resized
NoneFit = 0
// ContainFit - value of the "object-fit" property of an ImageView. The replaced content
@ -29,10 +36,19 @@ const (
// ImageView - image View
type ImageView interface {
View
// NaturalSize returns the intrinsic, density-corrected size (width, height) of the image in pixels.
// If the image hasn't been loaded yet or an load error has occurred, then (0, 0) is returned.
NaturalSize() (float64, float64)
// CurrentSource() return the full URL of the image currently visible in the ImageView.
// If the image hasn't been loaded yet or an load error has occurred, then "" is returned.
CurrentSource() string
}
type imageViewData struct {
viewData
naturalWidth float64
naturalHeight float64
currentSrc string
}
// NewImageView create new ImageView object and return it
@ -102,6 +118,77 @@ func (imageView *imageViewData) Set(tag string, value interface{}) bool {
return imageView.set(imageView.normalizeTag(tag), value)
}
func valueToImageListeners(value interface{}) ([]func(ImageView), bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case func(ImageView):
return []func(ImageView){value}, true
case func():
fn := func(ImageView) {
value()
}
return []func(ImageView){fn}, true
case []func(ImageView):
if len(value) == 0 {
return nil, true
}
for _, fn := range value {
if fn == nil {
return nil, false
}
}
return value, true
case []func():
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(ImageView), count)
for i, v := range value {
if v == nil {
return nil, false
}
listeners[i] = func(ImageView) {
v()
}
}
return listeners, true
case []interface{}:
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(ImageView), count)
for i, v := range value {
if v == nil {
return nil, false
}
switch v := v.(type) {
case func(ImageView):
listeners[i] = v
case func():
listeners[i] = func(ImageView) {
v()
}
default:
return nil, false
}
}
return listeners, true
}
return nil, false
}
func (imageView *imageViewData) set(tag string, value interface{}) bool {
if value == nil {
imageView.remove(tag)
@ -140,6 +227,12 @@ func (imageView *imageViewData) set(tag string, value interface{}) bool {
}
notCompatibleType(tag, value)
case LoadedEvent, ErrorEvent:
if listeners, ok := valueToImageListeners(value); ok {
imageView.properties[tag] = listeners
return true
}
default:
if imageView.viewData.set(tag, value) {
if imageView.created {
@ -159,6 +252,15 @@ func (imageView *imageViewData) Get(tag string) interface{} {
return imageView.viewData.get(imageView.normalizeTag(tag))
}
func (imageView *imageViewData) imageListeners(tag string) []func(ImageView) {
if value := imageView.getRaw(tag); value != nil {
if listeners, ok := value.([]func(ImageView)); ok {
return listeners
}
}
return []func(ImageView){}
}
func (imageView *imageViewData) srcSet(path string) string {
if srcset, ok := resources.imageSrcSets[path]; ok {
buffer := allocStringBuilder()
@ -214,6 +316,12 @@ func (imageView *imageViewData) htmlProperties(self View, buffer *strings.Builde
buffer.WriteString(textToJS(text))
buffer.WriteString(`"`)
}
buffer.WriteString(` onload="imageLoaded(this, event)"`)
if len(imageView.imageListeners(ErrorEvent)) > 0 {
buffer.WriteString(` onerror="imageError(this, event)"`)
}
}
func (imageView *imageViewData) cssStyle(self View, builder cssBuilder) {
@ -251,6 +359,36 @@ func (imageView *imageViewData) cssStyle(self View, builder cssBuilder) {
}
}
func (imageView *imageViewData) handleCommand(self View, command string, data DataObject) bool {
switch command {
case "imageViewError":
for _, listener := range imageView.imageListeners(ErrorEvent) {
listener(imageView)
}
case "imageViewLoaded":
imageView.naturalWidth = dataFloatProperty(data, "natural-width")
imageView.naturalHeight = dataFloatProperty(data, "natural-height")
imageView.currentSrc, _ = data.PropertyValue("current-src")
for _, listener := range imageView.imageListeners(LoadedEvent) {
listener(imageView)
}
default:
return imageView.viewData.handleCommand(self, command, data)
}
return true
}
func (imageView *imageViewData) NaturalSize() (float64, float64) {
return imageView.naturalWidth, imageView.naturalHeight
}
func (imageView *imageViewData) CurrentSource() string {
return imageView.currentSrc
}
// GetImageViewSource returns the image URL of an ImageView subview.
// If the second argument (subviewID) is "" then a left position of the first argument (view) is returned
func GetImageViewSource(view View, subviewID string) string {

View File

@ -3,19 +3,42 @@ package rui
import "strings"
const (
// Title is the Popup string property
// Title is the constant for the "title" property tag.
// The "title" property is defined the Popup/Tabs title
Title = "title"
// TitleStyle is the Popup string property
// TitleStyle is the constant for the "title-style" property tag.
// The "title-style" string property is used to set the title style of the Popup.
TitleStyle = "title-style"
// CloseButton is the Popup bool property
// CloseButton is the constant for the "close-button" property tag.
// The "close-button" bool property allow to add the close button to the Popup.
// Setting this property to "true" adds a window close button to the title bar (the default value is "false").
CloseButton = "close-button"
// OutsideClose is the Popup bool property
// OutsideClose is the constant for the "outside-close" property tag.
// The "outside-close" is a bool property. If it is set to "true",
// then clicking outside the popup window automatically calls the Dismiss() method.
OutsideClose = "outside-close"
Buttons = "buttons"
// Buttons is the constant for the "buttons" property tag.
// Using the "buttons" property you can add buttons that will be placed at the bottom of the Popup.
// The "buttons" property can be assigned the following data types: PopupButton and []PopupButton
Buttons = "buttons"
// ButtonsAlign is the constant for the "buttons-align" property tag.
// The "buttons-align" int property is used for set the horizontal alignment of Popup buttons.
// Valid values: LeftAlign (0), RightAlign (1), CenterAlign (2), and StretchAlign (3)
ButtonsAlign = "buttons-align"
// DismissEvent is the constant for the "dismiss-event" property tag.
// The "dismiss-event" event is used to track the closing of the Popup.
// It occurs after the Popup disappears from the screen.
// The main listener for this event has the following format: func(Popup)
DismissEvent = "dismiss-event"
)
// PopupButton describes a button that will be placed at the bottom of the window.
type PopupButton struct {
Title string
OnClick func(Popup)
@ -23,11 +46,11 @@ type PopupButton struct {
// Popup interface
type Popup interface {
//Properties
View() View
Session() Session
Show()
Dismiss()
onDismiss()
html(buffer *strings.Builder)
viewByHTMLID(id string) View
}
@ -46,6 +69,10 @@ func (popup *popupData) init(view View, params Params) {
popup.view = view
session := view.Session()
if params == nil {
params = Params{}
}
popup.dismissListener = []func(Popup){}
if value, ok := params[DismissEvent]; ok && value != nil {
switch value := value.(type) {
@ -98,7 +125,6 @@ func (popup *popupData) init(view View, params Params) {
outsideClose, _ := boolProperty(params, OutsideClose, session)
vAlign, _ := enumProperty(params, VerticalAlign, session, CenterAlign)
hAlign, _ := enumProperty(params, HorizontalAlign, session, CenterAlign)
buttonsAlign, _ := enumProperty(params, ButtonsAlign, session, RightAlign)
buttons := []PopupButton{}
if value, ok := params[Buttons]; ok && value != nil {
@ -191,6 +217,7 @@ func (popup *popupData) init(view View, params Params) {
popupView.Append(view)
if buttonCount := len(buttons); buttonCount > 0 {
buttonsAlign, _ := enumProperty(params, ButtonsAlign, session, RightAlign)
cellHeight = append(cellHeight, AutoSize())
gap, _ := sizeConstant(session, "ruiPopupButtonGap")
cellWidth := []SizeUnit{}
@ -276,6 +303,12 @@ func (popup *popupData) viewByHTMLID(id string) View {
return viewByHTMLID(id, popup.layerView)
}
func (popup *popupData) onDismiss() {
for _, listener := range popup.dismissListener {
listener(popup)
}
}
// NewPopup creates a new Popup
func NewPopup(view View, param Params) Popup {
if view == nil {
@ -287,6 +320,15 @@ func NewPopup(view View, param Params) Popup {
return popup
}
// ShowPopup creates a new Popup and shows it
func ShowPopup(view View, param Params) Popup {
popup := NewPopup(view, param)
if popup != nil {
popup.Show()
}
return popup
}
func (manager *popupManager) updatePopupLayerInnerHTML(session Session) {
if manager.popups == nil {
manager.popups = []Popup{}
@ -343,6 +385,7 @@ func (manager *popupManager) dismissPopup(popup Popup) {
manager.popups = manager.popups[:count-1]
manager.updatePopupLayerInnerHTML(session)
}
popup.onDismiss()
return
}
@ -354,6 +397,7 @@ func (manager *popupManager) dismissPopup(popup Popup) {
manager.popups = append(manager.popups[:n], manager.popups[n+1:]...)
}
manager.updatePopupLayerInnerHTML(session)
popup.onDismiss()
return
}
}

View File

@ -1,6 +1,6 @@
package rui
// ShowMessage displays the popup with text message
// ShowMessage displays the popup with the title given in the "title" argument and the message text given in the "text" argument.
func ShowMessage(title, text string, session Session) {
textView := NewTextView(session, Params{
Text: text,
@ -16,6 +16,9 @@ func ShowMessage(title, text string, session Session) {
NewPopup(textView, params).Show()
}
// ShowQuestion displays a message with the given title and text and two buttons "Yes" and "No".
// When the "Yes" button is clicked, the message is closed and the onYes function is called (if it is not nil).
// When the "No" button is pressed, the message is closed and the onNo function is called (if it is not nil).
func ShowQuestion(title, text string, session Session, onYes func(), onNo func()) {
textView := NewTextView(session, Params{
Text: text,
@ -51,6 +54,9 @@ func ShowQuestion(title, text string, session Session, onYes func(), onNo func()
NewPopup(textView, params).Show()
}
// ShowCancellableQuestion displays a message with the given title and text and three buttons "Yes", "No" and "Cancel".
// When the "Yes", "No" or "Cancel" button is pressed, the message is closed and the onYes, onNo or onCancel function
// (if it is not nil) is called, respectively.
func ShowCancellableQuestion(title, text string, session Session, onYes func(), onNo func(), onCancel func()) {
textView := NewTextView(session, Params{
Text: text,
@ -128,9 +134,13 @@ func (popup *popupMenuData) IsListItemEnabled(index int) bool {
return true
}
// PopupMenuResult is the constant for the "popup-menu-result" property tag.
// The "popup-menu-result" property sets the function (format: func(int)) to be called when
// a menu item of popup menu is selected.
const PopupMenuResult = "popup-menu-result"
// ShowMenu displays the popup with text message
// ShowMenu displays the menu. Menu items are set using the Items property.
// The "popup-menu-result" property sets the function (format: func(int)) to be called when a menu item is selected.
func ShowMenu(session Session, params Params) Popup {
value, ok := params[Items]
if !ok || value == nil {

View File

@ -466,13 +466,7 @@ const (
// The "grid-column-gap" SizeUnit properties allow to set the distance between the columns of the GridLayout container.
// The default is 0px.
GridColumnGap = "grid-column-gap"
/*
// GridAutoRows is the constant for the "grid-auto-rows" property tag.
GridAutoRows = "grid-auto-rows"
// GridAutoColumns is the constant for the "grid-auto-columns" property tag.
GridAutoColumns = "grid-auto-columns"
*/
// Source is the constant for the "src" property tag.
Source = "src"
@ -638,4 +632,9 @@ const (
// The "resize" int property sets whether an element is resizable, and if so, in which directions.
// Valid values are "none" (0), "both" (1), horizontal (2), and "vertical" (3)
Resize = "resize"
// UserSelect is the constant for the "user-select" property tag.
// The "user-select" bool property controls whether the user can select text.
// This is an inherited property, i.e. if it is not defined, then the value of the parent view is used.
UserSelect = "user-select"
)

View File

@ -62,6 +62,7 @@ var boolProperties = []string{
Multiple,
TabCloseButton,
Repeating,
UserSelect,
}
var intProperties = []string{

15
view.go
View File

@ -583,6 +583,21 @@ func viewPropertyChanged(view *viewData, tag string) {
updateInnerHTML(parent, session)
}
return
case UserSelect:
if userSelect, ok := boolProperty(view, UserSelect, session); ok {
if userSelect {
updateCSSProperty(htmlID, "-webkit-user-select", "auto", session)
updateCSSProperty(htmlID, "user-select", "auto", session)
} else {
updateCSSProperty(htmlID, "-webkit-user-select", "none", session)
updateCSSProperty(htmlID, "user-select", "none", session)
}
} else {
updateCSSProperty(htmlID, "-webkit-user-select", "", session)
updateCSSProperty(htmlID, "user-select", "", session)
}
return
}
if cssTag, ok := sizeProperties[tag]; ok {

View File

@ -272,6 +272,16 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
builder.add("text-decoration", text)
}
if userSelect, ok := boolProperty(style, UserSelect, session); ok {
if userSelect {
builder.add("-webkit-user-select", "auto")
builder.add("user-select", "auto")
} else {
builder.add("-webkit-user-select", "none")
builder.add("user-select", "none")
}
}
if css := shadowCSS(style, Shadow, session); css != "" {
builder.add("box-shadow", css)
}

View File

@ -1103,3 +1103,46 @@ func GetCurrent(view View, subviewID string) int {
}
return defaultValue
}
// IsUserSelect returns "true" if the user can select text, "false" otherwise.
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
func IsUserSelect(view View, subviewID string) bool {
if subviewID != "" {
view = ViewByID(view, subviewID)
}
if view != nil {
value, _ := isUserSelect(view)
return value
}
return false
}
func isUserSelect(view View) (bool, bool) {
result, ok := boolStyledProperty(view, UserSelect)
if ok {
return result, true
}
if parent := view.Parent(); parent != nil {
result, ok = isUserSelect(parent)
if ok {
return result, true
}
}
if !result {
switch GetSemantics(view, "") {
case ParagraphSemantics, H1Semantics, H2Semantics, H3Semantics, H4Semantics, H5Semantics,
H6Semantics, BlockquoteSemantics, CodeSemantics:
return true, false
}
if _, ok := view.(TableView); ok {
return true, false
}
}
return result, false
}