forked from mbk-lab/rui_orig
2
0
Fork 0

Compare commits

..

No commits in common. "main" and "0.10" have entirely different histories.
main ... 0.10

65 changed files with 1133 additions and 3568 deletions

View File

@ -1,35 +1,6 @@
# v0.13.0
* Added SetHotKey function to Session interface
* Added ViewIndex function to ViewsContainer interface
* Added ReloadCell function to TableView interface
* Added ReloadTableViewCell function
* Added "tooltip" property and GetTooltip function
* Added "outline-offset" property and GetOutlineOffset function
* Changed the main event listener format for "drop-down-event", "edit-text-changed",
"color-changed", "number-changed", "date-changed", and "time-changed" events.
Old format is "<listener>(<view>, <new value>)", new format is "<listener>(<view>, <new value>, <old value>)"
* Changed FocusView function
* Added support for height and width range in media styles.
Changed MediaStyle, SetMediaStyle, and MediaStyles functions of Theme interface
* Bug fixing
# v0.12.0
* Added SvgImageView
* Added InlineImageFromResource function
# v0.11.0
* Added "tabindex", "order", "column-fill", "column-span-all", "background-blend-mode", and "mix-blend-mode" properties
* Added GetTabIndex, GetOrder, GetColumnFill, IsColumnSpanAll, GetBackgroundBlendMode, and GetMixBlendMode functions
* ClientItem, SetClientItem, and RemoveAllClientItems method added to Session interface
* PropertyWithTag method of DataObject interface renamed to PropertyByTag
# v0.10.0
# v.10.0
* The Canvas.TextWidth method replaced by Canvas.TextMetrics
* Added support of WebAssembly
# v0.9.0
@ -101,7 +72,7 @@ Changed MediaStyle, SetMediaStyle, and MediaStyles functions of Theme interface
# v0.2.0
* Added "animation" and "transition" properties, Animation interface, animation events
* Renamed ColorProperty constant to ColorTag
* Renamed ColorPropery constant to ColorTag
* Updated readme
* Added the Animation example to the demo
* Bug fixing

File diff suppressed because it is too large Load Diff

376
README.md
View File

@ -745,7 +745,7 @@ If all the lines of the frame are the same, then the following properties can be
|-----------|----------|----------|---------------------|
| "style" | Style | int | Border line style |
| "width" | Width | SizeUnit | Border line width |
| "color" | ColorTag | Color | Border line color |
| "color" | Color | Color | Border line color |
Example
@ -767,9 +767,9 @@ Example
equivalent to
view.Set(rui.Border, NewBorder(rui.Params{
rui.Style : rui.SolidBorder,
rui.Width : rui.Px(1),
rui.ColorTag: rui.Black,
rui.Style: rui.SolidBorder,
rui.Width: rui.Px(1),
rui.ColorProperty: rui.Black,
}))
The BorderProperty interface can be converted to a ViewBorders structure using the Border function.
@ -831,53 +831,11 @@ Example
equivalent to
view.Set(rui.Border, NewBorder(rui.Params{
rui.Style : rui.SolidBorder,
rui.Width : rui.Px(1),
rui.ColorTag: rui.Black,
rui.Style: rui.SolidBorder,
rui.Width: rui.Px(1),
rui.ColorProperty: rui.Black,
}))
### "outline" and "outline-offset" properties
The "outline" property defines the border outside the View.
A frame line is described by three attributes: line style, thickness, and color.
The "outline" property is similar to the "border" property. The three main differences between an "outline" frame and a "border" frame are:
1) the "border" frame is always located inside the boundaries of the View, and the "outline" frame can be located both inside the View and outside it;
2) all sides of the "outline" frame are the same, while the sides of the "border" frame can have different color, style and thickness.
3) "border" thickness is added to "padding" and "outline" thickness does not affect "padding".
The value of the "border" property is stored as an OutlineProperty interface that implements the Properties interface (see above).
OutlineProperty can contain the following properties:
| Property | Constant | Type | Description |
|-----------|----------|----------|---------------------|
| "style" | Style | int | Border line style |
| "width" | Width | SizeUnit | Border line width |
| "color" | ColorTag | Color | Border line color |
Line style can take the following values:
| Value | Constant | Name | Description |
|:-----:|------------|----------|---------------------|
| 0 | NoneLine | "none" | No frame |
| 1 | SolidLine | "solid" | Solid line |
| 2 | DashedLine | "dashed" | Dashed line |
| 3 | DottedLine | "dotted" | Dotted line |
| 4 | DoubleLine | "double" | Double solid line |
All other style values are ignored.
The NewOutline function is used to create the OutlineProperty interface.
By default, the inner border of the "outline" border is the same as the border of the View (i.e. the border is drawn around the View).
To change this behavior, use the "outline-offset" SizeUnit property (OutlineOffset constant).
This property defines the distance between the inner border of the frame and the border of the View.
A positive value moves the frame away from the View's boundaries, while a negative value forces the frame to be inside the View
(in this case, the frame is drawn on top of the contents of the View).
### "radius" property
The "radius" property sets the elliptical corner radius of the View. Radii are specified by the RadiusProperty
@ -1012,7 +970,7 @@ The shadow has the following properties:
| Property | Constant | Type | Description |
|-----------------|---------------|----------|-----------------------------------------------------------------------|
| "color" | ColorTag | Color | Shadow color |
| "color" | ColorProperty | Color | Shadow color |
| "inset" | Inset | bool | true - the shadow inside the View, false - outside |
| "x-offset" | XOffset | SizeUnit | Offset the shadow along the X axis |
| "y-offset" | YOffset | SizeUnit | Offset the shadow along the Y axis |
@ -1031,8 +989,8 @@ The NewShadowWithParams function is used when constants must be used as paramete
For example:
shadow := NewShadowWithParams(rui.Params{
rui.ColorTag : "@shadowColor",
rui.BlurRadius: 8.0,
rui.ColorProperty : "@shadowColor",
rui.BlurRadius : 8.0,
rui.Dilation : 16.0,
})
@ -1237,47 +1195,6 @@ Can be one of the following int values:
* ImageVerticalAlign,
### "background-blend-mode" property
The "background-blend-mode" int property (BackgroundBlendMode constant)sets how an view's background images should blend
with each other and with the view's background color.
Can take one of the following values:
| Constant | Value | Name | Description |
|-----------------|:-----:|--------------|------------------------------------------------------------------|
| BlendNormal | 0 | "normal" | The final color is the top color, regardless of what the bottom color is. The effect is like two opaque pieces of paper overlapping. |
| BlendMultiply | 1 | "multiply" | The final color is the result of multiplying the top and bottom colors. A black layer leads to a black final layer, and a white layer leads to no change. The effect is like two images printed on transparent film overlapping. |
| BlendScreen | 2 | "screen" | The final color is the result of inverting the colors, multiplying them, and inverting that value. A black layer leads to no change, and a white layer leads to a white final layer. The effect is like two images shone onto a projection screen. |
| BlendOverlay | 3 | "overlay" | The final color is the result of multiply if the bottom color is darker, or screen if the bottom color is lighter. This blend mode is equivalent to hard-light but with the layers swapped. |
| BlendDarken | 4 | "darken" | The final color is composed of the darkest values of each color channel. |
| BlendLighten | 5 | "lighten" | The final color is composed of the lightest values of each color channel. |
| BlendColorDodge | 6 | "color-dodge"| The final color is the result of dividing the bottom color by the inverse of the top color. A black foreground leads to no change. A foreground with the inverse color of the backdrop leads to a fully lit color. This blend mode is similar to screen, but the foreground need only be as light as the inverse of the backdrop to create a fully lit color. |
| BlendColorBurn | 7 | "color-burn" | The final color is the result of inverting the bottom color, dividing the value by the top color, and inverting that value. A white foreground leads to no change. A foreground with the inverse color of the backdrop leads to a black final image. This blend mode is similar to multiply, but the foreground need only be as dark as the inverse of the backdrop to make the final image black. |
| BlendHardLight | 8 | "hard-light" | The final color is the result of multiply if the top color is darker, or screen if the top color is lighter. This blend mode is equivalent to overlay but with the layers swapped. The effect is similar to shining a harsh spotlight on the backdrop. |
| BlendSoftLight | 9 | "soft-light" | The final color is similar to hard-light, but softer. This blend mode behaves similar to hard-light. The effect is similar to shining a diffused spotlight on the backdrop*.* |
| BlendDifference | 10 | "difference" | The final color is the result of subtracting the darker of the two colors from the lighter one. A black layer has no effect, while a white layer inverts the other layer's color. |
| BlendExclusion | 11 | "exclusion" | The final color is similar to difference, but with less contrast. As with difference, a black layer has no effect, while a white layer inverts the other layer's color. |
| BlendHue | 12 | "hue" | The final color has the hue of the top color, while using the saturation and luminosity of the bottom color. |
| BlendSaturation | 13 | "saturation" | The final color has the saturation of the top color, while using the hue and luminosity of the bottom color. A pure gray backdrop, having no saturation, will have no effect. |
| BlendColor | 14 | "color" | The final color has the hue and saturation of the top color, while using the luminosity of the bottom color. The effect preserves gray levels and can be used to colorize the foreground. |
| BlendLuminosity | 15 | "luminosity" | The final color has the luminosity of the top color, while using the hue and saturation of the bottom color. This blend mode is equivalent to BlendColor, but with the layers swapped. |
You can get the value of this property using the function
func GetBackgroundBlendMode(view View, subviewID ...string) int
### "mix-blend-mode" property
The "mix-blend-mode" int property (MixBlendMode constant) sets how a view's content should blend
with the content of the view's parent and the view's background.
Possible values of this property are similar to the values of the "background-blend-mode" property (see above)
You can get the value of this property using the function
func GetMixBlendMode(view View, subviewID ...string) int
### "clip" property
The "clip" property (Clip constant) of the ClipShape type specifies the crop area.
@ -1340,29 +1257,13 @@ The textual description of the polygonal cropping area is in the following forma
### "opacity" property
The "opacity" property (Opacity constant) of the float64 type sets the transparency of the View. Valid values are from 0 to 1.
The "opacity" property (constant Opacity) of the float64 type sets the transparency of the View. Valid values are from 0 to 1.
Where 1 - View is fully opaque, 0 - fully transparent.
You can get the value of this property using the function
func GetOpacity(view View, subviewID ...string) float64
### "tabindex" property
The "tabindex" int property (TabIndex constant) determines whether this View should participate in sequential navigation
throughout the page using the keyboard and in what order. It can take one of the following types of values:
* negative value - View can be selected with the mouse or touch, but does not participate in sequential navigation;
* 0 - View can be selected and reached using sequential navigation, the order of navigation is determined by the browser (usually in order of addition);
* positive value - the element will be reached (and selected) using sequential navigation, and navigation is performed by ascending "tabindex" value.
If multiple elements contain the same "tabindex" value, navigation is done in the order in which they were added.
You can get the value of this property using the function
func GetTabIndex(viewView, subviewID ...string) int
### "z-index" property
The "z-index" property (constant ZIndex) of type int defines the position of the element and its children along the z-axis.
@ -1453,12 +1354,6 @@ It also helps to voice the interface to systems for people with disabilities:
| 18 | "blockquote" | Quote. Changes the style of the text |
| 19 | "code" | Program code. Changes the style of the text |
### "tooltip" property
The "tooltip" string property (Tooltip constant) specifies the tooltip text.
Tooltip pops up when hovering the mouse cursor.
You can use html tags when formatting the tooltip text.
### Text properties
All properties listed in this section are inherited, i.e. the property will apply
@ -1623,7 +1518,7 @@ You can get the value of this property using the function
#### "text-weight" property
The "text-weight" int property (TextWeight constant) sets the font style. Valid values:
Свойство "text-weight" (константа TextWeight) - свойство типа int устанавливает начертание шрифта. Допустимые значения:
| Value | Constant | Common name of the face |
|:-----:|----------------|---------------------------|
@ -1658,8 +1553,8 @@ To create a ViewShadow for the text shadow, the following functions are used:
The NewShadowWithParams function is used when constants must be used as parameters. For example:
shadow := NewShadowWithParams(rui.Params{
rui.ColorTag : "@shadowColor",
rui.BlurRadius: 8.0,
rui.ColorProperty : "@shadowColor",
rui.BlurRadius : 8.0,
})
ViewShadow, ViewShadow array, ViewShadow textual representation can be assigned as a value to the "text-shadow" property (see above, section "The 'shadow' property").
@ -2276,15 +2171,6 @@ If index is less than 0, then to the beginning of the list.
This function removes the View from the given position and returns it.
If index points outside the bounds of the list, then nothing is removed, and the function returns nil.
ViewIndex(view View) int
This function returns the index of the child View, or -1 if there is no such View in the container.
It is often used in conjunction with RemoveView if the index of the child View is unknown:
if index := container.ViewIndex(view); index >= 0 {
container.RemoveView(index)
}
## ListLayout
ListLayout is a container that implements the ViewsContainer interface. To create it, use the function
@ -2352,17 +2238,6 @@ alignment of items in the list. Valid values:
The "list-row-gap" and "list-column-gap" SizeUnit properties (ListRowGap and ListColumnGap constants)
allow you to set the distance between the rows and columns of the container, respectively. The default is 0px.
### "order"
The "order" property (Order constant) of type int is used by Views placed in a ListLayout or GridLayout container (see below),
to change its position in the container.
The "order" property defines the order used to place the View in the container. The elements are arranged in ascending order by their order value.
Elements with the same order value are placed in the order in which they were added to the container.
The default value is 0. Therefore, negative values of the "order" property must be used to place the View at the beginning.
Note: The "order" property only affects the visual order of the elements, not the logical order or tabs.
## GridLayout
GridLayout is a container that implements the ViewsContainer interface. To create it, use the function
@ -2545,15 +2420,15 @@ The value of the "column-separator" property is stored as the ColumnSeparatorPro
which implements the Properties interface (see above). ColumnSeparatorProperty can contain the following properties:
| Property | Constant | Type | Description |
|----------|-----------|----------|----------------|
|----------|---------------|----------|----------------|
| "style" | Style | int | Line style |
| "width" | Width | SizeUnit | Line thickness |
| "color" | ColorTag | Color | Line color |
| "color" | ColorProperty | Color | Line color |
Line style can take the following values:
| Value | Constant | Name | Description |
|:-----:|------------|----------|-------------------|
|:-----:|------------|----------| ------------------|
| 0 | NoneLine | "none" | No frame |
| 1 | SolidLine | "solid" | Solid line |
| 2 | DashedLine | "dashed" | Dashed line |
@ -2602,25 +2477,11 @@ For example
equivalent to
view.Set(rui.ColumnSeparator, ColumnSeparatorProperty(rui.Params{
rui.Style : rui.SolidBorder,
rui.Width : rui.Px(1),
rui.ColorTag: rui.Black,
rui.Style: rui.SolidBorder,
rui.Width: rui.Px(1),
rui.ColorProperty: rui.Black,
}))
### "column-fill" property
The "column-fill" int property (ColumnFill constant) controls how an ColumnLayout's contents are balanced when broken into columns.
Valid values:
| Value | Constant | Name | Description |
|:-----:|-------------------|-----------|------------------------------------------------------------|
| 0 | ColumnFillBalance | "balance" | Content is equally divided between columns (default value) |
| 1 | ColumnFillAuto | "auto" | Columns are filled sequentially. Content takes up only the room it needs, possibly resulting in some columns remaining empty |
You can get the value of this property using the function
func GetColumnFill(view View, subviewID ...string) int
### "avoid-break" property
When forming columns, ColumnLayout can break some types of View, so that the beginning
@ -2636,20 +2497,6 @@ You can get the value of this property using the function
func GetAvoidBreak(view View, subviewID ...string) bool
### "column-span-all" property
The "column-span-all" bool property (ColumnSpanAll constant) is set for Views placed in the ColumnLayout.
If this property is set to true, then the View expands to the full width of the ColumnLayout, covering all columns.
Such a View will, as it were, break the container.
Typically, this property is used for headers.
The default value is "false".
You can get the value of this property using the function
func IsColumnSpanAll(view View, subviewID ...string) bool
## StackLayout
StackLayout is a container that implements the ViewsContainer interface.
@ -2892,7 +2739,7 @@ It determines how the text is cut if it goes out of bounds.
This property of type int can take the following values
| Value | Constant | Name | Cropping Text |
|:-----:|----------------------|------------|-------------------------------------------------------------|
|:-----:|----------------------| -----------|-------------------------------------------------------------|
| 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 |
@ -2905,25 +2752,7 @@ 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, or inline-image.
An inline-image is the content of an image file encoded in base64 format.
To get an inline-image from the application resources, use the function
func InlineImageFromResource(filename string) (string, bool)
Inline-images must be used in WebAssembly applications
if you want to host images in resources rather than on an external server.
Inline-images can cause app freezes in Safari and should be avoided.
Example
if runtime.GOOS == "js" {
if image, ok := rui.InlineImageFromResource("image.png"); ok {
view.Set(rui.Source, image)
}
} else {
view.Set(rui.Source, "image.png")
}
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").
@ -2989,39 +2818,6 @@ The following functions can be used to retrieve ImageView property values:
func GetImageViewVerticalAlign(view View, subviewID ...string) int
func GetImageViewHorizontalAlign(view View, subviewID ...string) int
## SvgImageView
The SvgImageView element extending the View interface is designed to display svg images.
To create an SvgImageView function is used:
func NewSvgImageView(session Session, params Params) ImageView
The image to be displayed is specified by the string property "content" (constant Content).
The value of this property can be assigned
* the image file name in the images folder of the resources;
* image url;
* content of the svg image.
Examples
rui.Set(rootView, "iconView", rui.Content, "icon.svg")
rui.Set(rootView, "iconView", rui.Content, `<svg width="32" height="32" version="1.1" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-499.08 -247.12)">
<path d="m508.08 249.12 14 14-14 14" fill="none" stroke="#0f0" stroke-linecap="round" stroke-width="1px"/>
</g>
</svg>`)
Regardless of how you determined the property of "Content" to the client is always transmitted the contents of the SVG image. For example, if you set the image as follows
rui.Set(rootView, "iconView", rui.Content, "icon.svg")
then the program will first upload the contents of the "icon.svg" file to the memory,
and then transmit this contents to the client as the value of the "content" property.
This allows you to include SVG images in the resources of a WebAssembly application.
## EditView
The EditView element is a test editor and extends the View interface.
@ -3086,21 +2882,13 @@ The following functions can be used to get the values of the properties of an Ed
The "edit-text-changed" event (EditTextChangedEvent constant) is used to track changes to the text.
The main event listener has the following format:
func(EditView, string, string)
func(EditView, string)
where the second argument is the new text value, the third argument is the previous text value.
Additional event listeners can have the following format
func(EditView, newText string)
func(newText, oldText string)
func(newText string)
func(EditView)
func()
where the second argument is the new text value
You can get the current list of text change listeners using the function
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string, string)
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string)
## NumberPicker
@ -3156,21 +2944,13 @@ You can read the values of these properties using the functions:
The "number-changed" event (NumberChangedEvent constant) is used to track the change in the entered value.
The main event listener has the following format:
func(picker NumberPicker, newValue, oldValue float64)
where the second argument is the new value, the third argument is the previous value.
Additional event listeners can have the following format
func(picker NumberPicker, newValue float64)
func(newValue, oldValue float64)
func(newValue float64)
func(picker NumberPicker)
func()
where the second argument is the new value
You can get the current list of value change listeners using the function
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64, float64)
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64)
## DatePicker
@ -3211,21 +2991,13 @@ You can read the values of these properties using the functions:
The "date-changed" event (DateChangedEvent constant) is used to track the change in the entered value.
The main event listener has the following format:
func(picker DatePicker, newDate, oldDate time.Time)
where the second argument is the new date value, the third argument is the previous date value.
Additional event listeners can have the following format
func(picker DatePicker, newDate time.Time)
func(newDate, oldDate time.Time)
func(newDate time.Time)
func(picker DatePicker)
func()
where the second argument is the new date value
You can get the current list of date change listeners using the function
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time, time.Time)
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time)
## TimePicker
@ -3266,21 +3038,13 @@ You can read the values of these properties using the functions:
The "time-changed" event (TimeChangedEvent constant) is used to track the change in the entered value.
The main event listener has the following format:
func(picker TimePicker, newTime, oldTime time.Time)
where the second argument is the new time value, the third argument is the previous time value.
Additional event listeners can have the following format
func(picker TimePicker, newTime time.Time)
func(newTime, oldTime time.Time)
func(newTime time.Time)
func(picker TimePicker)
func()
where the second argument is the new time value
You can get the current list of date change listeners using the function
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time, time.Time)
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time)
## ColorPicker
@ -3304,21 +3068,13 @@ The value of the property "color-picker-value" can also be read using the functi
The "color-changed" event (ColorChangedEvent constant) is used to track the change in the selected color.
The main event listener has the following format:
func(picker ColorPicker, newColor, oldColor Color)
func(picker ColorPicker, newColor Color)
where the second argument is the new color value, the third argument is the previous color value.
Additional event listeners can have the following format
func(picker ColorPicker, newColor string)
func(newColor, oldColor string)
func(newColor string)
func(picker ColorPicker)
func()
where the second argument is the new color value
You can get the current list of date change listeners using the function
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color, Color)
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color)
## FilePicker
@ -3448,19 +3204,11 @@ The main event listener has the following format:
func(list DropDownList, newCurrent int)
where the second argument is the index of the selected item, the third argument is the previous index value.
Additional event listeners can have the following format
func(list DropDownList, newCurrent int)
func(newCurrent, oldCurrent int)
func(newCurrent int)
func(list DropDownList)
func()
where the second argument is the index of the selected item
You can get the current list of date change listeners using the function
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int, int)
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int)
## ProgressBar
@ -3763,18 +3511,6 @@ The "content" property can also be assigned the following data types
[][]any and [][]string are converted to a TableAdapter when assigned.
If the elements of the table change during operation, then to update the contents of the table,
you must call one of the two methods of the TableView interface
* ReloadTableData()
* ReloadCell(row, column int)
The ReloadTableData method updates the entire table, while ReloadCell updates the contents of only a specific table cell.
Global functions can be used to call the ReloadTableData and ReloadCell methods
func ReloadTableViewData(view View, subviewID ...string) bool
func ReloadTableViewCell(row, column int, view View, subviewID ...string) bool
### "cell-style" property
The "cell-style" property (CellStyle constant) is used to customize the appearance of a table cell.
@ -4400,7 +4136,7 @@ AudioPlayer and VideoPlayer are elements for audio and video playback.
Both elements implement the MediaPlayer interface. Most of the properties and all events
of AudioPlayer and VideoPlayer are implemented through the MediaPlayer.
### "src" property
### Свойство "src"
The "src" property (Source constant) specifies one or more media sources. The "src" property can take on the following types:
@ -5171,9 +4907,6 @@ It is used when the client needs to transfer a file from the server.
* DownloadFileData(filename string, data [] byte) downloads (saves) on the client side a file
with a specified name and specified content. Typically used to transfer a file generated in server memory.
* SetHotKey(keyCode KeyCode, controlKeys ControlKeyMask, fn func(Session)) - sets the function that will be called
when the given hotkey is pressed.
## Resource description format
Application resources (themes, views, translations) can be described as text (utf-8).
@ -5461,17 +5194,9 @@ In addition to general styles, you can add styles for specific work modes. To do
* ":portrait" or ":landscape" are respectively styles for portrait or landscape mode of the program.
Attention means the aspect ratio of the program window, not the screen.
* ":width<min-width>-<max-width>" - styles for a screen whose width is in the range specified in logical pixels.
* ":width< size >" are styles for a screen whose width does not exceed the specified size in logical pixels.
* ":width<max-width>" - styles for a screen whose width does not exceed the specified value in logical pixels.
* ":width<min-width>-" - styles for a screen whose width is greater than the specified value in logical pixels.
* ":height<min-height>-<max-height>" - styles for a screen whose height is in the range specified in logical pixels.
* ":height<max-height>" - styles for a screen whose height does not exceed the specified value in logical pixels.
* ":height<minimum-height>-" - styles for a screen whose height is greater than the specified value in logical pixels.
* ":height< size >" are styles for a screen whose height does not exceed the specified size in logical pixels.
For example
@ -5503,19 +5228,7 @@ For example
width = 100%,
height = 50%,
},
],
styles:portrait:width320-640 = [
samplePage {
width = 90%,
height = 60%,
},
],
styles:portrait:width640- = [
samplePage {
width = 80%,
height = 70%,
},
],
]
}
## Standard constants and styles
@ -5583,9 +5296,6 @@ System color constants that you can override:
| ruiPopupTextColor | Popup text color |
| ruiPopupTitleColor | Popup title background color |
| ruiPopupTitleTextColor | Popup Title Text Color |
| ruiTooltipBackground | Tooltip background color |
| ruiTooltipTextColor | Tooltip text color |
| ruiTooltipShadowColor | Tooltip shadow color |
Constants that you can override:

View File

@ -85,7 +85,7 @@ const (
LinearTiming = "linear"
)
// StepsTiming return a timing function along stepCount stops along the transition, displaying each stop for equal lengths of time
// StepsTiming return a timing function along stepCount stops along the transition, diplaying each stop for equal lengths of time
func StepsTiming(stepCount int) string {
return "steps(" + strconv.Itoa(stepCount) + ")"
}
@ -130,7 +130,7 @@ type Animation interface {
writeTransitionString(tag string, buffer *strings.Builder)
animationCSS(session Session) string
transitionCSS(buffer *strings.Builder, session Session)
hasAnimatedProperty() bool
hasAnimatedPropery() bool
animationName() string
}
@ -160,7 +160,7 @@ func NewAnimation(params Params) Animation {
return animation
}
func (animation *animationData) hasAnimatedProperty() bool {
func (animation *animationData) hasAnimatedPropery() bool {
props := animation.getRaw(PropertyTag)
if props == nil {
ErrorLog("There are no animated properties.")

View File

@ -30,7 +30,7 @@ const (
AnimationStartEvent = "animation-start-event"
// AnimationEndEvent is the constant for "animation-end-event" property tag.
// The "animation-end-event" is fired when an animation has completed.
// The "animation-end-event" is fired when aт фnimation has completed.
// If the animation aborts before reaching completion, such as if the element is removed
// or the animation is removed from the element, the "animation-end-event" is not fired.
AnimationEndEvent = "animation-end-event"
@ -46,7 +46,7 @@ const (
// AnimationIterationEvent is the constant for "animation-iteration-event" property tag.
// The "animation-iteration-event" is fired when an iteration of an animation ends,
// and another one begins. This event does not occur at the same time as the animation end event,
// and another one begins. This event does not occur at the same time as the animationend event,
// and therefore does not occur for animations with an "iteration-count" of one.
AnimationIterationEvent = "animation-iteration-event"
)
@ -91,10 +91,7 @@ func transitionEventsHtml(view View, buffer *strings.Builder) {
for tag, js := range transitionEvents {
if value := view.getRaw(tag); value != nil {
if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
}
}
}
@ -160,10 +157,7 @@ func animationEventsHtml(view View, buffer *strings.Builder) {
for tag, js := range animationEvents {
if value := view.getRaw(tag); value != nil {
if listeners, ok := value.([]func(View)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
}
}
}

View File

@ -156,7 +156,14 @@ func (app *application) socketReader(bridge webBridge) {
ErrorLog(`"session" key not found`)
}
bridge.writeMessage("restartSession();")
answer := ""
if session, answer = app.startSession(obj, events, bridge); session != nil {
if !bridge.writeMessage(answer) {
return
}
session.onStart()
go sessionEventHandler(session, events, bridge)
}
case "answer":
session.handleAnswer(obj)
@ -288,7 +295,7 @@ func OpenBrowser(url string) bool {
case "linux":
for _, provider := range []string{"xdg-open", "x-www-browser", "www-browser"} {
if _, err = exec.LookPath(provider); err == nil {
if err = exec.Command(provider, url).Start(); err == nil {
if exec.Command(provider, url).Start(); err == nil {
return true
}
}

View File

@ -149,6 +149,7 @@ func (app *wasmApp) init(params AppParams) {
div = document.Call("createElement", "div")
div.Set("className", "ruiPopupLayer")
div.Set("id", "ruiPopupLayer")
div.Set("onclick", "clickOutsidePopup(event)")
div.Set("style", "visibility: hidden;")
body.Call("appendChild", div)

View File

@ -52,31 +52,9 @@ function sessionInfo() {
message += ",pixel-ratio=" + pixelRatio;
}
if (localStorage.length > 0) {
message += ",storage="
lead = "_{"
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i)
var value = localStorage.getItem(key)
key = key.replaceAll(/\\/g, "\\\\")
key = key.replaceAll(/\"/g, "\\\"")
key = key.replaceAll(/\'/g, "\\\'")
value = value.replaceAll(/\\/g, "\\\\")
value = value.replaceAll(/\"/g, "\\\"")
value = value.replaceAll(/\'/g, "\\\'")
message += lead + "\"" + key + "\"=\"" + value + "\""
lead = ","
}
message += "}"
}
return message + "}";
}
function restartSession() {
sendMessage( sessionInfo() );
}
function getIntAttribute(element, tag) {
let value = element.getAttribute(tag);
if (value) {
@ -246,26 +224,22 @@ function enterOrSpaceKeyClickEvent(event) {
function activateTab(layoutId, tabNumber) {
var element = document.getElementById(layoutId);
if (element) {
var currentNumber = element.getAttribute("data-current");
if (currentNumber != tabNumber) {
function setTab(number, styleProperty, display) {
var tab = document.getElementById(layoutId + '-' + number);
var currentTabId = element.getAttribute("data-current");
var newTabId = layoutId + '-' + tabNumber;
if (currentTabId != newTabId) {
function setTab(tabId, styleProperty, display) {
var tab = document.getElementById(tabId);
if (tab) {
tab.className = element.getAttribute(styleProperty);
var page = document.getElementById(tab.getAttribute("data-view"));
if (page) {
page.style.display = display;
}
return
}
var page = document.getElementById(layoutId + "-page" + number);
if (page) {
page.style.display = display;
}
}
setTab(currentNumber, "data-inactiveTabStyle", "none")
setTab(tabNumber, "data-activeTabStyle", "");
element.setAttribute("data-current", tabNumber);
setTab(currentTabId, "data-inactiveTabStyle", "none")
setTab(newTabId, "data-activeTabStyle", "");
element.setAttribute("data-current", newTabId);
scanElementsSize()
}
}
@ -311,19 +285,8 @@ function keyEvent(element, event, tag) {
message += ",timeStamp=" + event.timeStamp;
}
if (event.key) {
switch (event.key) {
case '"':
message += ",key=`\"`";
break
case '\\':
message += ",key=`\\`";
break
default:
message += ",key=\"" + event.key + "\"";
}
}
if (event.code) {
message += ",code=\"" + event.code + "\"";
}
@ -445,7 +408,6 @@ function mouseOutEvent(element, event) {
function clickEvent(element, event) {
mouseEvent(element, event, "click-event")
event.preventDefault();
event.stopPropagation();
}
function doubleClickEvent(element, event) {
@ -609,10 +571,6 @@ function selectDropDownListItem(elementId, number) {
function listItemClickEvent(element, event) {
event.stopPropagation();
if (element.getAttribute("data-disabled") == "1") {
return
}
var selected = false;
if (element.classList) {
const focusStyle = getListFocusedItemStyle(element);
@ -730,9 +688,6 @@ function findRightListItem(list, x, y) {
var count = list.childNodes.length;
for (var i = 0; i < count; i++) {
var item = list.childNodes[i];
if (item.getAttribute("data-disabled") == "1") {
continue;
}
if (item.offsetLeft >= x) {
if (result) {
var result_dy = Math.abs(result.offsetTop - y);
@ -754,9 +709,6 @@ function findLeftListItem(list, x, y) {
var count = list.childNodes.length;
for (var i = 0; i < count; i++) {
var item = list.childNodes[i];
if (item.getAttribute("data-disabled") == "1") {
continue;
}
if (item.offsetLeft < x) {
if (result) {
var result_dy = Math.abs(result.offsetTop - y);
@ -778,9 +730,6 @@ function findTopListItem(list, x, y) {
var count = list.childNodes.length;
for (var i = 0; i < count; i++) {
var item = list.childNodes[i];
if (item.getAttribute("data-disabled") == "1") {
continue;
}
if (item.offsetTop < y) {
if (result) {
var result_dx = Math.abs(result.offsetLeft - x);
@ -802,9 +751,6 @@ function findBottomListItem(list, x, y) {
var count = list.childNodes.length;
for (var i = 0; i < count; i++) {
var item = list.childNodes[i];
if (item.getAttribute("data-disabled") == "1") {
continue;
}
if (item.offsetTop >= y) {
if (result) {
var result_dx = Math.abs(result.offsetLeft - x);
@ -897,33 +843,6 @@ function listViewKeyDownEvent(element, event) {
if (item && item !== current) {
selectListItem(element, item, true);
}
} else {
switch (key) {
case " ":
case "Enter":
case "ArrowLeft":
case "ArrowUp":
case "ArrowRight":
case "ArrowDown":
case "Home":
case "End":
case "PageUp":
case "PageDown":
var list = element.childNodes[0];
var count = list.childNodes.length;
for (var i = 0; i < count; i++) {
var item = list.childNodes[i];
if (item.getAttribute("data-disabled") == "1") {
continue;
}
selectListItem(element, item, true);
return;
}
break;
default:
return;
}
}
}
@ -1021,8 +940,8 @@ function radioButtonKeyClickEvent(element, event) {
function editViewInputEvent(element) {
var text = element.value
text = text.replaceAll(/\\/g, "\\\\")
text = text.replaceAll(/\"/g, "\\\"")
text = text.replace(/\\/g, "\\\\")
text = text.replace(/\"/g, "\\\"")
var message = "textChanged{session=" + sessionID + ",id=" + element.id + ",text=\"" + text + "\"}"
sendMessage(message);
}
@ -1228,7 +1147,7 @@ function loadImage(url) {
img.addEventListener("error", function(event) {
var message = "imageError{session=" + sessionID + ",url=\"" + url + "\"";
if (event && event.message) {
var text = event.message.replaceAll(new RegExp("\"", 'g'), "\\\"")
var text = event.message.replace(new RegExp("\"", 'g'), "\\\"")
message += ",message=\"" + text + "\"";
}
sendMessage(message + "}")
@ -1259,7 +1178,7 @@ function loadInlineImage(url, content) {
img.addEventListener("error", function(event) {
var message = "imageError{session=" + sessionID + ",url=\"" + url + "\"";
if (event && event.message) {
var text = event.message.replaceAll(new RegExp("\"", 'g'), "\\\"")
var text = event.message.replace(new RegExp("\"", 'g'), "\\\"")
message += ",message=\"" + text + "\"";
}
sendMessage(message + "}")
@ -1268,6 +1187,11 @@ function loadInlineImage(url, content) {
img.src = content;
}
function clickOutsidePopup(e) {
sendMessage("clickOutsidePopup{session=" + sessionID + "}")
e.stopPropagation();
}
function clickClosePopup(element, e) {
var popupId = element.getAttribute("data-popupId");
sendMessage("clickClosePopup{session=" + sessionID + ",id=" + popupId + "}")
@ -1415,7 +1339,7 @@ function mediaSetVolume(elementId, volume) {
}
}
function startDownload(url, filename) {
function startDowndload(url, filename) {
var element = document.getElementById("ruiDownloader");
if (element) {
element.href = url;
@ -1484,24 +1408,6 @@ function tableViewBlurEvent(element, event) {
}
}
function setTableCellCursorByID(tableID, row, column) {
var table = document.getElementById(tableID);
if (table) {
if (!setTableCellCursor(table, row, column)) {
const focusStyle = getTableFocusedItemStyle(table);
const oldCellID = table.getAttribute("data-current");
if (oldCellID) {
const oldCell = document.getElementById(oldCellID);
if (oldCell && oldCell.classList) {
oldCell.classList.remove(focusStyle);
oldCell.classList.remove(getTableSelectedItemStyle(table));
}
table.removeAttribute("data-current");
}
}
}
}
function setTableCellCursor(element, row, column) {
const cellID = element.id + "-" + row + "-" + column;
var cell = document.getElementById(cellID);
@ -1589,8 +1495,6 @@ function tableViewCellKeyDownEvent(element, event) {
case "End":
case "PageUp":
case "PageDown":
event.stopPropagation();
event.preventDefault();
const rows = element.getAttribute("data-rows");
const columns = element.getAttribute("data-columns");
if (rows && columns) {
@ -1601,6 +1505,8 @@ function tableViewCellKeyDownEvent(element, event) {
column = 0;
while (columns < columnCount) {
if (setTableCellCursor(element, row, column)) {
event.stopPropagation();
event.preventDefault();
return;
}
column++;
@ -1608,6 +1514,8 @@ function tableViewCellKeyDownEvent(element, event) {
row++;
}
}
event.stopPropagation();
event.preventDefault();
break;
}
return;
@ -1675,24 +1583,6 @@ function tableViewCellKeyDownEvent(element, event) {
}
}
function setTableRowCursorByID(tableID, row) {
var table = document.getElementById(tableID);
if (table) {
if (!setTableRowCursor(table, row)) {
const focusStyle = getTableFocusedItemStyle(table);
const oldRowID = table.getAttribute("data-current");
if (oldRowID) {
const oldRow = document.getElementById(oldRowID);
if (oldRow && oldRow.classList) {
oldRow.classList.remove(focusStyle);
oldRow.classList.remove(getTableSelectedItemStyle(table));
}
table.removeAttribute("data-current");
}
}
}
}
function setTableRowCursor(element, row) {
const tableRowID = element.id + "-" + row;
var tableRow = document.getElementById(tableRowID);
@ -1707,6 +1597,7 @@ function setTableRowCursor(element, row) {
if (oldRow && oldRow.classList) {
oldRow.classList.remove(focusStyle);
oldRow.classList.remove(getTableSelectedItemStyle(element));
}
}
@ -1951,152 +1842,3 @@ function getCanvasContext(elementId) {
}
return null;
}
function localStorageSet(key, value) {
try {
localStorage.setItem(key, value)
} catch (err) {
sendMessage("storageError{session=" + sessionID + ", error=`" + err + "`}")
}
}
function localStorageClear() {
try {
localStorage.setItem(key, value)
} catch (err) {
sendMessage("storageError{session=" + sessionID + ", error=`" + err + "`}")
}
}
function showTooltip(element, tooltip) {
const layer = document.getElementById("ruiTooltipLayer");
if (!layer) {
return;
}
layer.style.left = "0px";
layer.style.right = "0px";
var tooltipBox = document.getElementById("ruiTooltipText");
if (tooltipBox) {
tooltipBox.innerHTML = tooltip;
}
var left = element.offsetLeft;
var top = element.offsetTop;
var width = element.offsetWidth;
var height = element.offsetHeight;
var parent = element.offsetParent;
while (parent) {
left += parent.offsetLeft;
top += parent.offsetTop;
width = parent.offsetWidth;
height = parent.offsetHeight;
parent = parent.offsetParent;
}
if (element.offsetWidth >= tooltipBox.offsetWidth) {
layer.style.left = left + "px";
layer.style.justifyItems = "start";
} else {
const rightOff = width - (left + element.offsetWidth);
if (left > rightOff) {
if (width - left < tooltipBox.offsetWidth) {
layer.style.right = rightOff + "px";
layer.style.justifyItems = "end";
} else {
layer.style.left = (left - rightOff) + "px";
layer.style.right = "0px";
layer.style.justifyItems = "center";
}
} else {
if (width - rightOff < tooltipBox.offsetWidth) {
layer.style.left = left + "px";
layer.style.justifyItems = "start";
} else {
layer.style.right = (rightOff - left) + "px";
layer.style.justifyItems = "center";
}
}
}
const bottomOff = height - (top + element.offsetHeight);
var arrow = document.getElementById("ruiTooltipTopArrow");
if (bottomOff < arrow.offsetHeight + tooltipBox.offsetHeight) {
if (arrow) {
arrow.style.visibility = "hidden";
}
arrow = document.getElementById("ruiTooltipBottomArrow");
if (arrow) {
arrow.style.visibility = "visible";
}
layer.style.top = "0px";
layer.style.bottom = height - top - arrow.offsetHeight / 2 + "px";
layer.style.gridTemplateRows = "1fr auto auto"
} else {
if (arrow) {
arrow.style.visibility = "visible";
}
layer.style.top = top + element.offsetHeight - arrow.offsetHeight / 2 + "px";
layer.style.bottom = "0px";
layer.style.gridTemplateRows = "auto auto 1fr"
arrow = document.getElementById("ruiTooltipBottomArrow");
if (arrow) {
arrow.style.visibility = "hidden";
}
}
layer.style.visibility = "visible";
layer.style.opacity = 1;
}
function mouseEnterEvent(element, event) {
event.stopPropagation();
let tooltip = element.getAttribute("data-tooltip");
if (tooltip) {
showTooltip(element, tooltip);
}
sendMessage("mouse-enter{session=" + sessionID + ",id=" + element.id + mouseEventData(element, event) + "}");
}
function mouseLeaveEvent(element, event) {
event.stopPropagation();
if (element.getAttribute("data-tooltip")) {
const layer = document.getElementById("ruiTooltipLayer");
if (layer) {
layer.style.opacity = 0;
layer.style.visibility = "hidden";
}
}
sendMessage("mouse-leave{session=" + sessionID + ",id=" + element.id + mouseEventData(element, event) + "}");
}
function hideTooltip() {
const layer = document.getElementById("ruiTooltipLayer");
if (layer) {
layer.style.opacity = 0;
layer.style.visibility = "hidden";
}
}
function stopEventPropagation(element, event) {
event.stopPropagation();
}
function setCssVar(tag, value) {
const root = document.querySelector(':root');
if (root) {
root.style.setProperty(tag, value);
}
}

View File

@ -8,13 +8,6 @@
text-overflow: ellipsis;
}
:root {
--tooltip-arrow-size: 6px;
--tooltip-background: white;
--tooltip-text-color: black;
--tooltip-shadow-color: gray;
}
body {
-webkit-touch-callout: none;
-webkit-user-select: none;
@ -83,54 +76,7 @@ ul:focus {
left: 0px;
}
.ruiTooltipLayer {
display: grid;
grid-template-rows: 1fr auto 1fr;
justify-items: center;
align-items: center;
position: absolute;
top: 0px;
bottom: 0px;
right: 0px;
left: 0px;
transition: opacity 0.5s ease-out;
filter: drop-shadow(0px 0px 2px var(--tooltip-shadow-color));
}
.ruiTooltipTopArrow {
grid-row-start: 1;
grid-row-end: 2;
border-width: var(--tooltip-arrow-size);
border-style: solid;
border-color: transparent transparent var(--tooltip-background) transparent;
margin-left: 12px;
margin-right: 12px;
}
.ruiTooltipBottomArrow {
grid-row-start: 3;
grid-row-end: 4;
border-width: var(--tooltip-arrow-size);
border-style: solid;
border-color: var(--tooltip-background) transparent transparent transparent;
margin-left: 12px;
margin-right: 12px;
}
.ruiTooltipText {
grid-row-start: 2;
grid-row-end: 3;
padding: 4px 8px 4px 8px;
margin-left: 8px;
margin-right: 8px;
background-color: var(--tooltip-background);
color: var(--tooltip-text-color);
/*box-shadow: 0px 0px 4px 2px #8888;*/
border-radius: 4px;
}
.ruiView {
box-sizing: border-box;
}
.ruiAbsoluteLayout {
@ -166,17 +112,16 @@ ul:focus {
}
.ruiImageView {
display: block;
}
.ruiSvgImageView {
display: grid;
}
.ruiListView {
overflow: auto;
/*
display: flex;
align-content: stretch;
*/
}
/*
@media (prefers-color-scheme: light) {
body {

View File

@ -5,6 +5,8 @@ import (
"strings"
)
var wasmMediaResources = false
//go:embed app_scripts.js
var defaultScripts string
@ -73,15 +75,9 @@ func getStartPage(buffer *strings.Builder, params AppParams, addScripts string)
buffer.WriteString(addScripts)
buffer.WriteString(`</script>
</head>
<body id="body" onkeydown="keyDownEvent(this, event)">
<body>
<div class="ruiRoot" id="ruiRootView"></div>
<div class="ruiPopupLayer" id="ruiPopupLayer" style="visibility: hidden; isolation: isolate;"></div>
<div class="ruiTooltipLayer" id="ruiTooltipLayer" style="visibility: hidden; opacity: 0;">
<div id="ruiTooltipText" class="ruiTooltipText"></div>
<div id="ruiTooltipTopArrow" class="ruiTooltipTopArrow"></div>
<div id="ruiTooltipBottomArrow" class="ruiTooltipBottomArrow"></div>
</div>
<div class="ruiPopupLayer" id="ruiPopupLayer" style="visibility: hidden;" onclick="clickOutsidePopup(event)"></div>
<a id="ruiDownloader" download style="display: none;"></a>
</body>`)
}

View File

@ -209,7 +209,7 @@ func (gradient *backgroundConicGradient) parseGradientText(value string) []Backg
for i, element := range elements {
var ok bool
if vector[i], ok = gradient.stringToGradientPoint(strings.Trim(element, " ")); !ok {
ErrorLogF(`Invalid %d element of the conic gradient: "%s"`, i, element)
ErrorLogF(`Ivalid %d element of the conic gradient: "%s"`, i, element)
return nil
}
}
@ -240,7 +240,7 @@ func (gradient *backgroundConicGradient) setGradient(value any) bool {
return true
}
ErrorLogF(`Invalid conic gradient: "%s"`, value)
ErrorLogF(`Ivalid conic gradient: "%s"`, value)
return false
case []BackgroundGradientAngle:
@ -252,7 +252,7 @@ func (gradient *backgroundConicGradient) setGradient(value any) bool {
for i, point := range value {
if point.Color == nil {
ErrorLogF("Invalid %d element of the conic gradient: Color is nil", i)
ErrorLogF("Ivalid %d element of the conic gradient: Color is nil", i)
return false
}
}

View File

@ -99,7 +99,7 @@ func (gradient *backgroundGradient) parseGradientText(value string) []Background
points := make([]BackgroundGradientPoint, count)
for i, element := range elements {
if !points[i].setValue(element) {
ErrorLogF(`Invalid %d element of the conic gradient: "%s"`, i, element)
ErrorLogF(`Ivalid %d element of the conic gradient: "%s"`, i, element)
return nil
}
}

View File

@ -286,7 +286,7 @@ func (border *borderProperty) setBorderObject(obj DataObject) bool {
result := true
for _, side := range []string{Top, Right, Bottom, Left} {
if node := obj.PropertyByTag(side); node != nil {
if node := obj.PropertyWithTag(side); node != nil {
if node.Type() == ObjectNode {
if !border.setSingleBorderObject(side, node.Object()) {
result = false

View File

@ -28,7 +28,6 @@ func (button *buttonData) CreateSuperView(session Session) View {
HorizontalAlign: CenterAlign,
VerticalAlign: CenterAlign,
Orientation: StartToEndOrientation,
TabIndex: 0,
})
}

View File

@ -182,7 +182,7 @@ type Canvas interface {
SetRadialGradientStrokeStyle(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint)
// SetImageFillStyle set the image as the filling pattern.
// repeat - indicating how to repeat the pattern's image. Possible values are:
// repeate - indicating how to repeat the pattern's image. Possible values are:
// NoRepeat (0) - neither direction,
// RepeatXY (1) - both directions,
// RepeatX (2) - horizontal only,
@ -286,7 +286,7 @@ type Canvas interface {
DrawImage(x, y float64, image Image)
// DrawImageInRect draws the image in the rectangle (x, y, width, height), scaling in height and width if necessary
DrawImageInRect(x, y, width, height float64, image Image)
// DrawImageFragment draws the fragment (described by srcX, srcY, srcWidth, srcHeight) of image
// DrawImageFragment draws the frament (described by srcX, srcY, srcWidth, srcHeight) of image
// in the rectangle (dstX, dstY, dstWidth, dstHeight), scaling in height and width if necessary
DrawImageFragment(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight float64, image Image)
@ -302,12 +302,12 @@ func newCanvas(view CanvasView) Canvas {
canvas := new(canvasData)
canvas.view = view
canvas.session = view.Session()
canvas.session.canvasStart(view.htmlID())
canvas.session.cavnasStart(view.htmlID())
return canvas
}
func (canvas *canvasData) finishDraw() {
canvas.session.canvasFinish()
canvas.session.cavnasFinish()
}
func (canvas *canvasData) View() CanvasView {

View File

@ -55,8 +55,8 @@ const (
BlueViolet Color = 0xff8a2be2
// Brown color constant
Brown Color = 0xffa52a2a
// BurlyWood color constant
BurlyWood Color = 0xffdeb887
// Burlywood color constant
Burlywood Color = 0xffdeb887
// CadetBlue color constant
CadetBlue Color = 0xff5f9ea0
// Chartreuse color constant
@ -67,8 +67,8 @@ const (
Coral Color = 0xffff7f50
// CornflowerBlue color constant
CornflowerBlue Color = 0xff6495ed
// CornSilk color constant
CornSilk Color = 0xfffff8dc
// Cornsilk color constant
Cornsilk Color = 0xfffff8dc
// Crimson color constant
Crimson Color = 0xffdc143c
// Cyan color constant
@ -105,8 +105,8 @@ const (
DarkSlateBlue Color = 0xff483d8b
// DarkSlateGray color constant
DarkSlateGray Color = 0xff2f4f4f
// DarkSlateGrey color constant
DarkSlateGrey Color = 0xff2f4f4f
// Darkslategrey color constant
Darkslategrey Color = 0xff2f4f4f
// DarkTurquoise color constant
DarkTurquoise Color = 0xff00ced1
// DarkViolet color constant
@ -135,8 +135,8 @@ const (
Gold Color = 0xffffd700
// GoldenRod color constant
GoldenRod Color = 0xffdaa520
// GreenYellow color constant
GreenYellow Color = 0xffadff2f
// GreenyEllow color constant
GreenyEllow Color = 0xffadff2f
// Grey color constant
Grey Color = 0xff808080
// Honeydew color constant
@ -293,8 +293,8 @@ const (
Violet Color = 0xffee82ee
// Wheat color constant
Wheat Color = 0xfff5deb3
// WhiteSmoke color constant
WhiteSmoke Color = 0xfff5f5f5
// Whitesmoke color constant
Whitesmoke Color = 0xfff5f5f5
// YellowGreen color constant
YellowGreen Color = 0xff9acd32
)

View File

@ -16,7 +16,7 @@ type ColorPicker interface {
type colorPickerData struct {
viewData
colorChangedListeners []func(ColorPicker, Color, Color)
colorChangedListeners []func(ColorPicker, Color)
}
// NewColorPicker create new ColorPicker object and return it
@ -34,7 +34,7 @@ func newColorPicker(session Session) View {
func (picker *colorPickerData) init(session Session) {
picker.viewData.init(session)
picker.tag = "ColorPicker"
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
picker.colorChangedListeners = []func(ColorPicker, Color){}
picker.properties[Padding] = Px(0)
}
@ -60,7 +60,7 @@ func (picker *colorPickerData) remove(tag string) {
switch tag {
case ColorChangedEvent:
if len(picker.colorChangedListeners) > 0 {
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
picker.colorChangedListeners = []func(ColorPicker, Color){}
picker.propertyChangedEvent(tag)
}
@ -86,12 +86,12 @@ func (picker *colorPickerData) set(tag string, value any) bool {
switch tag {
case ColorChangedEvent:
listeners, ok := valueToEventWithOldListeners[ColorPicker, Color](value)
listeners, ok := valueToEventListeners[ColorPicker, Color](value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(ColorPicker, Color, Color){}
listeners = []func(ColorPicker, Color){}
}
picker.colorChangedListeners = listeners
picker.propertyChangedEvent(tag)
@ -116,7 +116,7 @@ func (picker *colorPickerData) colorChanged(oldColor Color) {
picker.session.callFunc("setInputValue", picker.htmlID(), newColor.rgbString())
}
for _, listener := range picker.colorChangedListeners {
listener(picker, newColor, oldColor)
listener(picker, newColor)
}
picker.propertyChangedEvent(ColorTag)
}
@ -169,7 +169,7 @@ func (picker *colorPickerData) handleCommand(self View, command string, data Dat
picker.properties[ColorPickerValue] = color
if color != oldColor {
for _, listener := range picker.colorChangedListeners {
listener(picker, color, oldColor)
listener(picker, color)
}
}
}
@ -204,6 +204,6 @@ func GetColorPickerValue(view View, subviewID ...string) Color {
// GetColorChangedListeners returns the ColorChangedListener list of an ColorPicker subview.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color, Color) {
return getEventWithOldListeners[ColorPicker, Color](view, subviewID, ColorChangedEvent)
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color) {
return getEventListeners[ColorPicker, Color](view, subviewID, ColorChangedEvent)
}

View File

@ -11,45 +11,28 @@ const (
// Values less than zero are not valid. if the "column-count" property value is 0 then
// the number of columns is calculated based on the "column-width" property
ColumnCount = "column-count"
// ColumnWidth is the constant for the "column-width" property tag.
// The "column-width" SizeUnit property specifies the width of each column.
ColumnWidth = "column-width"
// ColumnGap is the constant for the "column-gap" property tag.
// The "column-width" SizeUnit property sets the size of the gap (gutter) between columns.
ColumnGap = "column-gap"
// ColumnSeparator is the constant for the "column-separator" property tag.
// The "column-separator" property specifies the line drawn between columns in a multi-column layout.
ColumnSeparator = "column-separator"
// ColumnSeparatorStyle is the constant for the "column-separator-style" property tag.
// The "column-separator-style" int property sets the style of the line drawn between
// columns in a multi-column layout.
// Valid values are NoneLine (0), SolidLine (1), DashedLine (2), DottedLine (3), and DoubleLine (4).
ColumnSeparatorStyle = "column-separator-style"
// ColumnSeparatorWidth is the constant for the "column-separator-width" property tag.
// The "column-separator-width" SizeUnit property sets the width of the line drawn between
// columns in a multi-column layout.
ColumnSeparatorWidth = "column-separator-width"
// ColumnSeparatorColor is the constant for the "column-separator-color" property tag.
// The "column-separator-color" Color property sets the color of the line drawn between
// columns in a multi-column layout.
ColumnSeparatorColor = "column-separator-color"
// ColumnFill is the constant for the "column-fill" property tag.
// The "column-fill" int property controls how an ColumnLayout's contents are balanced when broken into columns.
// Valid values are
// * ColumnFillBalance (0) - Content is equally divided between columns (default value);
// * ColumnFillAuto (1) - Columns are filled sequentially. Content takes up only the room it needs, possibly resulting in some columns remaining empty.
ColumnFill = "column-fill"
// ColumnSpanAll is the constant for the "column-span-all" property tag.
// The "column-span-all" bool property makes it possible for a view to span across all columns when its value is set to true.
ColumnSpanAll = "column-span-all"
)
// ColumnLayout - grid-container of View
@ -223,16 +206,3 @@ func GetColumnSeparatorColor(view View, subviewID ...string) Color {
border := getColumnSeparator(view, subviewID)
return border.Color
}
// GetColumnFill returns a "column-fill" property value of the subview.
// Returns one of next values: ColumnFillBalance (0) or ColumnFillAuto (1)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetColumnFill(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, ColumnFill, ColumnFillBalance, true)
}
// IsColumnSpanAll returns a "column-span-all" property value of the subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func IsColumnSpanAll(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, ColumnSpanAll, false)
}

View File

@ -243,17 +243,8 @@ func (customView *CustomViewData) RemoveView(index int) View {
return container.RemoveView(index)
}
}
return nil
}
// Remove removes a view from the list of a view children and return it
func (customView *CustomViewData) ViewIndex(view View) int {
if customView.superView != nil {
if container, ok := customView.superView.(ViewsContainer); ok {
return container.ViewIndex(view)
}
}
return -1
return nil
}
func (customView *CustomViewData) String() string {

66
data.go
View File

@ -18,12 +18,11 @@ type DataObject interface {
Tag() string
PropertyCount() int
Property(index int) DataNode
PropertyByTag(tag string) DataNode
PropertyWithTag(tag string) DataNode
PropertyValue(tag string) (string, bool)
PropertyObject(tag string) DataObject
SetPropertyValue(tag, value string)
SetPropertyObject(tag string, object DataObject)
ToParams() Params
}
const (
@ -44,7 +43,6 @@ type DataNode interface {
ArraySize() int
ArrayElement(index int) DataValue
ArrayElements() []DataValue
ArrayAsParams() []Params
}
/******************************************************************************/
@ -108,7 +106,7 @@ func (object *dataObject) Property(index int) DataNode {
return object.property[index]
}
func (object *dataObject) PropertyByTag(tag string) DataNode {
func (object *dataObject) PropertyWithTag(tag string) DataNode {
if object.property != nil {
for _, node := range object.property {
if node.Tag() == tag {
@ -120,14 +118,14 @@ func (object *dataObject) PropertyByTag(tag string) DataNode {
}
func (object *dataObject) PropertyValue(tag string) (string, bool) {
if node := object.PropertyByTag(tag); node != nil && node.Type() == TextNode {
if node := object.PropertyWithTag(tag); node != nil && node.Type() == TextNode {
return node.Text(), true
}
return "", false
}
func (object *dataObject) PropertyObject(tag string) DataObject {
if node := object.PropertyByTag(tag); node != nil && node.Type() == ObjectNode {
if node := object.PropertyWithTag(tag); node != nil && node.Type() == ObjectNode {
return node.Object()
}
return nil
@ -167,42 +165,6 @@ func (object *dataObject) SetPropertyObject(tag string, obj DataObject) {
object.setNode(node)
}
func (object *dataObject) ToParams() Params {
params := Params{}
for _, node := range object.property {
switch node.Type() {
case TextNode:
if text := node.Text(); text != "" {
params[node.Tag()] = text
}
case ObjectNode:
if obj := node.Object(); obj != nil {
params[node.Tag()] = node.Object()
}
case ArrayNode:
array := []any{}
for i := 0; i < node.ArraySize(); i++ {
if data := node.ArrayElement(i); data != nil {
if data.IsObject() {
if obj := data.Object(); obj != nil {
array = append(array, obj)
}
} else if text := data.Value(); text != "" {
array = append(array, text)
}
}
}
if len(array) > 0 {
params[node.Tag()] = array
}
}
}
return params
}
/******************************************************************************/
type dataNode struct {
tag string
@ -259,22 +221,6 @@ func (node *dataNode) ArrayElements() []DataValue {
return []DataValue{}
}
func (node *dataNode) ArrayAsParams() []Params {
result := []Params{}
if node.array != nil {
for _, data := range node.array {
if data.IsObject() {
if obj := data.Object(); obj != nil {
if params := obj.ToParams(); len(params) > 0 {
result = append(result, params)
}
}
}
}
}
return result
}
// ParseDataText - parse text and return DataNode
func ParseDataText(text string) DataObject {
@ -494,8 +440,8 @@ func ParseDataText(text string) DataObject {
endPos := pos
skipSpaces(false)
if startPos == endPos {
//ErrorLog("empty tag")
return "", true
ErrorLog("empty tag")
return "", false
}
return string(data[startPos:endPos]), true
}

View File

@ -22,7 +22,7 @@ type DatePicker interface {
type datePickerData struct {
viewData
dateChangedListeners []func(DatePicker, time.Time, time.Time)
dateChangedListeners []func(DatePicker, time.Time)
}
// NewDatePicker create new DatePicker object and return it
@ -40,7 +40,7 @@ func newDatePicker(session Session) View {
func (picker *datePickerData) init(session Session) {
picker.viewData.init(session)
picker.tag = "DatePicker"
picker.dateChangedListeners = []func(DatePicker, time.Time, time.Time){}
picker.dateChangedListeners = []func(DatePicker, time.Time){}
}
func (picker *datePickerData) String() string {
@ -69,7 +69,7 @@ func (picker *datePickerData) remove(tag string) {
switch tag {
case DateChangedEvent:
if len(picker.dateChangedListeners) > 0 {
picker.dateChangedListeners = []func(DatePicker, time.Time, time.Time){}
picker.dateChangedListeners = []func(DatePicker, time.Time){}
picker.propertyChangedEvent(tag)
}
return
@ -94,14 +94,13 @@ func (picker *datePickerData) remove(tag string) {
case DatePickerValue:
if _, ok := picker.properties[DatePickerValue]; ok {
oldDate := GetDatePickerValue(picker)
delete(picker.properties, DatePickerValue)
date := GetDatePickerValue(picker)
if picker.created {
picker.session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
}
for _, listener := range picker.dateChangedListeners {
listener(picker, date, oldDate)
listener(picker, date)
}
} else {
return
@ -227,7 +226,7 @@ func (picker *datePickerData) set(tag string, value any) bool {
picker.session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
}
for _, listener := range picker.dateChangedListeners {
listener(picker, date, oldDate)
listener(picker, date)
}
picker.propertyChangedEvent(tag)
}
@ -235,12 +234,12 @@ func (picker *datePickerData) set(tag string, value any) bool {
}
case DateChangedEvent:
listeners, ok := valueToEventWithOldListeners[DatePicker, time.Time](value)
listeners, ok := valueToEventListeners[DatePicker, time.Time](value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(DatePicker, time.Time, time.Time){}
listeners = []func(DatePicker, time.Time){}
}
picker.dateChangedListeners = listeners
picker.propertyChangedEvent(tag)
@ -319,7 +318,7 @@ func (picker *datePickerData) handleCommand(self View, command string, data Data
picker.properties[DatePickerValue] = value
if value != oldValue {
for _, listener := range picker.dateChangedListeners {
listener(picker, value, oldValue)
listener(picker, value)
}
}
}
@ -411,6 +410,6 @@ func GetDatePickerValue(view View, subviewID ...string) time.Time {
// GetDateChangedListeners returns the DateChangedListener list of an DatePicker subview.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time, time.Time) {
return getEventWithOldListeners[DatePicker, time.Time](view, subviewID, DateChangedEvent)
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time) {
return getEventListeners[DatePicker, time.Time](view, subviewID, DateChangedEvent)
}

View File

@ -23,9 +23,6 @@ theme {
ruiTabTextColor = #FF404040,
ruiCurrentTabColor = #FFFFFFFF,
ruiCurrentTabTextColor = #FF000000,
ruiTooltipBackground = #FFFFFFFF,
ruiTooltipTextColor = #FF000000,
ruiTooltipShadowColor = #FF808080,
},
colors:dark = _{
ruiTextColor = #FFE0E0E0,
@ -46,9 +43,6 @@ theme {
ruiTabTextColor = #FFE0E0E0,
ruiCurrentTabColor = #FF000000,
ruiCurrentTabTextColor = #FFFFFFFF,
ruiTooltipBackground = #FF303030,
ruiTooltipTextColor = #FFDDDDDD,
ruiTooltipShadowColor = #FFDDDDDD,
},
constants = _{
ruiButtonHorizontalPadding = 16px,
@ -110,26 +104,6 @@ theme {
ruiButton:active {
background-color = @ruiButtonActiveColor
},
ruiDefaultButton {
align = center,
padding = "@ruiButtonVerticalPadding, @ruiButtonHorizontalPadding, @ruiButtonVerticalPadding, @ruiButtonHorizontalPadding",
margin = @ruiButtonMargin,
radius = @ruiButtonRadius,
background-color = @ruiButtonColor,
text-color = @ruiButtonTextColor,
text-weight = bold,
border = _{width = 1px, style = solid, color = @ruiButtonTextColor}
},
ruiDefaultButton:hover {
text-color = @ruiTextColor,
background-color = @ruiBackgroundColor,
},
ruiDefaultButton:focus {
shadow = _{spread-radius = @ruiButtonHighlightDilation, blur = @ruiButtonHighlightBlur, color = @ruiHighlightColor },
},
ruiDefaultButton:active {
background-color = @ruiButtonActiveColor
},
ruiCheckbox {
radius = 2px,
padding = 1px,

View File

@ -23,7 +23,7 @@ func (session *sessionData) startDownload(file downloadFile) {
currentDownloadId++
id := strconv.Itoa(currentDownloadId)
downloadFiles[id] = file
session.callFunc("startDownload", id, file.filename)
session.callFunc("startDowndload", id, file.filename)
}
func serveDownloadFile(id string, w http.ResponseWriter, r *http.Request) bool {

View File

@ -21,7 +21,7 @@ type dropDownListData struct {
viewData
items []string
disabledItems []any
dropDownListener []func(DropDownList, int, int)
dropDownListener []func(DropDownList, int)
}
// NewDropDownList create new DropDownList object and return it
@ -41,7 +41,7 @@ func (list *dropDownListData) init(session Session) {
list.tag = "DropDownList"
list.items = []string{}
list.disabledItems = []any{}
list.dropDownListener = []func(DropDownList, int, int){}
list.dropDownListener = []func(DropDownList, int){}
}
func (list *dropDownListData) String() string {
@ -78,7 +78,7 @@ func (list *dropDownListData) remove(tag string) {
case DropDownEvent:
if len(list.dropDownListener) > 0 {
list.dropDownListener = []func(DropDownList, int, int){}
list.dropDownListener = []func(DropDownList, int){}
list.propertyChangedEvent(tag)
}
@ -89,7 +89,7 @@ func (list *dropDownListData) remove(tag string) {
if list.created {
list.session.callFunc("selectDropDownListItem", list.htmlID(), 0)
}
list.onSelectedItemChanged(0, oldCurrent)
list.onSelectedItemChanged(0)
}
default:
@ -116,12 +116,12 @@ func (list *dropDownListData) set(tag string, value any) bool {
return list.setDisabledItems(value)
case DropDownEvent:
listeners, ok := valueToEventWithOldListeners[DropDownList, int](value)
listeners, ok := valueToEventListeners[DropDownList, int](value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(DropDownList, int, int){}
listeners = []func(DropDownList, int){}
}
list.dropDownListener = listeners
list.propertyChangedEvent(tag)
@ -137,7 +137,7 @@ func (list *dropDownListData) set(tag string, value any) bool {
if list.created {
list.session.callFunc("selectDropDownListItem", list.htmlID(), current)
}
list.onSelectedItemChanged(current, oldCurrent)
list.onSelectedItemChanged(current)
}
return true
}
@ -377,9 +377,9 @@ func (list *dropDownListData) htmlDisabledProperties(self View, buffer *strings.
}
}
func (list *dropDownListData) onSelectedItemChanged(number, old int) {
func (list *dropDownListData) onSelectedItemChanged(number int) {
for _, listener := range list.dropDownListener {
listener(list, number, old)
listener(list, number)
}
list.propertyChangedEvent(Current)
}
@ -390,9 +390,8 @@ func (list *dropDownListData) handleCommand(self View, command string, data Data
if text, ok := data.PropertyValue("number"); ok {
if number, err := strconv.Atoi(text); err == nil {
if GetCurrent(list) != number && number >= 0 && number < len(list.items) {
old := GetCurrent(list)
list.properties[Current] = number
list.onSelectedItemChanged(number, old)
list.onSelectedItemChanged(number)
}
} else {
ErrorLog(err.Error())
@ -407,8 +406,8 @@ func (list *dropDownListData) handleCommand(self View, command string, data Data
// GetDropDownListeners returns the "drop-down-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int, int) {
return getEventWithOldListeners[DropDownList, int](view, subviewID, DropDownEvent)
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int) {
return getEventListeners[DropDownList, int](view, subviewID, DropDownEvent)
}
// GetDropDownItems return the DropDownList items list.

View File

@ -23,7 +23,7 @@ const (
PasswordText = 1
// EmailText - e-mail type of EditView. Allows to enter one email
EmailText = 2
// EmailsText - e-mail type of EditView. Allows to enter multiple emails separated by comma
// EmailsText - e-mail type of EditView. Allows to enter multiple emails separeted by comma
EmailsText = 3
// URLText - url type of EditView. Allows to enter one url
URLText = 4
@ -41,7 +41,7 @@ type EditView interface {
type editViewData struct {
viewData
textChangeListeners []func(EditView, string, string)
textChangeListeners []func(EditView, string)
}
// NewEditView create new EditView object and return it
@ -58,7 +58,7 @@ func newEditView(session Session) View {
func (edit *editViewData) init(session Session) {
edit.viewData.init(session)
edit.textChangeListeners = []func(EditView, string, string){}
edit.textChangeListeners = []func(EditView, string){}
edit.tag = "EditView"
}
@ -125,7 +125,7 @@ func (edit *editViewData) remove(tag string) {
case EditTextChangedEvent:
if len(edit.textChangeListeners) > 0 {
edit.textChangeListeners = []func(EditView, string, string){}
edit.textChangeListeners = []func(EditView, string){}
edit.propertyChangedEvent(tag)
}
@ -134,7 +134,7 @@ func (edit *editViewData) remove(tag string) {
oldText := GetText(edit)
delete(edit.properties, tag)
if oldText != "" {
edit.textChanged("", oldText)
edit.textChanged("")
if edit.created {
edit.session.callFunc("setInputValue", edit.htmlID(), "")
}
@ -205,7 +205,7 @@ func (edit *editViewData) set(tag string, value any) bool {
if text, ok := value.(string); ok {
edit.properties[Text] = text
if text = GetText(edit); oldText != text {
edit.textChanged(text, oldText)
edit.textChanged(text)
if edit.created {
if GetEditViewType(edit) == MultiLineText {
updateInnerHTML(edit.htmlID(), edit.Session())
@ -328,12 +328,12 @@ func (edit *editViewData) set(tag string, value any) bool {
return false
case EditTextChangedEvent:
listeners, ok := valueToEventWithOldListeners[EditView, string](value)
listeners, ok := valueToEventListeners[EditView, string](value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(EditView, string, string){}
listeners = []func(EditView, string){}
}
edit.textChangeListeners = listeners
edit.propertyChangedEvent(tag)
@ -358,11 +358,10 @@ func (edit *editViewData) AppendText(text string) {
if GetEditViewType(edit) == MultiLineText {
if value := edit.getRaw(Text); value != nil {
if textValue, ok := value.(string); ok {
oldText := textValue
textValue += text
edit.properties[Text] = textValue
edit.session.callFunc("appendToInnerHTML", edit.htmlID(), text)
edit.textChanged(textValue, oldText)
edit.textChanged(textValue)
return
}
}
@ -372,9 +371,9 @@ func (edit *editViewData) AppendText(text string) {
}
}
func (edit *editViewData) textChanged(newText, oldText string) {
func (edit *editViewData) textChanged(newText string) {
for _, listener := range edit.textChangeListeners {
listener(edit, newText, oldText)
listener(edit, newText)
}
edit.propertyChangedEvent(Text)
}
@ -486,7 +485,7 @@ func (edit *editViewData) handleCommand(self View, command string, data DataObje
if text, ok := data.PropertyValue("text"); ok {
edit.properties[Text] = text
if text := GetText(edit); text != oldText {
edit.textChanged(text, oldText)
edit.textChanged(text)
}
}
return true
@ -532,7 +531,7 @@ func GetHint(view View, subviewID ...string) string {
return ""
}
// GetMaxLength returns a maximal length of EditView. If a maximal length is not limited then 0 is returned
// GetMaxLength returns a maximal lenght of EditView. If a maximal lenght is not limited then 0 is returned
// If the second argument (subviewID) is not specified or it is "" then a value of the first argument (view) is returned.
func GetMaxLength(view View, subviewID ...string) int {
return intStyledProperty(view, subviewID, MaxLength, 0)
@ -553,8 +552,8 @@ func IsSpellcheck(view View, subviewID ...string) bool {
// GetTextChangedListeners returns the TextChangedListener list of an EditView or MultiLineEditView subview.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string, string) {
return getEventWithOldListeners[EditView, string](view, subviewID, EditTextChangedEvent)
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string) {
return getEventListeners[EditView, string](view, subviewID, EditTextChangedEvent)
}
// GetEditViewType returns a value of the Type property of EditView.
@ -605,7 +604,7 @@ func AppendEditText(view View, subviewID string, text string) {
}
}
// GetCaretColor returns the color of the text input caret.
// GetCaretColor returns the color of the text input carret.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetCaretColor(view View, subviewID ...string) Color {
return colorStyledProperty(view, subviewID, CaretColor, false)

View File

@ -270,7 +270,7 @@ func (picker *filePickerData) htmlDisabledProperties(self View, buffer *strings.
func (picker *filePickerData) handleCommand(self View, command string, data DataObject) bool {
switch command {
case "fileSelected":
if node := data.PropertyByTag("files"); node != nil && node.Type() == ArrayNode {
if node := data.PropertyWithTag("files"); node != nil && node.Type() == ArrayNode {
count := node.ArraySize()
files := make([]FileInfo, count)
for i := 0; i < count; i++ {

View File

@ -143,10 +143,7 @@ func getFocusListeners(view View, subviewID []string, tag string) []func(View) {
func focusEventsHtml(view View, buffer *strings.Builder) {
if view.Focusable() {
for _, js := range focusEvents {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
}
}
}

8
go.mod
View File

@ -1,7 +1,5 @@
module git.mbk-lab.ru/mbk-lab/rui
module github.com/anoshenko/rui
go 1.20
go 1.18
require github.com/gorilla/websocket v1.5.1
require golang.org/x/net v0.17.0 // indirect
require github.com/gorilla/websocket v1.5.0

6
go.sum
View File

@ -1,4 +1,2 @@
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=

View File

@ -1,55 +0,0 @@
package rui
import (
"net/http"
"strings"
)
type httpHandler struct {
app *application
prefix string
}
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodGet:
path := `/` + strings.TrimPrefix(req.URL.Path, `/`)
req.URL.Path = `/` + strings.TrimPrefix(strings.TrimPrefix(path, h.prefix), `/`)
h.app.ServeHTTP(w, req)
}
}
/*
NewHandler используется для встраивания приложения rui в сторонние WEB-фреймворки (net/http, gin, echo...).
Пример для echo:
e := echo.New()
e.Any(`/ui/*`, func()echo.HandlerFunc{
rui.AddEmbedResources(&resources)
h := rui.NewHandler("/ui", CreateSessionContent, rui.AppParams{
Title: `Awesome app`,
Icon: `favicon.png`,
})
return func(c echo.Context) error {
h.ServeHTTP(c.Response(), c.Request())
return nil
}
})
*/
func NewHandler(urlPrefix string, createContentFunc func(Session) SessionContent, params AppParams) *httpHandler {
app := new(application)
app.params = params
app.sessions = map[int]Session{}
app.createContentFunc = createContentFunc
apps = append(apps, app)
h := &httpHandler{
app: app,
prefix: `/` + strings.Trim(urlPrefix, `/`),
}
return h
}

View File

@ -1,7 +1,11 @@
package rui
import (
"encoding/base64"
"path/filepath"
"runtime"
"strconv"
"strings"
)
const (
@ -79,6 +83,27 @@ func (manager *imageManager) loadImage(url string, onLoaded func(Image), session
image.loadingStatus = ImageLoading
manager.images[url] = image
if runtime.GOOS == "js" && wasmMediaResources {
if file, ok := resources.images[url]; ok && file.fs != nil {
dataType := map[string]string{
".svg": "data:image/svg+xml",
".png": "data:image/png",
".jpg": "data:image/jpg",
".jpeg": "data:image/jpg",
".gif": "data:image/gif",
}
ext := strings.ToLower(filepath.Ext(url))
if prefix, ok := dataType[ext]; ok {
if data, err := file.fs.ReadFile(file.path); err == nil {
session.callFunc("loadInlineImage", url, prefix+";base64,"+base64.StdEncoding.EncodeToString(data))
return image
} else {
DebugLog(err.Error())
}
}
}
}
session.callFunc("loadImage", url)
return image
}

View File

@ -1,7 +1,10 @@
package rui
import (
"encoding/base64"
"fmt"
"path/filepath"
"runtime"
"strings"
)
@ -67,7 +70,8 @@ func newImageView(session Session) View {
func (imageView *imageViewData) init(session Session) {
imageView.viewData.init(session)
imageView.tag = "ImageView"
imageView.systemClass = "ruiImageView"
//imageView.systemClass = "ruiImageView"
}
func (imageView *imageViewData) String() string {
@ -268,7 +272,27 @@ func (imageView *imageViewData) src(src string) (string, string) {
}
if src != "" {
return src, imageView.srcSet(src)
srcset := imageView.srcSet(src)
if runtime.GOOS == "js" && wasmMediaResources {
if image, ok := resources.images[src]; ok && image.fs != nil {
dataType := map[string]string{
".svg": "data:image/svg+xml",
".png": "data:image/png",
".jpg": "data:image/jpg",
".jpeg": "data:image/jpg",
".gif": "data:image/gif",
}
ext := strings.ToLower(filepath.Ext(src))
if prefix, ok := dataType[ext]; ok {
if data, err := image.fs.ReadFile(image.path); err == nil {
return prefix + ";base64," + base64.StdEncoding.EncodeToString(data), ""
} else {
DebugLog(err.Error())
}
}
}
}
return src, srcset
}
return "", ""
}

View File

@ -20,127 +20,6 @@ const (
KeyUpEvent = "key-up-event"
)
type ControlKeyMask int
type KeyCode string
const (
// AltKey is the mask of the "alt" key
AltKey ControlKeyMask = 1
// CtrlKey is the mask of the "ctrl" key
CtrlKey ControlKeyMask = 2
// ShiftKey is the mask of the "shift" key
ShiftKey ControlKeyMask = 4
// MetaKey is the mask of the "meta" key
MetaKey ControlKeyMask = 8
KeyA KeyCode = "KeyA"
KeyB KeyCode = "KeyB"
KeyC KeyCode = "KeyC"
KeyD KeyCode = "KeyD"
KeyE KeyCode = "KeyE"
KeyF KeyCode = "KeyF"
KeyG KeyCode = "KeyG"
KeyH KeyCode = "KeyH"
KeyI KeyCode = "KeyI"
KeyJ KeyCode = "KeyJ"
KeyK KeyCode = "KeyK"
KeyL KeyCode = "KeyL"
KeyM KeyCode = "KeyM"
KeyN KeyCode = "KeyN"
KeyO KeyCode = "KeyO"
KeyP KeyCode = "KeyP"
KeyQ KeyCode = "KeyQ"
KeyR KeyCode = "KeyR"
KeyS KeyCode = "KeyS"
KeyT KeyCode = "KeyT"
KeyU KeyCode = "KeyU"
KeyV KeyCode = "KeyV"
KeyW KeyCode = "KeyW"
KeyX KeyCode = "KeyX"
KeyY KeyCode = "KeyY"
KeyZ KeyCode = "KeyZ"
Digit0Key KeyCode = "Digit0"
Digit1Key KeyCode = "Digit1"
Digit2Key KeyCode = "Digit2"
Digit3Key KeyCode = "Digit3"
Digit4Key KeyCode = "Digit4"
Digit5Key KeyCode = "Digit5"
Digit6Key KeyCode = "Digit6"
Digit7Key KeyCode = "Digit7"
Digit8Key KeyCode = "Digit8"
Digit9Key KeyCode = "Digit9"
SpaceKey KeyCode = "Space"
MinusKey KeyCode = "Minus"
EqualKey KeyCode = "Equal"
IntlBackslashKey KeyCode = "IntlBackslash"
BracketLeftKey KeyCode = "BracketLeft"
BracketRightKey KeyCode = "BracketRight"
SemicolonKey KeyCode = "Semicolon"
CommaKey KeyCode = "Comma"
PeriodKey KeyCode = "Period"
QuoteKey KeyCode = "Quote"
BackquoteKey KeyCode = "Backquote"
SlashKey KeyCode = "Slash"
EscapeKey KeyCode = "Escape"
EnterKey KeyCode = "Enter"
TabKey KeyCode = "Tab"
CapsLockKey KeyCode = "CapsLock"
DeleteKey KeyCode = "Delete"
InsertKey KeyCode = "Insert"
HelpKey KeyCode = "Help"
BackspaceKey KeyCode = "Backspace"
PrintScreenKey KeyCode = "PrintScreen"
ScrollLockKey KeyCode = "ScrollLock"
PauseKey KeyCode = "Pause"
ContextMenuKey KeyCode = "ContextMenu"
ArrowLeftKey KeyCode = "ArrowLeft"
ArrowRightKey KeyCode = "ArrowRight"
ArrowUpKey KeyCode = "ArrowUp"
ArrowDownKey KeyCode = "ArrowDown"
HomeKey KeyCode = "Home"
EndKey KeyCode = "End"
PageUpKey KeyCode = "PageUp"
PageDownKey KeyCode = "PageDown"
F1Key KeyCode = "F1"
F2Key KeyCode = "F2"
F3Key KeyCode = "F3"
F4Key KeyCode = "F4"
F5Key KeyCode = "F5"
F6Key KeyCode = "F6"
F7Key KeyCode = "F7"
F8Key KeyCode = "F8"
F9Key KeyCode = "F9"
F10Key KeyCode = "F10"
F11Key KeyCode = "F11"
F12Key KeyCode = "F12"
F13Key KeyCode = "F13"
NumLockKey KeyCode = "NumLock"
NumpadKey0 KeyCode = "Numpad0"
NumpadKey1 KeyCode = "Numpad1"
NumpadKey2 KeyCode = "Numpad2"
NumpadKey3 KeyCode = "Numpad3"
NumpadKey4 KeyCode = "Numpad4"
NumpadKey5 KeyCode = "Numpad5"
NumpadKey6 KeyCode = "Numpad6"
NumpadKey7 KeyCode = "Numpad7"
NumpadKey8 KeyCode = "Numpad8"
NumpadKey9 KeyCode = "Numpad9"
NumpadDecimalKey KeyCode = "NumpadDecimal"
NumpadEnterKey KeyCode = "NumpadEnter"
NumpadAddKey KeyCode = "NumpadAdd"
NumpadSubtractKey KeyCode = "NumpadSubtract"
NumpadMultiplyKey KeyCode = "NumpadMultiply"
NumpadDivideKey KeyCode = "NumpadDivide"
ShiftLeftKey KeyCode = "ShiftLeft"
ShiftRightKey KeyCode = "ShiftRight"
ControlLeftKey KeyCode = "ControlLeft"
ControlRightKey KeyCode = "ControlRight"
AltLeftKey KeyCode = "AltLeft"
AltRightKey KeyCode = "AltRight"
MetaLeftKey KeyCode = "MetaLeft"
MetaRightKey KeyCode = "MetaRight"
)
type KeyEvent struct {
// TimeStamp is the time at which the event was created (in milliseconds).
// This value is time since epoch—but in reality, browsers' definitions vary.
@ -153,7 +32,7 @@ type KeyEvent struct {
// Code holds a string that identifies the physical key being pressed. The value is not affected
// by the current keyboard layout or modifier state, so a particular key will always return the same value.
Code KeyCode
Code string
// Repeat == true if a key has been depressed long enough to trigger key repetition, otherwise false.
Repeat bool
@ -180,8 +59,7 @@ func (event *KeyEvent) init(data DataObject) {
}
event.Key, _ = data.PropertyValue("key")
code, _ := data.PropertyValue("code")
event.Code = KeyCode(code)
event.Code, _ = data.PropertyValue("code")
event.TimeStamp = getTimeStamp(data)
event.Repeat = getBool("repeat")
event.CtrlKey = getBool("ctrlKey")
@ -315,183 +193,9 @@ func valueToEventListeners[V View, E any](value any) ([]func(V, E), bool) {
return nil, false
}
func valueToEventWithOldListeners[V View, E any](value any) ([]func(V, E, E), bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case func(V, E, E):
return []func(V, E, E){value}, true
case func(V, E):
fn := func(v V, val, _ E) {
value(v, val)
}
return []func(V, E, E){fn}, true
case func(E, E):
fn := func(_ V, val, old E) {
value(val, old)
}
return []func(V, E, E){fn}, true
case func(E):
fn := func(_ V, val, _ E) {
value(val)
}
return []func(V, E, E){fn}, true
case func(V):
fn := func(v V, _, _ E) {
value(v)
}
return []func(V, E, E){fn}, true
case func():
fn := func(V, E, E) {
value()
}
return []func(V, E, E){fn}, true
case []func(V, E, E):
if len(value) == 0 {
return nil, true
}
for _, fn := range value {
if fn == nil {
return nil, false
}
}
return value, true
case []func(V, E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(view V, val, _ E) {
fn(view, val)
}
}
return listeners, true
case []func(E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(_ V, val, _ E) {
fn(val)
}
}
return listeners, true
case []func(E, E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(_ V, val, old E) {
fn(val, old)
}
}
return listeners, true
case []func(V):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(view V, _, _ E) {
fn(view)
}
}
return listeners, true
case []func():
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(V, E, E) {
fn()
}
}
return listeners, true
case []any:
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
switch fn := v.(type) {
case func(V, E, E):
listeners[i] = fn
case func(V, E):
listeners[i] = func(view V, val, _ E) {
fn(view, val)
}
case func(E, E):
listeners[i] = func(_ V, val, old E) {
fn(val, old)
}
case func(E):
listeners[i] = func(_ V, val, _ E) {
fn(val)
}
case func(V):
listeners[i] = func(view V, _, _ E) {
fn(view)
}
case func():
listeners[i] = func(V, E, E) {
fn()
}
default:
return nil, false
}
}
return listeners, true
}
return nil, false
var keyEvents = map[string]struct{ jsEvent, jsFunc string }{
KeyDownEvent: {jsEvent: "onkeydown", jsFunc: "keyDownEvent"},
KeyUpEvent: {jsEvent: "onkeyup", jsFunc: "keyUpEvent"},
}
func (view *viewData) setKeyListener(tag string, value any) bool {
@ -503,57 +207,26 @@ func (view *viewData) setKeyListener(tag string, value any) bool {
if listeners == nil {
view.removeKeyListener(tag)
} else if js, ok := keyEvents[tag]; ok {
view.properties[tag] = listeners
if view.created {
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
}
} else {
switch tag {
case KeyDownEvent:
view.properties[tag] = listeners
if view.created {
view.session.updateProperty(view.htmlID(), "onkeydown", "keyDownEvent(this, event)")
}
case KeyUpEvent:
view.properties[tag] = listeners
if view.created {
view.session.updateProperty(view.htmlID(), "onkeyup", "keyUpEvent(this, event)")
}
default:
return false
}
}
return true
}
func (view *viewData) removeKeyListener(tag string) {
delete(view.properties, tag)
if view.created {
switch tag {
case KeyDownEvent:
if !view.Focusable() {
view.session.removeProperty(view.htmlID(), "onkeydown")
}
case KeyUpEvent:
view.session.removeProperty(view.htmlID(), "onkeyup")
if js, ok := keyEvents[tag]; ok {
view.session.removeProperty(view.htmlID(), js.jsEvent)
}
}
}
func getEventWithOldListeners[V View, E any](view View, subviewID []string, tag string) []func(V, E, E) {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
}
if view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]func(V, E, E)); ok {
return result
}
}
}
return []func(V, E, E){}
}
func getEventListeners[V View, E any](view View, subviewID []string, tag string) []func(V, E) {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
@ -569,52 +242,22 @@ func getEventListeners[V View, E any](view View, subviewID []string, tag string)
}
func keyEventsHtml(view View, buffer *strings.Builder) {
if len(getEventListeners[View, KeyEvent](view, nil, KeyDownEvent)) > 0 {
buffer.WriteString(`onkeydown="keyDownEvent(this, event)" `)
} else if view.Focusable() {
if len(getEventListeners[View, MouseEvent](view, nil, ClickEvent)) > 0 {
buffer.WriteString(`onkeydown="keyDownEvent(this, event)" `)
for tag, js := range keyEvents {
if listeners := getEventListeners[View, KeyEvent](view, nil, tag); len(listeners) > 0 {
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
}
}
if listeners := getEventListeners[View, KeyEvent](view, nil, KeyUpEvent); len(listeners) > 0 {
buffer.WriteString(`onkeyup="keyUpEvent(this, event)" `)
}
}
func handleKeyEvents(view View, tag string, data DataObject) {
listeners := getEventListeners[View, KeyEvent](view, nil, tag)
if len(listeners) > 0 {
var event KeyEvent
event.init(data)
listeners := getEventListeners[View, KeyEvent](view, nil, tag)
if len(listeners) > 0 {
for _, listener := range listeners {
listener(view, event)
}
return
}
if tag == KeyDownEvent && view.Focusable() && (event.Key == " " || event.Key == "Enter") && !IsDisabled(view) {
if listeners := getEventListeners[View, MouseEvent](view, nil, ClickEvent); len(listeners) > 0 {
clickEvent := MouseEvent{
TimeStamp: event.TimeStamp,
Button: PrimaryMouseButton,
Buttons: PrimaryMouseMask,
CtrlKey: event.CtrlKey,
AltKey: event.AltKey,
ShiftKey: event.ShiftKey,
MetaKey: event.MetaKey,
ClientX: view.Frame().Width / 2,
ClientY: view.Frame().Height / 2,
X: view.Frame().Width / 2,
Y: view.Frame().Height / 2,
ScreenX: view.Frame().Left + view.Frame().Width/2,
ScreenY: view.Frame().Top + view.Frame().Height/2,
}
for _, listener := range listeners {
listener(view, clickEvent)
}
}
}
}

View File

@ -47,7 +47,7 @@ const (
// ListView - the list view interface
type ListView interface {
View
ParentView
ParanetView
// ReloadListViewData updates ListView content
ReloadListViewData()
@ -129,92 +129,78 @@ func (listView *listViewData) remove(tag string) {
case Gap:
listView.remove(ListRowGap)
listView.remove(ListColumnGap)
return
case Checked:
if len(listView.checkedItem) == 0 {
return
}
if len(listView.checkedItem) > 0 {
listView.checkedItem = []int{}
if listView.created {
updateInnerHTML(listView.htmlID(), listView.session)
}
listView.propertyChangedEvent(tag)
}
case Items:
if listView.adapter == nil {
return
}
if listView.adapter != nil {
listView.adapter = nil
if listView.created {
updateInnerHTML(listView.htmlID(), listView.session)
}
listView.propertyChangedEvent(tag)
}
case Orientation, ListWrap:
if _, ok := listView.properties[tag]; !ok {
return
}
if _, ok := listView.properties[tag]; ok {
delete(listView.properties, tag)
if listView.created {
updateCSSStyle(listView.htmlID(), listView.session)
}
listView.propertyChangedEvent(tag)
}
case Current:
current := GetCurrent(listView)
if current == -1 {
return
}
delete(listView.properties, tag)
if listView.created {
htmlID := listView.htmlID()
session := listView.session
session.removeProperty(htmlID, "data-current")
updateInnerHTML(htmlID, session)
updateInnerHTML(listView.htmlID(), listView.session)
}
if current != -1 {
for _, listener := range listView.selectedListeners {
listener(listView, -1)
}
listView.propertyChangedEvent(tag)
}
case ItemWidth, ItemHeight, ItemHorizontalAlign, ItemVerticalAlign, ItemCheckbox,
CheckboxHorizontalAlign, CheckboxVerticalAlign:
if _, ok := listView.properties[tag]; !ok {
return
}
CheckboxHorizontalAlign, CheckboxVerticalAlign, ListItemStyle, CurrentStyle, CurrentInactiveStyle:
if _, ok := listView.properties[tag]; ok {
delete(listView.properties, tag)
if listView.created {
updateInnerHTML(listView.htmlID(), listView.session)
}
case ListItemStyle, CurrentStyle, CurrentInactiveStyle:
if !listView.setItemStyle(tag, "") {
return
listView.propertyChangedEvent(tag)
}
case ListItemClickedEvent:
if len(listView.clickedListeners) == 0 {
return
}
if len(listView.clickedListeners) > 0 {
listView.clickedListeners = []func(ListView, int){}
listView.propertyChangedEvent(tag)
}
case ListItemSelectedEvent:
if len(listView.selectedListeners) == 0 {
return
}
if len(listView.selectedListeners) > 0 {
listView.selectedListeners = []func(ListView, int){}
listView.propertyChangedEvent(tag)
}
case ListItemCheckedEvent:
if len(listView.checkedListeners) == 0 {
return
}
if len(listView.checkedListeners) > 0 {
listView.checkedListeners = []func(ListView, []int){}
listView.propertyChangedEvent(tag)
}
default:
listView.viewData.remove(tag)
return
}
listView.propertyChangedEvent(tag)
}
func (listView *listViewData) Set(tag string, value any) bool {
@ -282,20 +268,11 @@ func (listView *listViewData) set(tag string, value any) bool {
if !listView.setIntProperty(Current, value) {
return false
}
current := GetCurrent(listView)
if oldCurrent == current {
return true
}
if listView.created {
htmlID := listView.htmlID()
if current >= 0 {
listView.session.updateProperty(htmlID, "data-current", fmt.Sprintf("%s-%d", htmlID, current))
} else {
listView.session.removeProperty(htmlID, "data-current")
}
}
for _, listener := range listView.selectedListeners {
listener(listView, current)
}
@ -313,7 +290,12 @@ func (listView *listViewData) set(tag string, value any) bool {
}
case ListItemStyle, CurrentStyle, CurrentInactiveStyle:
if !listView.setItemStyle(tag, value) {
switch value := value.(type) {
case string:
listView.properties[tag] = value
default:
notCompatibleType(tag, value)
return false
}
@ -328,33 +310,6 @@ func (listView *listViewData) set(tag string, value any) bool {
return true
}
func (listView *listViewData) setItemStyle(tag string, value any) bool {
switch value := value.(type) {
case string:
if value == "" {
delete(listView.properties, tag)
} else {
listView.properties[tag] = value
}
default:
notCompatibleType(tag, value)
return false
}
if listView.created {
switch tag {
case CurrentStyle:
listView.session.updateProperty(listView.htmlID(), "data-focusitemstyle", listView.currentStyle())
case CurrentInactiveStyle:
listView.session.updateProperty(listView.htmlID(), "data-bluritemstyle", listView.currentInactiveStyle())
}
}
return true
}
func (listView *listViewData) Get(tag string) any {
return listView.get(listView.normalizeTag(tag))
}
@ -785,9 +740,6 @@ func (listView *listViewData) checkboxSubviews(self View, buffer *strings.Builde
}
buffer.WriteString(`" onclick="listItemClickEvent(this, event)" data-left="0" data-top="0" data-width="0" data-height="0" style="display: grid; justify-items: stretch; align-items: stretch;`)
listView.itemSize(self, buffer)
if !listView.adapter.IsListItemEnabled(i) {
buffer.WriteString(`" data-disabled="1`)
}
buffer.WriteString(`">`)
buffer.WriteString(itemDiv)
@ -844,9 +796,6 @@ func (listView *listViewData) noneCheckboxSubviews(self View, buffer *strings.Bu
}
buffer.WriteString(`" `)
buffer.WriteString(itemStyle)
if !listView.adapter.IsListItemEnabled(i) {
buffer.WriteString(` data-disabled="1"`)
}
buffer.WriteString(`>`)
if view := listView.getItemView(i); view != nil {

View File

@ -184,22 +184,14 @@ func (view *viewData) removeMouseListener(tag string) {
}
}
func mouseEventsHtml(view View, buffer *strings.Builder, hasTooltip bool) {
func mouseEventsHtml(view View, buffer *strings.Builder) {
for tag, js := range mouseEvents {
if value := view.getRaw(tag); value != nil {
if listeners, ok := value.([]func(View, MouseEvent)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
}
}
}
if hasTooltip {
buffer.WriteString(`onmouseenter="mouseEnterEvent(this, event)" `)
buffer.WriteString(`onmouseleave="mouseLeaveEvent(this, event)" `)
}
}
func getTimeStamp(data DataObject) uint64 {

View File

@ -29,7 +29,7 @@ type NumberPicker interface {
type numberPickerData struct {
viewData
numberChangedListeners []func(NumberPicker, float64, float64)
numberChangedListeners []func(NumberPicker, float64)
}
// NewNumberPicker create new NumberPicker object and return it
@ -47,7 +47,7 @@ func newNumberPicker(session Session) View {
func (picker *numberPickerData) init(session Session) {
picker.viewData.init(session)
picker.tag = "NumberPicker"
picker.numberChangedListeners = []func(NumberPicker, float64, float64){}
picker.numberChangedListeners = []func(NumberPicker, float64){}
}
func (picker *numberPickerData) String() string {
@ -76,20 +76,7 @@ func (picker *numberPickerData) remove(tag string) {
switch tag {
case NumberChangedEvent:
if len(picker.numberChangedListeners) > 0 {
picker.numberChangedListeners = []func(NumberPicker, float64, float64){}
picker.propertyChangedEvent(tag)
}
case NumberPickerValue:
oldValue := GetNumberPickerValue(picker)
picker.viewData.remove(tag)
if oldValue != 0 {
if picker.created {
picker.session.callFunc("setInputValue", picker.htmlID(), 0)
}
for _, listener := range picker.numberChangedListeners {
listener(picker, 0, oldValue)
}
picker.numberChangedListeners = []func(NumberPicker, float64){}
picker.propertyChangedEvent(tag)
}
@ -111,12 +98,12 @@ func (picker *numberPickerData) set(tag string, value any) bool {
switch tag {
case NumberChangedEvent:
listeners, ok := valueToEventWithOldListeners[NumberPicker, float64](value)
listeners, ok := valueToEventListeners[NumberPicker, float64](value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(NumberPicker, float64, float64){}
listeners = []func(NumberPicker, float64){}
}
picker.numberChangedListeners = listeners
picker.propertyChangedEvent(tag)
@ -132,7 +119,7 @@ func (picker *numberPickerData) set(tag string, value any) bool {
picker.session.callFunc("setInputValue", picker.htmlID(), newValue)
}
for _, listener := range picker.numberChangedListeners {
listener(picker, f, oldValue)
listener(picker, f)
}
picker.propertyChangedEvent(tag)
}
@ -172,6 +159,13 @@ func (picker *numberPickerData) propertyChanged(tag string) {
} else {
picker.session.updateProperty(picker.htmlID(), Step, "any")
}
case NumberPickerValue:
value := GetNumberPickerValue(picker)
picker.session.callFunc("setInputValue", picker.htmlID(), value)
for _, listener := range picker.numberChangedListeners {
listener(picker, value)
}
}
}
}
@ -248,7 +242,7 @@ func (picker *numberPickerData) handleCommand(self View, command string, data Da
picker.properties[NumberPickerValue] = text
if value != oldValue {
for _, listener := range picker.numberChangedListeners {
listener(picker, value, oldValue)
listener(picker, value)
}
}
}
@ -329,6 +323,6 @@ func GetNumberPickerValue(view View, subviewID ...string) float64 {
// GetNumberChangedListeners returns the NumberChangedListener list of an NumberPicker subview.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64, float64) {
return getEventWithOldListeners[NumberPicker, float64](view, subviewID, NumberChangedEvent)
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64) {
return getEventListeners[NumberPicker, float64](view, subviewID, NumberChangedEvent)
}

View File

@ -129,10 +129,7 @@ func pointerEventsHtml(view View, buffer *strings.Builder) {
for tag, js := range pointerEvents {
if value := view.getRaw(tag); value != nil {
if listeners, ok := value.([]func(View, PointerEvent)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
}
}
}

View File

@ -78,21 +78,11 @@ const (
// LeftArrow is value of the popup "arrow" property:
// Arrow on the left side of the pop-up window
LeftArrow = 4
// NormalButton is the constant of the popup button type: the normal button
NormalButton PopupButtonType = 0
// DefaultButton is the constant of the popup button type: button that fires when the "Enter" key is pressed
DefaultButton PopupButtonType = 1
// CancelButton is the constant of the popup button type: button that fires when the "Escape" key is pressed
CancelButton PopupButtonType = 2
)
type PopupButtonType int
// PopupButton describes a button that will be placed at the bottom of the window.
type PopupButton struct {
Title string
Type PopupButtonType
OnClick func(Popup)
}
@ -105,14 +95,11 @@ type Popup interface {
onDismiss()
html(buffer *strings.Builder)
viewByHTMLID(id string) View
keyEvent(event KeyEvent) bool
}
type popupData struct {
layerView View
view View
buttons []PopupButton
cancelable bool
dismissListener []func(Popup)
}
@ -286,7 +273,6 @@ func (arrow *popupArrow) createView(popupView View) View {
func (popup *popupData) init(view View, popupParams Params) {
popup.view = view
popup.cancelable = false
session := view.Session()
columnCount := 3
@ -406,15 +392,13 @@ func (popup *popupData) init(view View, popupParams Params) {
TextSize: Px(20),
Content: "✕",
NotTranslate: true,
ClickEvent: popup.cancel,
ClickEvent: func(View) {
popup.Dismiss()
},
})
popup.cancelable = true
case OutsideClose:
outsideClose, _ = boolProperty(popupParams, OutsideClose, session)
if outsideClose {
popup.cancelable = true
}
case Buttons:
switch value := value.(type) {
@ -510,7 +494,6 @@ func (popup *popupData) init(view View, popupParams Params) {
view.Set(Row, viewRow)
popupView.Append(view)
popup.buttons = buttons
if buttonCount := len(buttons); buttonCount > 0 {
buttonsAlign, _ := enumProperty(params, ButtonsAlign, session, RightAlign)
popupCellHeight = append(popupCellHeight, AutoSize())
@ -528,31 +511,21 @@ func (popup *popupData) init(view View, popupParams Params) {
buttonsPanel.Set(Margin, gap)
}
for i, button := range buttons {
title := button.Title
if title == "" && button.Type == CancelButton {
title = "Cancel"
}
buttonView := NewButton(session, Params{
Column: i,
Content: title,
})
createButton := func(n int, button PopupButton) Button {
return NewButton(session, Params{
Column: n,
Content: button.Title,
ClickEvent: func() {
if button.OnClick != nil {
fn := button.OnClick
buttonView.Set(ClickEvent, func() {
fn(popup)
button.OnClick(popup)
} else {
popup.Dismiss()
}
},
})
} else if button.Type == CancelButton {
buttonView.Set(ClickEvent, popup.cancel)
}
if button.Type == DefaultButton {
buttonView.Set(Style, "ruiDefaultButton")
}
buttonsPanel.Append(buttonView)
for i, button := range buttons {
buttonsPanel.Append(createButton(i, button))
}
popupView.Append(NewGridLayout(session, Params{
@ -571,8 +544,11 @@ func (popup *popupData) init(view View, popupParams Params) {
}
popup.layerView = NewGridLayout(session, layerParams)
if outsideClose {
popup.layerView.Set(ClickEvent, popup.cancel)
popup.layerView.Set(ClickEvent, func(View) {
popup.Dismiss()
})
}
}
@ -584,21 +560,12 @@ func (popup *popupData) Session() Session {
return popup.view.Session()
}
func (popup *popupData) cancel() {
for _, button := range popup.buttons {
if button.Type == CancelButton && button.OnClick != nil {
button.OnClick(popup)
return
}
}
popup.Dismiss()
}
func (popup *popupData) Dismiss() {
popup.Session().popupManager().dismissPopup(popup)
for _, listener := range popup.dismissListener {
listener(popup)
}
// TODO
}
func (popup *popupData) Show() {
@ -620,27 +587,6 @@ func (popup *popupData) onDismiss() {
}
}
func (popup *popupData) keyEvent(event KeyEvent) bool {
if !event.AltKey && !event.CtrlKey && !event.ShiftKey && !event.MetaKey {
switch event.Code {
case EnterKey:
for _, button := range popup.buttons {
if button.Type == DefaultButton && button.OnClick != nil {
button.OnClick(popup)
return true
}
}
case EscapeKey:
if popup.cancelable {
popup.Dismiss()
return true
}
}
}
return false
}
// NewPopup creates a new Popup
func NewPopup(view View, param Params) Popup {
if view == nil {
@ -691,8 +637,6 @@ func (manager *popupManager) showPopup(popup Popup) {
session.callFunc("blurCurrent")
manager.updatePopupLayerInnerHTML(session)
session.updateCSSProperty("ruiTooltipLayer", "visibility", "hidden")
session.updateCSSProperty("ruiTooltipLayer", "opacity", "0")
session.updateCSSProperty("ruiPopupLayer", "visibility", "visible")
session.updateCSSProperty("ruiRoot", "pointer-events", "none")
}

View File

@ -28,19 +28,8 @@ func ShowQuestion(title, text string, session Session, onYes func(), onNo func()
CloseButton: false,
OutsideClose: false,
Buttons: []PopupButton{
{
Title: "Yes",
Type: DefaultButton,
OnClick: func(popup Popup) {
popup.Dismiss()
if onYes != nil {
onYes()
}
},
},
{
Title: "No",
Type: CancelButton,
OnClick: func(popup Popup) {
popup.Dismiss()
if onNo != nil {
@ -48,6 +37,15 @@ func ShowQuestion(title, text string, session Session, onYes func(), onNo func()
}
},
},
{
Title: "Yes",
OnClick: func(popup Popup) {
popup.Dismiss()
if onYes != nil {
onYes()
}
},
},
},
}
if title != "" {
@ -70,12 +68,11 @@ func ShowCancellableQuestion(title, text string, session Session, onYes func(),
OutsideClose: false,
Buttons: []PopupButton{
{
Title: "Yes",
Type: DefaultButton,
Title: "Cancel",
OnClick: func(popup Popup) {
popup.Dismiss()
if onYes != nil {
onYes()
if onCancel != nil {
onCancel()
}
},
},
@ -89,12 +86,11 @@ func ShowCancellableQuestion(title, text string, session Session, onYes func(),
},
},
{
Title: "Cancel",
Type: CancelButton,
Title: "Yes",
OnClick: func(popup Popup) {
popup.Dismiss()
if onCancel != nil {
onCancel()
if onYes != nil {
onYes()
}
},
},
@ -131,14 +127,10 @@ func (popup *popupMenuData) ListSize() int {
}
func (popup *popupMenuData) ListItem(index int, session Session) View {
view := NewTextView(popup.session, Params{
return NewTextView(popup.session, Params{
Text: popup.items[index],
Style: "ruiPopupMenuItem",
})
if !popup.IsListItemEnabled(index) {
view.Set(TextColor, "@ruiDisabledTextColor")
}
return view
}
func (popup *popupMenuData) IsListItemEnabled(index int) bool {

View File

@ -277,10 +277,6 @@ const (
// The "outline-width" SizeUnit property sets the width of an view's outline.
OutlineWidth = "outline-width"
// OutlineWidth is the constant for the "outline-offset" property tag.
// The "outline-offset" SizeUnit property sets the amount of space between an outline and the edge or border of an element..
OutlineOffset = "outline-offset"
// Shadow is the constant for the "shadow" property tag.
// The "shadow" property adds shadow effects around a view's frame. A shadow is described
// by X and Y offsets relative to the element, blur and spread radius, and color.
@ -425,7 +421,7 @@ const (
// This is an inherited property, i.e. if it is not defined, then the value of the parent view is used.
VerticalTextOrientation = "vertical-text-orientation"
// TextOverflow is the constant for the "text-overflow" property tag.
// TextTverflow is the constant for the "text-overflow" property tag.
// The "text-overflow" int property sets how hidden overflow content is signaled to users.
// It can be clipped or display an ellipsis ('…'). Valid values are
TextOverflow = "text-overflow"
@ -542,7 +538,7 @@ const (
// AvoidBreak is the constant for the "avoid-break" property tag.
// The "avoid-break" bool property sets how region breaks should behave inside a generated box.
// If the property value is "true" then avoids any break from being inserted within the principal box.
// If the property value is "true" then fvoids any break from being inserted within the principal box.
// If the property value is "false" then allows, but does not force, any break to be inserted within
// the principal box.
AvoidBreak = "avoid-break"
@ -660,7 +656,7 @@ const (
// allowing text and inline Views to wrap around it.
Float = "float"
// UserData is the constant for the "user-data" property tag.
// UsetData is the constant for the "user-data" property tag.
// The "user-data" property can contain any user data
UserData = "user-data"
@ -673,35 +669,4 @@ const (
// 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"
// Order is the constant for the "Order" property tag.
// The "Order" int property sets the order to layout an item in a ListLayout or GridLayout container.
// Items in a container are sorted by ascending order value and then by their source code order.
Order = "Order"
// BackgroundBlendMode is the constant for the "background-blend-mode" property tag.
// The "background-blend-mode" int property sets how an view's background images should blend
// with each other and with the view's background color.
// Valid values are "normal" (0), "multiply" (1), "screen" (2), "overlay" (3), "darken" (4), "lighten" (5),
// "color-dodge" (6), "color-burn" (7), "hard-light" (8), "soft-light" (9), "difference" (10),
// "exclusion" (11), "hue" (12), "saturation" (13), "color" (14), "luminosity" (15).
BackgroundBlendMode = "background-blend-mode"
// MixBlendMode is the constant for the "mix-blend-mode" property tag.
// The "mix-blend-mode" int property sets how a view's content should blend
// with the content of the view's parent and the view's background.
// Valid values are "normal" (0), "multiply" (1), "screen" (2), "overlay" (3), "darken" (4), "lighten" (5),
// "color-dodge" (6), "color-burn" (7), "hard-light" (8), "soft-light" (9), "difference" (10),
// "exclusion" (11), "hue" (12), "saturation" (13), "color" (14), "luminosity" (15).
MixBlendMode = "mix-blend-mode"
// TabIndex is the constant for the "tabindex" property tag.
// The "tabindex" int property indicates that View can be focused, and where it participates in sequential keyboard navigation
// (usually with the Tab key, hence the name).
// * A negative value means that View is not reachable via sequential keyboard navigation, but could be focused by clicking with the mouse or touching.
// * tabindex="0" means that View should be focusable in sequential keyboard navigation, after any positive tabindex values and its order is defined in order of its addition.
// * A positive value means View should be focusable in sequential keyboard navigation, with its order defined by the value of the number.
TabIndex = "tabindex"
Tooltip = "tooltip"
)

View File

@ -63,7 +63,6 @@ var boolProperties = []string{
TabCloseButton,
Repeating,
UserSelect,
ColumnSpanAll,
}
var intProperties = []string{
@ -74,8 +73,6 @@ var intProperties = []string{
RowSpan,
ColumnSpan,
ColumnCount,
Order,
TabIndex,
}
var floatProperties = map[string]struct{ min, max float64 }{
@ -136,7 +133,6 @@ var sizeProperties = map[string]string{
BorderTopWidth: BorderTopWidth,
BorderBottomWidth: BorderBottomWidth,
OutlineWidth: OutlineWidth,
OutlineOffset: OutlineOffset,
XOffset: XOffset,
YOffset: YOffset,
BlurRadius: BlurRadius,
@ -451,21 +447,6 @@ var enumProperties = map[string]struct {
"",
[]string{"none", "top", "right", "bottom", "left"},
},
MixBlendMode: {
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
MixBlendMode,
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
},
BackgroundBlendMode: {
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
BackgroundBlendMode,
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
},
ColumnFill: {
[]string{"balance", "auto"},
ColumnFill,
[]string{"balance", "auto"},
},
}
func notCompatibleType(tag string, value any) {

View File

@ -307,92 +307,4 @@ const (
// "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later.
// This may cause views to appear out-of-order, when doing so would fill in holes left by larger views.
ColumnDenseAutoFlow = 3
// BlendNormal - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color is the top color, regardless of what the bottom color is.
// The effect is like two opaque pieces of paper overlapping.
BlendNormal = 0
// BlendMultiply - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color is the result of multiplying the top and bottom colors.
// A black layer leads to a black final layer, and a white layer leads to no change.
// The effect is like two images printed on transparent film overlapping.
BlendMultiply = 1
// BlendScreen - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color is the result of inverting the colors, multiplying them, and inverting that value.
// A black layer leads to no change, and a white layer leads to a white final layer.
// The effect is like two images shone onto a projection screen.
BlendScreen = 2
// BlendOverlay - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color is the result of multiply if the bottom color is darker, or screen if the bottom color is lighter.
// This blend mode is equivalent to hard-light but with the layers swapped.
BlendOverlay = 3
// BlendDarken - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color is composed of the darkest values of each color channel.
BlendDarken = 4
// BlendLighten - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color is composed of the lightest values of each color channel.
BlendLighten = 5
// BlendColorDodge - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color is the result of dividing the bottom color by the inverse of the top color.
// A black foreground leads to no change. A foreground with the inverse color of the backdrop leads to a fully lit color.
// This blend mode is similar to screen, but the foreground need only be as light as the inverse of the backdrop to create a fully lit color.
BlendColorDodge = 6
// BlendColorBurn - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color is the result of inverting the bottom color, dividing the value by the top color, and inverting that value.
// A white foreground leads to no change. A foreground with the inverse color of the backdrop leads to a black final image.
// This blend mode is similar to multiply, but the foreground need only be as dark as the inverse of the backdrop to make the final image black.
BlendColorBurn = 7
// BlendHardLight - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color is the result of multiply if the top color is darker, or screen if the top color is lighter.
// This blend mode is equivalent to overlay but with the layers swapped. The effect is similar to shining a harsh spotlight on the backdrop.
BlendHardLight = 8
// BlendSoftLight - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color is similar to hard-light, but softer. This blend mode behaves similar to hard-light.
// The effect is similar to shining a diffused spotlight on the backdrop*.*
BlendSoftLight = 9
// BlendDifference - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color is the result of subtracting the darker of the two colors from the lighter one.
// A black layer has no effect, while a white layer inverts the other layer's color.
BlendDifference = 10
// BlendExclusion - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color is similar to difference, but with less contrast.
// As with difference, a black layer has no effect, while a white layer inverts the other layer's color.
BlendExclusion = 11
// BlendHue - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color has the hue of the top color, while using the saturation and luminosity of the bottom color.
BlendHue = 12
// BlendSaturation - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color has the saturation of the top color, while using the hue and luminosity of the bottom color.
// A pure gray backdrop, having no saturation, will have no effect.
BlendSaturation = 13
// BlendColor - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color has the hue and saturation of the top color, while using the luminosity of the bottom color.
// The effect preserves gray levels and can be used to colorize the foreground.
BlendColor = 14
// BlendLuminosity - value of the "mix-blend-mode" and "background-blend-mode" property:
// The final color has the luminosity of the top color, while using the hue and saturation of the bottom color.
// This blend mode is equivalent to BlendColor, but with the layers swapped.
BlendLuminosity = 15
// ColumnFillBalance - value of the "column-fill" property: content is equally divided between columns.
ColumnFillBalance = 0
// ColumnFillAuto - value of the "column-fill" property:
// Columns are filled sequentially. Content takes up only the room it needs, possibly resulting in some columns remaining empty.
ColumnFillAuto = 1
)

View File

@ -34,7 +34,7 @@ const (
// Resizable - grid-container of View
type Resizable interface {
View
ParentView
ParanetView
}
type resizableData struct {

View File

@ -123,7 +123,7 @@ func scanEmbedImagesDir(fs *embed.FS, dir, prefix string) {
} else {
ext := strings.ToLower(filepath.Ext(name))
switch ext {
case ".png", ".jpg", ".jpeg", ".svg", ".gif", ".bmp", ".webp":
case ".png", ".jpg", ".jpeg", ".svg", ".gif", ".bmp":
registerImage(fs, path, prefix+name)
}
}
@ -272,8 +272,8 @@ func serveResourceFile(filename string, w http.ResponseWriter, r *http.Request)
if serveEmbed(fs, dir+"/"+filename) {
return true
}
if subDirs, err := fs.ReadDir(dir); err == nil {
for _, subdir := range subDirs {
if subdirs, err := fs.ReadDir(dir); err == nil {
for _, subdir := range subdirs {
if subdir.IsDir() {
if serveEmbed(fs, dir+"/"+subdir.Name()+"/"+filename) {
return true

View File

@ -20,13 +20,13 @@ type webBridge interface {
writeMessage(text string) bool
addAnimationCSS(css string)
clearAnimation()
canvasStart(htmlID string)
cavnasStart(htmlID string)
callCanvasFunc(funcName string, args ...any)
callCanvasVarFunc(v any, funcName string, args ...any)
callCanvasImageFunc(url string, property string, funcName string, args ...any)
createCanvasVar(funcName string, args ...any) any
updateCanvasProperty(property string, value any)
canvasFinish()
cavnasFinish()
canvasTextMetrics(htmlID, font, text string) TextMetrics
htmlPropertyValue(htmlID, name string) string
answerReceived(answer DataObject)
@ -100,17 +100,6 @@ type Session interface {
// OpenURL opens the url in the new browser tab
OpenURL(url string)
// ClientItem reads value by key from the client-side storage
ClientItem(key string) (string, bool)
// SetClientItem stores a key-value pair in the client-side storage
SetClientItem(key, value string)
// RemoveAllClientItems removes all key-value pair from the client-side storage
RemoveAllClientItems()
// SetHotKey sets the function that will be called when the given hotkey is pressed.
// Invoke SetHotKey(..., ..., nil) for remove hotkey function.
SetHotKey(keyCode KeyCode, controlKeys ControlKeyMask, fn func(Session))
getCurrentTheme() Theme
registerAnimation(props []AnimatedProperty) string
@ -136,13 +125,13 @@ type Session interface {
finishUpdateScript(htmlID string)
addAnimationCSS(css string)
clearAnimation()
canvasStart(htmlID string)
cavnasStart(htmlID string)
callCanvasFunc(funcName string, args ...any)
createCanvasVar(funcName string, args ...any) any
callCanvasVarFunc(v any, funcName string, args ...any)
callCanvasImageFunc(url string, property string, funcName string, args ...any)
updateCanvasProperty(property string, value any)
canvasFinish()
cavnasFinish()
canvasTextMetrics(htmlID, font, text string) TextMetrics
htmlPropertyValue(htmlID, name string) string
handleAnswer(data DataObject)
@ -194,8 +183,6 @@ type sessionData struct {
animationCounter int
animationCSS string
updateScripts map[string]*strings.Builder
clientStorage map[string]string
hotkeys map[string]func(Session)
}
func newSession(app Application, id int, customTheme string, params DataObject) Session {
@ -212,8 +199,6 @@ func newSession(app Application, id int, customTheme string, params DataObject)
session.animationCounter = 0
session.animationCSS = ""
session.updateScripts = map[string]*strings.Builder{}
session.clientStorage = map[string]string{}
session.hotkeys = map[string]func(Session){}
if customTheme != "" {
if theme, ok := CreateThemeFromText(customTheme); ok {
@ -305,28 +290,9 @@ func (session *sessionData) writeInitScript(writer *strings.Builder) {
if session.rootView != nil {
writer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
viewHTML(session.rootView, buffer)
text := strings.ReplaceAll(buffer.String(), "'", `\'`)
writer.WriteString(text)
viewHTML(session.rootView, writer)
writer.WriteString("';\nscanElementsSize();")
}
session.updateTooltipConstants()
}
func (session *sessionData) updateTooltipConstants() {
if color, ok := session.Color("ruiTooltipBackground"); ok {
session.bridge.callFunc("setCssVar", "--tooltip-background", color.cssString())
}
if color, ok := session.Color("ruiTooltipTextColor"); ok {
session.bridge.callFunc("setCssVar", "--tooltip-text-color", color.cssString())
}
if color, ok := session.Color("ruiTooltipShadowColor"); ok {
session.bridge.callFunc("setCssVar", "--tooltip-shadow-color", color.cssString())
}
}
func (session *sessionData) reload() {
@ -347,7 +313,6 @@ func (session *sessionData) reload() {
}
session.bridge.writeMessage(buffer.String())
session.updateTooltipConstants()
}
func (session *sessionData) ignoreViewUpdates() bool {
@ -459,9 +424,9 @@ func (session *sessionData) clearAnimation() {
}
}
func (session *sessionData) canvasStart(htmlID string) {
func (session *sessionData) cavnasStart(htmlID string) {
if session.bridge != nil {
session.bridge.canvasStart(htmlID)
session.bridge.cavnasStart(htmlID)
}
}
@ -496,9 +461,9 @@ func (session *sessionData) callCanvasImageFunc(url string, property string, fun
}
}
func (session *sessionData) canvasFinish() {
func (session *sessionData) cavnasFinish() {
if session.bridge != nil {
session.bridge.canvasFinish()
session.bridge.cavnasFinish()
}
}
@ -547,7 +512,7 @@ func (session *sessionData) handleRootSize(data DataObject) {
}
func (session *sessionData) handleResize(data DataObject) {
if node := data.PropertyByTag("views"); node != nil && node.Type() == ArrayNode {
if node := data.PropertyWithTag("views"); node != nil && node.Type() == ArrayNode {
for _, el := range node.ArrayElements() {
if el.IsObject() {
obj := el.Object()
@ -622,16 +587,6 @@ func (session *sessionData) handleSessionInfo(params DataObject) {
session.pixelRatio = f
}
}
if node := params.PropertyByTag("storage"); node != nil && node.Type() == ObjectNode {
if obj := node.Object(); obj != nil {
for i := 0; i < obj.PropertyCount(); i++ {
if element := obj.Property(i); element.Type() == TextNode {
session.clientStorage[element.Tag()] = element.Text()
}
}
}
}
}
func (session *sessionData) handleEvent(command string, data DataObject) {
@ -651,89 +606,17 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
case "sessionInfo":
session.handleSessionInfo(data)
case "storageError":
if text, ok := data.PropertyValue("error"); ok {
ErrorLog(text)
}
default:
if viewID, ok := data.PropertyValue("id"); ok {
if viewID != "body" {
if view := session.viewByHTMLID(viewID); view != nil {
view.handleCommand(view, command, data)
}
}
if command == KeyDownEvent {
var event KeyEvent
event.init(data)
session.hotKey(event)
}
} else {
} else if command != "clickOutsidePopup" {
ErrorLog(`"id" property not found. Event: ` + command)
}
}
}
func (session *sessionData) hotKey(event KeyEvent) {
popups := session.popupManager().popups
if count := len(popups); count > 0 {
if popups[count-1].keyEvent(event) {
return
}
}
var controlKeys ControlKeyMask = 0
if event.AltKey {
controlKeys |= AltKey
}
if event.CtrlKey {
controlKeys |= CtrlKey
}
if event.MetaKey {
controlKeys |= MetaKey
}
if event.ShiftKey {
controlKeys |= ShiftKey
}
key := hotkeyCode(KeyCode(event.Code), controlKeys)
if fn, ok := session.hotkeys[key]; ok && fn != nil {
fn(session)
}
}
func hotkeyCode(keyCode KeyCode, controlKeys ControlKeyMask) string {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
buffer.WriteString(strings.ToLower(string(keyCode)))
if controlKeys != 0 {
buffer.WriteRune('-')
if controlKeys&AltKey != 0 {
buffer.WriteRune('a')
}
if controlKeys&CtrlKey != 0 {
buffer.WriteRune('c')
}
if controlKeys&MetaKey != 0 {
buffer.WriteRune('m')
}
if controlKeys&ShiftKey != 0 {
buffer.WriteRune('s')
}
}
return buffer.String()
}
func (session *sessionData) SetHotKey(keyCode KeyCode, controlKeys ControlKeyMask, fn func(Session)) {
hotkey := hotkeyCode(keyCode, controlKeys)
if fn == nil {
delete(session.hotkeys, hotkey)
} else {
session.hotkeys[hotkey] = fn
}
}
func (session *sessionData) SetTitle(title string) {
title, _ = session.GetString(title)
session.callFunc("setTitle", title)
@ -754,18 +637,3 @@ func (session *sessionData) OpenURL(urlStr string) {
}
session.callFunc("openURL", urlStr)
}
func (session *sessionData) ClientItem(key string) (string, bool) {
value, ok := session.clientStorage[key]
return value, ok
}
func (session *sessionData) SetClientItem(key, value string) {
session.clientStorage[key] = value
session.bridge.callFunc("localStorageSet", key, value)
}
func (session *sessionData) RemoveAllClientItems() {
session.clientStorage = map[string]string{}
session.bridge.callFunc("localStorageClear")
}

View File

@ -27,8 +27,6 @@ func updateInnerHTML(htmlID string, session Session) {
view = session.viewByHTMLID(htmlID)
}
if view != nil {
session.callFunc("hideTooltip")
script := allocStringBuilder()
defer freeStringBuilder(script)
@ -44,7 +42,7 @@ func viewByHTMLID(id string, startView View) View {
if startView.htmlID() == id {
return startView
}
if container, ok := startView.(ParentView); ok {
if container, ok := startView.(ParanetView); ok {
for _, view := range container.Views() {
if view != nil {
if v := viewByHTMLID(id, view); v != nil {

View File

@ -104,7 +104,7 @@ func (data *sizeFuncData) parseArgs(args []any, allowNumber bool) bool {
}
}
ErrorLogF(`The %s function argument can't be a number`, data.tag)
ErrorLogF(`The %s function argument cann't be a number`, data.tag)
return false
}

View File

@ -1,161 +0,0 @@
package rui
import (
"io"
"net/http"
"os"
"strings"
)
// SvgImageView - image View
type SvgImageView interface {
View
}
type svgImageViewData struct {
viewData
}
// NewSvgImageView create new SvgImageView object and return it
func NewSvgImageView(session Session, params Params) SvgImageView {
view := new(svgImageViewData)
view.init(session)
setInitParams(view, params)
return view
}
func newSvgImageView(session Session) View {
return NewSvgImageView(session, nil)
}
// Init initialize fields of imageView by default values
func (imageView *svgImageViewData) init(session Session) {
imageView.viewData.init(session)
imageView.tag = "SvgImageView"
imageView.systemClass = "ruiSvgImageView"
}
func (imageView *svgImageViewData) String() string {
return getViewString(imageView)
}
func (imageView *svgImageViewData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag {
case Source, "source":
tag = Content
case VerticalAlign:
tag = CellVerticalAlign
case HorizontalAlign:
tag = CellHorizontalAlign
}
return tag
}
func (imageView *svgImageViewData) Remove(tag string) {
imageView.remove(imageView.normalizeTag(tag))
}
func (imageView *svgImageViewData) remove(tag string) {
imageView.viewData.remove(tag)
if imageView.created {
switch tag {
case Content:
updateInnerHTML(imageView.htmlID(), imageView.session)
}
}
}
func (imageView *svgImageViewData) Set(tag string, value any) bool {
return imageView.set(imageView.normalizeTag(tag), value)
}
func (imageView *svgImageViewData) set(tag string, value any) bool {
if value == nil {
imageView.remove(tag)
return true
}
switch tag {
case Content:
if text, ok := value.(string); ok {
imageView.properties[Content] = text
if imageView.created {
updateInnerHTML(imageView.htmlID(), imageView.session)
}
imageView.propertyChangedEvent(Content)
return true
}
notCompatibleType(Source, value)
return false
default:
return imageView.viewData.set(tag, value)
}
}
func (imageView *svgImageViewData) Get(tag string) any {
return imageView.viewData.get(imageView.normalizeTag(tag))
}
func (imageView *svgImageViewData) htmlTag() string {
return "div"
}
func (imageView *svgImageViewData) htmlSubviews(self View, buffer *strings.Builder) {
if value := imageView.getRaw(Content); value != nil {
if content, ok := value.(string); ok && content != "" {
if strings.HasPrefix(content, "@") {
if name, ok := imageView.session.ImageConstant(content[1:]); ok {
content = name
}
}
if image, ok := resources.images[content]; ok {
if image.fs != nil {
if data, err := image.fs.ReadFile(image.path); err == nil {
buffer.WriteString(string(data))
return
} else {
DebugLog(err.Error())
}
} else if data, err := os.ReadFile(image.path); err == nil {
buffer.WriteString(string(data))
return
} else {
DebugLog(err.Error())
}
}
if strings.HasPrefix(content, "http://") || strings.HasPrefix(content, "https://") {
resp, err := http.Get(content)
if err == nil {
defer resp.Body.Close()
if body, err := io.ReadAll(resp.Body); err == nil {
buffer.WriteString(string(body))
return
}
}
DebugLog(err.Error())
}
buffer.WriteString(content)
}
}
}
// GetSvgImageViewVerticalAlign return the vertical align of an SvgImageView subview: TopAlign (0), BottomAlign (1), CenterAlign (2)
// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned
func GetSvgImageViewVerticalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, CellVerticalAlign, LeftAlign, false)
}
// GetSvgImageViewHorizontalAlign return the vertical align of an SvgImageView subview: LeftAlign (0), RightAlign (1), CenterAlign (2)
// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned
func GetSvgImageViewHorizontalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, CellHorizontalAlign, LeftAlign, false)
}

View File

@ -228,21 +228,11 @@ func (adapter *textTableAdapter) Cell(row, column int) any {
return nil
}
type simpleTableLineStyle struct {
type simpleTableRowStyle struct {
params []Params
}
func (style *simpleTableLineStyle) ColumnStyle(column int) Params {
if column < len(style.params) {
params := style.params[column]
if len(params) > 0 {
return params
}
}
return nil
}
func (style *simpleTableLineStyle) RowStyle(row int) Params {
func (style *simpleTableRowStyle) RowStyle(row int) Params {
if row < len(style.params) {
params := style.params[row]
if len(params) > 0 {
@ -252,24 +242,50 @@ func (style *simpleTableLineStyle) RowStyle(row int) Params {
return nil
}
func (table *tableViewData) setLineStyle(tag string, value any) bool {
func (table *tableViewData) setRowStyle(value any) bool {
newSimpleTableRowStyle := func(params []Params) TableRowStyle {
if len(params) == 0 {
return nil
}
result := new(simpleTableRowStyle)
result.params = params
return result
}
switch value := value.(type) {
case TableRowStyle:
table.properties[RowStyle] = value
case []Params:
if len(value) > 0 {
style := new(simpleTableLineStyle)
style.params = value
table.properties[tag] = style
if style := newSimpleTableRowStyle(value); style != nil {
table.properties[RowStyle] = style
} else {
delete(table.properties, tag)
delete(table.properties, RowStyle)
}
case DataNode:
if params := value.ArrayAsParams(); len(params) > 0 {
style := new(simpleTableLineStyle)
style.params = params
table.properties[tag] = style
if value.Type() == ArrayNode {
params := make([]Params, value.ArraySize())
for i, element := range value.ArrayElements() {
params[i] = Params{}
if element.IsObject() {
obj := element.Object()
for k := 0; k < obj.PropertyCount(); k++ {
if prop := obj.Property(k); prop != nil && prop.Type() == TextNode {
params[i][prop.Tag()] = prop.Text()
}
}
} else {
delete(table.properties, tag)
params[i][Style] = element.Value()
}
}
if style := newSimpleTableRowStyle(params); style != nil {
table.properties[RowStyle] = style
} else {
delete(table.properties, RowStyle)
}
} else {
return false
}
default:
@ -278,14 +294,6 @@ func (table *tableViewData) setLineStyle(tag string, value any) bool {
return true
}
func (table *tableViewData) setRowStyle(value any) bool {
switch value := value.(type) {
case TableRowStyle:
table.properties[RowStyle] = value
}
return table.setLineStyle(RowStyle, value)
}
func (table *tableViewData) getRowStyle() TableRowStyle {
for _, tag := range []string{RowStyle, Content} {
if value := table.getRaw(tag); value != nil {
@ -297,12 +305,70 @@ func (table *tableViewData) getRowStyle() TableRowStyle {
return nil
}
type simpleTableColumnStyle struct {
params []Params
}
func (style *simpleTableColumnStyle) ColumnStyle(row int) Params {
if row < len(style.params) {
params := style.params[row]
if len(params) > 0 {
return params
}
}
return nil
}
func (table *tableViewData) setColumnStyle(value any) bool {
newSimpleTableColumnStyle := func(params []Params) TableColumnStyle {
if len(params) == 0 {
return nil
}
result := new(simpleTableColumnStyle)
result.params = params
return result
}
switch value := value.(type) {
case TableColumnStyle:
table.properties[ColumnStyle] = value
case []Params:
if style := newSimpleTableColumnStyle(value); style != nil {
table.properties[ColumnStyle] = style
} else {
delete(table.properties, ColumnStyle)
}
return table.setLineStyle(ColumnStyle, value)
case DataNode:
if value.Type() == ArrayNode {
params := make([]Params, value.ArraySize())
for i, element := range value.ArrayElements() {
params[i] = Params{}
if element.IsObject() {
obj := element.Object()
for k := 0; k < obj.PropertyCount(); k++ {
if prop := obj.Property(k); prop != nil && prop.Type() == TextNode {
params[i][prop.Tag()] = prop.Text()
}
}
} else {
params[i][Style] = element.Value()
}
}
if style := newSimpleTableColumnStyle(params); style != nil {
table.properties[ColumnStyle] = style
} else {
delete(table.properties, ColumnStyle)
}
} else {
return false
}
default:
return false
}
return true
}
func (table *tableViewData) getColumnStyle() TableColumnStyle {

View File

@ -218,9 +218,8 @@ type CellIndex struct {
// TableView - text View
type TableView interface {
View
ParentView
ParanetView
ReloadTableData()
ReloadCell(row, column int)
CellFrame(row, column int) Frame
content() TableAdapter
@ -525,6 +524,10 @@ func (table *tableViewData) set(tag string, value any) bool {
case Current:
switch value := value.(type) {
case int:
table.current.Row = value
table.current.Column = -1
case CellIndex:
table.current = value
@ -551,7 +554,6 @@ func (table *tableViewData) set(tag string, value any) bool {
table.current.Column = n[1]
} else {
notCompatibleType(tag, value)
return false
}
} else {
n, err := strconv.Atoi(value)
@ -564,14 +566,9 @@ func (table *tableViewData) set(tag string, value any) bool {
}
default:
if n, ok := isInt(value); ok {
table.current.Row = n
table.current.Column = -1
} else {
notCompatibleType(tag, value)
return false
}
}
default:
return table.viewData.set(tag, value)
@ -588,18 +585,9 @@ func (table *tableViewData) propertyChanged(tag string) {
CellBorder, HeadHeight, HeadStyle, FootHeight, FootStyle,
CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft,
TableCellClickedEvent, TableCellSelectedEvent, TableRowClickedEvent,
TableRowSelectedEvent, AllowSelection:
TableRowSelectedEvent, AllowSelection, Current:
table.ReloadTableData()
case Current:
switch GetTableSelectionMode(table) {
case CellSelection:
table.session.callFunc("setTableCellCursorByID", table.htmlID(), table.current.Row, table.current.Column)
case RowSelection:
table.session.callFunc("setTableRowCursorByID", table.htmlID(), table.current.Row)
}
case Gap:
htmlID := table.htmlID()
session := table.Session()
@ -618,8 +606,7 @@ func (table *tableViewData) propertyChanged(tag string) {
switch GetTableSelectionMode(table) {
case CellSelection:
tabIndex, _ := intProperty(table, TabIndex, session, 0)
session.updateProperty(htmlID, "tabindex", tabIndex)
session.updateProperty(htmlID, "tabindex", "0")
session.updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)")
session.updateProperty(htmlID, "onblur", "tableViewBlurEvent(this, event)")
session.updateProperty(htmlID, "data-selection", "cell")
@ -634,8 +621,7 @@ func (table *tableViewData) propertyChanged(tag string) {
session.updateProperty(htmlID, "onkeydown", "tableViewCellKeyDownEvent(this, event)")
case RowSelection:
tabIndex, _ := intProperty(table, TabIndex, session, 0)
session.updateProperty(htmlID, "tabindex", tabIndex)
session.updateProperty(htmlID, "tabindex", "0")
session.updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)")
session.updateProperty(htmlID, "onblur", "tableViewBlurEvent(this, event)")
session.updateProperty(htmlID, "data-selection", "row")
@ -650,11 +636,7 @@ func (table *tableViewData) propertyChanged(tag string) {
session.updateProperty(htmlID, "onkeydown", "tableViewRowKeyDownEvent(this, event)")
default: // NoneSelection
if tabIndex, ok := intProperty(table, TabIndex, session, -1); !ok || tabIndex < 0 {
session.removeProperty(htmlID, "tabindex")
}
for _, prop := range []string{"data-current", "onfocus", "onblur", "onkeydown", "data-selection"} {
for _, prop := range []string{"tabindex", "data-current", "onfocus", "onblur", "onkeydown", "data-selection"} {
session.removeProperty(htmlID, prop)
}
}
@ -849,7 +831,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
var view tableCellView
view.init(session)
ignoreCells := []struct{ row, column int }{}
ignorCells := []struct{ row, column int }{}
selectionMode := GetTableSelectionMode(table)
var allowCellSelection TableAllowCellSelection = nil
@ -881,7 +863,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
vAlign := vAlignCss[vAlignValue]
tableCSS := func(startRow, endRow int, cellTag string, cellBorder BorderProperty, cellPadding BoundsProperty) {
//var namedColors []NamedColor = nil
var namedColors []NamedColor = nil
for row := startRow; row < endRow; row++ {
cssBuilder.buffer.Reset()
@ -926,7 +908,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
for column := 0; column < columnCount; column++ {
ignore := false
for _, cell := range ignoreCells {
for _, cell := range ignorCells {
if cell.row == row && cell.column == column {
ignore = true
break
@ -1012,7 +994,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
buffer.WriteString(strconv.Itoa(columnSpan))
buffer.WriteRune('"')
for c := column + 1; c < column+columnSpan; c++ {
ignoreCells = append(ignoreCells, struct {
ignorCells = append(ignorCells, struct {
row int
column int
}{row: row, column: c})
@ -1028,7 +1010,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
}
for r := row + 1; r < row+rowSpan; r++ {
for c := column; c < column+columnSpan; c++ {
ignoreCells = append(ignoreCells, struct {
ignorCells = append(ignorCells, struct {
row int
column int
}{row: r, column: c})
@ -1043,8 +1025,6 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
}
buffer.WriteRune('>')
table.writeCellHtml(adapter, row, column, buffer)
/*
switch value := adapter.Cell(row, column).(type) {
case string:
buffer.WriteString(value)
@ -1096,7 +1076,6 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
buffer.WriteString("<Unsupported value>")
}
}
*/
buffer.WriteString(`</`)
buffer.WriteString(cellTag)
@ -1135,8 +1114,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
footHeight := GetTableFootHeight(table)
cellBorder := table.getCellBorder()
cellPadding := table.boundsProperty(CellPadding)
if cellPadding == nil || len(cellPadding.AllTags()) == 0 {
cellPadding = nil
if cellPadding == nil {
if style, ok := stringProperty(table, Style, table.Session()); ok {
if style, ok := table.Session().resolveConstants(style); ok {
cellPadding = table.cellPaddingFromStyle(style)
@ -1312,59 +1290,6 @@ func (table *tableViewData) cellPaddingFromStyle(style string) BoundsProperty {
return nil
}
func (table *tableViewData) writeCellHtml(adapter TableAdapter, row, column int, buffer *strings.Builder) {
switch value := adapter.Cell(row, column).(type) {
case string:
buffer.WriteString(value)
case View:
viewHTML(value, buffer)
table.cellViews = append(table.cellViews, value)
case Color:
buffer.WriteString(`<div style="display: inline; height: 1em; background-color: `)
buffer.WriteString(value.cssString())
buffer.WriteString(`">&nbsp;&nbsp;&nbsp;&nbsp;</div> `)
buffer.WriteString(value.String())
namedColors := NamedColors()
for _, namedColor := range namedColors {
if namedColor.Color == value {
buffer.WriteString(" (")
buffer.WriteString(namedColor.Name)
buffer.WriteRune(')')
break
}
}
case fmt.Stringer:
buffer.WriteString(value.String())
case rune:
buffer.WriteString(string(value))
case float32:
buffer.WriteString(fmt.Sprintf("%g", float64(value)))
case float64:
buffer.WriteString(fmt.Sprintf("%g", value))
case bool:
if value {
buffer.WriteString(table.Session().checkboxOnImage())
} else {
buffer.WriteString(table.Session().checkboxOffImage())
}
default:
if n, ok := isInt(value); ok {
buffer.WriteString(fmt.Sprintf("%d", n))
} else {
buffer.WriteString("<Unsupported value>")
}
}
}
func (table *tableViewData) cellBorderFromStyle(style string) BorderProperty {
if value := table.Session().styleProperty(style, CellBorder); value != nil {
if border, ok := value.(BorderProperty); ok {
@ -1452,19 +1377,6 @@ func (table *tableViewData) CellFrame(row, column int) Frame {
return Frame{}
}
func (table *tableViewData) ReloadCell(row, column int) {
adapter := table.content()
if adapter == nil {
return
}
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
table.writeCellHtml(adapter, row, column, buffer)
table.session.updateInnerHTML(table.cellID(row, column), buffer.String())
}
func (table *tableViewData) Views() []View {
return table.cellViews
}

View File

@ -94,7 +94,7 @@ func GetTableSelectionMode(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, SelectionMode, NoneSelection, false)
}
// GetTableVerticalAlign returns a vertical align in a TableView cell. Returns one of next values:
// 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 not specified or it is "" then a value from the first argument (view) is returned.
func GetTableVerticalAlign(view View, subviewID ...string) int {
@ -182,7 +182,6 @@ func GetTableRowSelectedListeners(view View, subviewID ...string) []func(TableVi
}
// ReloadTableViewData updates TableView
// If the second argument (subviewID) is not specified or it is "" then updates the first argument (TableView).
func ReloadTableViewData(view View, subviewID ...string) bool {
var tableView TableView
if len(subviewID) > 0 && subviewID[0] != "" {
@ -199,22 +198,3 @@ func ReloadTableViewData(view View, subviewID ...string) bool {
tableView.ReloadTableData()
return true
}
// ReloadTableViewCell updates the given table cell.
// If the last argument (subviewID) is not specified or it is "" then updates the cell of the first argument (TableView).
func ReloadTableViewCell(row, column int, view View, subviewID ...string) bool {
var tableView TableView
if len(subviewID) > 0 && subviewID[0] != "" {
if tableView = TableViewByID(view, subviewID[0]); tableView == nil {
return false
}
} else {
var ok bool
if tableView, ok = view.(TableView); !ok {
return false
}
}
tableView.ReloadCell(row, column)
return true
}

View File

@ -1,6 +1,7 @@
package rui
import (
"fmt"
"strconv"
"strings"
)
@ -566,22 +567,7 @@ func (tabsLayout *tabsLayoutData) IsListItemEnabled(index int) bool {
return true
}
func (tabsLayout *tabsLayoutData) updateTitle(view View, tag string) {
session := tabsLayout.session
title, _ := stringProperty(view, Title, session)
if !GetNotTranslate(tabsLayout) {
title, _ = session.GetString(title)
}
session.updateInnerHTML(view.htmlID()+"-title", title)
}
func (tabsLayout *tabsLayoutData) updateIcon(view View, tag string) {
session := tabsLayout.session
icon, _ := stringProperty(view, Icon, session)
session.updateProperty(view.htmlID()+"-icon", "src", icon)
}
func (tabsLayout *tabsLayoutData) updateTabCloseButton(view View, tag string) {
func (tabsLayout *tabsLayoutData) updateContent(view View, tag string) {
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
}
@ -592,9 +578,9 @@ func (tabsLayout *tabsLayoutData) Append(view View) {
}
if view != nil {
tabsLayout.viewsContainerData.Append(view)
view.SetChangeListener(Title, tabsLayout.updateTitle)
view.SetChangeListener(Icon, tabsLayout.updateIcon)
view.SetChangeListener(TabCloseButton, tabsLayout.updateTabCloseButton)
view.SetChangeListener(Title, tabsLayout.updateContent)
view.SetChangeListener(Icon, tabsLayout.updateContent)
view.SetChangeListener(TabCloseButton, tabsLayout.updateContent)
if len(tabsLayout.views) == 1 {
tabsLayout.properties[Current] = 0
for _, listener := range tabsLayout.tabListener {
@ -616,9 +602,9 @@ func (tabsLayout *tabsLayoutData) Insert(view View, index int) {
defer tabsLayout.propertyChangedEvent(Current)
}
tabsLayout.viewsContainerData.Insert(view, index)
view.SetChangeListener(Title, tabsLayout.updateTitle)
view.SetChangeListener(Icon, tabsLayout.updateIcon)
view.SetChangeListener(TabCloseButton, tabsLayout.updateTabCloseButton)
view.SetChangeListener(Title, tabsLayout.updateContent)
view.SetChangeListener(Icon, tabsLayout.updateContent)
view.SetChangeListener(TabCloseButton, tabsLayout.updateContent)
}
}
@ -664,6 +650,10 @@ func (tabsLayout *tabsLayoutData) RemoveView(index int) View {
return view
}
func (tabsLayout *tabsLayoutData) currentID() string {
return fmt.Sprintf("%s-%d", tabsLayout.htmlID(), tabsLayout.currentItem(0))
}
func (tabsLayout *tabsLayoutData) htmlProperties(self View, buffer *strings.Builder) {
tabsLayout.viewsContainerData.htmlProperties(self, buffer)
buffer.WriteString(` data-inactiveTabStyle="`)
@ -671,7 +661,7 @@ func (tabsLayout *tabsLayoutData) htmlProperties(self View, buffer *strings.Buil
buffer.WriteString(`" data-activeTabStyle="`)
buffer.WriteString(tabsLayout.activeTabStyle())
buffer.WriteString(`" data-current="`)
buffer.WriteString(strconv.Itoa(tabsLayout.currentItem(0)))
buffer.WriteString(tabsLayout.currentID())
buffer.WriteRune('"')
}
@ -736,7 +726,7 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
notTranslate := GetNotTranslate(tabsLayout)
closeButton, _ := boolProperty(tabsLayout, TabCloseButton, tabsLayout.session)
var tabStyle, titleStyle string
var tabStyle, titleDiv string
switch location {
case LeftTabs, RightTabs:
tabStyle = `display: grid; grid-template-rows: auto 1fr auto; align-items: center; justify-items: center; grid-row-gap: 8px;`
@ -750,13 +740,13 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
switch location {
case LeftTabs:
titleStyle = ` style="writing-mode: vertical-lr; transform: rotate(180deg); grid-row-start: 2; grid-row-end: 3; grid-column-start: 1; grid-column-end: 2;">`
titleDiv = `<div style="writing-mode: vertical-lr; transform: rotate(180deg); grid-row-start: 2; grid-row-end: 3; grid-column-start: 1; grid-column-end: 2;">`
case RightTabs:
titleStyle = ` style="writing-mode: vertical-lr; grid-row-start: 2; grid-row-end: 3; grid-column-start: 1; grid-column-end: 2;">`
titleDiv = `<div style="writing-mode: vertical-lr; grid-row-start: 2; grid-row-end: 3; grid-column-start: 1; grid-column-end: 2;">`
default:
titleStyle = ` style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 2; grid-column-end: 3;">`
titleDiv = `<div style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 2; grid-column-end: 3;">`
}
for n, view := range tabsLayout.views {
@ -795,26 +785,21 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
buffer.WriteString(`">`)
if icon != "" {
buffer.WriteString(`<img id="`)
buffer.WriteString(view.htmlID())
switch location {
case LeftTabs:
buffer.WriteString(`-icon" style="grid-row-start: 3; grid-row-end: 4; grid-column-start: 1; grid-column-end: 2;" src="`)
buffer.WriteString(`<img style="grid-row-start: 3; grid-row-end: 4; grid-column-start: 1; grid-column-end: 2;" src="`)
case RightTabs:
buffer.WriteString(`-icon" style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
buffer.WriteString(`<img style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
default:
buffer.WriteString(`-icon" style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
buffer.WriteString(`<img style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
}
buffer.WriteString(icon)
buffer.WriteString(`">`)
}
buffer.WriteString(`<div id="`)
buffer.WriteString(view.htmlID())
buffer.WriteString(`-title"`)
buffer.WriteString(titleStyle)
buffer.WriteString(titleDiv)
buffer.WriteString(title)
buffer.WriteString(`</div>`)

305
theme.go
View File

@ -13,16 +13,10 @@ const (
LandscapeMedia = 2
)
type MediaStyleParams struct {
Orientation int
MinWidth int
MaxWidth int
MinHeight int
MaxHeight int
}
type mediaStyle struct {
MediaStyleParams
orientation int
maxWidth int
maxHeight int
styles map[string]ViewStyle
}
@ -55,13 +49,12 @@ type Theme interface {
ImageConstantTags() []string
Style(tag string) ViewStyle
SetStyle(tag string, style ViewStyle)
RemoveStyle(tag string)
MediaStyle(tag string, params MediaStyleParams) ViewStyle
SetMediaStyle(tag string, params MediaStyleParams, style ViewStyle)
MediaStyle(tag string, orientation, maxWidth, maxHeight int) ViewStyle
SetMediaStyle(tag string, orientation, maxWidth, maxHeight int, style ViewStyle)
StyleTags() []string
MediaStyles(tag string) []struct {
Selectors string
Params MediaStyleParams
Orientation, MaxWidth, MaxHeight int
}
Append(anotherTheme Theme)
@ -77,7 +70,7 @@ func (rule mediaStyle) cssText() string {
builder := allocStringBuilder()
defer freeStringBuilder(builder)
switch rule.Orientation {
switch rule.orientation {
case PortraitMedia:
builder.WriteString(" and (orientation: portrait)")
@ -85,27 +78,26 @@ func (rule mediaStyle) cssText() string {
builder.WriteString(" and (orientation: landscape)")
}
writeSize := func(tag string, minSize, maxSize int) {
if minSize != maxSize {
if minSize > 0 {
builder.WriteString(fmt.Sprintf(" and (min-%s: %d.001px)", tag, minSize))
}
if maxSize > 0 {
builder.WriteString(fmt.Sprintf(" and (max-%s: %dpx)", tag, maxSize))
}
} else if minSize > 0 {
builder.WriteString(fmt.Sprintf(" and (%s: %dpx)", tag, minSize))
}
if rule.maxWidth > 0 {
builder.WriteString(" and (max-width: ")
builder.WriteString(strconv.Itoa(rule.maxWidth))
builder.WriteString("px)")
}
writeSize("width", rule.MinWidth, rule.MaxWidth)
writeSize("height", rule.MinHeight, rule.MaxHeight)
if rule.maxHeight > 0 {
builder.WriteString(" and (max-height: ")
builder.WriteString(strconv.Itoa(rule.maxHeight))
builder.WriteString("px)")
}
return builder.String()
}
func parseMediaRule(text string) (mediaStyle, bool) {
rule := mediaStyle{
orientation: DefaultMedia,
maxWidth: 0,
maxHeight: 0,
styles: map[string]ViewStyle{},
}
@ -113,80 +105,52 @@ func parseMediaRule(text string) (mediaStyle, bool) {
for i := 1; i < len(elements); i++ {
switch element := elements[i]; element {
case "portrait":
if rule.Orientation != DefaultMedia {
if rule.orientation != DefaultMedia {
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
return rule, false
}
rule.Orientation = PortraitMedia
rule.orientation = PortraitMedia
case "landscape":
if rule.Orientation != DefaultMedia {
if rule.orientation != DefaultMedia {
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
return rule, false
}
rule.Orientation = LandscapeMedia
rule.orientation = LandscapeMedia
default:
elementSize := func(name string) (int, int, bool, error) {
elementSize := func(name string) (int, bool) {
if strings.HasPrefix(element, name) {
var err error = nil
min := 0
max := 0
data := element[len(name):]
if pos := strings.Index(data, "-"); pos >= 0 {
if pos > 0 {
min, err = strconv.Atoi(data[:pos])
size, err := strconv.Atoi(element[len(name):])
if err == nil && size > 0 {
return size, true
}
if err == nil && pos+1 < len(data) {
max, err = strconv.Atoi(data[pos+1:])
}
} else {
max, err = strconv.Atoi(data)
}
return min, max, true, err
}
return 0, 0, false, nil
}
if min, max, ok, err := elementSize("width"); ok {
if err != nil {
ErrorLogF(`Invalid style section name "%s": %s`, text, err.Error())
return 0, false
}
return 0, true
}
if size, ok := elementSize("width"); !ok || size > 0 {
if !ok {
return rule, false
}
if rule.MinWidth != 0 || rule.MaxWidth != 0 {
if rule.maxWidth != 0 {
ErrorLog(`Duplicate "width" tag in the style section "` + text + `"`)
return rule, false
}
if min == 0 && max == 0 {
ErrorLog(`Invalid arguments of "width" tag in the style section "` + text + `"`)
rule.maxWidth = size
} else if size, ok := elementSize("height"); !ok || size > 0 {
if !ok {
return rule, false
}
rule.MinWidth = min
rule.MaxWidth = max
} else if min, max, ok, err := elementSize("height"); ok {
if err != nil {
ErrorLogF(`Invalid style section name "%s": %s`, text, err.Error())
return rule, false
}
if rule.MinHeight != 0 || rule.MaxHeight != 0 {
if rule.maxHeight != 0 {
ErrorLog(`Duplicate "height" tag in the style section "` + text + `"`)
return rule, false
}
if min == 0 && max == 0 {
ErrorLog(`Invalid arguments of "height" tag in the style section "` + text + `"`)
return rule, false
}
rule.MinHeight = min
rule.MaxHeight = max
rule.maxHeight = size
} else {
ErrorLogF(`Unknown element "%s" in the style section name "%s"`, element, text)
ErrorLogF(`Unknown elemnet "%s" in the style section name "%s"`, element, text)
return rule, false
}
}
@ -300,72 +264,36 @@ func (theme *theme) SetStyle(tag string, style ViewStyle) {
}
}
func (theme *theme) RemoveStyle(tag string) {
tag2 := tag + ":"
remove := func(styles map[string]ViewStyle) {
tags := []string{tag}
for t := range styles {
if strings.HasPrefix(t, tag2) {
tags = append(tags, t)
}
}
for _, t := range tags {
delete(styles, t)
}
}
remove(theme.styles)
for _, mediaStyle := range theme.mediaStyles {
remove(mediaStyle.styles)
}
}
func (theme *theme) MediaStyle(tag string, params MediaStyleParams) ViewStyle {
func (theme *theme) MediaStyle(tag string, orientation, maxWidth, maxHeight int) ViewStyle {
for _, styles := range theme.mediaStyles {
if styles.Orientation == params.Orientation &&
styles.MaxWidth == params.MaxWidth &&
styles.MinWidth == params.MinWidth &&
styles.MaxHeight == params.MaxHeight &&
styles.MinHeight == params.MinHeight {
if styles.orientation == orientation && styles.maxWidth == maxWidth && styles.maxHeight == maxHeight {
if style, ok := styles.styles[tag]; ok {
return style
}
}
}
if params.Orientation == 0 && params.MaxWidth == 0 && params.MinWidth == 0 &&
params.MaxHeight == 0 && params.MinHeight == 0 {
if orientation == 0 && maxWidth <= 0 && maxHeight <= 0 {
return theme.style(tag)
}
return nil
}
func (theme *theme) SetMediaStyle(tag string, params MediaStyleParams, style ViewStyle) {
if params.MaxWidth < 0 {
params.MaxWidth = 0
func (theme *theme) SetMediaStyle(tag string, orientation, maxWidth, maxHeight int, style ViewStyle) {
if maxWidth < 0 {
maxWidth = 0
}
if params.MinWidth < 0 {
params.MinWidth = 0
}
if params.MaxHeight < 0 {
params.MaxHeight = 0
}
if params.MinHeight < 0 {
params.MinHeight = 0
if maxHeight < 0 {
maxHeight = 0
}
if params.Orientation == 0 && params.MaxWidth == 0 && params.MinWidth == 0 &&
params.MaxHeight == 0 && params.MinHeight == 0 {
if orientation == DefaultMedia && maxWidth == 0 && maxHeight == 0 {
theme.SetStyle(tag, style)
return
}
for i, styles := range theme.mediaStyles {
if styles.Orientation == params.Orientation &&
styles.MaxWidth == params.MaxWidth &&
styles.MinWidth == params.MinWidth &&
styles.MaxHeight == params.MaxHeight &&
styles.MinHeight == params.MinHeight {
if styles.orientation == orientation && styles.maxWidth == maxWidth && styles.maxHeight == maxHeight {
if style != nil {
theme.mediaStyles[i].styles[tag] = style
} else {
@ -377,7 +305,9 @@ func (theme *theme) SetMediaStyle(tag string, params MediaStyleParams, style Vie
if style != nil {
theme.mediaStyles = append(theme.mediaStyles, mediaStyle{
MediaStyleParams: params,
orientation: orientation,
maxWidth: maxWidth,
maxHeight: maxHeight,
styles: map[string]ViewStyle{tag: style},
})
theme.sortMediaStyles()
@ -477,11 +407,11 @@ func (theme *theme) StyleTags() []string {
func (theme *theme) MediaStyles(tag string) []struct {
Selectors string
Params MediaStyleParams
Orientation, MaxWidth, MaxHeight int
} {
result := []struct {
Selectors string
Params MediaStyleParams
Orientation, MaxWidth, MaxHeight int
}{}
prefix := tag + ":"
@ -490,10 +420,12 @@ func (theme *theme) MediaStyles(tag string) []struct {
if strings.HasPrefix(themeTag, prefix) {
result = append(result, struct {
Selectors string
Params MediaStyleParams
Orientation, MaxWidth, MaxHeight int
}{
Selectors: themeTag[prefixLen:],
Params: MediaStyleParams{},
Orientation: DefaultMedia,
MaxWidth: 0,
MaxHeight: 0,
})
}
}
@ -502,20 +434,24 @@ func (theme *theme) MediaStyles(tag string) []struct {
if _, ok := media.styles[tag]; ok {
result = append(result, struct {
Selectors string
Params MediaStyleParams
Orientation, MaxWidth, MaxHeight int
}{
Selectors: "",
Params: media.MediaStyleParams,
Orientation: media.orientation,
MaxWidth: media.maxWidth,
MaxHeight: media.maxHeight,
})
}
for themeTag := range media.styles {
if strings.HasPrefix(themeTag, prefix) {
result = append(result, struct {
Selectors string
Params MediaStyleParams
Orientation, MaxWidth, MaxHeight int
}{
Selectors: themeTag[prefixLen:],
Params: media.MediaStyleParams,
Orientation: media.orientation,
MaxWidth: media.maxWidth,
MaxHeight: media.maxHeight,
})
}
}
@ -564,11 +500,9 @@ func (theme *theme) Append(anotherTheme Theme) {
for _, anotherMedia := range another.mediaStyles {
exists := false
for _, media := range theme.mediaStyles {
if anotherMedia.MinHeight == media.MinHeight &&
anotherMedia.MaxHeight == media.MaxHeight &&
anotherMedia.MinWidth == media.MinWidth &&
anotherMedia.MaxWidth == media.MaxWidth &&
anotherMedia.Orientation == media.Orientation {
if anotherMedia.maxHeight == media.maxHeight &&
anotherMedia.maxWidth == media.maxWidth &&
anotherMedia.orientation == media.orientation {
for tag, style := range anotherMedia.styles {
media.styles[tag] = style
}
@ -591,38 +525,19 @@ func (theme *theme) cssText(session Session) string {
var builder cssStyleBuilder
builder.init()
styleList := func(styles map[string]ViewStyle) []string {
ruiStyles := []string{}
customStyles := []string{}
for tag := range styles {
if strings.HasPrefix(tag, "rui") {
ruiStyles = append(ruiStyles, tag)
} else {
customStyles = append(customStyles, tag)
}
}
sort.Strings(ruiStyles)
sort.Strings(customStyles)
return append(ruiStyles, customStyles...)
}
for _, tag := range styleList(theme.styles) {
if style := theme.styles[tag]; style != nil {
for tag, style := range theme.styles {
builder.startStyle(tag)
style.cssViewStyle(&builder, session)
builder.endStyle()
}
}
for _, media := range theme.mediaStyles {
builder.startMedia(media.cssText())
for _, tag := range styleList(media.styles) {
if style := media.styles[tag]; style != nil {
for tag, style := range media.styles {
builder.startStyle(tag)
style.cssViewStyle(&builder, session)
builder.endStyle()
}
}
builder.endMedia()
}
@ -772,19 +687,13 @@ func (theme *theme) addText(themeText string) bool {
func (theme *theme) sortMediaStyles() {
if len(theme.mediaStyles) > 1 {
sort.SliceStable(theme.mediaStyles, func(i, j int) bool {
if theme.mediaStyles[i].Orientation != theme.mediaStyles[j].Orientation {
return theme.mediaStyles[i].Orientation < theme.mediaStyles[j].Orientation
if theme.mediaStyles[i].orientation != theme.mediaStyles[j].orientation {
return theme.mediaStyles[i].orientation < theme.mediaStyles[j].orientation
}
if theme.mediaStyles[i].MinWidth != theme.mediaStyles[j].MinWidth {
return theme.mediaStyles[i].MinWidth < theme.mediaStyles[j].MinWidth
if theme.mediaStyles[i].maxWidth != theme.mediaStyles[j].maxWidth {
return theme.mediaStyles[i].maxWidth < theme.mediaStyles[j].maxWidth
}
if theme.mediaStyles[i].MinHeight != theme.mediaStyles[j].MinHeight {
return theme.mediaStyles[i].MinHeight < theme.mediaStyles[j].MinHeight
}
if theme.mediaStyles[i].MaxWidth != theme.mediaStyles[j].MaxWidth {
return theme.mediaStyles[i].MaxWidth < theme.mediaStyles[j].MaxWidth
}
return theme.mediaStyles[i].MaxHeight < theme.mediaStyles[j].MaxHeight
return theme.mediaStyles[i].maxHeight < theme.mediaStyles[j].maxHeight
})
}
}
@ -902,7 +811,7 @@ func (theme *theme) String() string {
writeConstants("constants", theme.constants)
writeConstants("constants:touch", theme.touchConstants)
writeStyles := func(orientation, maxWidth, maxHeight int, styles map[string]ViewStyle) bool {
writeStyles := func(orientation, maxWidth, maxHeihgt int, styles map[string]ViewStyle) bool {
count := len(styles)
if count == 0 {
return false
@ -925,16 +834,16 @@ func (theme *theme) String() string {
if maxWidth > 0 {
buffer.WriteString(fmt.Sprintf(":width%d", maxWidth))
}
if maxHeight > 0 {
buffer.WriteString(fmt.Sprintf(":height%d", maxHeight))
if maxHeihgt > 0 {
buffer.WriteString(fmt.Sprintf(":heihgt%d", maxHeihgt))
}
buffer.WriteString(" = [\n")
for _, tag := range tags {
if style, ok := styles[tag]; ok && len(style.AllTags()) > 0 {
if style, ok := styles[tag]; ok {
buffer.WriteString("\t\t")
writeViewStyle(tag, style, buffer, "\t\t")
buffer.WriteString(",\n")
buffer.WriteString(",")
}
}
buffer.WriteString("\t],\n")
@ -943,53 +852,7 @@ func (theme *theme) String() string {
writeStyles(0, 0, 0, theme.styles)
for _, media := range theme.mediaStyles {
//writeStyles(media.orientation, media.maxWidth, media.maxHeight, media.styles)
if count := len(media.styles); count > 0 {
tags := make([]string, 0, count)
for name := range media.styles {
tags = append(tags, name)
}
sort.Strings(tags)
buffer.WriteString("\tstyles")
switch media.Orientation {
case PortraitMedia:
buffer.WriteString(":portrait")
case LandscapeMedia:
buffer.WriteString(":landscape")
}
if media.MinWidth > 0 {
buffer.WriteString(fmt.Sprintf(":width%d-", media.MinWidth))
if media.MaxWidth > 0 {
buffer.WriteString(strconv.Itoa(media.MaxWidth))
}
} else if media.MaxWidth > 0 {
buffer.WriteString(fmt.Sprintf(":width%d", media.MaxWidth))
}
if media.MinHeight > 0 {
buffer.WriteString(fmt.Sprintf(":height%d-", media.MinHeight))
if media.MaxHeight > 0 {
buffer.WriteString(strconv.Itoa(media.MaxHeight))
}
} else if media.MaxHeight > 0 {
buffer.WriteString(fmt.Sprintf(":height%d", media.MaxHeight))
}
buffer.WriteString(" = [\n")
for _, tag := range tags {
if style, ok := media.styles[tag]; ok && len(style.AllTags()) > 0 {
buffer.WriteString("\t\t")
writeViewStyle(tag, style, buffer, "\t\t")
buffer.WriteString(",\n")
}
}
buffer.WriteString("\t],\n")
}
writeStyles(media.orientation, media.maxWidth, media.maxHeight, media.styles)
}
buffer.WriteString("}\n")

View File

@ -22,7 +22,7 @@ type TimePicker interface {
type timePickerData struct {
viewData
timeChangedListeners []func(TimePicker, time.Time, time.Time)
timeChangedListeners []func(TimePicker, time.Time)
}
// NewTimePicker create new TimePicker object and return it
@ -40,7 +40,7 @@ func newTimePicker(session Session) View {
func (picker *timePickerData) init(session Session) {
picker.viewData.init(session)
picker.tag = "TimePicker"
picker.timeChangedListeners = []func(TimePicker, time.Time, time.Time){}
picker.timeChangedListeners = []func(TimePicker, time.Time){}
}
func (picker *timePickerData) String() string {
@ -69,7 +69,7 @@ func (picker *timePickerData) remove(tag string) {
switch tag {
case TimeChangedEvent:
if len(picker.timeChangedListeners) > 0 {
picker.timeChangedListeners = []func(TimePicker, time.Time, time.Time){}
picker.timeChangedListeners = []func(TimePicker, time.Time){}
picker.propertyChangedEvent(tag)
}
return
@ -94,14 +94,13 @@ func (picker *timePickerData) remove(tag string) {
case TimePickerValue:
if _, ok := picker.properties[TimePickerValue]; ok {
oldTime := GetTimePickerValue(picker)
delete(picker.properties, TimePickerValue)
time := GetTimePickerValue(picker)
if picker.created {
picker.session.callFunc("setInputValue", picker.htmlID(), time.Format(timeFormat))
}
for _, listener := range picker.timeChangedListeners {
listener(picker, time, oldTime)
listener(picker, time)
}
} else {
return
@ -215,7 +214,7 @@ func (picker *timePickerData) set(tag string, value any) bool {
picker.session.callFunc("setInputValue", picker.htmlID(), time.Format(timeFormat))
}
for _, listener := range picker.timeChangedListeners {
listener(picker, time, oldTime)
listener(picker, time)
}
picker.propertyChangedEvent(tag)
}
@ -223,12 +222,12 @@ func (picker *timePickerData) set(tag string, value any) bool {
}
case TimeChangedEvent:
listeners, ok := valueToEventWithOldListeners[TimePicker, time.Time](value)
listeners, ok := valueToEventListeners[TimePicker, time.Time](value)
if !ok {
notCompatibleType(tag, value)
return false
} else if listeners == nil {
listeners = []func(TimePicker, time.Time, time.Time){}
listeners = []func(TimePicker, time.Time){}
}
picker.timeChangedListeners = listeners
picker.propertyChangedEvent(tag)
@ -307,7 +306,7 @@ func (picker *timePickerData) handleCommand(self View, command string, data Data
picker.properties[TimePickerValue] = value
if value != oldValue {
for _, listener := range picker.timeChangedListeners {
listener(picker, value, oldValue)
listener(picker, value)
}
}
}
@ -399,6 +398,6 @@ func GetTimePickerValue(view View, subviewID ...string) time.Time {
// GetTimeChangedListeners returns the TimeChangedListener list of an TimePicker subview.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time, time.Time) {
return getEventWithOldListeners[TimePicker, time.Time](view, subviewID, TimeChangedEvent)
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time) {
return getEventListeners[TimePicker, time.Time](view, subviewID, TimeChangedEvent)
}

View File

@ -130,10 +130,7 @@ func touchEventsHtml(view View, buffer *strings.Builder) {
for tag, js := range touchEvents {
if value := view.getRaw(tag); value != nil {
if listeners, ok := value.([]func(View, TouchEvent)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
}
}
}
@ -143,7 +140,7 @@ func (event *TouchEvent) init(data DataObject) {
event.Touches = []Touch{}
event.TimeStamp = getTimeStamp(data)
if node := data.PropertyByTag("touches"); node != nil && node.Type() == ArrayNode {
if node := data.PropertyWithTag("touches"); node != nil && node.Type() == ArrayNode {
for i := 0; i < node.ArraySize(); i++ {
if element := node.ArrayElement(i); element != nil && element.IsObject() {
if obj := element.Object(); obj != nil {

View File

@ -1,9 +1,7 @@
package rui
import (
"encoding/base64"
"net"
"path/filepath"
"strconv"
"strings"
)
@ -78,31 +76,3 @@ func dataFloatProperty(data DataObject, tag string) float64 {
}
return 0
}
// InlineImageFromResource reads image from resources and converts it to an inline image.
// Supported png, jpeg, gif, and svg files
func InlineImageFromResource(filename string) (string, bool) {
if image, ok := resources.images[filename]; ok && image.fs != nil {
dataType := map[string]string{
".svg": "data:image/svg+xml",
".png": "data:image/png",
".jpg": "data:image/jpg",
".jpeg": "data:image/jpg",
".gif": "data:image/gif",
}
ext := strings.ToLower(filepath.Ext(filename))
if prefix, ok := dataType[ext]; ok {
if data, err := image.fs.ReadFile(image.path); err == nil {
return prefix + ";base64," + base64.StdEncoding.EncodeToString(data), true
} else {
DebugLog(err.Error())
}
} else {
DebugLogF(`InlineImageFromResource("%s") error: Unsupported file`, filename)
}
} else {
DebugLogF(`The resource image "%s" not found`, filename)
}
return "", false
}

73
view.go
View File

@ -45,7 +45,7 @@ type View interface {
Focusable() bool
// Frame returns the location and size of the view in pixels
Frame() Frame
// Scroll returns the location size of the scrollable view in pixels
// Scroll returns the location size of the scrolable view in pixels
Scroll() Frame
// SetAnimated sets the value (second argument) of the property with name defined by the first argument.
// Return "true" if the value has been set, in the opposite case "false" are returned and
@ -175,17 +175,6 @@ func (view *viewData) Focusable() bool {
if focus, ok := boolProperty(view, Focusable, view.session); ok {
return focus
}
if style, ok := stringProperty(view, Style, view.session); ok {
if style, ok := view.session.resolveConstants(style); ok {
if value := view.session.styleProperty(style, Focusable); ok {
if focus, ok := valueToBool(value, view.Session()); ok {
return focus
}
}
}
}
return false
}
@ -198,14 +187,6 @@ func (view *viewData) remove(tag string) {
case ID:
view.viewID = ""
case TabIndex, "tab-index":
delete(view.properties, tag)
if view.Focusable() {
view.session.updateProperty(view.htmlID(), "tabindex", "0")
} else {
view.session.updateProperty(view.htmlID(), "tabindex", "-1")
}
case UserData:
delete(view.properties, tag)
@ -331,18 +312,6 @@ func (view *viewData) set(tag string, value any) bool {
}
view.viewID = text
case TabIndex, "tab-index":
if !view.setIntProperty(tag, value) {
return false
}
if value, ok := intProperty(view, TabIndex, view.Session(), 0); ok {
view.session.updateProperty(view.htmlID(), "tabindex", strconv.Itoa(value))
} else if view.Focusable() {
view.session.updateProperty(view.htmlID(), "tabindex", "0")
} else {
view.session.updateProperty(view.htmlID(), "tabindex", "-1")
}
case UserData:
view.properties[tag] = value
@ -412,12 +381,10 @@ func viewPropertyChanged(view *viewData, tag string) {
case Invisible:
session.updateCSSProperty(htmlID, Visibility, "hidden")
session.updateCSSProperty(htmlID, "display", "")
session.callFunc("hideTooltip")
case Gone:
session.updateCSSProperty(htmlID, Visibility, "hidden")
session.updateCSSProperty(htmlID, "display", "none")
session.callFunc("hideTooltip")
default:
session.updateCSSProperty(htmlID, Visibility, "visible")
@ -610,7 +577,7 @@ func viewPropertyChanged(view *viewData, tag string) {
}
return
case ZIndex, Order, TabSize:
case ZIndex, TabSize:
if i, ok := intProperty(view, tag, session, 0); ok {
session.updateCSSProperty(htmlID, tag, strconv.Itoa(i))
}
@ -639,24 +606,6 @@ func viewPropertyChanged(view *viewData, tag string) {
session.updateCSSProperty(htmlID, "user-select", "")
}
return
case ColumnSpanAll:
if spanAll, ok := boolProperty(view, ColumnSpanAll, session); ok && spanAll {
session.updateCSSProperty(htmlID, `column-span`, `all`)
} else {
session.updateCSSProperty(htmlID, `column-span`, `none`)
}
return
case Tooltip:
if tooltip := GetTooltip(view); tooltip == "" {
session.removeProperty(htmlID, "data-tooltip")
} else {
session.updateProperty(htmlID, "data-tooltip", tooltip)
session.updateProperty(htmlID, "onmouseenter", "mouseEnterEvent(this, event)")
session.updateProperty(htmlID, "onmouseleave", "mouseLeaveEvent(this, event)")
}
return
}
if cssTag, ok := sizeProperties[tag]; ok {
@ -809,28 +758,14 @@ func viewHTML(view View, buffer *strings.Builder) {
buffer.WriteRune(' ')
}
if !disabled {
if value, ok := intProperty(view, TabIndex, view.Session(), -1); ok {
buffer.WriteString(`tabindex="`)
buffer.WriteString(strconv.Itoa(value))
buffer.WriteString(`" `)
} else if view.Focusable() {
if view.Focusable() && !disabled {
buffer.WriteString(`tabindex="0" `)
}
}
hasTooltip := false
if tooltip := GetTooltip(view); tooltip != "" {
buffer.WriteString(`data-tooltip=" `)
buffer.WriteString(tooltip)
buffer.WriteString(`" `)
hasTooltip = true
}
buffer.WriteString(`onscroll="scrollEvent(this, event)" `)
keyEventsHtml(view, buffer)
mouseEventsHtml(view, buffer, hasTooltip)
mouseEventsHtml(view, buffer)
pointerEventsHtml(view, buffer)
touchEventsHtml(view, buffer)
focusEventsHtml(view, buffer)

View File

@ -12,7 +12,7 @@ func ViewByID(rootView View, id string) View {
return rootView
}
if container, ok := rootView.(ParentView); ok {
if container, ok := rootView.(ParanetView); ok {
if view := viewByID(container, id); view != nil {
return view
}
@ -32,13 +32,13 @@ func ViewByID(rootView View, id string) View {
return nil
}
func viewByID(rootView ParentView, id string) View {
func viewByID(rootView ParanetView, id string) View {
for _, view := range rootView.Views() {
if view != nil {
if view.ID() == id {
return view
}
if container, ok := view.(ParentView); ok {
if container, ok := view.(ParanetView); ok {
if v := viewByID(container, id); v != nil {
return v
}

View File

@ -30,7 +30,6 @@ var viewCreators = map[string]func(Session) View{
"ListView": newListView,
"CanvasView": newCanvasView,
"ImageView": newImageView,
"SvgImageView": newSvgImageView,
"TableView": newTableView,
"AudioPlayer": newAudioPlayer,
"VideoPlayer": newVideoPlayer,

View File

@ -193,26 +193,22 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
outline.ViewOutline(session).cssValue(builder, session)
}
for _, tag := range []string{ZIndex, Order} {
if value, ok := intProperty(style, tag, session, 0); ok {
builder.add(tag, strconv.Itoa(value))
}
if z, ok := intProperty(style, ZIndex, session, 0); ok {
builder.add(ZIndex, strconv.Itoa(z))
}
if opacity, ok := floatProperty(style, Opacity, session, 1.0); ok && opacity >= 0 && opacity <= 1 {
builder.add(Opacity, strconv.FormatFloat(opacity, 'f', 3, 32))
}
for _, tag := range []string{ColumnCount, TabSize} {
if value, ok := intProperty(style, tag, session, 0); ok && value > 0 {
builder.add(tag, strconv.Itoa(value))
}
if n, ok := intProperty(style, ColumnCount, session, 0); ok && n > 0 {
builder.add(ColumnCount, strconv.Itoa(n))
}
for _, tag := range []string{
Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight, Left, Right, Top, Bottom,
TextSize, TextIndent, LetterSpacing, WordSpacing, LineHeight, TextLineThickness,
ListRowGap, ListColumnGap, GridRowGap, GridColumnGap, ColumnGap, ColumnWidth, OutlineOffset} {
ListRowGap, ListColumnGap, GridRowGap, GridColumnGap, ColumnGap, ColumnWidth} {
if size, ok := sizeProperty(style, tag, session); ok && size.Type != Auto {
cssTag, ok := sizeProperties[tag]
@ -252,7 +248,7 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
for _, tag := range []string{
Overflow, TextAlign, TextTransform, TextWeight, TextLineStyle, WritingMode, TextDirection,
VerticalTextOrientation, CellVerticalAlign, CellHorizontalAlign, GridAutoFlow, Cursor,
WhiteSpace, WordBreak, TextOverflow, Float, TableVerticalAlign, Resize, MixBlendMode, BackgroundBlendMode} {
WhiteSpace, WordBreak, TextOverflow, Float, TableVerticalAlign, Resize} {
if data, ok := enumProperties[tag]; ok {
if tag != VerticalTextOrientation || (writingMode != VerticalLeftToRight && writingMode != VerticalRightToLeft) {
@ -283,6 +279,10 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
}
}
if tabSize, ok := intProperty(style, TabSize, session, 8); ok && tabSize > 0 {
builder.add(TabSize, strconv.Itoa(tabSize))
}
if text := style.cssTextDecoration(session); text != "" {
builder.add("text-decoration", text)
}
@ -453,14 +453,6 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
builder.add(`animation-play-state`, `running`)
}
}
if spanAll, ok := boolProperty(style, ColumnSpanAll, session); ok {
if spanAll {
builder.add(`column-span`, `all`)
} else {
builder.add(`column-span`, `none`)
}
}
}
func valueToOrientation(value any, session Session) (int, bool) {

View File

@ -202,60 +202,6 @@ func (style *viewStyle) set(tag string, value any) bool {
case CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft:
return style.setBoundsSide(CellPadding, tag, value)
case HeadStyle, FootStyle:
switch value := value.(type) {
case string:
style.properties[tag] = value
return true
case Params:
style.properties[tag] = value
return true
case DataObject:
if params := value.ToParams(); len(params) > 0 {
style.properties[tag] = params
}
return true
}
case CellStyle, ColumnStyle, RowStyle:
switch value := value.(type) {
case string:
style.properties[tag] = value
return true
case Params:
style.properties[tag] = value
return true
case DataObject:
if params := value.ToParams(); len(params) > 0 {
style.properties[tag] = params
}
return true
case DataNode:
switch value.Type() {
case TextNode:
if text := value.Text(); text != "" {
style.properties[tag] = text
}
return true
case ObjectNode:
if obj := value.Object(); obj != nil {
if params := obj.ToParams(); len(params) > 0 {
style.properties[tag] = params
}
return true
}
case ArrayNode:
// TODO
}
}
case Outline:
return style.setOutline(value)
@ -392,7 +338,7 @@ func (style *viewStyle) set(tag string, value any) bool {
return true
case DataObject:
if animation := parseAnimation(value); animation.hasAnimatedProperty() {
if animation := parseAnimation(value); animation.hasAnimatedPropery() {
style.properties[tag] = []Animation{animation}
return true
}
@ -402,7 +348,7 @@ func (style *viewStyle) set(tag string, value any) bool {
result := true
for i := 0; i < value.ArraySize(); i++ {
if obj := value.ArrayElement(i).Object(); obj != nil {
if anim := parseAnimation(obj); anim.hasAnimatedProperty() {
if anim := parseAnimation(obj); anim.hasAnimatedPropery() {
animations = append(animations, anim)
} else {
result = false

View File

@ -153,37 +153,12 @@ func GetOverflow(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, Overflow, defaultOverflow, false)
}
// GetTabIndex returns the subview tab-index.
// If the second argument (subviewID) is not specified or it is "" then a tab-index of the first argument (view) is returned
func GetTabIndex(view View, subviewID ...string) int {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
}
defaultValue := -1
if view != nil {
if view.Focusable() {
defaultValue = 0
}
if value, ok := intProperty(view, TabIndex, view.Session(), defaultValue); ok {
return value
}
}
return defaultValue
}
// GetZIndex returns the subview z-order.
// If the second argument (subviewID) is not specified or it is "" then a z-order of the first argument (view) is returned
func GetZIndex(view View, subviewID ...string) int {
return intStyledProperty(view, subviewID, ZIndex, 0)
}
// GetOrder returns the subview order to layout an item in a ListLayout or GridLayout container.
// If the second argument (subviewID) is not specified or it is "" then an order of the first argument (view) is returned
func GetOrder(view View, subviewID ...string) int {
return intStyledProperty(view, subviewID, Order, 0)
}
// GetWidth returns the subview width.
// If the second argument (subviewID) is not specified or it is "" then a width of the first argument (view) is returned
func GetWidth(view View, subviewID ...string) SizeUnit {
@ -321,12 +296,6 @@ func GetOutline(view View, subviewID ...string) ViewOutline {
return ViewOutline{Style: NoneLine, Width: AutoSize(), Color: 0}
}
// GetOutlineOffset returns the subview outline offset.
// If the second argument (subviewID) is not specified or it is "" then a offset of the first argument (view) is returned
func GetOutlineOffset(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, OutlineOffset, false)
}
// GetViewShadows returns shadows of the subview.
// If the second argument (subviewID) is not specified or it is "" then shadows of the first argument (view) is returned.
func GetViewShadows(view View, subviewID ...string) []ViewShadow {
@ -830,13 +799,9 @@ func colorStyledProperty(view View, subviewID []string, tag string, inherit bool
return Color(0)
}
// FocusView sets focus on the specified subview, if it can be focused.
// FocusView sets focus on the specified View, if it can be focused.
// The focused View is the View which will receive keyboard events by default.
// If the second argument (subviewID) is not specified or it is "" then focus is set on the first argument (view)
func FocusView(view View, subviewID ...string) {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
}
func FocusView(view View) {
if view != nil {
view.Session().callFunc("focus", view.htmlID())
}
@ -925,45 +890,3 @@ func isUserSelect(view View) (bool, bool) {
return false, false
}
// GetMixBlendMode returns a "mix-blend-mode" of the subview. Returns one of next values:
//
// BlendNormal (0), BlendMultiply (1), BlendScreen (2), BlendOverlay (3), BlendDarken (4),
// BlendLighten (5), BlendColorDodge (6), BlendColorBurn (7), BlendHardLight (8),
// BlendSoftLight (9), BlendDifference (10), BlendExclusion (11), BlendHue (12),
// BlendSaturation (13), BlendColor (14), BlendLuminosity (15)
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetMixBlendMode(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, MixBlendMode, BlendNormal, true)
}
// GetBackgroundBlendMode returns a "background-blend-mode" of the subview. Returns one of next values:
//
// BlendNormal (0), BlendMultiply (1), BlendScreen (2), BlendOverlay (3), BlendDarken (4),
// BlendLighten (5), BlendColorDodge (6), BlendColorBurn (7), BlendHardLight (8),
// BlendSoftLight (9), BlendDifference (10), BlendExclusion (11), BlendHue (12),
// BlendSaturation (13), BlendColor (14), BlendLuminosity (15)
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetBackgroundBlendMode(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, BackgroundBlendMode, BlendNormal, true)
}
// GetTooltip returns a tooltip text of the subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTooltip(view View, subviewID ...string) string {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
}
if view != nil {
if value := view.Get(Tooltip); value != nil {
if text, ok := value.(string); ok {
return text
}
}
}
return ""
}

View File

@ -2,7 +2,7 @@ package rui
import "strings"
type ParentView interface {
type ParanetView interface {
// Views return a list of child views
Views() []View
}
@ -10,15 +10,13 @@ type ParentView interface {
// ViewsContainer - mutable list-container of Views
type ViewsContainer interface {
View
ParentView
ParanetView
// Append appends a view to the end of the list of a view children
Append(view View)
// Insert inserts a view to the "index" position in the list of a view children
Insert(view View, index int)
// Remove removes a view from the list of a view children and return it
RemoveView(index int) View
// ViewIndex returns the index of view, -1 overwise
ViewIndex(view View) int
}
type viewsContainerData struct {
@ -119,15 +117,6 @@ func (container *viewsContainerData) RemoveView(index int) View {
return view
}
func (container *viewsContainerData) ViewIndex(view View) int {
for index, v := range container.views {
if v == view {
return index
}
}
return -1
}
func (container *viewsContainerData) cssStyle(self View, builder cssBuilder) {
container.viewData.cssStyle(self, builder)
builder.add(`overflow`, `auto`)

View File

@ -135,7 +135,7 @@ func (bridge *wasmBridge) clearAnimation() {
styles.Set("textContent", "")
}
func (bridge *wasmBridge) canvasStart(htmlID string) {
func (bridge *wasmBridge) cavnasStart(htmlID string) {
if ProtocolInDebugLog {
DebugLog("const ctx = document.getElementById('" + htmlID + "'elementId').getContext('2d');\nctx.save();")
}
@ -204,7 +204,7 @@ func (bridge *wasmBridge) updateCanvasProperty(property string, value any) {
}
}
func (bridge *wasmBridge) canvasFinish() {
func (bridge *wasmBridge) cavnasFinish() {
if !bridge.canvas.IsNull() {
DebugLog("ctx.restore()")
bridge.canvas.Call("restore")

View File

@ -146,7 +146,7 @@ func (bridge *wsBridge) argToString(arg any) (string, bool) {
}
}
ErrorLog("Unsupported argument type")
ErrorLog("Unsupported agument type")
return "", false
}
@ -238,7 +238,7 @@ if (styles) {
}`)
}
func (bridge *wsBridge) canvasStart(htmlID string) {
func (bridge *wsBridge) cavnasStart(htmlID string) {
bridge.canvasBuffer.Reset()
bridge.canvasBuffer.WriteString(`const ctx = getCanvasContext('`)
bridge.canvasBuffer.WriteString(htmlID)
@ -328,7 +328,7 @@ func (bridge *wsBridge) callCanvasImageFunc(url string, property string, funcNam
bridge.canvasBuffer.WriteString(");\n}")
}
func (bridge *wsBridge) canvasFinish() {
func (bridge *wsBridge) cavnasFinish() {
bridge.canvasBuffer.WriteString("\n")
script := bridge.canvasBuffer.String()
if ProtocolInDebugLog {