forked from mbk-lab/rui_orig
Compare commits
50 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
d8ab76da20 | |
|
|
d4831bb68d | |
|
|
7b3caa64bd | |
|
|
08dc88ca86 | |
|
|
882e9bcce3 | |
|
|
e41e61966f | |
|
|
d7f0dd4358 | |
|
|
d2204a8627 | |
|
|
c44093e6f4 | |
|
|
600f56d8ea | |
|
|
3d44aa3ba3 | |
|
|
dc2ea14cac | |
|
|
ab421b4c32 | |
|
|
904859ff6e | |
|
|
3bf3b9c2ba | |
|
|
1cf0ee0bae | |
|
|
b15305d727 | |
|
|
7c0449775c | |
|
|
4ec3fe2ff2 | |
|
|
b6b5183f21 | |
|
|
f75435eb6c | |
|
|
b7a0aa9a6d | |
|
|
43a8b9fe58 | |
|
|
43cb889ab1 | |
|
|
dec7e723ef | |
|
|
677e0e8692 | |
|
|
a83d634f54 | |
|
|
ac8bb47677 | |
|
|
f3c9bf7f56 | |
|
|
4e7dd37f6a | |
|
|
05fa725003 | |
|
|
763de29698 | |
|
|
2a480cc6ac | |
|
|
eac3379fb1 | |
|
|
372f5971e8 | |
|
|
d1b30c56da | |
|
|
62910bf41f | |
|
|
644ec6d8c5 | |
|
|
42b34ac4df | |
|
|
b99c39f947 | |
|
|
01e2e2e00b | |
|
|
c7a7b3ed1e | |
|
|
3993dbad20 | |
|
|
3c3271663d | |
|
|
c5485c27c2 | |
|
|
60783f2f22 | |
|
|
e5842180ef | |
|
|
9fe570ec22 | |
|
|
c31b2f9d8c | |
|
|
d3002ced0e |
33
CHANGELOG.md
33
CHANGELOG.md
|
|
@ -1,6 +1,35 @@
|
||||||
# v.10.0
|
# 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
|
||||||
|
|
||||||
* The Canvas.TextWidth method replaced by Canvas.TextMetrics
|
* The Canvas.TextWidth method replaced by Canvas.TextMetrics
|
||||||
|
* Added support of WebAssembly
|
||||||
|
|
||||||
# v0.9.0
|
# v0.9.0
|
||||||
|
|
||||||
|
|
@ -72,7 +101,7 @@
|
||||||
# v0.2.0
|
# v0.2.0
|
||||||
|
|
||||||
* Added "animation" and "transition" properties, Animation interface, animation events
|
* Added "animation" and "transition" properties, Animation interface, animation events
|
||||||
* Renamed ColorPropery constant to ColorTag
|
* Renamed ColorProperty constant to ColorTag
|
||||||
* Updated readme
|
* Updated readme
|
||||||
* Added the Animation example to the demo
|
* Added the Animation example to the demo
|
||||||
* Bug fixing
|
* Bug fixing
|
||||||
|
|
|
||||||
1101
README-ru.md
1101
README-ru.md
File diff suppressed because it is too large
Load Diff
360
README.md
360
README.md
|
|
@ -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 |
|
| "style" | Style | int | Border line style |
|
||||||
| "width" | Width | SizeUnit | Border line width |
|
| "width" | Width | SizeUnit | Border line width |
|
||||||
| "color" | Color | Color | Border line color |
|
| "color" | ColorTag | Color | Border line color |
|
||||||
|
|
||||||
Example
|
Example
|
||||||
|
|
||||||
|
|
@ -769,7 +769,7 @@ equivalent to
|
||||||
view.Set(rui.Border, NewBorder(rui.Params{
|
view.Set(rui.Border, NewBorder(rui.Params{
|
||||||
rui.Style : rui.SolidBorder,
|
rui.Style : rui.SolidBorder,
|
||||||
rui.Width : rui.Px(1),
|
rui.Width : rui.Px(1),
|
||||||
rui.ColorProperty: rui.Black,
|
rui.ColorTag: rui.Black,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
The BorderProperty interface can be converted to a ViewBorders structure using the Border function.
|
The BorderProperty interface can be converted to a ViewBorders structure using the Border function.
|
||||||
|
|
@ -833,9 +833,51 @@ equivalent to
|
||||||
view.Set(rui.Border, NewBorder(rui.Params{
|
view.Set(rui.Border, NewBorder(rui.Params{
|
||||||
rui.Style : rui.SolidBorder,
|
rui.Style : rui.SolidBorder,
|
||||||
rui.Width : rui.Px(1),
|
rui.Width : rui.Px(1),
|
||||||
rui.ColorProperty: rui.Black,
|
rui.ColorTag: 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
|
### "radius" property
|
||||||
|
|
||||||
The "radius" property sets the elliptical corner radius of the View. Radii are specified by the RadiusProperty
|
The "radius" property sets the elliptical corner radius of the View. Radii are specified by the RadiusProperty
|
||||||
|
|
@ -970,7 +1012,7 @@ The shadow has the following properties:
|
||||||
|
|
||||||
| Property | Constant | Type | Description |
|
| Property | Constant | Type | Description |
|
||||||
|-----------------|---------------|----------|-----------------------------------------------------------------------|
|
|-----------------|---------------|----------|-----------------------------------------------------------------------|
|
||||||
| "color" | ColorProperty | Color | Shadow color |
|
| "color" | ColorTag | Color | Shadow color |
|
||||||
| "inset" | Inset | bool | true - the shadow inside the View, false - outside |
|
| "inset" | Inset | bool | true - the shadow inside the View, false - outside |
|
||||||
| "x-offset" | XOffset | SizeUnit | Offset the shadow along the X axis |
|
| "x-offset" | XOffset | SizeUnit | Offset the shadow along the X axis |
|
||||||
| "y-offset" | YOffset | SizeUnit | Offset the shadow along the Y axis |
|
| "y-offset" | YOffset | SizeUnit | Offset the shadow along the Y axis |
|
||||||
|
|
@ -989,7 +1031,7 @@ The NewShadowWithParams function is used when constants must be used as paramete
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
shadow := NewShadowWithParams(rui.Params{
|
shadow := NewShadowWithParams(rui.Params{
|
||||||
rui.ColorProperty : "@shadowColor",
|
rui.ColorTag : "@shadowColor",
|
||||||
rui.BlurRadius: 8.0,
|
rui.BlurRadius: 8.0,
|
||||||
rui.Dilation : 16.0,
|
rui.Dilation : 16.0,
|
||||||
})
|
})
|
||||||
|
|
@ -1195,6 +1237,47 @@ Can be one of the following int values:
|
||||||
|
|
||||||
* ImageVerticalAlign,
|
* 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
|
### "clip" property
|
||||||
|
|
||||||
The "clip" property (Clip constant) of the ClipShape type specifies the crop area.
|
The "clip" property (Clip constant) of the ClipShape type specifies the crop area.
|
||||||
|
|
@ -1257,13 +1340,29 @@ The textual description of the polygonal cropping area is in the following forma
|
||||||
|
|
||||||
### "opacity" property
|
### "opacity" property
|
||||||
|
|
||||||
The "opacity" property (constant Opacity) of the float64 type sets the transparency of the View. Valid values are from 0 to 1.
|
The "opacity" property (Opacity constant) 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.
|
Where 1 - View is fully opaque, 0 - fully transparent.
|
||||||
|
|
||||||
You can get the value of this property using the function
|
You can get the value of this property using the function
|
||||||
|
|
||||||
func GetOpacity(view View, subviewID ...string) float64
|
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
|
### "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.
|
The "z-index" property (constant ZIndex) of type int defines the position of the element and its children along the z-axis.
|
||||||
|
|
@ -1354,6 +1453,12 @@ It also helps to voice the interface to systems for people with disabilities:
|
||||||
| 18 | "blockquote" | Quote. Changes the style of the text |
|
| 18 | "blockquote" | Quote. Changes the style of the text |
|
||||||
| 19 | "code" | Program code. 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
|
### Text properties
|
||||||
|
|
||||||
All properties listed in this section are inherited, i.e. the property will apply
|
All properties listed in this section are inherited, i.e. the property will apply
|
||||||
|
|
@ -1518,7 +1623,7 @@ You can get the value of this property using the function
|
||||||
|
|
||||||
#### "text-weight" property
|
#### "text-weight" property
|
||||||
|
|
||||||
Свойство "text-weight" (константа TextWeight) - свойство типа int устанавливает начертание шрифта. Допустимые значения:
|
The "text-weight" int property (TextWeight constant) sets the font style. Valid values:
|
||||||
|
|
||||||
| Value | Constant | Common name of the face |
|
| Value | Constant | Common name of the face |
|
||||||
|:-----:|----------------|---------------------------|
|
|:-----:|----------------|---------------------------|
|
||||||
|
|
@ -1553,7 +1658,7 @@ 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:
|
The NewShadowWithParams function is used when constants must be used as parameters. For example:
|
||||||
|
|
||||||
shadow := NewShadowWithParams(rui.Params{
|
shadow := NewShadowWithParams(rui.Params{
|
||||||
rui.ColorProperty : "@shadowColor",
|
rui.ColorTag : "@shadowColor",
|
||||||
rui.BlurRadius: 8.0,
|
rui.BlurRadius: 8.0,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -2171,6 +2276,15 @@ 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.
|
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.
|
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
|
||||||
|
|
||||||
ListLayout is a container that implements the ViewsContainer interface. To create it, use the function
|
ListLayout is a container that implements the ViewsContainer interface. To create it, use the function
|
||||||
|
|
@ -2238,6 +2352,17 @@ alignment of items in the list. Valid values:
|
||||||
The "list-row-gap" and "list-column-gap" SizeUnit properties (ListRowGap and ListColumnGap constants)
|
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.
|
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
|
||||||
|
|
||||||
GridLayout is a container that implements the ViewsContainer interface. To create it, use the function
|
GridLayout is a container that implements the ViewsContainer interface. To create it, use the function
|
||||||
|
|
@ -2420,15 +2545,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:
|
which implements the Properties interface (see above). ColumnSeparatorProperty can contain the following properties:
|
||||||
|
|
||||||
| Property | Constant | Type | Description |
|
| Property | Constant | Type | Description |
|
||||||
|----------|---------------|----------|----------------|
|
|----------|-----------|----------|----------------|
|
||||||
| "style" | Style | int | Line style |
|
| "style" | Style | int | Line style |
|
||||||
| "width" | Width | SizeUnit | Line thickness |
|
| "width" | Width | SizeUnit | Line thickness |
|
||||||
| "color" | ColorProperty | Color | Line color |
|
| "color" | ColorTag | Color | Line color |
|
||||||
|
|
||||||
Line style can take the following values:
|
Line style can take the following values:
|
||||||
|
|
||||||
| Value | Constant | Name | Description |
|
| Value | Constant | Name | Description |
|
||||||
|:-----:|------------|----------| ------------------|
|
|:-----:|------------|----------|-------------------|
|
||||||
| 0 | NoneLine | "none" | No frame |
|
| 0 | NoneLine | "none" | No frame |
|
||||||
| 1 | SolidLine | "solid" | Solid line |
|
| 1 | SolidLine | "solid" | Solid line |
|
||||||
| 2 | DashedLine | "dashed" | Dashed line |
|
| 2 | DashedLine | "dashed" | Dashed line |
|
||||||
|
|
@ -2479,9 +2604,23 @@ equivalent to
|
||||||
view.Set(rui.ColumnSeparator, ColumnSeparatorProperty(rui.Params{
|
view.Set(rui.ColumnSeparator, ColumnSeparatorProperty(rui.Params{
|
||||||
rui.Style : rui.SolidBorder,
|
rui.Style : rui.SolidBorder,
|
||||||
rui.Width : rui.Px(1),
|
rui.Width : rui.Px(1),
|
||||||
rui.ColorProperty: rui.Black,
|
rui.ColorTag: 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
|
### "avoid-break" property
|
||||||
|
|
||||||
When forming columns, ColumnLayout can break some types of View, so that the beginning
|
When forming columns, ColumnLayout can break some types of View, so that the beginning
|
||||||
|
|
@ -2497,6 +2636,20 @@ You can get the value of this property using the function
|
||||||
|
|
||||||
func GetAvoidBreak(view View, subviewID ...string) bool
|
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
|
||||||
|
|
||||||
StackLayout is a container that implements the ViewsContainer interface.
|
StackLayout is a container that implements the ViewsContainer interface.
|
||||||
|
|
@ -2739,7 +2892,7 @@ It determines how the text is cut if it goes out of bounds.
|
||||||
This property of type int can take the following values
|
This property of type int can take the following values
|
||||||
|
|
||||||
| Value | Constant | Name | Cropping Text |
|
| Value | Constant | Name | Cropping Text |
|
||||||
|:-----:|----------------------| -----------|-------------------------------------------------------------|
|
|:-----:|----------------------|------------|-------------------------------------------------------------|
|
||||||
| 0 | TextOverflowClip | "clip" | Text is clipped at the border (default) |
|
| 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 |
|
| 1 | TextOverflowEllipsis | "ellipsis" | At the end of the visible part of the text '…' is displayed |
|
||||||
|
|
||||||
|
|
@ -2752,7 +2905,25 @@ To create an ImageView function is used:
|
||||||
func NewImageView(session Session, params Params) ImageView
|
func NewImageView(session Session, params Params) ImageView
|
||||||
|
|
||||||
The displayed image is specified by the string property "src" (Source constant).
|
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.
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
ImageView allows you to display different images depending on screen density
|
ImageView allows you to display different images depending on screen density
|
||||||
(See section "Images for screens with different pixel densities").
|
(See section "Images for screens with different pixel densities").
|
||||||
|
|
@ -2818,6 +2989,39 @@ The following functions can be used to retrieve ImageView property values:
|
||||||
func GetImageViewVerticalAlign(view View, subviewID ...string) int
|
func GetImageViewVerticalAlign(view View, subviewID ...string) int
|
||||||
func GetImageViewHorizontalAlign(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
|
## EditView
|
||||||
|
|
||||||
The EditView element is a test editor and extends the View interface.
|
The EditView element is a test editor and extends the View interface.
|
||||||
|
|
@ -2882,13 +3086,21 @@ 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 "edit-text-changed" event (EditTextChangedEvent constant) is used to track changes to the text.
|
||||||
The main event listener has the following format:
|
The main event listener has the following format:
|
||||||
|
|
||||||
func(EditView, string)
|
func(EditView, string, string)
|
||||||
|
|
||||||
where the second argument is the new text value
|
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()
|
||||||
|
|
||||||
You can get the current list of text change listeners using the function
|
You can get the current list of text change listeners using the function
|
||||||
|
|
||||||
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string)
|
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string, string)
|
||||||
|
|
||||||
## NumberPicker
|
## NumberPicker
|
||||||
|
|
||||||
|
|
@ -2944,13 +3156,21 @@ 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 "number-changed" event (NumberChangedEvent constant) is used to track the change in the entered value.
|
||||||
The main event listener has the following format:
|
The main event listener has the following format:
|
||||||
|
|
||||||
func(picker NumberPicker, newValue float64)
|
func(picker NumberPicker, newValue, oldValue float64)
|
||||||
|
|
||||||
where the second argument is the new value
|
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()
|
||||||
|
|
||||||
You can get the current list of value change listeners using the function
|
You can get the current list of value change listeners using the function
|
||||||
|
|
||||||
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64)
|
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64, float64)
|
||||||
|
|
||||||
## DatePicker
|
## DatePicker
|
||||||
|
|
||||||
|
|
@ -2991,13 +3211,21 @@ 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 "date-changed" event (DateChangedEvent constant) is used to track the change in the entered value.
|
||||||
The main event listener has the following format:
|
The main event listener has the following format:
|
||||||
|
|
||||||
func(picker DatePicker, newDate time.Time)
|
func(picker DatePicker, newDate, oldDate time.Time)
|
||||||
|
|
||||||
where the second argument is the new date value
|
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()
|
||||||
|
|
||||||
You can get the current list of date change listeners using the function
|
You can get the current list of date change listeners using the function
|
||||||
|
|
||||||
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time)
|
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time, time.Time)
|
||||||
|
|
||||||
## TimePicker
|
## TimePicker
|
||||||
|
|
||||||
|
|
@ -3038,13 +3266,21 @@ 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 "time-changed" event (TimeChangedEvent constant) is used to track the change in the entered value.
|
||||||
The main event listener has the following format:
|
The main event listener has the following format:
|
||||||
|
|
||||||
func(picker TimePicker, newTime time.Time)
|
func(picker TimePicker, newTime, oldTime time.Time)
|
||||||
|
|
||||||
where the second argument is the new time value
|
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()
|
||||||
|
|
||||||
You can get the current list of date change listeners using the function
|
You can get the current list of date change listeners using the function
|
||||||
|
|
||||||
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time)
|
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time, time.Time)
|
||||||
|
|
||||||
## ColorPicker
|
## ColorPicker
|
||||||
|
|
||||||
|
|
@ -3068,13 +3304,21 @@ 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 "color-changed" event (ColorChangedEvent constant) is used to track the change in the selected color.
|
||||||
The main event listener has the following format:
|
The main event listener has the following format:
|
||||||
|
|
||||||
func(picker ColorPicker, newColor Color)
|
func(picker ColorPicker, newColor, oldColor Color)
|
||||||
|
|
||||||
where the second argument is the new color value
|
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()
|
||||||
|
|
||||||
You can get the current list of date change listeners using the function
|
You can get the current list of date change listeners using the function
|
||||||
|
|
||||||
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color)
|
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color, Color)
|
||||||
|
|
||||||
## FilePicker
|
## FilePicker
|
||||||
|
|
||||||
|
|
@ -3204,11 +3448,19 @@ The main event listener has the following format:
|
||||||
|
|
||||||
func(list DropDownList, newCurrent int)
|
func(list DropDownList, newCurrent int)
|
||||||
|
|
||||||
where the second argument is the index of the selected item
|
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()
|
||||||
|
|
||||||
You can get the current list of date change listeners using the function
|
You can get the current list of date change listeners using the function
|
||||||
|
|
||||||
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int)
|
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int, int)
|
||||||
|
|
||||||
## ProgressBar
|
## ProgressBar
|
||||||
|
|
||||||
|
|
@ -3511,6 +3763,18 @@ The "content" property can also be assigned the following data types
|
||||||
|
|
||||||
[][]any and [][]string are converted to a TableAdapter when assigned.
|
[][]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
|
### "cell-style" property
|
||||||
|
|
||||||
The "cell-style" property (CellStyle constant) is used to customize the appearance of a table cell.
|
The "cell-style" property (CellStyle constant) is used to customize the appearance of a table cell.
|
||||||
|
|
@ -4136,7 +4400,7 @@ AudioPlayer and VideoPlayer are elements for audio and video playback.
|
||||||
Both elements implement the MediaPlayer interface. Most of the properties and all events
|
Both elements implement the MediaPlayer interface. Most of the properties and all events
|
||||||
of AudioPlayer and VideoPlayer are implemented through the MediaPlayer.
|
of AudioPlayer and VideoPlayer are implemented through the MediaPlayer.
|
||||||
|
|
||||||
### Свойство "src"
|
### "src" property
|
||||||
|
|
||||||
The "src" property (Source constant) specifies one or more media sources. The "src" property can take on the following types:
|
The "src" property (Source constant) specifies one or more media sources. The "src" property can take on the following types:
|
||||||
|
|
||||||
|
|
@ -4907,6 +5171,9 @@ 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
|
* 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.
|
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
|
## Resource description format
|
||||||
|
|
||||||
Application resources (themes, views, translations) can be described as text (utf-8).
|
Application resources (themes, views, translations) can be described as text (utf-8).
|
||||||
|
|
@ -5194,9 +5461,17 @@ 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.
|
* ":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.
|
Attention means the aspect ratio of the program window, not the screen.
|
||||||
|
|
||||||
* ":width< size >" are styles for a screen whose width does not exceed the specified size in logical pixels.
|
* ":width<min-width>-<max-width>" - styles for a screen whose width is in the range specified in logical pixels.
|
||||||
|
|
||||||
* ":height< size >" are styles for a screen whose height 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.
|
||||||
|
|
||||||
For example
|
For example
|
||||||
|
|
||||||
|
|
@ -5228,7 +5503,19 @@ For example
|
||||||
width = 100%,
|
width = 100%,
|
||||||
height = 50%,
|
height = 50%,
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
styles:portrait:width320-640 = [
|
||||||
|
samplePage {
|
||||||
|
width = 90%,
|
||||||
|
height = 60%,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
styles:portrait:width640- = [
|
||||||
|
samplePage {
|
||||||
|
width = 80%,
|
||||||
|
height = 70%,
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
## Standard constants and styles
|
## Standard constants and styles
|
||||||
|
|
@ -5296,6 +5583,9 @@ System color constants that you can override:
|
||||||
| ruiPopupTextColor | Popup text color |
|
| ruiPopupTextColor | Popup text color |
|
||||||
| ruiPopupTitleColor | Popup title background color |
|
| ruiPopupTitleColor | Popup title background color |
|
||||||
| ruiPopupTitleTextColor | Popup Title Text Color |
|
| ruiPopupTitleTextColor | Popup Title Text Color |
|
||||||
|
| ruiTooltipBackground | Tooltip background color |
|
||||||
|
| ruiTooltipTextColor | Tooltip text color |
|
||||||
|
| ruiTooltipShadowColor | Tooltip shadow color |
|
||||||
|
|
||||||
Constants that you can override:
|
Constants that you can override:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ const (
|
||||||
LinearTiming = "linear"
|
LinearTiming = "linear"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StepsTiming return a timing function along stepCount stops along the transition, diplaying each stop for equal lengths of time
|
// StepsTiming return a timing function along stepCount stops along the transition, displaying each stop for equal lengths of time
|
||||||
func StepsTiming(stepCount int) string {
|
func StepsTiming(stepCount int) string {
|
||||||
return "steps(" + strconv.Itoa(stepCount) + ")"
|
return "steps(" + strconv.Itoa(stepCount) + ")"
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +130,7 @@ type Animation interface {
|
||||||
writeTransitionString(tag string, buffer *strings.Builder)
|
writeTransitionString(tag string, buffer *strings.Builder)
|
||||||
animationCSS(session Session) string
|
animationCSS(session Session) string
|
||||||
transitionCSS(buffer *strings.Builder, session Session)
|
transitionCSS(buffer *strings.Builder, session Session)
|
||||||
hasAnimatedPropery() bool
|
hasAnimatedProperty() bool
|
||||||
animationName() string
|
animationName() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,7 +160,7 @@ func NewAnimation(params Params) Animation {
|
||||||
return animation
|
return animation
|
||||||
}
|
}
|
||||||
|
|
||||||
func (animation *animationData) hasAnimatedPropery() bool {
|
func (animation *animationData) hasAnimatedProperty() bool {
|
||||||
props := animation.getRaw(PropertyTag)
|
props := animation.getRaw(PropertyTag)
|
||||||
if props == nil {
|
if props == nil {
|
||||||
ErrorLog("There are no animated properties.")
|
ErrorLog("There are no animated properties.")
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ const (
|
||||||
AnimationStartEvent = "animation-start-event"
|
AnimationStartEvent = "animation-start-event"
|
||||||
|
|
||||||
// AnimationEndEvent is the constant for "animation-end-event" property tag.
|
// AnimationEndEvent is the constant for "animation-end-event" property tag.
|
||||||
// The "animation-end-event" is fired when aт фnimation has completed.
|
// The "animation-end-event" is fired when an animation has completed.
|
||||||
// If the animation aborts before reaching completion, such as if the element is removed
|
// 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.
|
// or the animation is removed from the element, the "animation-end-event" is not fired.
|
||||||
AnimationEndEvent = "animation-end-event"
|
AnimationEndEvent = "animation-end-event"
|
||||||
|
|
@ -91,7 +91,10 @@ func transitionEventsHtml(view View, buffer *strings.Builder) {
|
||||||
for tag, js := range transitionEvents {
|
for tag, js := range transitionEvents {
|
||||||
if value := view.getRaw(tag); value != nil {
|
if value := view.getRaw(tag); value != nil {
|
||||||
if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
|
if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
|
||||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
buffer.WriteString(js.jsEvent)
|
||||||
|
buffer.WriteString(`="`)
|
||||||
|
buffer.WriteString(js.jsFunc)
|
||||||
|
buffer.WriteString(`(this, event)" `)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -157,7 +160,10 @@ func animationEventsHtml(view View, buffer *strings.Builder) {
|
||||||
for tag, js := range animationEvents {
|
for tag, js := range animationEvents {
|
||||||
if value := view.getRaw(tag); value != nil {
|
if value := view.getRaw(tag); value != nil {
|
||||||
if listeners, ok := value.([]func(View)); ok && len(listeners) > 0 {
|
if listeners, ok := value.([]func(View)); ok && len(listeners) > 0 {
|
||||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
buffer.WriteString(js.jsEvent)
|
||||||
|
buffer.WriteString(`="`)
|
||||||
|
buffer.WriteString(js.jsFunc)
|
||||||
|
buffer.WriteString(`(this, event)" `)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
appServer.go
11
appServer.go
|
|
@ -156,14 +156,7 @@ func (app *application) socketReader(bridge webBridge) {
|
||||||
ErrorLog(`"session" key not found`)
|
ErrorLog(`"session" key not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
answer := ""
|
bridge.writeMessage("restartSession();")
|
||||||
if session, answer = app.startSession(obj, events, bridge); session != nil {
|
|
||||||
if !bridge.writeMessage(answer) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
session.onStart()
|
|
||||||
go sessionEventHandler(session, events, bridge)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "answer":
|
case "answer":
|
||||||
session.handleAnswer(obj)
|
session.handleAnswer(obj)
|
||||||
|
|
@ -295,7 +288,7 @@ func OpenBrowser(url string) bool {
|
||||||
case "linux":
|
case "linux":
|
||||||
for _, provider := range []string{"xdg-open", "x-www-browser", "www-browser"} {
|
for _, provider := range []string{"xdg-open", "x-www-browser", "www-browser"} {
|
||||||
if _, err = exec.LookPath(provider); err == nil {
|
if _, err = exec.LookPath(provider); err == nil {
|
||||||
if exec.Command(provider, url).Start(); err == nil {
|
if err = exec.Command(provider, url).Start(); err == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,6 @@ func (app *wasmApp) init(params AppParams) {
|
||||||
div = document.Call("createElement", "div")
|
div = document.Call("createElement", "div")
|
||||||
div.Set("className", "ruiPopupLayer")
|
div.Set("className", "ruiPopupLayer")
|
||||||
div.Set("id", "ruiPopupLayer")
|
div.Set("id", "ruiPopupLayer")
|
||||||
div.Set("onclick", "clickOutsidePopup(event)")
|
|
||||||
div.Set("style", "visibility: hidden;")
|
div.Set("style", "visibility: hidden;")
|
||||||
body.Call("appendChild", div)
|
body.Call("appendChild", div)
|
||||||
|
|
||||||
|
|
|
||||||
304
app_scripts.js
304
app_scripts.js
|
|
@ -52,9 +52,31 @@ function sessionInfo() {
|
||||||
message += ",pixel-ratio=" + pixelRatio;
|
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 + "}";
|
return message + "}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function restartSession() {
|
||||||
|
sendMessage( sessionInfo() );
|
||||||
|
}
|
||||||
|
|
||||||
function getIntAttribute(element, tag) {
|
function getIntAttribute(element, tag) {
|
||||||
let value = element.getAttribute(tag);
|
let value = element.getAttribute(tag);
|
||||||
if (value) {
|
if (value) {
|
||||||
|
|
@ -224,22 +246,26 @@ function enterOrSpaceKeyClickEvent(event) {
|
||||||
function activateTab(layoutId, tabNumber) {
|
function activateTab(layoutId, tabNumber) {
|
||||||
var element = document.getElementById(layoutId);
|
var element = document.getElementById(layoutId);
|
||||||
if (element) {
|
if (element) {
|
||||||
var currentTabId = element.getAttribute("data-current");
|
var currentNumber = element.getAttribute("data-current");
|
||||||
var newTabId = layoutId + '-' + tabNumber;
|
if (currentNumber != tabNumber) {
|
||||||
if (currentTabId != newTabId) {
|
function setTab(number, styleProperty, display) {
|
||||||
function setTab(tabId, styleProperty, display) {
|
var tab = document.getElementById(layoutId + '-' + number);
|
||||||
var tab = document.getElementById(tabId);
|
|
||||||
if (tab) {
|
if (tab) {
|
||||||
tab.className = element.getAttribute(styleProperty);
|
tab.className = element.getAttribute(styleProperty);
|
||||||
var page = document.getElementById(tab.getAttribute("data-view"));
|
var page = document.getElementById(tab.getAttribute("data-view"));
|
||||||
if (page) {
|
if (page) {
|
||||||
page.style.display = display;
|
page.style.display = display;
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var page = document.getElementById(layoutId + "-page" + number);
|
||||||
|
if (page) {
|
||||||
|
page.style.display = display;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTab(currentTabId, "data-inactiveTabStyle", "none")
|
setTab(currentNumber, "data-inactiveTabStyle", "none")
|
||||||
setTab(newTabId, "data-activeTabStyle", "");
|
setTab(tabNumber, "data-activeTabStyle", "");
|
||||||
element.setAttribute("data-current", newTabId);
|
element.setAttribute("data-current", tabNumber);
|
||||||
scanElementsSize()
|
scanElementsSize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -285,8 +311,19 @@ function keyEvent(element, event, tag) {
|
||||||
message += ",timeStamp=" + event.timeStamp;
|
message += ",timeStamp=" + event.timeStamp;
|
||||||
}
|
}
|
||||||
if (event.key) {
|
if (event.key) {
|
||||||
|
switch (event.key) {
|
||||||
|
case '"':
|
||||||
|
message += ",key=`\"`";
|
||||||
|
break
|
||||||
|
|
||||||
|
case '\\':
|
||||||
|
message += ",key=`\\`";
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
message += ",key=\"" + event.key + "\"";
|
message += ",key=\"" + event.key + "\"";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (event.code) {
|
if (event.code) {
|
||||||
message += ",code=\"" + event.code + "\"";
|
message += ",code=\"" + event.code + "\"";
|
||||||
}
|
}
|
||||||
|
|
@ -408,6 +445,7 @@ function mouseOutEvent(element, event) {
|
||||||
function clickEvent(element, event) {
|
function clickEvent(element, event) {
|
||||||
mouseEvent(element, event, "click-event")
|
mouseEvent(element, event, "click-event")
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
function doubleClickEvent(element, event) {
|
function doubleClickEvent(element, event) {
|
||||||
|
|
@ -571,6 +609,10 @@ function selectDropDownListItem(elementId, number) {
|
||||||
function listItemClickEvent(element, event) {
|
function listItemClickEvent(element, event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (element.getAttribute("data-disabled") == "1") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var selected = false;
|
var selected = false;
|
||||||
if (element.classList) {
|
if (element.classList) {
|
||||||
const focusStyle = getListFocusedItemStyle(element);
|
const focusStyle = getListFocusedItemStyle(element);
|
||||||
|
|
@ -688,6 +730,9 @@ function findRightListItem(list, x, y) {
|
||||||
var count = list.childNodes.length;
|
var count = list.childNodes.length;
|
||||||
for (var i = 0; i < count; i++) {
|
for (var i = 0; i < count; i++) {
|
||||||
var item = list.childNodes[i];
|
var item = list.childNodes[i];
|
||||||
|
if (item.getAttribute("data-disabled") == "1") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (item.offsetLeft >= x) {
|
if (item.offsetLeft >= x) {
|
||||||
if (result) {
|
if (result) {
|
||||||
var result_dy = Math.abs(result.offsetTop - y);
|
var result_dy = Math.abs(result.offsetTop - y);
|
||||||
|
|
@ -709,6 +754,9 @@ function findLeftListItem(list, x, y) {
|
||||||
var count = list.childNodes.length;
|
var count = list.childNodes.length;
|
||||||
for (var i = 0; i < count; i++) {
|
for (var i = 0; i < count; i++) {
|
||||||
var item = list.childNodes[i];
|
var item = list.childNodes[i];
|
||||||
|
if (item.getAttribute("data-disabled") == "1") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (item.offsetLeft < x) {
|
if (item.offsetLeft < x) {
|
||||||
if (result) {
|
if (result) {
|
||||||
var result_dy = Math.abs(result.offsetTop - y);
|
var result_dy = Math.abs(result.offsetTop - y);
|
||||||
|
|
@ -730,6 +778,9 @@ function findTopListItem(list, x, y) {
|
||||||
var count = list.childNodes.length;
|
var count = list.childNodes.length;
|
||||||
for (var i = 0; i < count; i++) {
|
for (var i = 0; i < count; i++) {
|
||||||
var item = list.childNodes[i];
|
var item = list.childNodes[i];
|
||||||
|
if (item.getAttribute("data-disabled") == "1") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (item.offsetTop < y) {
|
if (item.offsetTop < y) {
|
||||||
if (result) {
|
if (result) {
|
||||||
var result_dx = Math.abs(result.offsetLeft - x);
|
var result_dx = Math.abs(result.offsetLeft - x);
|
||||||
|
|
@ -751,6 +802,9 @@ function findBottomListItem(list, x, y) {
|
||||||
var count = list.childNodes.length;
|
var count = list.childNodes.length;
|
||||||
for (var i = 0; i < count; i++) {
|
for (var i = 0; i < count; i++) {
|
||||||
var item = list.childNodes[i];
|
var item = list.childNodes[i];
|
||||||
|
if (item.getAttribute("data-disabled") == "1") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (item.offsetTop >= y) {
|
if (item.offsetTop >= y) {
|
||||||
if (result) {
|
if (result) {
|
||||||
var result_dx = Math.abs(result.offsetLeft - x);
|
var result_dx = Math.abs(result.offsetLeft - x);
|
||||||
|
|
@ -843,6 +897,33 @@ function listViewKeyDownEvent(element, event) {
|
||||||
if (item && item !== current) {
|
if (item && item !== current) {
|
||||||
selectListItem(element, item, true);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -940,8 +1021,8 @@ function radioButtonKeyClickEvent(element, event) {
|
||||||
|
|
||||||
function editViewInputEvent(element) {
|
function editViewInputEvent(element) {
|
||||||
var text = element.value
|
var text = element.value
|
||||||
text = text.replace(/\\/g, "\\\\")
|
text = text.replaceAll(/\\/g, "\\\\")
|
||||||
text = text.replace(/\"/g, "\\\"")
|
text = text.replaceAll(/\"/g, "\\\"")
|
||||||
var message = "textChanged{session=" + sessionID + ",id=" + element.id + ",text=\"" + text + "\"}"
|
var message = "textChanged{session=" + sessionID + ",id=" + element.id + ",text=\"" + text + "\"}"
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
}
|
}
|
||||||
|
|
@ -1147,7 +1228,7 @@ function loadImage(url) {
|
||||||
img.addEventListener("error", function(event) {
|
img.addEventListener("error", function(event) {
|
||||||
var message = "imageError{session=" + sessionID + ",url=\"" + url + "\"";
|
var message = "imageError{session=" + sessionID + ",url=\"" + url + "\"";
|
||||||
if (event && event.message) {
|
if (event && event.message) {
|
||||||
var text = event.message.replace(new RegExp("\"", 'g'), "\\\"")
|
var text = event.message.replaceAll(new RegExp("\"", 'g'), "\\\"")
|
||||||
message += ",message=\"" + text + "\"";
|
message += ",message=\"" + text + "\"";
|
||||||
}
|
}
|
||||||
sendMessage(message + "}")
|
sendMessage(message + "}")
|
||||||
|
|
@ -1178,7 +1259,7 @@ function loadInlineImage(url, content) {
|
||||||
img.addEventListener("error", function(event) {
|
img.addEventListener("error", function(event) {
|
||||||
var message = "imageError{session=" + sessionID + ",url=\"" + url + "\"";
|
var message = "imageError{session=" + sessionID + ",url=\"" + url + "\"";
|
||||||
if (event && event.message) {
|
if (event && event.message) {
|
||||||
var text = event.message.replace(new RegExp("\"", 'g'), "\\\"")
|
var text = event.message.replaceAll(new RegExp("\"", 'g'), "\\\"")
|
||||||
message += ",message=\"" + text + "\"";
|
message += ",message=\"" + text + "\"";
|
||||||
}
|
}
|
||||||
sendMessage(message + "}")
|
sendMessage(message + "}")
|
||||||
|
|
@ -1187,11 +1268,6 @@ function loadInlineImage(url, content) {
|
||||||
img.src = content;
|
img.src = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clickOutsidePopup(e) {
|
|
||||||
sendMessage("clickOutsidePopup{session=" + sessionID + "}")
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
function clickClosePopup(element, e) {
|
function clickClosePopup(element, e) {
|
||||||
var popupId = element.getAttribute("data-popupId");
|
var popupId = element.getAttribute("data-popupId");
|
||||||
sendMessage("clickClosePopup{session=" + sessionID + ",id=" + popupId + "}")
|
sendMessage("clickClosePopup{session=" + sessionID + ",id=" + popupId + "}")
|
||||||
|
|
@ -1339,7 +1415,7 @@ function mediaSetVolume(elementId, volume) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function startDowndload(url, filename) {
|
function startDownload(url, filename) {
|
||||||
var element = document.getElementById("ruiDownloader");
|
var element = document.getElementById("ruiDownloader");
|
||||||
if (element) {
|
if (element) {
|
||||||
element.href = url;
|
element.href = url;
|
||||||
|
|
@ -1408,6 +1484,24 @@ 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) {
|
function setTableCellCursor(element, row, column) {
|
||||||
const cellID = element.id + "-" + row + "-" + column;
|
const cellID = element.id + "-" + row + "-" + column;
|
||||||
var cell = document.getElementById(cellID);
|
var cell = document.getElementById(cellID);
|
||||||
|
|
@ -1495,6 +1589,8 @@ function tableViewCellKeyDownEvent(element, event) {
|
||||||
case "End":
|
case "End":
|
||||||
case "PageUp":
|
case "PageUp":
|
||||||
case "PageDown":
|
case "PageDown":
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
const rows = element.getAttribute("data-rows");
|
const rows = element.getAttribute("data-rows");
|
||||||
const columns = element.getAttribute("data-columns");
|
const columns = element.getAttribute("data-columns");
|
||||||
if (rows && columns) {
|
if (rows && columns) {
|
||||||
|
|
@ -1505,8 +1601,6 @@ function tableViewCellKeyDownEvent(element, event) {
|
||||||
column = 0;
|
column = 0;
|
||||||
while (columns < columnCount) {
|
while (columns < columnCount) {
|
||||||
if (setTableCellCursor(element, row, column)) {
|
if (setTableCellCursor(element, row, column)) {
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
column++;
|
column++;
|
||||||
|
|
@ -1514,8 +1608,6 @@ function tableViewCellKeyDownEvent(element, event) {
|
||||||
row++;
|
row++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -1583,6 +1675,24 @@ 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) {
|
function setTableRowCursor(element, row) {
|
||||||
const tableRowID = element.id + "-" + row;
|
const tableRowID = element.id + "-" + row;
|
||||||
var tableRow = document.getElementById(tableRowID);
|
var tableRow = document.getElementById(tableRowID);
|
||||||
|
|
@ -1597,7 +1707,6 @@ function setTableRowCursor(element, row) {
|
||||||
if (oldRow && oldRow.classList) {
|
if (oldRow && oldRow.classList) {
|
||||||
oldRow.classList.remove(focusStyle);
|
oldRow.classList.remove(focusStyle);
|
||||||
oldRow.classList.remove(getTableSelectedItemStyle(element));
|
oldRow.classList.remove(getTableSelectedItemStyle(element));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1842,3 +1951,152 @@ function getCanvasContext(elementId) {
|
||||||
}
|
}
|
||||||
return null;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,13 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--tooltip-arrow-size: 6px;
|
||||||
|
--tooltip-background: white;
|
||||||
|
--tooltip-text-color: black;
|
||||||
|
--tooltip-shadow-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
-webkit-touch-callout: none;
|
-webkit-touch-callout: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
|
@ -76,7 +83,54 @@ ul:focus {
|
||||||
left: 0px;
|
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 {
|
.ruiView {
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ruiAbsoluteLayout {
|
.ruiAbsoluteLayout {
|
||||||
|
|
@ -112,16 +166,17 @@ ul:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
.ruiImageView {
|
.ruiImageView {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ruiSvgImageView {
|
||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ruiListView {
|
.ruiListView {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
/*
|
|
||||||
display: flex;
|
|
||||||
align-content: stretch;
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
body {
|
body {
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var wasmMediaResources = false
|
|
||||||
|
|
||||||
//go:embed app_scripts.js
|
//go:embed app_scripts.js
|
||||||
var defaultScripts string
|
var defaultScripts string
|
||||||
|
|
||||||
|
|
@ -75,9 +73,15 @@ func getStartPage(buffer *strings.Builder, params AppParams, addScripts string)
|
||||||
buffer.WriteString(addScripts)
|
buffer.WriteString(addScripts)
|
||||||
buffer.WriteString(`</script>
|
buffer.WriteString(`</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body id="body" onkeydown="keyDownEvent(this, event)">
|
||||||
<div class="ruiRoot" id="ruiRootView"></div>
|
<div class="ruiRoot" id="ruiRootView"></div>
|
||||||
<div class="ruiPopupLayer" id="ruiPopupLayer" style="visibility: hidden;" onclick="clickOutsidePopup(event)"></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>
|
||||||
|
|
||||||
<a id="ruiDownloader" download style="display: none;"></a>
|
<a id="ruiDownloader" download style="display: none;"></a>
|
||||||
</body>`)
|
</body>`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ func (gradient *backgroundConicGradient) parseGradientText(value string) []Backg
|
||||||
for i, element := range elements {
|
for i, element := range elements {
|
||||||
var ok bool
|
var ok bool
|
||||||
if vector[i], ok = gradient.stringToGradientPoint(strings.Trim(element, " ")); !ok {
|
if vector[i], ok = gradient.stringToGradientPoint(strings.Trim(element, " ")); !ok {
|
||||||
ErrorLogF(`Ivalid %d element of the conic gradient: "%s"`, i, element)
|
ErrorLogF(`Invalid %d element of the conic gradient: "%s"`, i, element)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -240,7 +240,7 @@ func (gradient *backgroundConicGradient) setGradient(value any) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorLogF(`Ivalid conic gradient: "%s"`, value)
|
ErrorLogF(`Invalid conic gradient: "%s"`, value)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
case []BackgroundGradientAngle:
|
case []BackgroundGradientAngle:
|
||||||
|
|
@ -252,7 +252,7 @@ func (gradient *backgroundConicGradient) setGradient(value any) bool {
|
||||||
|
|
||||||
for i, point := range value {
|
for i, point := range value {
|
||||||
if point.Color == nil {
|
if point.Color == nil {
|
||||||
ErrorLogF("Ivalid %d element of the conic gradient: Color is nil", i)
|
ErrorLogF("Invalid %d element of the conic gradient: Color is nil", i)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ func (gradient *backgroundGradient) parseGradientText(value string) []Background
|
||||||
points := make([]BackgroundGradientPoint, count)
|
points := make([]BackgroundGradientPoint, count)
|
||||||
for i, element := range elements {
|
for i, element := range elements {
|
||||||
if !points[i].setValue(element) {
|
if !points[i].setValue(element) {
|
||||||
ErrorLogF(`Ivalid %d element of the conic gradient: "%s"`, i, element)
|
ErrorLogF(`Invalid %d element of the conic gradient: "%s"`, i, element)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -286,7 +286,7 @@ func (border *borderProperty) setBorderObject(obj DataObject) bool {
|
||||||
result := true
|
result := true
|
||||||
|
|
||||||
for _, side := range []string{Top, Right, Bottom, Left} {
|
for _, side := range []string{Top, Right, Bottom, Left} {
|
||||||
if node := obj.PropertyWithTag(side); node != nil {
|
if node := obj.PropertyByTag(side); node != nil {
|
||||||
if node.Type() == ObjectNode {
|
if node.Type() == ObjectNode {
|
||||||
if !border.setSingleBorderObject(side, node.Object()) {
|
if !border.setSingleBorderObject(side, node.Object()) {
|
||||||
result = false
|
result = false
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ func (button *buttonData) CreateSuperView(session Session) View {
|
||||||
HorizontalAlign: CenterAlign,
|
HorizontalAlign: CenterAlign,
|
||||||
VerticalAlign: CenterAlign,
|
VerticalAlign: CenterAlign,
|
||||||
Orientation: StartToEndOrientation,
|
Orientation: StartToEndOrientation,
|
||||||
|
TabIndex: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ type Canvas interface {
|
||||||
SetRadialGradientStrokeStyle(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint)
|
SetRadialGradientStrokeStyle(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint)
|
||||||
|
|
||||||
// SetImageFillStyle set the image as the filling pattern.
|
// SetImageFillStyle set the image as the filling pattern.
|
||||||
// repeate - indicating how to repeat the pattern's image. Possible values are:
|
// repeat - indicating how to repeat the pattern's image. Possible values are:
|
||||||
// NoRepeat (0) - neither direction,
|
// NoRepeat (0) - neither direction,
|
||||||
// RepeatXY (1) - both directions,
|
// RepeatXY (1) - both directions,
|
||||||
// RepeatX (2) - horizontal only,
|
// RepeatX (2) - horizontal only,
|
||||||
|
|
@ -286,7 +286,7 @@ type Canvas interface {
|
||||||
DrawImage(x, y float64, image Image)
|
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 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)
|
DrawImageInRect(x, y, width, height float64, image Image)
|
||||||
// DrawImageFragment draws the frament (described by srcX, srcY, srcWidth, srcHeight) of image
|
// DrawImageFragment draws the fragment (described by srcX, srcY, srcWidth, srcHeight) of image
|
||||||
// in the rectangle (dstX, dstY, dstWidth, dstHeight), scaling in height and width if necessary
|
// 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)
|
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 := new(canvasData)
|
||||||
canvas.view = view
|
canvas.view = view
|
||||||
canvas.session = view.Session()
|
canvas.session = view.Session()
|
||||||
canvas.session.cavnasStart(view.htmlID())
|
canvas.session.canvasStart(view.htmlID())
|
||||||
return canvas
|
return canvas
|
||||||
}
|
}
|
||||||
|
|
||||||
func (canvas *canvasData) finishDraw() {
|
func (canvas *canvasData) finishDraw() {
|
||||||
canvas.session.cavnasFinish()
|
canvas.session.canvasFinish()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (canvas *canvasData) View() CanvasView {
|
func (canvas *canvasData) View() CanvasView {
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,8 @@ const (
|
||||||
BlueViolet Color = 0xff8a2be2
|
BlueViolet Color = 0xff8a2be2
|
||||||
// Brown color constant
|
// Brown color constant
|
||||||
Brown Color = 0xffa52a2a
|
Brown Color = 0xffa52a2a
|
||||||
// Burlywood color constant
|
// BurlyWood color constant
|
||||||
Burlywood Color = 0xffdeb887
|
BurlyWood Color = 0xffdeb887
|
||||||
// CadetBlue color constant
|
// CadetBlue color constant
|
||||||
CadetBlue Color = 0xff5f9ea0
|
CadetBlue Color = 0xff5f9ea0
|
||||||
// Chartreuse color constant
|
// Chartreuse color constant
|
||||||
|
|
@ -67,8 +67,8 @@ const (
|
||||||
Coral Color = 0xffff7f50
|
Coral Color = 0xffff7f50
|
||||||
// CornflowerBlue color constant
|
// CornflowerBlue color constant
|
||||||
CornflowerBlue Color = 0xff6495ed
|
CornflowerBlue Color = 0xff6495ed
|
||||||
// Cornsilk color constant
|
// CornSilk color constant
|
||||||
Cornsilk Color = 0xfffff8dc
|
CornSilk Color = 0xfffff8dc
|
||||||
// Crimson color constant
|
// Crimson color constant
|
||||||
Crimson Color = 0xffdc143c
|
Crimson Color = 0xffdc143c
|
||||||
// Cyan color constant
|
// Cyan color constant
|
||||||
|
|
@ -105,8 +105,8 @@ const (
|
||||||
DarkSlateBlue Color = 0xff483d8b
|
DarkSlateBlue Color = 0xff483d8b
|
||||||
// DarkSlateGray color constant
|
// DarkSlateGray color constant
|
||||||
DarkSlateGray Color = 0xff2f4f4f
|
DarkSlateGray Color = 0xff2f4f4f
|
||||||
// Darkslategrey color constant
|
// DarkSlateGrey color constant
|
||||||
Darkslategrey Color = 0xff2f4f4f
|
DarkSlateGrey Color = 0xff2f4f4f
|
||||||
// DarkTurquoise color constant
|
// DarkTurquoise color constant
|
||||||
DarkTurquoise Color = 0xff00ced1
|
DarkTurquoise Color = 0xff00ced1
|
||||||
// DarkViolet color constant
|
// DarkViolet color constant
|
||||||
|
|
@ -135,8 +135,8 @@ const (
|
||||||
Gold Color = 0xffffd700
|
Gold Color = 0xffffd700
|
||||||
// GoldenRod color constant
|
// GoldenRod color constant
|
||||||
GoldenRod Color = 0xffdaa520
|
GoldenRod Color = 0xffdaa520
|
||||||
// GreenyEllow color constant
|
// GreenYellow color constant
|
||||||
GreenyEllow Color = 0xffadff2f
|
GreenYellow Color = 0xffadff2f
|
||||||
// Grey color constant
|
// Grey color constant
|
||||||
Grey Color = 0xff808080
|
Grey Color = 0xff808080
|
||||||
// Honeydew color constant
|
// Honeydew color constant
|
||||||
|
|
@ -293,8 +293,8 @@ const (
|
||||||
Violet Color = 0xffee82ee
|
Violet Color = 0xffee82ee
|
||||||
// Wheat color constant
|
// Wheat color constant
|
||||||
Wheat Color = 0xfff5deb3
|
Wheat Color = 0xfff5deb3
|
||||||
// Whitesmoke color constant
|
// WhiteSmoke color constant
|
||||||
Whitesmoke Color = 0xfff5f5f5
|
WhiteSmoke Color = 0xfff5f5f5
|
||||||
// YellowGreen color constant
|
// YellowGreen color constant
|
||||||
YellowGreen Color = 0xff9acd32
|
YellowGreen Color = 0xff9acd32
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ type ColorPicker interface {
|
||||||
|
|
||||||
type colorPickerData struct {
|
type colorPickerData struct {
|
||||||
viewData
|
viewData
|
||||||
colorChangedListeners []func(ColorPicker, Color)
|
colorChangedListeners []func(ColorPicker, Color, Color)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewColorPicker create new ColorPicker object and return it
|
// NewColorPicker create new ColorPicker object and return it
|
||||||
|
|
@ -34,7 +34,7 @@ func newColorPicker(session Session) View {
|
||||||
func (picker *colorPickerData) init(session Session) {
|
func (picker *colorPickerData) init(session Session) {
|
||||||
picker.viewData.init(session)
|
picker.viewData.init(session)
|
||||||
picker.tag = "ColorPicker"
|
picker.tag = "ColorPicker"
|
||||||
picker.colorChangedListeners = []func(ColorPicker, Color){}
|
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
|
||||||
picker.properties[Padding] = Px(0)
|
picker.properties[Padding] = Px(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ func (picker *colorPickerData) remove(tag string) {
|
||||||
switch tag {
|
switch tag {
|
||||||
case ColorChangedEvent:
|
case ColorChangedEvent:
|
||||||
if len(picker.colorChangedListeners) > 0 {
|
if len(picker.colorChangedListeners) > 0 {
|
||||||
picker.colorChangedListeners = []func(ColorPicker, Color){}
|
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
|
||||||
picker.propertyChangedEvent(tag)
|
picker.propertyChangedEvent(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,12 +86,12 @@ func (picker *colorPickerData) set(tag string, value any) bool {
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case ColorChangedEvent:
|
case ColorChangedEvent:
|
||||||
listeners, ok := valueToEventListeners[ColorPicker, Color](value)
|
listeners, ok := valueToEventWithOldListeners[ColorPicker, Color](value)
|
||||||
if !ok {
|
if !ok {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return false
|
||||||
} else if listeners == nil {
|
} else if listeners == nil {
|
||||||
listeners = []func(ColorPicker, Color){}
|
listeners = []func(ColorPicker, Color, Color){}
|
||||||
}
|
}
|
||||||
picker.colorChangedListeners = listeners
|
picker.colorChangedListeners = listeners
|
||||||
picker.propertyChangedEvent(tag)
|
picker.propertyChangedEvent(tag)
|
||||||
|
|
@ -116,7 +116,7 @@ func (picker *colorPickerData) colorChanged(oldColor Color) {
|
||||||
picker.session.callFunc("setInputValue", picker.htmlID(), newColor.rgbString())
|
picker.session.callFunc("setInputValue", picker.htmlID(), newColor.rgbString())
|
||||||
}
|
}
|
||||||
for _, listener := range picker.colorChangedListeners {
|
for _, listener := range picker.colorChangedListeners {
|
||||||
listener(picker, newColor)
|
listener(picker, newColor, oldColor)
|
||||||
}
|
}
|
||||||
picker.propertyChangedEvent(ColorTag)
|
picker.propertyChangedEvent(ColorTag)
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +169,7 @@ func (picker *colorPickerData) handleCommand(self View, command string, data Dat
|
||||||
picker.properties[ColorPickerValue] = color
|
picker.properties[ColorPickerValue] = color
|
||||||
if color != oldColor {
|
if color != oldColor {
|
||||||
for _, listener := range picker.colorChangedListeners {
|
for _, listener := range picker.colorChangedListeners {
|
||||||
listener(picker, color)
|
listener(picker, color, oldColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -204,6 +204,6 @@ func GetColorPickerValue(view View, subviewID ...string) Color {
|
||||||
// GetColorChangedListeners returns the ColorChangedListener list of an ColorPicker subview.
|
// GetColorChangedListeners returns the ColorChangedListener list of an ColorPicker subview.
|
||||||
// If there are no listeners then the empty list is returned
|
// 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.
|
// 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) {
|
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color, Color) {
|
||||||
return getEventListeners[ColorPicker, Color](view, subviewID, ColorChangedEvent)
|
return getEventWithOldListeners[ColorPicker, Color](view, subviewID, ColorChangedEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,28 +11,45 @@ const (
|
||||||
// Values less than zero are not valid. if the "column-count" property value is 0 then
|
// 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
|
// the number of columns is calculated based on the "column-width" property
|
||||||
ColumnCount = "column-count"
|
ColumnCount = "column-count"
|
||||||
|
|
||||||
// ColumnWidth is the constant for the "column-width" property tag.
|
// ColumnWidth is the constant for the "column-width" property tag.
|
||||||
// The "column-width" SizeUnit property specifies the width of each column.
|
// The "column-width" SizeUnit property specifies the width of each column.
|
||||||
ColumnWidth = "column-width"
|
ColumnWidth = "column-width"
|
||||||
|
|
||||||
// ColumnGap is the constant for the "column-gap" property tag.
|
// ColumnGap is the constant for the "column-gap" property tag.
|
||||||
// The "column-width" SizeUnit property sets the size of the gap (gutter) between columns.
|
// The "column-width" SizeUnit property sets the size of the gap (gutter) between columns.
|
||||||
ColumnGap = "column-gap"
|
ColumnGap = "column-gap"
|
||||||
|
|
||||||
// ColumnSeparator is the constant for the "column-separator" property tag.
|
// 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.
|
// The "column-separator" property specifies the line drawn between columns in a multi-column layout.
|
||||||
ColumnSeparator = "column-separator"
|
ColumnSeparator = "column-separator"
|
||||||
|
|
||||||
// ColumnSeparatorStyle is the constant for the "column-separator-style" property tag.
|
// 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
|
// The "column-separator-style" int property sets the style of the line drawn between
|
||||||
// columns in a multi-column layout.
|
// columns in a multi-column layout.
|
||||||
// Valid values are NoneLine (0), SolidLine (1), DashedLine (2), DottedLine (3), and DoubleLine (4).
|
// Valid values are NoneLine (0), SolidLine (1), DashedLine (2), DottedLine (3), and DoubleLine (4).
|
||||||
ColumnSeparatorStyle = "column-separator-style"
|
ColumnSeparatorStyle = "column-separator-style"
|
||||||
|
|
||||||
// ColumnSeparatorWidth is the constant for the "column-separator-width" property tag.
|
// 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
|
// The "column-separator-width" SizeUnit property sets the width of the line drawn between
|
||||||
// columns in a multi-column layout.
|
// columns in a multi-column layout.
|
||||||
ColumnSeparatorWidth = "column-separator-width"
|
ColumnSeparatorWidth = "column-separator-width"
|
||||||
|
|
||||||
// ColumnSeparatorColor is the constant for the "column-separator-color" property tag.
|
// 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
|
// The "column-separator-color" Color property sets the color of the line drawn between
|
||||||
// columns in a multi-column layout.
|
// columns in a multi-column layout.
|
||||||
ColumnSeparatorColor = "column-separator-color"
|
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
|
// ColumnLayout - grid-container of View
|
||||||
|
|
@ -206,3 +223,16 @@ func GetColumnSeparatorColor(view View, subviewID ...string) Color {
|
||||||
border := getColumnSeparator(view, subviewID)
|
border := getColumnSeparator(view, subviewID)
|
||||||
return border.Color
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -243,10 +243,19 @@ func (customView *CustomViewData) RemoveView(index int) View {
|
||||||
return container.RemoveView(index)
|
return container.RemoveView(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
||||||
func (customView *CustomViewData) String() string {
|
func (customView *CustomViewData) String() string {
|
||||||
if customView.superView != nil {
|
if customView.superView != nil {
|
||||||
return getViewString(customView)
|
return getViewString(customView)
|
||||||
|
|
|
||||||
66
data.go
66
data.go
|
|
@ -18,11 +18,12 @@ type DataObject interface {
|
||||||
Tag() string
|
Tag() string
|
||||||
PropertyCount() int
|
PropertyCount() int
|
||||||
Property(index int) DataNode
|
Property(index int) DataNode
|
||||||
PropertyWithTag(tag string) DataNode
|
PropertyByTag(tag string) DataNode
|
||||||
PropertyValue(tag string) (string, bool)
|
PropertyValue(tag string) (string, bool)
|
||||||
PropertyObject(tag string) DataObject
|
PropertyObject(tag string) DataObject
|
||||||
SetPropertyValue(tag, value string)
|
SetPropertyValue(tag, value string)
|
||||||
SetPropertyObject(tag string, object DataObject)
|
SetPropertyObject(tag string, object DataObject)
|
||||||
|
ToParams() Params
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -43,6 +44,7 @@ type DataNode interface {
|
||||||
ArraySize() int
|
ArraySize() int
|
||||||
ArrayElement(index int) DataValue
|
ArrayElement(index int) DataValue
|
||||||
ArrayElements() []DataValue
|
ArrayElements() []DataValue
|
||||||
|
ArrayAsParams() []Params
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
@ -106,7 +108,7 @@ func (object *dataObject) Property(index int) DataNode {
|
||||||
return object.property[index]
|
return object.property[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (object *dataObject) PropertyWithTag(tag string) DataNode {
|
func (object *dataObject) PropertyByTag(tag string) DataNode {
|
||||||
if object.property != nil {
|
if object.property != nil {
|
||||||
for _, node := range object.property {
|
for _, node := range object.property {
|
||||||
if node.Tag() == tag {
|
if node.Tag() == tag {
|
||||||
|
|
@ -118,14 +120,14 @@ func (object *dataObject) PropertyWithTag(tag string) DataNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (object *dataObject) PropertyValue(tag string) (string, bool) {
|
func (object *dataObject) PropertyValue(tag string) (string, bool) {
|
||||||
if node := object.PropertyWithTag(tag); node != nil && node.Type() == TextNode {
|
if node := object.PropertyByTag(tag); node != nil && node.Type() == TextNode {
|
||||||
return node.Text(), true
|
return node.Text(), true
|
||||||
}
|
}
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (object *dataObject) PropertyObject(tag string) DataObject {
|
func (object *dataObject) PropertyObject(tag string) DataObject {
|
||||||
if node := object.PropertyWithTag(tag); node != nil && node.Type() == ObjectNode {
|
if node := object.PropertyByTag(tag); node != nil && node.Type() == ObjectNode {
|
||||||
return node.Object()
|
return node.Object()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -165,6 +167,42 @@ func (object *dataObject) SetPropertyObject(tag string, obj DataObject) {
|
||||||
object.setNode(node)
|
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 {
|
type dataNode struct {
|
||||||
tag string
|
tag string
|
||||||
|
|
@ -221,6 +259,22 @@ func (node *dataNode) ArrayElements() []DataValue {
|
||||||
return []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
|
// ParseDataText - parse text and return DataNode
|
||||||
func ParseDataText(text string) DataObject {
|
func ParseDataText(text string) DataObject {
|
||||||
|
|
||||||
|
|
@ -440,8 +494,8 @@ func ParseDataText(text string) DataObject {
|
||||||
endPos := pos
|
endPos := pos
|
||||||
skipSpaces(false)
|
skipSpaces(false)
|
||||||
if startPos == endPos {
|
if startPos == endPos {
|
||||||
ErrorLog("empty tag")
|
//ErrorLog("empty tag")
|
||||||
return "", false
|
return "", true
|
||||||
}
|
}
|
||||||
return string(data[startPos:endPos]), true
|
return string(data[startPos:endPos]), true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ type DatePicker interface {
|
||||||
|
|
||||||
type datePickerData struct {
|
type datePickerData struct {
|
||||||
viewData
|
viewData
|
||||||
dateChangedListeners []func(DatePicker, time.Time)
|
dateChangedListeners []func(DatePicker, time.Time, time.Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDatePicker create new DatePicker object and return it
|
// NewDatePicker create new DatePicker object and return it
|
||||||
|
|
@ -40,7 +40,7 @@ func newDatePicker(session Session) View {
|
||||||
func (picker *datePickerData) init(session Session) {
|
func (picker *datePickerData) init(session Session) {
|
||||||
picker.viewData.init(session)
|
picker.viewData.init(session)
|
||||||
picker.tag = "DatePicker"
|
picker.tag = "DatePicker"
|
||||||
picker.dateChangedListeners = []func(DatePicker, time.Time){}
|
picker.dateChangedListeners = []func(DatePicker, time.Time, time.Time){}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *datePickerData) String() string {
|
func (picker *datePickerData) String() string {
|
||||||
|
|
@ -69,7 +69,7 @@ func (picker *datePickerData) remove(tag string) {
|
||||||
switch tag {
|
switch tag {
|
||||||
case DateChangedEvent:
|
case DateChangedEvent:
|
||||||
if len(picker.dateChangedListeners) > 0 {
|
if len(picker.dateChangedListeners) > 0 {
|
||||||
picker.dateChangedListeners = []func(DatePicker, time.Time){}
|
picker.dateChangedListeners = []func(DatePicker, time.Time, time.Time){}
|
||||||
picker.propertyChangedEvent(tag)
|
picker.propertyChangedEvent(tag)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
@ -94,13 +94,14 @@ func (picker *datePickerData) remove(tag string) {
|
||||||
|
|
||||||
case DatePickerValue:
|
case DatePickerValue:
|
||||||
if _, ok := picker.properties[DatePickerValue]; ok {
|
if _, ok := picker.properties[DatePickerValue]; ok {
|
||||||
|
oldDate := GetDatePickerValue(picker)
|
||||||
delete(picker.properties, DatePickerValue)
|
delete(picker.properties, DatePickerValue)
|
||||||
date := GetDatePickerValue(picker)
|
date := GetDatePickerValue(picker)
|
||||||
if picker.created {
|
if picker.created {
|
||||||
picker.session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
|
picker.session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
|
||||||
}
|
}
|
||||||
for _, listener := range picker.dateChangedListeners {
|
for _, listener := range picker.dateChangedListeners {
|
||||||
listener(picker, date)
|
listener(picker, date, oldDate)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
|
|
@ -226,7 +227,7 @@ func (picker *datePickerData) set(tag string, value any) bool {
|
||||||
picker.session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
|
picker.session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
|
||||||
}
|
}
|
||||||
for _, listener := range picker.dateChangedListeners {
|
for _, listener := range picker.dateChangedListeners {
|
||||||
listener(picker, date)
|
listener(picker, date, oldDate)
|
||||||
}
|
}
|
||||||
picker.propertyChangedEvent(tag)
|
picker.propertyChangedEvent(tag)
|
||||||
}
|
}
|
||||||
|
|
@ -234,12 +235,12 @@ func (picker *datePickerData) set(tag string, value any) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
case DateChangedEvent:
|
case DateChangedEvent:
|
||||||
listeners, ok := valueToEventListeners[DatePicker, time.Time](value)
|
listeners, ok := valueToEventWithOldListeners[DatePicker, time.Time](value)
|
||||||
if !ok {
|
if !ok {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return false
|
||||||
} else if listeners == nil {
|
} else if listeners == nil {
|
||||||
listeners = []func(DatePicker, time.Time){}
|
listeners = []func(DatePicker, time.Time, time.Time){}
|
||||||
}
|
}
|
||||||
picker.dateChangedListeners = listeners
|
picker.dateChangedListeners = listeners
|
||||||
picker.propertyChangedEvent(tag)
|
picker.propertyChangedEvent(tag)
|
||||||
|
|
@ -318,7 +319,7 @@ func (picker *datePickerData) handleCommand(self View, command string, data Data
|
||||||
picker.properties[DatePickerValue] = value
|
picker.properties[DatePickerValue] = value
|
||||||
if value != oldValue {
|
if value != oldValue {
|
||||||
for _, listener := range picker.dateChangedListeners {
|
for _, listener := range picker.dateChangedListeners {
|
||||||
listener(picker, value)
|
listener(picker, value, oldValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -410,6 +411,6 @@ func GetDatePickerValue(view View, subviewID ...string) time.Time {
|
||||||
// GetDateChangedListeners returns the DateChangedListener list of an DatePicker subview.
|
// GetDateChangedListeners returns the DateChangedListener list of an DatePicker subview.
|
||||||
// If there are no listeners then the empty list is returned
|
// 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.
|
// 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) {
|
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time, time.Time) {
|
||||||
return getEventListeners[DatePicker, time.Time](view, subviewID, DateChangedEvent)
|
return getEventWithOldListeners[DatePicker, time.Time](view, subviewID, DateChangedEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,9 @@ theme {
|
||||||
ruiTabTextColor = #FF404040,
|
ruiTabTextColor = #FF404040,
|
||||||
ruiCurrentTabColor = #FFFFFFFF,
|
ruiCurrentTabColor = #FFFFFFFF,
|
||||||
ruiCurrentTabTextColor = #FF000000,
|
ruiCurrentTabTextColor = #FF000000,
|
||||||
|
ruiTooltipBackground = #FFFFFFFF,
|
||||||
|
ruiTooltipTextColor = #FF000000,
|
||||||
|
ruiTooltipShadowColor = #FF808080,
|
||||||
},
|
},
|
||||||
colors:dark = _{
|
colors:dark = _{
|
||||||
ruiTextColor = #FFE0E0E0,
|
ruiTextColor = #FFE0E0E0,
|
||||||
|
|
@ -43,6 +46,9 @@ theme {
|
||||||
ruiTabTextColor = #FFE0E0E0,
|
ruiTabTextColor = #FFE0E0E0,
|
||||||
ruiCurrentTabColor = #FF000000,
|
ruiCurrentTabColor = #FF000000,
|
||||||
ruiCurrentTabTextColor = #FFFFFFFF,
|
ruiCurrentTabTextColor = #FFFFFFFF,
|
||||||
|
ruiTooltipBackground = #FF303030,
|
||||||
|
ruiTooltipTextColor = #FFDDDDDD,
|
||||||
|
ruiTooltipShadowColor = #FFDDDDDD,
|
||||||
},
|
},
|
||||||
constants = _{
|
constants = _{
|
||||||
ruiButtonHorizontalPadding = 16px,
|
ruiButtonHorizontalPadding = 16px,
|
||||||
|
|
@ -104,6 +110,26 @@ theme {
|
||||||
ruiButton:active {
|
ruiButton:active {
|
||||||
background-color = @ruiButtonActiveColor
|
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 {
|
ruiCheckbox {
|
||||||
radius = 2px,
|
radius = 2px,
|
||||||
padding = 1px,
|
padding = 1px,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ func (session *sessionData) startDownload(file downloadFile) {
|
||||||
currentDownloadId++
|
currentDownloadId++
|
||||||
id := strconv.Itoa(currentDownloadId)
|
id := strconv.Itoa(currentDownloadId)
|
||||||
downloadFiles[id] = file
|
downloadFiles[id] = file
|
||||||
session.callFunc("startDowndload", id, file.filename)
|
session.callFunc("startDownload", id, file.filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveDownloadFile(id string, w http.ResponseWriter, r *http.Request) bool {
|
func serveDownloadFile(id string, w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ type dropDownListData struct {
|
||||||
viewData
|
viewData
|
||||||
items []string
|
items []string
|
||||||
disabledItems []any
|
disabledItems []any
|
||||||
dropDownListener []func(DropDownList, int)
|
dropDownListener []func(DropDownList, int, int)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDropDownList create new DropDownList object and return it
|
// NewDropDownList create new DropDownList object and return it
|
||||||
|
|
@ -41,7 +41,7 @@ func (list *dropDownListData) init(session Session) {
|
||||||
list.tag = "DropDownList"
|
list.tag = "DropDownList"
|
||||||
list.items = []string{}
|
list.items = []string{}
|
||||||
list.disabledItems = []any{}
|
list.disabledItems = []any{}
|
||||||
list.dropDownListener = []func(DropDownList, int){}
|
list.dropDownListener = []func(DropDownList, int, int){}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dropDownListData) String() string {
|
func (list *dropDownListData) String() string {
|
||||||
|
|
@ -78,7 +78,7 @@ func (list *dropDownListData) remove(tag string) {
|
||||||
|
|
||||||
case DropDownEvent:
|
case DropDownEvent:
|
||||||
if len(list.dropDownListener) > 0 {
|
if len(list.dropDownListener) > 0 {
|
||||||
list.dropDownListener = []func(DropDownList, int){}
|
list.dropDownListener = []func(DropDownList, int, int){}
|
||||||
list.propertyChangedEvent(tag)
|
list.propertyChangedEvent(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,7 +89,7 @@ func (list *dropDownListData) remove(tag string) {
|
||||||
if list.created {
|
if list.created {
|
||||||
list.session.callFunc("selectDropDownListItem", list.htmlID(), 0)
|
list.session.callFunc("selectDropDownListItem", list.htmlID(), 0)
|
||||||
}
|
}
|
||||||
list.onSelectedItemChanged(0)
|
list.onSelectedItemChanged(0, oldCurrent)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -116,12 +116,12 @@ func (list *dropDownListData) set(tag string, value any) bool {
|
||||||
return list.setDisabledItems(value)
|
return list.setDisabledItems(value)
|
||||||
|
|
||||||
case DropDownEvent:
|
case DropDownEvent:
|
||||||
listeners, ok := valueToEventListeners[DropDownList, int](value)
|
listeners, ok := valueToEventWithOldListeners[DropDownList, int](value)
|
||||||
if !ok {
|
if !ok {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return false
|
||||||
} else if listeners == nil {
|
} else if listeners == nil {
|
||||||
listeners = []func(DropDownList, int){}
|
listeners = []func(DropDownList, int, int){}
|
||||||
}
|
}
|
||||||
list.dropDownListener = listeners
|
list.dropDownListener = listeners
|
||||||
list.propertyChangedEvent(tag)
|
list.propertyChangedEvent(tag)
|
||||||
|
|
@ -137,7 +137,7 @@ func (list *dropDownListData) set(tag string, value any) bool {
|
||||||
if list.created {
|
if list.created {
|
||||||
list.session.callFunc("selectDropDownListItem", list.htmlID(), current)
|
list.session.callFunc("selectDropDownListItem", list.htmlID(), current)
|
||||||
}
|
}
|
||||||
list.onSelectedItemChanged(current)
|
list.onSelectedItemChanged(current, oldCurrent)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -377,9 +377,9 @@ func (list *dropDownListData) htmlDisabledProperties(self View, buffer *strings.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dropDownListData) onSelectedItemChanged(number int) {
|
func (list *dropDownListData) onSelectedItemChanged(number, old int) {
|
||||||
for _, listener := range list.dropDownListener {
|
for _, listener := range list.dropDownListener {
|
||||||
listener(list, number)
|
listener(list, number, old)
|
||||||
}
|
}
|
||||||
list.propertyChangedEvent(Current)
|
list.propertyChangedEvent(Current)
|
||||||
}
|
}
|
||||||
|
|
@ -390,8 +390,9 @@ func (list *dropDownListData) handleCommand(self View, command string, data Data
|
||||||
if text, ok := data.PropertyValue("number"); ok {
|
if text, ok := data.PropertyValue("number"); ok {
|
||||||
if number, err := strconv.Atoi(text); err == nil {
|
if number, err := strconv.Atoi(text); err == nil {
|
||||||
if GetCurrent(list) != number && number >= 0 && number < len(list.items) {
|
if GetCurrent(list) != number && number >= 0 && number < len(list.items) {
|
||||||
|
old := GetCurrent(list)
|
||||||
list.properties[Current] = number
|
list.properties[Current] = number
|
||||||
list.onSelectedItemChanged(number)
|
list.onSelectedItemChanged(number, old)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ErrorLog(err.Error())
|
ErrorLog(err.Error())
|
||||||
|
|
@ -406,8 +407,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.
|
// 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.
|
// 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) {
|
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int, int) {
|
||||||
return getEventListeners[DropDownList, int](view, subviewID, DropDownEvent)
|
return getEventWithOldListeners[DropDownList, int](view, subviewID, DropDownEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDropDownItems return the DropDownList items list.
|
// GetDropDownItems return the DropDownList items list.
|
||||||
|
|
|
||||||
33
editView.go
33
editView.go
|
|
@ -23,7 +23,7 @@ const (
|
||||||
PasswordText = 1
|
PasswordText = 1
|
||||||
// EmailText - e-mail type of EditView. Allows to enter one email
|
// EmailText - e-mail type of EditView. Allows to enter one email
|
||||||
EmailText = 2
|
EmailText = 2
|
||||||
// EmailsText - e-mail type of EditView. Allows to enter multiple emails separeted by comma
|
// EmailsText - e-mail type of EditView. Allows to enter multiple emails separated by comma
|
||||||
EmailsText = 3
|
EmailsText = 3
|
||||||
// URLText - url type of EditView. Allows to enter one url
|
// URLText - url type of EditView. Allows to enter one url
|
||||||
URLText = 4
|
URLText = 4
|
||||||
|
|
@ -41,7 +41,7 @@ type EditView interface {
|
||||||
|
|
||||||
type editViewData struct {
|
type editViewData struct {
|
||||||
viewData
|
viewData
|
||||||
textChangeListeners []func(EditView, string)
|
textChangeListeners []func(EditView, string, string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEditView create new EditView object and return it
|
// NewEditView create new EditView object and return it
|
||||||
|
|
@ -58,7 +58,7 @@ func newEditView(session Session) View {
|
||||||
|
|
||||||
func (edit *editViewData) init(session Session) {
|
func (edit *editViewData) init(session Session) {
|
||||||
edit.viewData.init(session)
|
edit.viewData.init(session)
|
||||||
edit.textChangeListeners = []func(EditView, string){}
|
edit.textChangeListeners = []func(EditView, string, string){}
|
||||||
edit.tag = "EditView"
|
edit.tag = "EditView"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,7 +125,7 @@ func (edit *editViewData) remove(tag string) {
|
||||||
|
|
||||||
case EditTextChangedEvent:
|
case EditTextChangedEvent:
|
||||||
if len(edit.textChangeListeners) > 0 {
|
if len(edit.textChangeListeners) > 0 {
|
||||||
edit.textChangeListeners = []func(EditView, string){}
|
edit.textChangeListeners = []func(EditView, string, string){}
|
||||||
edit.propertyChangedEvent(tag)
|
edit.propertyChangedEvent(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,7 +134,7 @@ func (edit *editViewData) remove(tag string) {
|
||||||
oldText := GetText(edit)
|
oldText := GetText(edit)
|
||||||
delete(edit.properties, tag)
|
delete(edit.properties, tag)
|
||||||
if oldText != "" {
|
if oldText != "" {
|
||||||
edit.textChanged("")
|
edit.textChanged("", oldText)
|
||||||
if edit.created {
|
if edit.created {
|
||||||
edit.session.callFunc("setInputValue", edit.htmlID(), "")
|
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 {
|
if text, ok := value.(string); ok {
|
||||||
edit.properties[Text] = text
|
edit.properties[Text] = text
|
||||||
if text = GetText(edit); oldText != text {
|
if text = GetText(edit); oldText != text {
|
||||||
edit.textChanged(text)
|
edit.textChanged(text, oldText)
|
||||||
if edit.created {
|
if edit.created {
|
||||||
if GetEditViewType(edit) == MultiLineText {
|
if GetEditViewType(edit) == MultiLineText {
|
||||||
updateInnerHTML(edit.htmlID(), edit.Session())
|
updateInnerHTML(edit.htmlID(), edit.Session())
|
||||||
|
|
@ -328,12 +328,12 @@ func (edit *editViewData) set(tag string, value any) bool {
|
||||||
return false
|
return false
|
||||||
|
|
||||||
case EditTextChangedEvent:
|
case EditTextChangedEvent:
|
||||||
listeners, ok := valueToEventListeners[EditView, string](value)
|
listeners, ok := valueToEventWithOldListeners[EditView, string](value)
|
||||||
if !ok {
|
if !ok {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return false
|
||||||
} else if listeners == nil {
|
} else if listeners == nil {
|
||||||
listeners = []func(EditView, string){}
|
listeners = []func(EditView, string, string){}
|
||||||
}
|
}
|
||||||
edit.textChangeListeners = listeners
|
edit.textChangeListeners = listeners
|
||||||
edit.propertyChangedEvent(tag)
|
edit.propertyChangedEvent(tag)
|
||||||
|
|
@ -358,10 +358,11 @@ func (edit *editViewData) AppendText(text string) {
|
||||||
if GetEditViewType(edit) == MultiLineText {
|
if GetEditViewType(edit) == MultiLineText {
|
||||||
if value := edit.getRaw(Text); value != nil {
|
if value := edit.getRaw(Text); value != nil {
|
||||||
if textValue, ok := value.(string); ok {
|
if textValue, ok := value.(string); ok {
|
||||||
|
oldText := textValue
|
||||||
textValue += text
|
textValue += text
|
||||||
edit.properties[Text] = textValue
|
edit.properties[Text] = textValue
|
||||||
edit.session.callFunc("appendToInnerHTML", edit.htmlID(), text)
|
edit.session.callFunc("appendToInnerHTML", edit.htmlID(), text)
|
||||||
edit.textChanged(textValue)
|
edit.textChanged(textValue, oldText)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -371,9 +372,9 @@ func (edit *editViewData) AppendText(text string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edit *editViewData) textChanged(newText string) {
|
func (edit *editViewData) textChanged(newText, oldText string) {
|
||||||
for _, listener := range edit.textChangeListeners {
|
for _, listener := range edit.textChangeListeners {
|
||||||
listener(edit, newText)
|
listener(edit, newText, oldText)
|
||||||
}
|
}
|
||||||
edit.propertyChangedEvent(Text)
|
edit.propertyChangedEvent(Text)
|
||||||
}
|
}
|
||||||
|
|
@ -485,7 +486,7 @@ func (edit *editViewData) handleCommand(self View, command string, data DataObje
|
||||||
if text, ok := data.PropertyValue("text"); ok {
|
if text, ok := data.PropertyValue("text"); ok {
|
||||||
edit.properties[Text] = text
|
edit.properties[Text] = text
|
||||||
if text := GetText(edit); text != oldText {
|
if text := GetText(edit); text != oldText {
|
||||||
edit.textChanged(text)
|
edit.textChanged(text, oldText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
@ -531,7 +532,7 @@ func GetHint(view View, subviewID ...string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMaxLength returns a maximal lenght of EditView. If a maximal lenght is not limited then 0 is returned
|
// GetMaxLength returns a maximal length of EditView. If a maximal length 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.
|
// 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 {
|
func GetMaxLength(view View, subviewID ...string) int {
|
||||||
return intStyledProperty(view, subviewID, MaxLength, 0)
|
return intStyledProperty(view, subviewID, MaxLength, 0)
|
||||||
|
|
@ -552,8 +553,8 @@ func IsSpellcheck(view View, subviewID ...string) bool {
|
||||||
// GetTextChangedListeners returns the TextChangedListener list of an EditView or MultiLineEditView subview.
|
// GetTextChangedListeners returns the TextChangedListener list of an EditView or MultiLineEditView subview.
|
||||||
// If there are no listeners then the empty list is returned
|
// 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.
|
// 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) {
|
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string, string) {
|
||||||
return getEventListeners[EditView, string](view, subviewID, EditTextChangedEvent)
|
return getEventWithOldListeners[EditView, string](view, subviewID, EditTextChangedEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEditViewType returns a value of the Type property of EditView.
|
// GetEditViewType returns a value of the Type property of EditView.
|
||||||
|
|
@ -604,7 +605,7 @@ func AppendEditText(view View, subviewID string, text string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCaretColor returns the color of the text input carret.
|
// GetCaretColor returns the color of the text input caret.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
// 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 {
|
func GetCaretColor(view View, subviewID ...string) Color {
|
||||||
return colorStyledProperty(view, subviewID, CaretColor, false)
|
return colorStyledProperty(view, subviewID, CaretColor, false)
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ func (picker *filePickerData) htmlDisabledProperties(self View, buffer *strings.
|
||||||
func (picker *filePickerData) handleCommand(self View, command string, data DataObject) bool {
|
func (picker *filePickerData) handleCommand(self View, command string, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "fileSelected":
|
case "fileSelected":
|
||||||
if node := data.PropertyWithTag("files"); node != nil && node.Type() == ArrayNode {
|
if node := data.PropertyByTag("files"); node != nil && node.Type() == ArrayNode {
|
||||||
count := node.ArraySize()
|
count := node.ArraySize()
|
||||||
files := make([]FileInfo, count)
|
files := make([]FileInfo, count)
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,10 @@ func getFocusListeners(view View, subviewID []string, tag string) []func(View) {
|
||||||
func focusEventsHtml(view View, buffer *strings.Builder) {
|
func focusEventsHtml(view View, buffer *strings.Builder) {
|
||||||
if view.Focusable() {
|
if view.Focusable() {
|
||||||
for _, js := range focusEvents {
|
for _, js := range focusEvents {
|
||||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
buffer.WriteString(js.jsEvent)
|
||||||
|
buffer.WriteString(`="`)
|
||||||
|
buffer.WriteString(js.jsFunc)
|
||||||
|
buffer.WriteString(`(this, event)" `)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
go.mod
8
go.mod
|
|
@ -1,5 +1,7 @@
|
||||||
module github.com/anoshenko/rui
|
module git.mbk-lab.ru/mbk-lab/rui
|
||||||
|
|
||||||
go 1.18
|
go 1.20
|
||||||
|
|
||||||
require github.com/gorilla/websocket v1.5.0
|
require github.com/gorilla/websocket v1.5.1
|
||||||
|
|
||||||
|
require golang.org/x/net v0.17.0 // indirect
|
||||||
|
|
|
||||||
6
go.sum
6
go.sum
|
|
@ -1,2 +1,4 @@
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
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=
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
25
image.go
25
image.go
|
|
@ -1,11 +1,7 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -83,27 +79,6 @@ func (manager *imageManager) loadImage(url string, onLoaded func(Image), session
|
||||||
image.loadingStatus = ImageLoading
|
image.loadingStatus = ImageLoading
|
||||||
manager.images[url] = image
|
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)
|
session.callFunc("loadImage", url)
|
||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
imageView.go
28
imageView.go
|
|
@ -1,10 +1,7 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -70,8 +67,7 @@ func newImageView(session Session) View {
|
||||||
func (imageView *imageViewData) init(session Session) {
|
func (imageView *imageViewData) init(session Session) {
|
||||||
imageView.viewData.init(session)
|
imageView.viewData.init(session)
|
||||||
imageView.tag = "ImageView"
|
imageView.tag = "ImageView"
|
||||||
//imageView.systemClass = "ruiImageView"
|
imageView.systemClass = "ruiImageView"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imageView *imageViewData) String() string {
|
func (imageView *imageViewData) String() string {
|
||||||
|
|
@ -272,27 +268,7 @@ func (imageView *imageViewData) src(src string) (string, string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if src != "" {
|
if src != "" {
|
||||||
srcset := imageView.srcSet(src)
|
return src, 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 "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
|
|
|
||||||
387
keyEvents.go
387
keyEvents.go
|
|
@ -20,6 +20,127 @@ const (
|
||||||
KeyUpEvent = "key-up-event"
|
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 {
|
type KeyEvent struct {
|
||||||
// TimeStamp is the time at which the event was created (in milliseconds).
|
// TimeStamp is the time at which the event was created (in milliseconds).
|
||||||
// This value is time since epoch—but in reality, browsers' definitions vary.
|
// This value is time since epoch—but in reality, browsers' definitions vary.
|
||||||
|
|
@ -32,7 +153,7 @@ type KeyEvent struct {
|
||||||
|
|
||||||
// Code holds a string that identifies the physical key being pressed. The value is not affected
|
// 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.
|
// by the current keyboard layout or modifier state, so a particular key will always return the same value.
|
||||||
Code string
|
Code KeyCode
|
||||||
|
|
||||||
// Repeat == true if a key has been depressed long enough to trigger key repetition, otherwise false.
|
// Repeat == true if a key has been depressed long enough to trigger key repetition, otherwise false.
|
||||||
Repeat bool
|
Repeat bool
|
||||||
|
|
@ -59,7 +180,8 @@ func (event *KeyEvent) init(data DataObject) {
|
||||||
}
|
}
|
||||||
|
|
||||||
event.Key, _ = data.PropertyValue("key")
|
event.Key, _ = data.PropertyValue("key")
|
||||||
event.Code, _ = data.PropertyValue("code")
|
code, _ := data.PropertyValue("code")
|
||||||
|
event.Code = KeyCode(code)
|
||||||
event.TimeStamp = getTimeStamp(data)
|
event.TimeStamp = getTimeStamp(data)
|
||||||
event.Repeat = getBool("repeat")
|
event.Repeat = getBool("repeat")
|
||||||
event.CtrlKey = getBool("ctrlKey")
|
event.CtrlKey = getBool("ctrlKey")
|
||||||
|
|
@ -193,9 +315,183 @@ func valueToEventListeners[V View, E any](value any) ([]func(V, E), bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
var keyEvents = map[string]struct{ jsEvent, jsFunc string }{
|
func valueToEventWithOldListeners[V View, E any](value any) ([]func(V, E, E), bool) {
|
||||||
KeyDownEvent: {jsEvent: "onkeydown", jsFunc: "keyDownEvent"},
|
if value == nil {
|
||||||
KeyUpEvent: {jsEvent: "onkeyup", jsFunc: "keyUpEvent"},
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *viewData) setKeyListener(tag string, value any) bool {
|
func (view *viewData) setKeyListener(tag string, value any) bool {
|
||||||
|
|
@ -207,26 +503,57 @@ func (view *viewData) setKeyListener(tag string, value any) bool {
|
||||||
|
|
||||||
if listeners == nil {
|
if listeners == nil {
|
||||||
view.removeKeyListener(tag)
|
view.removeKeyListener(tag)
|
||||||
} else if js, ok := keyEvents[tag]; ok {
|
} else {
|
||||||
|
switch tag {
|
||||||
|
case KeyDownEvent:
|
||||||
view.properties[tag] = listeners
|
view.properties[tag] = listeners
|
||||||
if view.created {
|
if view.created {
|
||||||
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
|
view.session.updateProperty(view.htmlID(), "onkeydown", "keyDownEvent(this, event)")
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
case KeyUpEvent:
|
||||||
|
view.properties[tag] = listeners
|
||||||
|
if view.created {
|
||||||
|
view.session.updateProperty(view.htmlID(), "onkeyup", "keyUpEvent(this, event)")
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *viewData) removeKeyListener(tag string) {
|
func (view *viewData) removeKeyListener(tag string) {
|
||||||
delete(view.properties, tag)
|
delete(view.properties, tag)
|
||||||
if view.created {
|
if view.created {
|
||||||
if js, ok := keyEvents[tag]; ok {
|
switch tag {
|
||||||
view.session.removeProperty(view.htmlID(), js.jsEvent)
|
case KeyDownEvent:
|
||||||
|
if !view.Focusable() {
|
||||||
|
view.session.removeProperty(view.htmlID(), "onkeydown")
|
||||||
|
}
|
||||||
|
|
||||||
|
case KeyUpEvent:
|
||||||
|
view.session.removeProperty(view.htmlID(), "onkeyup")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func getEventListeners[V View, E any](view View, subviewID []string, tag string) []func(V, E) {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if len(subviewID) > 0 && subviewID[0] != "" {
|
||||||
view = ViewByID(view, subviewID[0])
|
view = ViewByID(view, subviewID[0])
|
||||||
|
|
@ -242,22 +569,52 @@ func getEventListeners[V View, E any](view View, subviewID []string, tag string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyEventsHtml(view View, buffer *strings.Builder) {
|
func keyEventsHtml(view View, buffer *strings.Builder) {
|
||||||
for tag, js := range keyEvents {
|
if len(getEventListeners[View, KeyEvent](view, nil, KeyDownEvent)) > 0 {
|
||||||
if listeners := getEventListeners[View, KeyEvent](view, nil, tag); len(listeners) > 0 {
|
buffer.WriteString(`onkeydown="keyDownEvent(this, event)" `)
|
||||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
} else if view.Focusable() {
|
||||||
|
if len(getEventListeners[View, MouseEvent](view, nil, ClickEvent)) > 0 {
|
||||||
|
buffer.WriteString(`onkeydown="keyDownEvent(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) {
|
func handleKeyEvents(view View, tag string, data DataObject) {
|
||||||
listeners := getEventListeners[View, KeyEvent](view, nil, tag)
|
|
||||||
if len(listeners) > 0 {
|
|
||||||
var event KeyEvent
|
var event KeyEvent
|
||||||
event.init(data)
|
event.init(data)
|
||||||
|
listeners := getEventListeners[View, KeyEvent](view, nil, tag)
|
||||||
|
|
||||||
|
if len(listeners) > 0 {
|
||||||
for _, listener := range listeners {
|
for _, listener := range listeners {
|
||||||
listener(view, event)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
111
listView.go
111
listView.go
|
|
@ -47,7 +47,7 @@ const (
|
||||||
// ListView - the list view interface
|
// ListView - the list view interface
|
||||||
type ListView interface {
|
type ListView interface {
|
||||||
View
|
View
|
||||||
ParanetView
|
ParentView
|
||||||
// ReloadListViewData updates ListView content
|
// ReloadListViewData updates ListView content
|
||||||
ReloadListViewData()
|
ReloadListViewData()
|
||||||
|
|
||||||
|
|
@ -129,78 +129,92 @@ func (listView *listViewData) remove(tag string) {
|
||||||
case Gap:
|
case Gap:
|
||||||
listView.remove(ListRowGap)
|
listView.remove(ListRowGap)
|
||||||
listView.remove(ListColumnGap)
|
listView.remove(ListColumnGap)
|
||||||
|
return
|
||||||
|
|
||||||
case Checked:
|
case Checked:
|
||||||
if len(listView.checkedItem) > 0 {
|
if len(listView.checkedItem) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
listView.checkedItem = []int{}
|
listView.checkedItem = []int{}
|
||||||
if listView.created {
|
if listView.created {
|
||||||
updateInnerHTML(listView.htmlID(), listView.session)
|
updateInnerHTML(listView.htmlID(), listView.session)
|
||||||
}
|
}
|
||||||
listView.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case Items:
|
case Items:
|
||||||
if listView.adapter != nil {
|
if listView.adapter == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
listView.adapter = nil
|
listView.adapter = nil
|
||||||
if listView.created {
|
if listView.created {
|
||||||
updateInnerHTML(listView.htmlID(), listView.session)
|
updateInnerHTML(listView.htmlID(), listView.session)
|
||||||
}
|
}
|
||||||
listView.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case Orientation, ListWrap:
|
case Orientation, ListWrap:
|
||||||
if _, ok := listView.properties[tag]; ok {
|
if _, ok := listView.properties[tag]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
delete(listView.properties, tag)
|
delete(listView.properties, tag)
|
||||||
if listView.created {
|
if listView.created {
|
||||||
updateCSSStyle(listView.htmlID(), listView.session)
|
updateCSSStyle(listView.htmlID(), listView.session)
|
||||||
}
|
}
|
||||||
listView.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case Current:
|
case Current:
|
||||||
current := GetCurrent(listView)
|
current := GetCurrent(listView)
|
||||||
|
if current == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
delete(listView.properties, tag)
|
delete(listView.properties, tag)
|
||||||
if listView.created {
|
if listView.created {
|
||||||
updateInnerHTML(listView.htmlID(), listView.session)
|
htmlID := listView.htmlID()
|
||||||
|
session := listView.session
|
||||||
|
session.removeProperty(htmlID, "data-current")
|
||||||
|
updateInnerHTML(htmlID, session)
|
||||||
}
|
}
|
||||||
if current != -1 {
|
if current != -1 {
|
||||||
for _, listener := range listView.selectedListeners {
|
for _, listener := range listView.selectedListeners {
|
||||||
listener(listView, -1)
|
listener(listView, -1)
|
||||||
}
|
}
|
||||||
listView.propertyChangedEvent(tag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case ItemWidth, ItemHeight, ItemHorizontalAlign, ItemVerticalAlign, ItemCheckbox,
|
case ItemWidth, ItemHeight, ItemHorizontalAlign, ItemVerticalAlign, ItemCheckbox,
|
||||||
CheckboxHorizontalAlign, CheckboxVerticalAlign, ListItemStyle, CurrentStyle, CurrentInactiveStyle:
|
CheckboxHorizontalAlign, CheckboxVerticalAlign:
|
||||||
if _, ok := listView.properties[tag]; ok {
|
if _, ok := listView.properties[tag]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
delete(listView.properties, tag)
|
delete(listView.properties, tag)
|
||||||
if listView.created {
|
if listView.created {
|
||||||
updateInnerHTML(listView.htmlID(), listView.session)
|
updateInnerHTML(listView.htmlID(), listView.session)
|
||||||
}
|
}
|
||||||
listView.propertyChangedEvent(tag)
|
|
||||||
|
case ListItemStyle, CurrentStyle, CurrentInactiveStyle:
|
||||||
|
if !listView.setItemStyle(tag, "") {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case ListItemClickedEvent:
|
case ListItemClickedEvent:
|
||||||
if len(listView.clickedListeners) > 0 {
|
if len(listView.clickedListeners) == 0 {
|
||||||
listView.clickedListeners = []func(ListView, int){}
|
return
|
||||||
listView.propertyChangedEvent(tag)
|
|
||||||
}
|
}
|
||||||
|
listView.clickedListeners = []func(ListView, int){}
|
||||||
|
|
||||||
case ListItemSelectedEvent:
|
case ListItemSelectedEvent:
|
||||||
if len(listView.selectedListeners) > 0 {
|
if len(listView.selectedListeners) == 0 {
|
||||||
listView.selectedListeners = []func(ListView, int){}
|
return
|
||||||
listView.propertyChangedEvent(tag)
|
|
||||||
}
|
}
|
||||||
|
listView.selectedListeners = []func(ListView, int){}
|
||||||
|
|
||||||
case ListItemCheckedEvent:
|
case ListItemCheckedEvent:
|
||||||
if len(listView.checkedListeners) > 0 {
|
if len(listView.checkedListeners) == 0 {
|
||||||
listView.checkedListeners = []func(ListView, []int){}
|
return
|
||||||
listView.propertyChangedEvent(tag)
|
|
||||||
}
|
}
|
||||||
|
listView.checkedListeners = []func(ListView, []int){}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
listView.viewData.remove(tag)
|
listView.viewData.remove(tag)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listView.propertyChangedEvent(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) Set(tag string, value any) bool {
|
func (listView *listViewData) Set(tag string, value any) bool {
|
||||||
|
|
@ -268,11 +282,20 @@ func (listView *listViewData) set(tag string, value any) bool {
|
||||||
if !listView.setIntProperty(Current, value) {
|
if !listView.setIntProperty(Current, value) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
current := GetCurrent(listView)
|
current := GetCurrent(listView)
|
||||||
if oldCurrent == current {
|
if oldCurrent == current {
|
||||||
return true
|
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 {
|
for _, listener := range listView.selectedListeners {
|
||||||
listener(listView, current)
|
listener(listView, current)
|
||||||
}
|
}
|
||||||
|
|
@ -290,12 +313,7 @@ func (listView *listViewData) set(tag string, value any) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
case ListItemStyle, CurrentStyle, CurrentInactiveStyle:
|
case ListItemStyle, CurrentStyle, CurrentInactiveStyle:
|
||||||
switch value := value.(type) {
|
if !listView.setItemStyle(tag, value) {
|
||||||
case string:
|
|
||||||
listView.properties[tag] = value
|
|
||||||
|
|
||||||
default:
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,6 +328,33 @@ func (listView *listViewData) set(tag string, value any) bool {
|
||||||
return true
|
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 {
|
func (listView *listViewData) Get(tag string) any {
|
||||||
return listView.get(listView.normalizeTag(tag))
|
return listView.get(listView.normalizeTag(tag))
|
||||||
}
|
}
|
||||||
|
|
@ -740,6 +785,9 @@ 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;`)
|
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)
|
listView.itemSize(self, buffer)
|
||||||
|
if !listView.adapter.IsListItemEnabled(i) {
|
||||||
|
buffer.WriteString(`" data-disabled="1`)
|
||||||
|
}
|
||||||
buffer.WriteString(`">`)
|
buffer.WriteString(`">`)
|
||||||
buffer.WriteString(itemDiv)
|
buffer.WriteString(itemDiv)
|
||||||
|
|
||||||
|
|
@ -796,6 +844,9 @@ func (listView *listViewData) noneCheckboxSubviews(self View, buffer *strings.Bu
|
||||||
}
|
}
|
||||||
buffer.WriteString(`" `)
|
buffer.WriteString(`" `)
|
||||||
buffer.WriteString(itemStyle)
|
buffer.WriteString(itemStyle)
|
||||||
|
if !listView.adapter.IsListItemEnabled(i) {
|
||||||
|
buffer.WriteString(` data-disabled="1"`)
|
||||||
|
}
|
||||||
buffer.WriteString(`>`)
|
buffer.WriteString(`>`)
|
||||||
|
|
||||||
if view := listView.getItemView(i); view != nil {
|
if view := listView.getItemView(i); view != nil {
|
||||||
|
|
|
||||||
|
|
@ -184,14 +184,22 @@ func (view *viewData) removeMouseListener(tag string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mouseEventsHtml(view View, buffer *strings.Builder) {
|
func mouseEventsHtml(view View, buffer *strings.Builder, hasTooltip bool) {
|
||||||
for tag, js := range mouseEvents {
|
for tag, js := range mouseEvents {
|
||||||
if value := view.getRaw(tag); value != nil {
|
if value := view.getRaw(tag); value != nil {
|
||||||
if listeners, ok := value.([]func(View, MouseEvent)); ok && len(listeners) > 0 {
|
if listeners, ok := value.([]func(View, MouseEvent)); ok && len(listeners) > 0 {
|
||||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
buffer.WriteString(js.jsEvent)
|
||||||
|
buffer.WriteString(`="`)
|
||||||
|
buffer.WriteString(js.jsFunc)
|
||||||
|
buffer.WriteString(`(this, event)" `)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hasTooltip {
|
||||||
|
buffer.WriteString(`onmouseenter="mouseEnterEvent(this, event)" `)
|
||||||
|
buffer.WriteString(`onmouseleave="mouseLeaveEvent(this, event)" `)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTimeStamp(data DataObject) uint64 {
|
func getTimeStamp(data DataObject) uint64 {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ type NumberPicker interface {
|
||||||
|
|
||||||
type numberPickerData struct {
|
type numberPickerData struct {
|
||||||
viewData
|
viewData
|
||||||
numberChangedListeners []func(NumberPicker, float64)
|
numberChangedListeners []func(NumberPicker, float64, float64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNumberPicker create new NumberPicker object and return it
|
// NewNumberPicker create new NumberPicker object and return it
|
||||||
|
|
@ -47,7 +47,7 @@ func newNumberPicker(session Session) View {
|
||||||
func (picker *numberPickerData) init(session Session) {
|
func (picker *numberPickerData) init(session Session) {
|
||||||
picker.viewData.init(session)
|
picker.viewData.init(session)
|
||||||
picker.tag = "NumberPicker"
|
picker.tag = "NumberPicker"
|
||||||
picker.numberChangedListeners = []func(NumberPicker, float64){}
|
picker.numberChangedListeners = []func(NumberPicker, float64, float64){}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *numberPickerData) String() string {
|
func (picker *numberPickerData) String() string {
|
||||||
|
|
@ -76,7 +76,20 @@ func (picker *numberPickerData) remove(tag string) {
|
||||||
switch tag {
|
switch tag {
|
||||||
case NumberChangedEvent:
|
case NumberChangedEvent:
|
||||||
if len(picker.numberChangedListeners) > 0 {
|
if len(picker.numberChangedListeners) > 0 {
|
||||||
picker.numberChangedListeners = []func(NumberPicker, float64){}
|
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.propertyChangedEvent(tag)
|
picker.propertyChangedEvent(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,12 +111,12 @@ func (picker *numberPickerData) set(tag string, value any) bool {
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case NumberChangedEvent:
|
case NumberChangedEvent:
|
||||||
listeners, ok := valueToEventListeners[NumberPicker, float64](value)
|
listeners, ok := valueToEventWithOldListeners[NumberPicker, float64](value)
|
||||||
if !ok {
|
if !ok {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return false
|
||||||
} else if listeners == nil {
|
} else if listeners == nil {
|
||||||
listeners = []func(NumberPicker, float64){}
|
listeners = []func(NumberPicker, float64, float64){}
|
||||||
}
|
}
|
||||||
picker.numberChangedListeners = listeners
|
picker.numberChangedListeners = listeners
|
||||||
picker.propertyChangedEvent(tag)
|
picker.propertyChangedEvent(tag)
|
||||||
|
|
@ -119,7 +132,7 @@ func (picker *numberPickerData) set(tag string, value any) bool {
|
||||||
picker.session.callFunc("setInputValue", picker.htmlID(), newValue)
|
picker.session.callFunc("setInputValue", picker.htmlID(), newValue)
|
||||||
}
|
}
|
||||||
for _, listener := range picker.numberChangedListeners {
|
for _, listener := range picker.numberChangedListeners {
|
||||||
listener(picker, f)
|
listener(picker, f, oldValue)
|
||||||
}
|
}
|
||||||
picker.propertyChangedEvent(tag)
|
picker.propertyChangedEvent(tag)
|
||||||
}
|
}
|
||||||
|
|
@ -159,13 +172,6 @@ func (picker *numberPickerData) propertyChanged(tag string) {
|
||||||
} else {
|
} else {
|
||||||
picker.session.updateProperty(picker.htmlID(), Step, "any")
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +248,7 @@ func (picker *numberPickerData) handleCommand(self View, command string, data Da
|
||||||
picker.properties[NumberPickerValue] = text
|
picker.properties[NumberPickerValue] = text
|
||||||
if value != oldValue {
|
if value != oldValue {
|
||||||
for _, listener := range picker.numberChangedListeners {
|
for _, listener := range picker.numberChangedListeners {
|
||||||
listener(picker, value)
|
listener(picker, value, oldValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -323,6 +329,6 @@ func GetNumberPickerValue(view View, subviewID ...string) float64 {
|
||||||
// GetNumberChangedListeners returns the NumberChangedListener list of an NumberPicker subview.
|
// GetNumberChangedListeners returns the NumberChangedListener list of an NumberPicker subview.
|
||||||
// If there are no listeners then the empty list is returned
|
// 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.
|
// 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) {
|
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64, float64) {
|
||||||
return getEventListeners[NumberPicker, float64](view, subviewID, NumberChangedEvent)
|
return getEventWithOldListeners[NumberPicker, float64](view, subviewID, NumberChangedEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,10 @@ func pointerEventsHtml(view View, buffer *strings.Builder) {
|
||||||
for tag, js := range pointerEvents {
|
for tag, js := range pointerEvents {
|
||||||
if value := view.getRaw(tag); value != nil {
|
if value := view.getRaw(tag); value != nil {
|
||||||
if listeners, ok := value.([]func(View, PointerEvent)); ok && len(listeners) > 0 {
|
if listeners, ok := value.([]func(View, PointerEvent)); ok && len(listeners) > 0 {
|
||||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
buffer.WriteString(js.jsEvent)
|
||||||
|
buffer.WriteString(`="`)
|
||||||
|
buffer.WriteString(js.jsFunc)
|
||||||
|
buffer.WriteString(`(this, event)" `)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
100
popup.go
100
popup.go
|
|
@ -78,11 +78,21 @@ const (
|
||||||
// LeftArrow is value of the popup "arrow" property:
|
// LeftArrow is value of the popup "arrow" property:
|
||||||
// Arrow on the left side of the pop-up window
|
// Arrow on the left side of the pop-up window
|
||||||
LeftArrow = 4
|
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.
|
// PopupButton describes a button that will be placed at the bottom of the window.
|
||||||
type PopupButton struct {
|
type PopupButton struct {
|
||||||
Title string
|
Title string
|
||||||
|
Type PopupButtonType
|
||||||
OnClick func(Popup)
|
OnClick func(Popup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,11 +105,14 @@ type Popup interface {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
html(buffer *strings.Builder)
|
html(buffer *strings.Builder)
|
||||||
viewByHTMLID(id string) View
|
viewByHTMLID(id string) View
|
||||||
|
keyEvent(event KeyEvent) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type popupData struct {
|
type popupData struct {
|
||||||
layerView View
|
layerView View
|
||||||
view View
|
view View
|
||||||
|
buttons []PopupButton
|
||||||
|
cancelable bool
|
||||||
dismissListener []func(Popup)
|
dismissListener []func(Popup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,6 +286,7 @@ func (arrow *popupArrow) createView(popupView View) View {
|
||||||
|
|
||||||
func (popup *popupData) init(view View, popupParams Params) {
|
func (popup *popupData) init(view View, popupParams Params) {
|
||||||
popup.view = view
|
popup.view = view
|
||||||
|
popup.cancelable = false
|
||||||
session := view.Session()
|
session := view.Session()
|
||||||
|
|
||||||
columnCount := 3
|
columnCount := 3
|
||||||
|
|
@ -392,13 +406,15 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
TextSize: Px(20),
|
TextSize: Px(20),
|
||||||
Content: "✕",
|
Content: "✕",
|
||||||
NotTranslate: true,
|
NotTranslate: true,
|
||||||
ClickEvent: func(View) {
|
ClickEvent: popup.cancel,
|
||||||
popup.Dismiss()
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
popup.cancelable = true
|
||||||
|
|
||||||
case OutsideClose:
|
case OutsideClose:
|
||||||
outsideClose, _ = boolProperty(popupParams, OutsideClose, session)
|
outsideClose, _ = boolProperty(popupParams, OutsideClose, session)
|
||||||
|
if outsideClose {
|
||||||
|
popup.cancelable = true
|
||||||
|
}
|
||||||
|
|
||||||
case Buttons:
|
case Buttons:
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
|
|
@ -494,6 +510,7 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
view.Set(Row, viewRow)
|
view.Set(Row, viewRow)
|
||||||
popupView.Append(view)
|
popupView.Append(view)
|
||||||
|
|
||||||
|
popup.buttons = buttons
|
||||||
if buttonCount := len(buttons); buttonCount > 0 {
|
if buttonCount := len(buttons); buttonCount > 0 {
|
||||||
buttonsAlign, _ := enumProperty(params, ButtonsAlign, session, RightAlign)
|
buttonsAlign, _ := enumProperty(params, ButtonsAlign, session, RightAlign)
|
||||||
popupCellHeight = append(popupCellHeight, AutoSize())
|
popupCellHeight = append(popupCellHeight, AutoSize())
|
||||||
|
|
@ -511,21 +528,31 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
buttonsPanel.Set(Margin, gap)
|
buttonsPanel.Set(Margin, gap)
|
||||||
}
|
}
|
||||||
|
|
||||||
createButton := func(n int, button PopupButton) Button {
|
|
||||||
return NewButton(session, Params{
|
|
||||||
Column: n,
|
|
||||||
Content: button.Title,
|
|
||||||
ClickEvent: func() {
|
|
||||||
if button.OnClick != nil {
|
|
||||||
button.OnClick(popup)
|
|
||||||
} else {
|
|
||||||
popup.Dismiss()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for i, button := range buttons {
|
for i, button := range buttons {
|
||||||
buttonsPanel.Append(createButton(i, button))
|
title := button.Title
|
||||||
|
if title == "" && button.Type == CancelButton {
|
||||||
|
title = "Cancel"
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonView := NewButton(session, Params{
|
||||||
|
Column: i,
|
||||||
|
Content: title,
|
||||||
|
})
|
||||||
|
|
||||||
|
if button.OnClick != nil {
|
||||||
|
fn := button.OnClick
|
||||||
|
buttonView.Set(ClickEvent, func() {
|
||||||
|
fn(popup)
|
||||||
|
})
|
||||||
|
} else if button.Type == CancelButton {
|
||||||
|
buttonView.Set(ClickEvent, popup.cancel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if button.Type == DefaultButton {
|
||||||
|
buttonView.Set(Style, "ruiDefaultButton")
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonsPanel.Append(buttonView)
|
||||||
}
|
}
|
||||||
|
|
||||||
popupView.Append(NewGridLayout(session, Params{
|
popupView.Append(NewGridLayout(session, Params{
|
||||||
|
|
@ -544,11 +571,8 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
popup.layerView = NewGridLayout(session, layerParams)
|
popup.layerView = NewGridLayout(session, layerParams)
|
||||||
|
|
||||||
if outsideClose {
|
if outsideClose {
|
||||||
popup.layerView.Set(ClickEvent, func(View) {
|
popup.layerView.Set(ClickEvent, popup.cancel)
|
||||||
popup.Dismiss()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -560,12 +584,21 @@ func (popup *popupData) Session() Session {
|
||||||
return popup.view.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() {
|
func (popup *popupData) Dismiss() {
|
||||||
popup.Session().popupManager().dismissPopup(popup)
|
popup.Session().popupManager().dismissPopup(popup)
|
||||||
for _, listener := range popup.dismissListener {
|
for _, listener := range popup.dismissListener {
|
||||||
listener(popup)
|
listener(popup)
|
||||||
}
|
}
|
||||||
// TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (popup *popupData) Show() {
|
func (popup *popupData) Show() {
|
||||||
|
|
@ -587,6 +620,27 @@ 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
|
// NewPopup creates a new Popup
|
||||||
func NewPopup(view View, param Params) Popup {
|
func NewPopup(view View, param Params) Popup {
|
||||||
if view == nil {
|
if view == nil {
|
||||||
|
|
@ -637,6 +691,8 @@ func (manager *popupManager) showPopup(popup Popup) {
|
||||||
|
|
||||||
session.callFunc("blurCurrent")
|
session.callFunc("blurCurrent")
|
||||||
manager.updatePopupLayerInnerHTML(session)
|
manager.updatePopupLayerInnerHTML(session)
|
||||||
|
session.updateCSSProperty("ruiTooltipLayer", "visibility", "hidden")
|
||||||
|
session.updateCSSProperty("ruiTooltipLayer", "opacity", "0")
|
||||||
session.updateCSSProperty("ruiPopupLayer", "visibility", "visible")
|
session.updateCSSProperty("ruiPopupLayer", "visibility", "visible")
|
||||||
session.updateCSSProperty("ruiRoot", "pointer-events", "none")
|
session.updateCSSProperty("ruiRoot", "pointer-events", "none")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,17 +28,9 @@ func ShowQuestion(title, text string, session Session, onYes func(), onNo func()
|
||||||
CloseButton: false,
|
CloseButton: false,
|
||||||
OutsideClose: false,
|
OutsideClose: false,
|
||||||
Buttons: []PopupButton{
|
Buttons: []PopupButton{
|
||||||
{
|
|
||||||
Title: "No",
|
|
||||||
OnClick: func(popup Popup) {
|
|
||||||
popup.Dismiss()
|
|
||||||
if onNo != nil {
|
|
||||||
onNo()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Title: "Yes",
|
Title: "Yes",
|
||||||
|
Type: DefaultButton,
|
||||||
OnClick: func(popup Popup) {
|
OnClick: func(popup Popup) {
|
||||||
popup.Dismiss()
|
popup.Dismiss()
|
||||||
if onYes != nil {
|
if onYes != nil {
|
||||||
|
|
@ -46,6 +38,16 @@ func ShowQuestion(title, text string, session Session, onYes func(), onNo func()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Title: "No",
|
||||||
|
Type: CancelButton,
|
||||||
|
OnClick: func(popup Popup) {
|
||||||
|
popup.Dismiss()
|
||||||
|
if onNo != nil {
|
||||||
|
onNo()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if title != "" {
|
if title != "" {
|
||||||
|
|
@ -68,11 +70,12 @@ func ShowCancellableQuestion(title, text string, session Session, onYes func(),
|
||||||
OutsideClose: false,
|
OutsideClose: false,
|
||||||
Buttons: []PopupButton{
|
Buttons: []PopupButton{
|
||||||
{
|
{
|
||||||
Title: "Cancel",
|
Title: "Yes",
|
||||||
|
Type: DefaultButton,
|
||||||
OnClick: func(popup Popup) {
|
OnClick: func(popup Popup) {
|
||||||
popup.Dismiss()
|
popup.Dismiss()
|
||||||
if onCancel != nil {
|
if onYes != nil {
|
||||||
onCancel()
|
onYes()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -86,11 +89,12 @@ func ShowCancellableQuestion(title, text string, session Session, onYes func(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Title: "Yes",
|
Title: "Cancel",
|
||||||
|
Type: CancelButton,
|
||||||
OnClick: func(popup Popup) {
|
OnClick: func(popup Popup) {
|
||||||
popup.Dismiss()
|
popup.Dismiss()
|
||||||
if onYes != nil {
|
if onCancel != nil {
|
||||||
onYes()
|
onCancel()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -127,10 +131,14 @@ func (popup *popupMenuData) ListSize() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (popup *popupMenuData) ListItem(index int, session Session) View {
|
func (popup *popupMenuData) ListItem(index int, session Session) View {
|
||||||
return NewTextView(popup.session, Params{
|
view := NewTextView(popup.session, Params{
|
||||||
Text: popup.items[index],
|
Text: popup.items[index],
|
||||||
Style: "ruiPopupMenuItem",
|
Style: "ruiPopupMenuItem",
|
||||||
})
|
})
|
||||||
|
if !popup.IsListItemEnabled(index) {
|
||||||
|
view.Set(TextColor, "@ruiDisabledTextColor")
|
||||||
|
}
|
||||||
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
func (popup *popupMenuData) IsListItemEnabled(index int) bool {
|
func (popup *popupMenuData) IsListItemEnabled(index int) bool {
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,10 @@ const (
|
||||||
// The "outline-width" SizeUnit property sets the width of an view's outline.
|
// The "outline-width" SizeUnit property sets the width of an view's outline.
|
||||||
OutlineWidth = "outline-width"
|
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.
|
// Shadow is the constant for the "shadow" property tag.
|
||||||
// The "shadow" property adds shadow effects around a view's frame. A shadow is described
|
// 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.
|
// by X and Y offsets relative to the element, blur and spread radius, and color.
|
||||||
|
|
@ -421,7 +425,7 @@ const (
|
||||||
// This is an inherited property, i.e. if it is not defined, then the value of the parent view is used.
|
// 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"
|
VerticalTextOrientation = "vertical-text-orientation"
|
||||||
|
|
||||||
// TextTverflow is the constant for the "text-overflow" property tag.
|
// TextOverflow is the constant for the "text-overflow" property tag.
|
||||||
// The "text-overflow" int property sets how hidden overflow content is signaled to users.
|
// 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
|
// It can be clipped or display an ellipsis ('…'). Valid values are
|
||||||
TextOverflow = "text-overflow"
|
TextOverflow = "text-overflow"
|
||||||
|
|
@ -538,7 +542,7 @@ const (
|
||||||
|
|
||||||
// AvoidBreak is the constant for the "avoid-break" property tag.
|
// 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.
|
// The "avoid-break" bool property sets how region breaks should behave inside a generated box.
|
||||||
// If the property value is "true" then fvoids any break from being inserted within the principal box.
|
// If the property value is "true" then avoids 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
|
// If the property value is "false" then allows, but does not force, any break to be inserted within
|
||||||
// the principal box.
|
// the principal box.
|
||||||
AvoidBreak = "avoid-break"
|
AvoidBreak = "avoid-break"
|
||||||
|
|
@ -656,7 +660,7 @@ const (
|
||||||
// allowing text and inline Views to wrap around it.
|
// allowing text and inline Views to wrap around it.
|
||||||
Float = "float"
|
Float = "float"
|
||||||
|
|
||||||
// UsetData is the constant for the "user-data" property tag.
|
// UserData is the constant for the "user-data" property tag.
|
||||||
// The "user-data" property can contain any user data
|
// The "user-data" property can contain any user data
|
||||||
UserData = "user-data"
|
UserData = "user-data"
|
||||||
|
|
||||||
|
|
@ -669,4 +673,35 @@ const (
|
||||||
// The "user-select" bool property controls whether the user can select text.
|
// 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.
|
// This is an inherited property, i.e. if it is not defined, then the value of the parent view is used.
|
||||||
UserSelect = "user-select"
|
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"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ var boolProperties = []string{
|
||||||
TabCloseButton,
|
TabCloseButton,
|
||||||
Repeating,
|
Repeating,
|
||||||
UserSelect,
|
UserSelect,
|
||||||
|
ColumnSpanAll,
|
||||||
}
|
}
|
||||||
|
|
||||||
var intProperties = []string{
|
var intProperties = []string{
|
||||||
|
|
@ -73,6 +74,8 @@ var intProperties = []string{
|
||||||
RowSpan,
|
RowSpan,
|
||||||
ColumnSpan,
|
ColumnSpan,
|
||||||
ColumnCount,
|
ColumnCount,
|
||||||
|
Order,
|
||||||
|
TabIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
var floatProperties = map[string]struct{ min, max float64 }{
|
var floatProperties = map[string]struct{ min, max float64 }{
|
||||||
|
|
@ -133,6 +136,7 @@ var sizeProperties = map[string]string{
|
||||||
BorderTopWidth: BorderTopWidth,
|
BorderTopWidth: BorderTopWidth,
|
||||||
BorderBottomWidth: BorderBottomWidth,
|
BorderBottomWidth: BorderBottomWidth,
|
||||||
OutlineWidth: OutlineWidth,
|
OutlineWidth: OutlineWidth,
|
||||||
|
OutlineOffset: OutlineOffset,
|
||||||
XOffset: XOffset,
|
XOffset: XOffset,
|
||||||
YOffset: YOffset,
|
YOffset: YOffset,
|
||||||
BlurRadius: BlurRadius,
|
BlurRadius: BlurRadius,
|
||||||
|
|
@ -447,6 +451,21 @@ var enumProperties = map[string]struct {
|
||||||
"",
|
"",
|
||||||
[]string{"none", "top", "right", "bottom", "left"},
|
[]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) {
|
func notCompatibleType(tag string, value any) {
|
||||||
|
|
|
||||||
|
|
@ -307,4 +307,92 @@ const (
|
||||||
// "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later.
|
// "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.
|
// This may cause views to appear out-of-order, when doing so would fill in holes left by larger views.
|
||||||
ColumnDenseAutoFlow = 3
|
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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ const (
|
||||||
// Resizable - grid-container of View
|
// Resizable - grid-container of View
|
||||||
type Resizable interface {
|
type Resizable interface {
|
||||||
View
|
View
|
||||||
ParanetView
|
ParentView
|
||||||
}
|
}
|
||||||
|
|
||||||
type resizableData struct {
|
type resizableData struct {
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ func scanEmbedImagesDir(fs *embed.FS, dir, prefix string) {
|
||||||
} else {
|
} else {
|
||||||
ext := strings.ToLower(filepath.Ext(name))
|
ext := strings.ToLower(filepath.Ext(name))
|
||||||
switch ext {
|
switch ext {
|
||||||
case ".png", ".jpg", ".jpeg", ".svg", ".gif", ".bmp":
|
case ".png", ".jpg", ".jpeg", ".svg", ".gif", ".bmp", ".webp":
|
||||||
registerImage(fs, path, prefix+name)
|
registerImage(fs, path, prefix+name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -272,8 +272,8 @@ func serveResourceFile(filename string, w http.ResponseWriter, r *http.Request)
|
||||||
if serveEmbed(fs, dir+"/"+filename) {
|
if serveEmbed(fs, dir+"/"+filename) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if subdirs, err := fs.ReadDir(dir); err == nil {
|
if subDirs, err := fs.ReadDir(dir); err == nil {
|
||||||
for _, subdir := range subdirs {
|
for _, subdir := range subDirs {
|
||||||
if subdir.IsDir() {
|
if subdir.IsDir() {
|
||||||
if serveEmbed(fs, dir+"/"+subdir.Name()+"/"+filename) {
|
if serveEmbed(fs, dir+"/"+subdir.Name()+"/"+filename) {
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
154
session.go
154
session.go
|
|
@ -20,13 +20,13 @@ type webBridge interface {
|
||||||
writeMessage(text string) bool
|
writeMessage(text string) bool
|
||||||
addAnimationCSS(css string)
|
addAnimationCSS(css string)
|
||||||
clearAnimation()
|
clearAnimation()
|
||||||
cavnasStart(htmlID string)
|
canvasStart(htmlID string)
|
||||||
callCanvasFunc(funcName string, args ...any)
|
callCanvasFunc(funcName string, args ...any)
|
||||||
callCanvasVarFunc(v any, funcName string, args ...any)
|
callCanvasVarFunc(v any, funcName string, args ...any)
|
||||||
callCanvasImageFunc(url string, property string, funcName string, args ...any)
|
callCanvasImageFunc(url string, property string, funcName string, args ...any)
|
||||||
createCanvasVar(funcName string, args ...any) any
|
createCanvasVar(funcName string, args ...any) any
|
||||||
updateCanvasProperty(property string, value any)
|
updateCanvasProperty(property string, value any)
|
||||||
cavnasFinish()
|
canvasFinish()
|
||||||
canvasTextMetrics(htmlID, font, text string) TextMetrics
|
canvasTextMetrics(htmlID, font, text string) TextMetrics
|
||||||
htmlPropertyValue(htmlID, name string) string
|
htmlPropertyValue(htmlID, name string) string
|
||||||
answerReceived(answer DataObject)
|
answerReceived(answer DataObject)
|
||||||
|
|
@ -100,6 +100,17 @@ type Session interface {
|
||||||
// OpenURL opens the url in the new browser tab
|
// OpenURL opens the url in the new browser tab
|
||||||
OpenURL(url string)
|
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
|
getCurrentTheme() Theme
|
||||||
registerAnimation(props []AnimatedProperty) string
|
registerAnimation(props []AnimatedProperty) string
|
||||||
|
|
||||||
|
|
@ -125,13 +136,13 @@ type Session interface {
|
||||||
finishUpdateScript(htmlID string)
|
finishUpdateScript(htmlID string)
|
||||||
addAnimationCSS(css string)
|
addAnimationCSS(css string)
|
||||||
clearAnimation()
|
clearAnimation()
|
||||||
cavnasStart(htmlID string)
|
canvasStart(htmlID string)
|
||||||
callCanvasFunc(funcName string, args ...any)
|
callCanvasFunc(funcName string, args ...any)
|
||||||
createCanvasVar(funcName string, args ...any) any
|
createCanvasVar(funcName string, args ...any) any
|
||||||
callCanvasVarFunc(v any, funcName string, args ...any)
|
callCanvasVarFunc(v any, funcName string, args ...any)
|
||||||
callCanvasImageFunc(url string, property string, funcName string, args ...any)
|
callCanvasImageFunc(url string, property string, funcName string, args ...any)
|
||||||
updateCanvasProperty(property string, value any)
|
updateCanvasProperty(property string, value any)
|
||||||
cavnasFinish()
|
canvasFinish()
|
||||||
canvasTextMetrics(htmlID, font, text string) TextMetrics
|
canvasTextMetrics(htmlID, font, text string) TextMetrics
|
||||||
htmlPropertyValue(htmlID, name string) string
|
htmlPropertyValue(htmlID, name string) string
|
||||||
handleAnswer(data DataObject)
|
handleAnswer(data DataObject)
|
||||||
|
|
@ -183,6 +194,8 @@ type sessionData struct {
|
||||||
animationCounter int
|
animationCounter int
|
||||||
animationCSS string
|
animationCSS string
|
||||||
updateScripts map[string]*strings.Builder
|
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 {
|
func newSession(app Application, id int, customTheme string, params DataObject) Session {
|
||||||
|
|
@ -199,6 +212,8 @@ func newSession(app Application, id int, customTheme string, params DataObject)
|
||||||
session.animationCounter = 0
|
session.animationCounter = 0
|
||||||
session.animationCSS = ""
|
session.animationCSS = ""
|
||||||
session.updateScripts = map[string]*strings.Builder{}
|
session.updateScripts = map[string]*strings.Builder{}
|
||||||
|
session.clientStorage = map[string]string{}
|
||||||
|
session.hotkeys = map[string]func(Session){}
|
||||||
|
|
||||||
if customTheme != "" {
|
if customTheme != "" {
|
||||||
if theme, ok := CreateThemeFromText(customTheme); ok {
|
if theme, ok := CreateThemeFromText(customTheme); ok {
|
||||||
|
|
@ -290,9 +305,28 @@ func (session *sessionData) writeInitScript(writer *strings.Builder) {
|
||||||
|
|
||||||
if session.rootView != nil {
|
if session.rootView != nil {
|
||||||
writer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
|
writer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
|
||||||
viewHTML(session.rootView, writer)
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
viewHTML(session.rootView, buffer)
|
||||||
|
text := strings.ReplaceAll(buffer.String(), "'", `\'`)
|
||||||
|
writer.WriteString(text)
|
||||||
writer.WriteString("';\nscanElementsSize();")
|
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() {
|
func (session *sessionData) reload() {
|
||||||
|
|
@ -313,6 +347,7 @@ func (session *sessionData) reload() {
|
||||||
}
|
}
|
||||||
|
|
||||||
session.bridge.writeMessage(buffer.String())
|
session.bridge.writeMessage(buffer.String())
|
||||||
|
session.updateTooltipConstants()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) ignoreViewUpdates() bool {
|
func (session *sessionData) ignoreViewUpdates() bool {
|
||||||
|
|
@ -424,9 +459,9 @@ func (session *sessionData) clearAnimation() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) cavnasStart(htmlID string) {
|
func (session *sessionData) canvasStart(htmlID string) {
|
||||||
if session.bridge != nil {
|
if session.bridge != nil {
|
||||||
session.bridge.cavnasStart(htmlID)
|
session.bridge.canvasStart(htmlID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -461,9 +496,9 @@ func (session *sessionData) callCanvasImageFunc(url string, property string, fun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) cavnasFinish() {
|
func (session *sessionData) canvasFinish() {
|
||||||
if session.bridge != nil {
|
if session.bridge != nil {
|
||||||
session.bridge.cavnasFinish()
|
session.bridge.canvasFinish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -512,7 +547,7 @@ func (session *sessionData) handleRootSize(data DataObject) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) handleResize(data DataObject) {
|
func (session *sessionData) handleResize(data DataObject) {
|
||||||
if node := data.PropertyWithTag("views"); node != nil && node.Type() == ArrayNode {
|
if node := data.PropertyByTag("views"); node != nil && node.Type() == ArrayNode {
|
||||||
for _, el := range node.ArrayElements() {
|
for _, el := range node.ArrayElements() {
|
||||||
if el.IsObject() {
|
if el.IsObject() {
|
||||||
obj := el.Object()
|
obj := el.Object()
|
||||||
|
|
@ -587,6 +622,16 @@ func (session *sessionData) handleSessionInfo(params DataObject) {
|
||||||
session.pixelRatio = f
|
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) {
|
func (session *sessionData) handleEvent(command string, data DataObject) {
|
||||||
|
|
@ -606,17 +651,89 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
|
||||||
case "sessionInfo":
|
case "sessionInfo":
|
||||||
session.handleSessionInfo(data)
|
session.handleSessionInfo(data)
|
||||||
|
|
||||||
|
case "storageError":
|
||||||
|
if text, ok := data.PropertyValue("error"); ok {
|
||||||
|
ErrorLog(text)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if viewID, ok := data.PropertyValue("id"); ok {
|
if viewID, ok := data.PropertyValue("id"); ok {
|
||||||
|
if viewID != "body" {
|
||||||
if view := session.viewByHTMLID(viewID); view != nil {
|
if view := session.viewByHTMLID(viewID); view != nil {
|
||||||
view.handleCommand(view, command, data)
|
view.handleCommand(view, command, data)
|
||||||
}
|
}
|
||||||
} else if command != "clickOutsidePopup" {
|
}
|
||||||
|
if command == KeyDownEvent {
|
||||||
|
var event KeyEvent
|
||||||
|
event.init(data)
|
||||||
|
session.hotKey(event)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ErrorLog(`"id" property not found. Event: ` + command)
|
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) {
|
func (session *sessionData) SetTitle(title string) {
|
||||||
title, _ = session.GetString(title)
|
title, _ = session.GetString(title)
|
||||||
session.callFunc("setTitle", title)
|
session.callFunc("setTitle", title)
|
||||||
|
|
@ -637,3 +754,18 @@ func (session *sessionData) OpenURL(urlStr string) {
|
||||||
}
|
}
|
||||||
session.callFunc("openURL", urlStr)
|
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")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ func updateInnerHTML(htmlID string, session Session) {
|
||||||
view = session.viewByHTMLID(htmlID)
|
view = session.viewByHTMLID(htmlID)
|
||||||
}
|
}
|
||||||
if view != nil {
|
if view != nil {
|
||||||
|
session.callFunc("hideTooltip")
|
||||||
|
|
||||||
script := allocStringBuilder()
|
script := allocStringBuilder()
|
||||||
defer freeStringBuilder(script)
|
defer freeStringBuilder(script)
|
||||||
|
|
||||||
|
|
@ -42,7 +44,7 @@ func viewByHTMLID(id string, startView View) View {
|
||||||
if startView.htmlID() == id {
|
if startView.htmlID() == id {
|
||||||
return startView
|
return startView
|
||||||
}
|
}
|
||||||
if container, ok := startView.(ParanetView); ok {
|
if container, ok := startView.(ParentView); ok {
|
||||||
for _, view := range container.Views() {
|
for _, view := range container.Views() {
|
||||||
if view != nil {
|
if view != nil {
|
||||||
if v := viewByHTMLID(id, view); v != nil {
|
if v := viewByHTMLID(id, view); v != nil {
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ func (data *sizeFuncData) parseArgs(args []any, allowNumber bool) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorLogF(`The %s function argument cann't be a number`, data.tag)
|
ErrorLogF(`The %s function argument can't be a number`, data.tag)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
130
tableAdapter.go
130
tableAdapter.go
|
|
@ -228,11 +228,21 @@ func (adapter *textTableAdapter) Cell(row, column int) any {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type simpleTableRowStyle struct {
|
type simpleTableLineStyle struct {
|
||||||
params []Params
|
params []Params
|
||||||
}
|
}
|
||||||
|
|
||||||
func (style *simpleTableRowStyle) RowStyle(row int) 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 {
|
||||||
if row < len(style.params) {
|
if row < len(style.params) {
|
||||||
params := style.params[row]
|
params := style.params[row]
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
|
|
@ -242,50 +252,24 @@ func (style *simpleTableRowStyle) RowStyle(row int) Params {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (table *tableViewData) setRowStyle(value any) bool {
|
func (table *tableViewData) setLineStyle(tag string, 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) {
|
switch value := value.(type) {
|
||||||
case TableRowStyle:
|
|
||||||
table.properties[RowStyle] = value
|
|
||||||
|
|
||||||
case []Params:
|
case []Params:
|
||||||
if style := newSimpleTableRowStyle(value); style != nil {
|
if len(value) > 0 {
|
||||||
table.properties[RowStyle] = style
|
style := new(simpleTableLineStyle)
|
||||||
|
style.params = value
|
||||||
|
table.properties[tag] = style
|
||||||
} else {
|
} else {
|
||||||
delete(table.properties, RowStyle)
|
delete(table.properties, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
case DataNode:
|
case DataNode:
|
||||||
if value.Type() == ArrayNode {
|
if params := value.ArrayAsParams(); len(params) > 0 {
|
||||||
params := make([]Params, value.ArraySize())
|
style := new(simpleTableLineStyle)
|
||||||
for i, element := range value.ArrayElements() {
|
style.params = params
|
||||||
params[i] = Params{}
|
table.properties[tag] = style
|
||||||
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 {
|
} else {
|
||||||
params[i][Style] = element.Value()
|
delete(table.properties, tag)
|
||||||
}
|
|
||||||
}
|
|
||||||
if style := newSimpleTableRowStyle(params); style != nil {
|
|
||||||
table.properties[RowStyle] = style
|
|
||||||
} else {
|
|
||||||
delete(table.properties, RowStyle)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -294,6 +278,14 @@ func (table *tableViewData) setRowStyle(value any) bool {
|
||||||
return true
|
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 {
|
func (table *tableViewData) getRowStyle() TableRowStyle {
|
||||||
for _, tag := range []string{RowStyle, Content} {
|
for _, tag := range []string{RowStyle, Content} {
|
||||||
if value := table.getRaw(tag); value != nil {
|
if value := table.getRaw(tag); value != nil {
|
||||||
|
|
@ -305,70 +297,12 @@ func (table *tableViewData) getRowStyle() TableRowStyle {
|
||||||
return nil
|
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 {
|
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) {
|
switch value := value.(type) {
|
||||||
case TableColumnStyle:
|
case TableColumnStyle:
|
||||||
table.properties[ColumnStyle] = value
|
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 {
|
func (table *tableViewData) getColumnStyle() TableColumnStyle {
|
||||||
|
|
|
||||||
118
tableView.go
118
tableView.go
|
|
@ -218,8 +218,9 @@ type CellIndex struct {
|
||||||
// TableView - text View
|
// TableView - text View
|
||||||
type TableView interface {
|
type TableView interface {
|
||||||
View
|
View
|
||||||
ParanetView
|
ParentView
|
||||||
ReloadTableData()
|
ReloadTableData()
|
||||||
|
ReloadCell(row, column int)
|
||||||
CellFrame(row, column int) Frame
|
CellFrame(row, column int) Frame
|
||||||
|
|
||||||
content() TableAdapter
|
content() TableAdapter
|
||||||
|
|
@ -524,10 +525,6 @@ func (table *tableViewData) set(tag string, value any) bool {
|
||||||
|
|
||||||
case Current:
|
case Current:
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case int:
|
|
||||||
table.current.Row = value
|
|
||||||
table.current.Column = -1
|
|
||||||
|
|
||||||
case CellIndex:
|
case CellIndex:
|
||||||
table.current = value
|
table.current = value
|
||||||
|
|
||||||
|
|
@ -554,6 +551,7 @@ func (table *tableViewData) set(tag string, value any) bool {
|
||||||
table.current.Column = n[1]
|
table.current.Column = n[1]
|
||||||
} else {
|
} else {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
n, err := strconv.Atoi(value)
|
n, err := strconv.Atoi(value)
|
||||||
|
|
@ -566,9 +564,14 @@ func (table *tableViewData) set(tag string, value any) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
if n, ok := isInt(value); ok {
|
||||||
|
table.current.Row = n
|
||||||
|
table.current.Column = -1
|
||||||
|
} else {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return table.viewData.set(tag, value)
|
return table.viewData.set(tag, value)
|
||||||
|
|
@ -585,9 +588,18 @@ func (table *tableViewData) propertyChanged(tag string) {
|
||||||
CellBorder, HeadHeight, HeadStyle, FootHeight, FootStyle,
|
CellBorder, HeadHeight, HeadStyle, FootHeight, FootStyle,
|
||||||
CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft,
|
CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft,
|
||||||
TableCellClickedEvent, TableCellSelectedEvent, TableRowClickedEvent,
|
TableCellClickedEvent, TableCellSelectedEvent, TableRowClickedEvent,
|
||||||
TableRowSelectedEvent, AllowSelection, Current:
|
TableRowSelectedEvent, AllowSelection:
|
||||||
table.ReloadTableData()
|
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:
|
case Gap:
|
||||||
htmlID := table.htmlID()
|
htmlID := table.htmlID()
|
||||||
session := table.Session()
|
session := table.Session()
|
||||||
|
|
@ -606,7 +618,8 @@ func (table *tableViewData) propertyChanged(tag string) {
|
||||||
|
|
||||||
switch GetTableSelectionMode(table) {
|
switch GetTableSelectionMode(table) {
|
||||||
case CellSelection:
|
case CellSelection:
|
||||||
session.updateProperty(htmlID, "tabindex", "0")
|
tabIndex, _ := intProperty(table, TabIndex, session, 0)
|
||||||
|
session.updateProperty(htmlID, "tabindex", tabIndex)
|
||||||
session.updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)")
|
session.updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)")
|
||||||
session.updateProperty(htmlID, "onblur", "tableViewBlurEvent(this, event)")
|
session.updateProperty(htmlID, "onblur", "tableViewBlurEvent(this, event)")
|
||||||
session.updateProperty(htmlID, "data-selection", "cell")
|
session.updateProperty(htmlID, "data-selection", "cell")
|
||||||
|
|
@ -621,7 +634,8 @@ func (table *tableViewData) propertyChanged(tag string) {
|
||||||
session.updateProperty(htmlID, "onkeydown", "tableViewCellKeyDownEvent(this, event)")
|
session.updateProperty(htmlID, "onkeydown", "tableViewCellKeyDownEvent(this, event)")
|
||||||
|
|
||||||
case RowSelection:
|
case RowSelection:
|
||||||
session.updateProperty(htmlID, "tabindex", "0")
|
tabIndex, _ := intProperty(table, TabIndex, session, 0)
|
||||||
|
session.updateProperty(htmlID, "tabindex", tabIndex)
|
||||||
session.updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)")
|
session.updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)")
|
||||||
session.updateProperty(htmlID, "onblur", "tableViewBlurEvent(this, event)")
|
session.updateProperty(htmlID, "onblur", "tableViewBlurEvent(this, event)")
|
||||||
session.updateProperty(htmlID, "data-selection", "row")
|
session.updateProperty(htmlID, "data-selection", "row")
|
||||||
|
|
@ -636,7 +650,11 @@ func (table *tableViewData) propertyChanged(tag string) {
|
||||||
session.updateProperty(htmlID, "onkeydown", "tableViewRowKeyDownEvent(this, event)")
|
session.updateProperty(htmlID, "onkeydown", "tableViewRowKeyDownEvent(this, event)")
|
||||||
|
|
||||||
default: // NoneSelection
|
default: // NoneSelection
|
||||||
for _, prop := range []string{"tabindex", "data-current", "onfocus", "onblur", "onkeydown", "data-selection"} {
|
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"} {
|
||||||
session.removeProperty(htmlID, prop)
|
session.removeProperty(htmlID, prop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -831,7 +849,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
var view tableCellView
|
var view tableCellView
|
||||||
view.init(session)
|
view.init(session)
|
||||||
|
|
||||||
ignorCells := []struct{ row, column int }{}
|
ignoreCells := []struct{ row, column int }{}
|
||||||
selectionMode := GetTableSelectionMode(table)
|
selectionMode := GetTableSelectionMode(table)
|
||||||
|
|
||||||
var allowCellSelection TableAllowCellSelection = nil
|
var allowCellSelection TableAllowCellSelection = nil
|
||||||
|
|
@ -863,7 +881,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
vAlign := vAlignCss[vAlignValue]
|
vAlign := vAlignCss[vAlignValue]
|
||||||
|
|
||||||
tableCSS := func(startRow, endRow int, cellTag string, cellBorder BorderProperty, cellPadding BoundsProperty) {
|
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++ {
|
for row := startRow; row < endRow; row++ {
|
||||||
cssBuilder.buffer.Reset()
|
cssBuilder.buffer.Reset()
|
||||||
|
|
@ -908,7 +926,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
|
|
||||||
for column := 0; column < columnCount; column++ {
|
for column := 0; column < columnCount; column++ {
|
||||||
ignore := false
|
ignore := false
|
||||||
for _, cell := range ignorCells {
|
for _, cell := range ignoreCells {
|
||||||
if cell.row == row && cell.column == column {
|
if cell.row == row && cell.column == column {
|
||||||
ignore = true
|
ignore = true
|
||||||
break
|
break
|
||||||
|
|
@ -994,7 +1012,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
buffer.WriteString(strconv.Itoa(columnSpan))
|
buffer.WriteString(strconv.Itoa(columnSpan))
|
||||||
buffer.WriteRune('"')
|
buffer.WriteRune('"')
|
||||||
for c := column + 1; c < column+columnSpan; c++ {
|
for c := column + 1; c < column+columnSpan; c++ {
|
||||||
ignorCells = append(ignorCells, struct {
|
ignoreCells = append(ignoreCells, struct {
|
||||||
row int
|
row int
|
||||||
column int
|
column int
|
||||||
}{row: row, column: c})
|
}{row: row, column: c})
|
||||||
|
|
@ -1010,7 +1028,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
}
|
}
|
||||||
for r := row + 1; r < row+rowSpan; r++ {
|
for r := row + 1; r < row+rowSpan; r++ {
|
||||||
for c := column; c < column+columnSpan; c++ {
|
for c := column; c < column+columnSpan; c++ {
|
||||||
ignorCells = append(ignorCells, struct {
|
ignoreCells = append(ignoreCells, struct {
|
||||||
row int
|
row int
|
||||||
column int
|
column int
|
||||||
}{row: r, column: c})
|
}{row: r, column: c})
|
||||||
|
|
@ -1025,6 +1043,8 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
}
|
}
|
||||||
buffer.WriteRune('>')
|
buffer.WriteRune('>')
|
||||||
|
|
||||||
|
table.writeCellHtml(adapter, row, column, buffer)
|
||||||
|
/*
|
||||||
switch value := adapter.Cell(row, column).(type) {
|
switch value := adapter.Cell(row, column).(type) {
|
||||||
case string:
|
case string:
|
||||||
buffer.WriteString(value)
|
buffer.WriteString(value)
|
||||||
|
|
@ -1076,6 +1096,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
buffer.WriteString("<Unsupported value>")
|
buffer.WriteString("<Unsupported value>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
buffer.WriteString(`</`)
|
buffer.WriteString(`</`)
|
||||||
buffer.WriteString(cellTag)
|
buffer.WriteString(cellTag)
|
||||||
|
|
@ -1114,7 +1135,8 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
footHeight := GetTableFootHeight(table)
|
footHeight := GetTableFootHeight(table)
|
||||||
cellBorder := table.getCellBorder()
|
cellBorder := table.getCellBorder()
|
||||||
cellPadding := table.boundsProperty(CellPadding)
|
cellPadding := table.boundsProperty(CellPadding)
|
||||||
if cellPadding == nil {
|
if cellPadding == nil || len(cellPadding.AllTags()) == 0 {
|
||||||
|
cellPadding = nil
|
||||||
if style, ok := stringProperty(table, Style, table.Session()); ok {
|
if style, ok := stringProperty(table, Style, table.Session()); ok {
|
||||||
if style, ok := table.Session().resolveConstants(style); ok {
|
if style, ok := table.Session().resolveConstants(style); ok {
|
||||||
cellPadding = table.cellPaddingFromStyle(style)
|
cellPadding = table.cellPaddingFromStyle(style)
|
||||||
|
|
@ -1290,6 +1312,59 @@ func (table *tableViewData) cellPaddingFromStyle(style string) BoundsProperty {
|
||||||
return nil
|
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(`"> </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 {
|
func (table *tableViewData) cellBorderFromStyle(style string) BorderProperty {
|
||||||
if value := table.Session().styleProperty(style, CellBorder); value != nil {
|
if value := table.Session().styleProperty(style, CellBorder); value != nil {
|
||||||
if border, ok := value.(BorderProperty); ok {
|
if border, ok := value.(BorderProperty); ok {
|
||||||
|
|
@ -1377,6 +1452,19 @@ func (table *tableViewData) CellFrame(row, column int) Frame {
|
||||||
return 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 {
|
func (table *tableViewData) Views() []View {
|
||||||
return table.cellViews
|
return table.cellViews
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ func GetTableSelectionMode(view View, subviewID ...string) int {
|
||||||
return enumStyledProperty(view, subviewID, SelectionMode, NoneSelection, false)
|
return enumStyledProperty(view, subviewID, SelectionMode, NoneSelection, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTableVerticalAlign returns a vertical align in a TavleView cell. Returns one of next values:
|
// GetTableVerticalAlign returns a vertical align in a TableView cell. Returns one of next values:
|
||||||
// TopAlign (0), BottomAlign (1), CenterAlign (2), and BaselineAlign (3)
|
// 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.
|
// 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 {
|
func GetTableVerticalAlign(view View, subviewID ...string) int {
|
||||||
|
|
@ -182,6 +182,7 @@ func GetTableRowSelectedListeners(view View, subviewID ...string) []func(TableVi
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReloadTableViewData updates TableView
|
// 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 {
|
func ReloadTableViewData(view View, subviewID ...string) bool {
|
||||||
var tableView TableView
|
var tableView TableView
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if len(subviewID) > 0 && subviewID[0] != "" {
|
||||||
|
|
@ -198,3 +199,22 @@ func ReloadTableViewData(view View, subviewID ...string) bool {
|
||||||
tableView.ReloadTableData()
|
tableView.ReloadTableData()
|
||||||
return true
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
@ -567,7 +566,22 @@ func (tabsLayout *tabsLayoutData) IsListItemEnabled(index int) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) updateContent(view View, tag string) {
|
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) {
|
||||||
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
|
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -578,9 +592,9 @@ func (tabsLayout *tabsLayoutData) Append(view View) {
|
||||||
}
|
}
|
||||||
if view != nil {
|
if view != nil {
|
||||||
tabsLayout.viewsContainerData.Append(view)
|
tabsLayout.viewsContainerData.Append(view)
|
||||||
view.SetChangeListener(Title, tabsLayout.updateContent)
|
view.SetChangeListener(Title, tabsLayout.updateTitle)
|
||||||
view.SetChangeListener(Icon, tabsLayout.updateContent)
|
view.SetChangeListener(Icon, tabsLayout.updateIcon)
|
||||||
view.SetChangeListener(TabCloseButton, tabsLayout.updateContent)
|
view.SetChangeListener(TabCloseButton, tabsLayout.updateTabCloseButton)
|
||||||
if len(tabsLayout.views) == 1 {
|
if len(tabsLayout.views) == 1 {
|
||||||
tabsLayout.properties[Current] = 0
|
tabsLayout.properties[Current] = 0
|
||||||
for _, listener := range tabsLayout.tabListener {
|
for _, listener := range tabsLayout.tabListener {
|
||||||
|
|
@ -602,9 +616,9 @@ func (tabsLayout *tabsLayoutData) Insert(view View, index int) {
|
||||||
defer tabsLayout.propertyChangedEvent(Current)
|
defer tabsLayout.propertyChangedEvent(Current)
|
||||||
}
|
}
|
||||||
tabsLayout.viewsContainerData.Insert(view, index)
|
tabsLayout.viewsContainerData.Insert(view, index)
|
||||||
view.SetChangeListener(Title, tabsLayout.updateContent)
|
view.SetChangeListener(Title, tabsLayout.updateTitle)
|
||||||
view.SetChangeListener(Icon, tabsLayout.updateContent)
|
view.SetChangeListener(Icon, tabsLayout.updateIcon)
|
||||||
view.SetChangeListener(TabCloseButton, tabsLayout.updateContent)
|
view.SetChangeListener(TabCloseButton, tabsLayout.updateTabCloseButton)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -650,10 +664,6 @@ func (tabsLayout *tabsLayoutData) RemoveView(index int) View {
|
||||||
return 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) {
|
func (tabsLayout *tabsLayoutData) htmlProperties(self View, buffer *strings.Builder) {
|
||||||
tabsLayout.viewsContainerData.htmlProperties(self, buffer)
|
tabsLayout.viewsContainerData.htmlProperties(self, buffer)
|
||||||
buffer.WriteString(` data-inactiveTabStyle="`)
|
buffer.WriteString(` data-inactiveTabStyle="`)
|
||||||
|
|
@ -661,7 +671,7 @@ func (tabsLayout *tabsLayoutData) htmlProperties(self View, buffer *strings.Buil
|
||||||
buffer.WriteString(`" data-activeTabStyle="`)
|
buffer.WriteString(`" data-activeTabStyle="`)
|
||||||
buffer.WriteString(tabsLayout.activeTabStyle())
|
buffer.WriteString(tabsLayout.activeTabStyle())
|
||||||
buffer.WriteString(`" data-current="`)
|
buffer.WriteString(`" data-current="`)
|
||||||
buffer.WriteString(tabsLayout.currentID())
|
buffer.WriteString(strconv.Itoa(tabsLayout.currentItem(0)))
|
||||||
buffer.WriteRune('"')
|
buffer.WriteRune('"')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -726,7 +736,7 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
|
||||||
notTranslate := GetNotTranslate(tabsLayout)
|
notTranslate := GetNotTranslate(tabsLayout)
|
||||||
closeButton, _ := boolProperty(tabsLayout, TabCloseButton, tabsLayout.session)
|
closeButton, _ := boolProperty(tabsLayout, TabCloseButton, tabsLayout.session)
|
||||||
|
|
||||||
var tabStyle, titleDiv string
|
var tabStyle, titleStyle string
|
||||||
switch location {
|
switch location {
|
||||||
case LeftTabs, RightTabs:
|
case LeftTabs, RightTabs:
|
||||||
tabStyle = `display: grid; grid-template-rows: auto 1fr auto; align-items: center; justify-items: center; grid-row-gap: 8px;`
|
tabStyle = `display: grid; grid-template-rows: auto 1fr auto; align-items: center; justify-items: center; grid-row-gap: 8px;`
|
||||||
|
|
@ -740,13 +750,13 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
|
||||||
|
|
||||||
switch location {
|
switch location {
|
||||||
case LeftTabs:
|
case LeftTabs:
|
||||||
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;">`
|
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;">`
|
||||||
|
|
||||||
case RightTabs:
|
case RightTabs:
|
||||||
titleDiv = `<div style="writing-mode: vertical-lr; grid-row-start: 2; grid-row-end: 3; grid-column-start: 1; grid-column-end: 2;">`
|
titleStyle = ` style="writing-mode: vertical-lr; grid-row-start: 2; grid-row-end: 3; grid-column-start: 1; grid-column-end: 2;">`
|
||||||
|
|
||||||
default:
|
default:
|
||||||
titleDiv = `<div style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 2; grid-column-end: 3;">`
|
titleStyle = ` style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 2; grid-column-end: 3;">`
|
||||||
}
|
}
|
||||||
|
|
||||||
for n, view := range tabsLayout.views {
|
for n, view := range tabsLayout.views {
|
||||||
|
|
@ -785,21 +795,26 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
|
||||||
buffer.WriteString(`">`)
|
buffer.WriteString(`">`)
|
||||||
|
|
||||||
if icon != "" {
|
if icon != "" {
|
||||||
|
buffer.WriteString(`<img id="`)
|
||||||
|
buffer.WriteString(view.htmlID())
|
||||||
switch location {
|
switch location {
|
||||||
case LeftTabs:
|
case LeftTabs:
|
||||||
buffer.WriteString(`<img style="grid-row-start: 3; grid-row-end: 4; grid-column-start: 1; grid-column-end: 2;" src="`)
|
buffer.WriteString(`-icon" style="grid-row-start: 3; grid-row-end: 4; grid-column-start: 1; grid-column-end: 2;" src="`)
|
||||||
|
|
||||||
case RightTabs:
|
case RightTabs:
|
||||||
buffer.WriteString(`<img style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
|
buffer.WriteString(`-icon" style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
buffer.WriteString(`<img style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
|
buffer.WriteString(`-icon" style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
|
||||||
}
|
}
|
||||||
buffer.WriteString(icon)
|
buffer.WriteString(icon)
|
||||||
buffer.WriteString(`">`)
|
buffer.WriteString(`">`)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(titleDiv)
|
buffer.WriteString(`<div id="`)
|
||||||
|
buffer.WriteString(view.htmlID())
|
||||||
|
buffer.WriteString(`-title"`)
|
||||||
|
buffer.WriteString(titleStyle)
|
||||||
buffer.WriteString(title)
|
buffer.WriteString(title)
|
||||||
buffer.WriteString(`</div>`)
|
buffer.WriteString(`</div>`)
|
||||||
|
|
||||||
|
|
|
||||||
301
theme.go
301
theme.go
|
|
@ -13,10 +13,16 @@ const (
|
||||||
LandscapeMedia = 2
|
LandscapeMedia = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type MediaStyleParams struct {
|
||||||
|
Orientation int
|
||||||
|
MinWidth int
|
||||||
|
MaxWidth int
|
||||||
|
MinHeight int
|
||||||
|
MaxHeight int
|
||||||
|
}
|
||||||
|
|
||||||
type mediaStyle struct {
|
type mediaStyle struct {
|
||||||
orientation int
|
MediaStyleParams
|
||||||
maxWidth int
|
|
||||||
maxHeight int
|
|
||||||
styles map[string]ViewStyle
|
styles map[string]ViewStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,12 +55,13 @@ type Theme interface {
|
||||||
ImageConstantTags() []string
|
ImageConstantTags() []string
|
||||||
Style(tag string) ViewStyle
|
Style(tag string) ViewStyle
|
||||||
SetStyle(tag string, style ViewStyle)
|
SetStyle(tag string, style ViewStyle)
|
||||||
MediaStyle(tag string, orientation, maxWidth, maxHeight int) ViewStyle
|
RemoveStyle(tag string)
|
||||||
SetMediaStyle(tag string, orientation, maxWidth, maxHeight int, style ViewStyle)
|
MediaStyle(tag string, params MediaStyleParams) ViewStyle
|
||||||
|
SetMediaStyle(tag string, params MediaStyleParams, style ViewStyle)
|
||||||
StyleTags() []string
|
StyleTags() []string
|
||||||
MediaStyles(tag string) []struct {
|
MediaStyles(tag string) []struct {
|
||||||
Selectors string
|
Selectors string
|
||||||
Orientation, MaxWidth, MaxHeight int
|
Params MediaStyleParams
|
||||||
}
|
}
|
||||||
Append(anotherTheme Theme)
|
Append(anotherTheme Theme)
|
||||||
|
|
||||||
|
|
@ -70,7 +77,7 @@ func (rule mediaStyle) cssText() string {
|
||||||
builder := allocStringBuilder()
|
builder := allocStringBuilder()
|
||||||
defer freeStringBuilder(builder)
|
defer freeStringBuilder(builder)
|
||||||
|
|
||||||
switch rule.orientation {
|
switch rule.Orientation {
|
||||||
case PortraitMedia:
|
case PortraitMedia:
|
||||||
builder.WriteString(" and (orientation: portrait)")
|
builder.WriteString(" and (orientation: portrait)")
|
||||||
|
|
||||||
|
|
@ -78,26 +85,27 @@ func (rule mediaStyle) cssText() string {
|
||||||
builder.WriteString(" and (orientation: landscape)")
|
builder.WriteString(" and (orientation: landscape)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if rule.maxWidth > 0 {
|
writeSize := func(tag string, minSize, maxSize int) {
|
||||||
builder.WriteString(" and (max-width: ")
|
if minSize != maxSize {
|
||||||
builder.WriteString(strconv.Itoa(rule.maxWidth))
|
if minSize > 0 {
|
||||||
builder.WriteString("px)")
|
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.maxHeight > 0 {
|
writeSize("width", rule.MinWidth, rule.MaxWidth)
|
||||||
builder.WriteString(" and (max-height: ")
|
writeSize("height", rule.MinHeight, rule.MaxHeight)
|
||||||
builder.WriteString(strconv.Itoa(rule.maxHeight))
|
|
||||||
builder.WriteString("px)")
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMediaRule(text string) (mediaStyle, bool) {
|
func parseMediaRule(text string) (mediaStyle, bool) {
|
||||||
rule := mediaStyle{
|
rule := mediaStyle{
|
||||||
orientation: DefaultMedia,
|
|
||||||
maxWidth: 0,
|
|
||||||
maxHeight: 0,
|
|
||||||
styles: map[string]ViewStyle{},
|
styles: map[string]ViewStyle{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,52 +113,80 @@ func parseMediaRule(text string) (mediaStyle, bool) {
|
||||||
for i := 1; i < len(elements); i++ {
|
for i := 1; i < len(elements); i++ {
|
||||||
switch element := elements[i]; element {
|
switch element := elements[i]; element {
|
||||||
case "portrait":
|
case "portrait":
|
||||||
if rule.orientation != DefaultMedia {
|
if rule.Orientation != DefaultMedia {
|
||||||
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
|
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
|
||||||
return rule, false
|
return rule, false
|
||||||
}
|
}
|
||||||
rule.orientation = PortraitMedia
|
rule.Orientation = PortraitMedia
|
||||||
|
|
||||||
case "landscape":
|
case "landscape":
|
||||||
if rule.orientation != DefaultMedia {
|
if rule.Orientation != DefaultMedia {
|
||||||
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
|
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
|
||||||
return rule, false
|
return rule, false
|
||||||
}
|
}
|
||||||
rule.orientation = LandscapeMedia
|
rule.Orientation = LandscapeMedia
|
||||||
|
|
||||||
default:
|
default:
|
||||||
elementSize := func(name string) (int, bool) {
|
elementSize := func(name string) (int, int, bool, error) {
|
||||||
if strings.HasPrefix(element, name) {
|
if strings.HasPrefix(element, name) {
|
||||||
size, err := strconv.Atoi(element[len(name):])
|
var err error = nil
|
||||||
if err == nil && size > 0 {
|
min := 0
|
||||||
return size, true
|
max := 0
|
||||||
|
data := element[len(name):]
|
||||||
|
if pos := strings.Index(data, "-"); pos >= 0 {
|
||||||
|
if pos > 0 {
|
||||||
|
min, err = strconv.Atoi(data[:pos])
|
||||||
}
|
}
|
||||||
ErrorLogF(`Invalid style section name "%s": %s`, text, err.Error())
|
if err == nil && pos+1 < len(data) {
|
||||||
return 0, false
|
max, err = strconv.Atoi(data[pos+1:])
|
||||||
}
|
}
|
||||||
return 0, true
|
} else {
|
||||||
|
max, err = strconv.Atoi(data)
|
||||||
|
}
|
||||||
|
return min, max, true, err
|
||||||
|
}
|
||||||
|
return 0, 0, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if size, ok := elementSize("width"); !ok || size > 0 {
|
if min, max, ok, err := elementSize("width"); ok {
|
||||||
if !ok {
|
|
||||||
|
if err != nil {
|
||||||
|
ErrorLogF(`Invalid style section name "%s": %s`, text, err.Error())
|
||||||
return rule, false
|
return rule, false
|
||||||
}
|
}
|
||||||
if rule.maxWidth != 0 {
|
if rule.MinWidth != 0 || rule.MaxWidth != 0 {
|
||||||
ErrorLog(`Duplicate "width" tag in the style section "` + text + `"`)
|
ErrorLog(`Duplicate "width" tag in the style section "` + text + `"`)
|
||||||
return rule, false
|
return rule, false
|
||||||
}
|
}
|
||||||
rule.maxWidth = size
|
if min == 0 && max == 0 {
|
||||||
} else if size, ok := elementSize("height"); !ok || size > 0 {
|
ErrorLog(`Invalid arguments of "width" tag in the style section "` + text + `"`)
|
||||||
if !ok {
|
|
||||||
return rule, false
|
return rule, false
|
||||||
}
|
}
|
||||||
if rule.maxHeight != 0 {
|
|
||||||
|
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 {
|
||||||
ErrorLog(`Duplicate "height" tag in the style section "` + text + `"`)
|
ErrorLog(`Duplicate "height" tag in the style section "` + text + `"`)
|
||||||
return rule, false
|
return rule, false
|
||||||
}
|
}
|
||||||
rule.maxHeight = size
|
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
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ErrorLogF(`Unknown elemnet "%s" in the style section name "%s"`, element, text)
|
|
||||||
|
ErrorLogF(`Unknown element "%s" in the style section name "%s"`, element, text)
|
||||||
return rule, false
|
return rule, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -264,36 +300,72 @@ func (theme *theme) SetStyle(tag string, style ViewStyle) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (theme *theme) MediaStyle(tag string, orientation, maxWidth, maxHeight int) 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 {
|
||||||
for _, styles := range theme.mediaStyles {
|
for _, styles := range theme.mediaStyles {
|
||||||
if styles.orientation == orientation && styles.maxWidth == maxWidth && styles.maxHeight == maxHeight {
|
if styles.Orientation == params.Orientation &&
|
||||||
|
styles.MaxWidth == params.MaxWidth &&
|
||||||
|
styles.MinWidth == params.MinWidth &&
|
||||||
|
styles.MaxHeight == params.MaxHeight &&
|
||||||
|
styles.MinHeight == params.MinHeight {
|
||||||
if style, ok := styles.styles[tag]; ok {
|
if style, ok := styles.styles[tag]; ok {
|
||||||
return style
|
return style
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if orientation == 0 && maxWidth <= 0 && maxHeight <= 0 {
|
|
||||||
|
if params.Orientation == 0 && params.MaxWidth == 0 && params.MinWidth == 0 &&
|
||||||
|
params.MaxHeight == 0 && params.MinHeight == 0 {
|
||||||
return theme.style(tag)
|
return theme.style(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (theme *theme) SetMediaStyle(tag string, orientation, maxWidth, maxHeight int, style ViewStyle) {
|
func (theme *theme) SetMediaStyle(tag string, params MediaStyleParams, style ViewStyle) {
|
||||||
if maxWidth < 0 {
|
if params.MaxWidth < 0 {
|
||||||
maxWidth = 0
|
params.MaxWidth = 0
|
||||||
}
|
}
|
||||||
if maxHeight < 0 {
|
if params.MinWidth < 0 {
|
||||||
maxHeight = 0
|
params.MinWidth = 0
|
||||||
|
}
|
||||||
|
if params.MaxHeight < 0 {
|
||||||
|
params.MaxHeight = 0
|
||||||
|
}
|
||||||
|
if params.MinHeight < 0 {
|
||||||
|
params.MinHeight = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if orientation == DefaultMedia && maxWidth == 0 && maxHeight == 0 {
|
if params.Orientation == 0 && params.MaxWidth == 0 && params.MinWidth == 0 &&
|
||||||
|
params.MaxHeight == 0 && params.MinHeight == 0 {
|
||||||
theme.SetStyle(tag, style)
|
theme.SetStyle(tag, style)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, styles := range theme.mediaStyles {
|
for i, styles := range theme.mediaStyles {
|
||||||
if styles.orientation == orientation && styles.maxWidth == maxWidth && styles.maxHeight == maxHeight {
|
if styles.Orientation == params.Orientation &&
|
||||||
|
styles.MaxWidth == params.MaxWidth &&
|
||||||
|
styles.MinWidth == params.MinWidth &&
|
||||||
|
styles.MaxHeight == params.MaxHeight &&
|
||||||
|
styles.MinHeight == params.MinHeight {
|
||||||
if style != nil {
|
if style != nil {
|
||||||
theme.mediaStyles[i].styles[tag] = style
|
theme.mediaStyles[i].styles[tag] = style
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -305,9 +377,7 @@ func (theme *theme) SetMediaStyle(tag string, orientation, maxWidth, maxHeight i
|
||||||
|
|
||||||
if style != nil {
|
if style != nil {
|
||||||
theme.mediaStyles = append(theme.mediaStyles, mediaStyle{
|
theme.mediaStyles = append(theme.mediaStyles, mediaStyle{
|
||||||
orientation: orientation,
|
MediaStyleParams: params,
|
||||||
maxWidth: maxWidth,
|
|
||||||
maxHeight: maxHeight,
|
|
||||||
styles: map[string]ViewStyle{tag: style},
|
styles: map[string]ViewStyle{tag: style},
|
||||||
})
|
})
|
||||||
theme.sortMediaStyles()
|
theme.sortMediaStyles()
|
||||||
|
|
@ -407,11 +477,11 @@ func (theme *theme) StyleTags() []string {
|
||||||
|
|
||||||
func (theme *theme) MediaStyles(tag string) []struct {
|
func (theme *theme) MediaStyles(tag string) []struct {
|
||||||
Selectors string
|
Selectors string
|
||||||
Orientation, MaxWidth, MaxHeight int
|
Params MediaStyleParams
|
||||||
} {
|
} {
|
||||||
result := []struct {
|
result := []struct {
|
||||||
Selectors string
|
Selectors string
|
||||||
Orientation, MaxWidth, MaxHeight int
|
Params MediaStyleParams
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
prefix := tag + ":"
|
prefix := tag + ":"
|
||||||
|
|
@ -420,12 +490,10 @@ func (theme *theme) MediaStyles(tag string) []struct {
|
||||||
if strings.HasPrefix(themeTag, prefix) {
|
if strings.HasPrefix(themeTag, prefix) {
|
||||||
result = append(result, struct {
|
result = append(result, struct {
|
||||||
Selectors string
|
Selectors string
|
||||||
Orientation, MaxWidth, MaxHeight int
|
Params MediaStyleParams
|
||||||
}{
|
}{
|
||||||
Selectors: themeTag[prefixLen:],
|
Selectors: themeTag[prefixLen:],
|
||||||
Orientation: DefaultMedia,
|
Params: MediaStyleParams{},
|
||||||
MaxWidth: 0,
|
|
||||||
MaxHeight: 0,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -434,24 +502,20 @@ func (theme *theme) MediaStyles(tag string) []struct {
|
||||||
if _, ok := media.styles[tag]; ok {
|
if _, ok := media.styles[tag]; ok {
|
||||||
result = append(result, struct {
|
result = append(result, struct {
|
||||||
Selectors string
|
Selectors string
|
||||||
Orientation, MaxWidth, MaxHeight int
|
Params MediaStyleParams
|
||||||
}{
|
}{
|
||||||
Selectors: "",
|
Selectors: "",
|
||||||
Orientation: media.orientation,
|
Params: media.MediaStyleParams,
|
||||||
MaxWidth: media.maxWidth,
|
|
||||||
MaxHeight: media.maxHeight,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for themeTag := range media.styles {
|
for themeTag := range media.styles {
|
||||||
if strings.HasPrefix(themeTag, prefix) {
|
if strings.HasPrefix(themeTag, prefix) {
|
||||||
result = append(result, struct {
|
result = append(result, struct {
|
||||||
Selectors string
|
Selectors string
|
||||||
Orientation, MaxWidth, MaxHeight int
|
Params MediaStyleParams
|
||||||
}{
|
}{
|
||||||
Selectors: themeTag[prefixLen:],
|
Selectors: themeTag[prefixLen:],
|
||||||
Orientation: media.orientation,
|
Params: media.MediaStyleParams,
|
||||||
MaxWidth: media.maxWidth,
|
|
||||||
MaxHeight: media.maxHeight,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -500,9 +564,11 @@ func (theme *theme) Append(anotherTheme Theme) {
|
||||||
for _, anotherMedia := range another.mediaStyles {
|
for _, anotherMedia := range another.mediaStyles {
|
||||||
exists := false
|
exists := false
|
||||||
for _, media := range theme.mediaStyles {
|
for _, media := range theme.mediaStyles {
|
||||||
if anotherMedia.maxHeight == media.maxHeight &&
|
if anotherMedia.MinHeight == media.MinHeight &&
|
||||||
anotherMedia.maxWidth == media.maxWidth &&
|
anotherMedia.MaxHeight == media.MaxHeight &&
|
||||||
anotherMedia.orientation == media.orientation {
|
anotherMedia.MinWidth == media.MinWidth &&
|
||||||
|
anotherMedia.MaxWidth == media.MaxWidth &&
|
||||||
|
anotherMedia.Orientation == media.Orientation {
|
||||||
for tag, style := range anotherMedia.styles {
|
for tag, style := range anotherMedia.styles {
|
||||||
media.styles[tag] = style
|
media.styles[tag] = style
|
||||||
}
|
}
|
||||||
|
|
@ -525,19 +591,38 @@ func (theme *theme) cssText(session Session) string {
|
||||||
var builder cssStyleBuilder
|
var builder cssStyleBuilder
|
||||||
builder.init()
|
builder.init()
|
||||||
|
|
||||||
for tag, style := range theme.styles {
|
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 {
|
||||||
builder.startStyle(tag)
|
builder.startStyle(tag)
|
||||||
style.cssViewStyle(&builder, session)
|
style.cssViewStyle(&builder, session)
|
||||||
builder.endStyle()
|
builder.endStyle()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, media := range theme.mediaStyles {
|
for _, media := range theme.mediaStyles {
|
||||||
builder.startMedia(media.cssText())
|
builder.startMedia(media.cssText())
|
||||||
for tag, style := range media.styles {
|
for _, tag := range styleList(media.styles) {
|
||||||
|
if style := media.styles[tag]; style != nil {
|
||||||
builder.startStyle(tag)
|
builder.startStyle(tag)
|
||||||
style.cssViewStyle(&builder, session)
|
style.cssViewStyle(&builder, session)
|
||||||
builder.endStyle()
|
builder.endStyle()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
builder.endMedia()
|
builder.endMedia()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -687,13 +772,19 @@ func (theme *theme) addText(themeText string) bool {
|
||||||
func (theme *theme) sortMediaStyles() {
|
func (theme *theme) sortMediaStyles() {
|
||||||
if len(theme.mediaStyles) > 1 {
|
if len(theme.mediaStyles) > 1 {
|
||||||
sort.SliceStable(theme.mediaStyles, func(i, j int) bool {
|
sort.SliceStable(theme.mediaStyles, func(i, j int) bool {
|
||||||
if 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
|
return theme.mediaStyles[i].Orientation < theme.mediaStyles[j].Orientation
|
||||||
}
|
}
|
||||||
if theme.mediaStyles[i].maxWidth != theme.mediaStyles[j].maxWidth {
|
if theme.mediaStyles[i].MinWidth != theme.mediaStyles[j].MinWidth {
|
||||||
return theme.mediaStyles[i].maxWidth < theme.mediaStyles[j].maxWidth
|
return theme.mediaStyles[i].MinWidth < theme.mediaStyles[j].MinWidth
|
||||||
}
|
}
|
||||||
return theme.mediaStyles[i].maxHeight < theme.mediaStyles[j].maxHeight
|
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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -811,7 +902,7 @@ func (theme *theme) String() string {
|
||||||
writeConstants("constants", theme.constants)
|
writeConstants("constants", theme.constants)
|
||||||
writeConstants("constants:touch", theme.touchConstants)
|
writeConstants("constants:touch", theme.touchConstants)
|
||||||
|
|
||||||
writeStyles := func(orientation, maxWidth, maxHeihgt int, styles map[string]ViewStyle) bool {
|
writeStyles := func(orientation, maxWidth, maxHeight int, styles map[string]ViewStyle) bool {
|
||||||
count := len(styles)
|
count := len(styles)
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return false
|
return false
|
||||||
|
|
@ -834,16 +925,16 @@ func (theme *theme) String() string {
|
||||||
if maxWidth > 0 {
|
if maxWidth > 0 {
|
||||||
buffer.WriteString(fmt.Sprintf(":width%d", maxWidth))
|
buffer.WriteString(fmt.Sprintf(":width%d", maxWidth))
|
||||||
}
|
}
|
||||||
if maxHeihgt > 0 {
|
if maxHeight > 0 {
|
||||||
buffer.WriteString(fmt.Sprintf(":heihgt%d", maxHeihgt))
|
buffer.WriteString(fmt.Sprintf(":height%d", maxHeight))
|
||||||
}
|
}
|
||||||
buffer.WriteString(" = [\n")
|
buffer.WriteString(" = [\n")
|
||||||
|
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
if style, ok := styles[tag]; ok {
|
if style, ok := styles[tag]; ok && len(style.AllTags()) > 0 {
|
||||||
buffer.WriteString("\t\t")
|
buffer.WriteString("\t\t")
|
||||||
writeViewStyle(tag, style, buffer, "\t\t")
|
writeViewStyle(tag, style, buffer, "\t\t")
|
||||||
buffer.WriteString(",")
|
buffer.WriteString(",\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buffer.WriteString("\t],\n")
|
buffer.WriteString("\t],\n")
|
||||||
|
|
@ -852,7 +943,53 @@ func (theme *theme) String() string {
|
||||||
|
|
||||||
writeStyles(0, 0, 0, theme.styles)
|
writeStyles(0, 0, 0, theme.styles)
|
||||||
for _, media := range theme.mediaStyles {
|
for _, media := range theme.mediaStyles {
|
||||||
writeStyles(media.orientation, media.maxWidth, media.maxHeight, media.styles)
|
//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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString("}\n")
|
buffer.WriteString("}\n")
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ type TimePicker interface {
|
||||||
|
|
||||||
type timePickerData struct {
|
type timePickerData struct {
|
||||||
viewData
|
viewData
|
||||||
timeChangedListeners []func(TimePicker, time.Time)
|
timeChangedListeners []func(TimePicker, time.Time, time.Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTimePicker create new TimePicker object and return it
|
// NewTimePicker create new TimePicker object and return it
|
||||||
|
|
@ -40,7 +40,7 @@ func newTimePicker(session Session) View {
|
||||||
func (picker *timePickerData) init(session Session) {
|
func (picker *timePickerData) init(session Session) {
|
||||||
picker.viewData.init(session)
|
picker.viewData.init(session)
|
||||||
picker.tag = "TimePicker"
|
picker.tag = "TimePicker"
|
||||||
picker.timeChangedListeners = []func(TimePicker, time.Time){}
|
picker.timeChangedListeners = []func(TimePicker, time.Time, time.Time){}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *timePickerData) String() string {
|
func (picker *timePickerData) String() string {
|
||||||
|
|
@ -69,7 +69,7 @@ func (picker *timePickerData) remove(tag string) {
|
||||||
switch tag {
|
switch tag {
|
||||||
case TimeChangedEvent:
|
case TimeChangedEvent:
|
||||||
if len(picker.timeChangedListeners) > 0 {
|
if len(picker.timeChangedListeners) > 0 {
|
||||||
picker.timeChangedListeners = []func(TimePicker, time.Time){}
|
picker.timeChangedListeners = []func(TimePicker, time.Time, time.Time){}
|
||||||
picker.propertyChangedEvent(tag)
|
picker.propertyChangedEvent(tag)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
@ -94,13 +94,14 @@ func (picker *timePickerData) remove(tag string) {
|
||||||
|
|
||||||
case TimePickerValue:
|
case TimePickerValue:
|
||||||
if _, ok := picker.properties[TimePickerValue]; ok {
|
if _, ok := picker.properties[TimePickerValue]; ok {
|
||||||
|
oldTime := GetTimePickerValue(picker)
|
||||||
delete(picker.properties, TimePickerValue)
|
delete(picker.properties, TimePickerValue)
|
||||||
time := GetTimePickerValue(picker)
|
time := GetTimePickerValue(picker)
|
||||||
if picker.created {
|
if picker.created {
|
||||||
picker.session.callFunc("setInputValue", picker.htmlID(), time.Format(timeFormat))
|
picker.session.callFunc("setInputValue", picker.htmlID(), time.Format(timeFormat))
|
||||||
}
|
}
|
||||||
for _, listener := range picker.timeChangedListeners {
|
for _, listener := range picker.timeChangedListeners {
|
||||||
listener(picker, time)
|
listener(picker, time, oldTime)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
|
|
@ -214,7 +215,7 @@ func (picker *timePickerData) set(tag string, value any) bool {
|
||||||
picker.session.callFunc("setInputValue", picker.htmlID(), time.Format(timeFormat))
|
picker.session.callFunc("setInputValue", picker.htmlID(), time.Format(timeFormat))
|
||||||
}
|
}
|
||||||
for _, listener := range picker.timeChangedListeners {
|
for _, listener := range picker.timeChangedListeners {
|
||||||
listener(picker, time)
|
listener(picker, time, oldTime)
|
||||||
}
|
}
|
||||||
picker.propertyChangedEvent(tag)
|
picker.propertyChangedEvent(tag)
|
||||||
}
|
}
|
||||||
|
|
@ -222,12 +223,12 @@ func (picker *timePickerData) set(tag string, value any) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
case TimeChangedEvent:
|
case TimeChangedEvent:
|
||||||
listeners, ok := valueToEventListeners[TimePicker, time.Time](value)
|
listeners, ok := valueToEventWithOldListeners[TimePicker, time.Time](value)
|
||||||
if !ok {
|
if !ok {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return false
|
||||||
} else if listeners == nil {
|
} else if listeners == nil {
|
||||||
listeners = []func(TimePicker, time.Time){}
|
listeners = []func(TimePicker, time.Time, time.Time){}
|
||||||
}
|
}
|
||||||
picker.timeChangedListeners = listeners
|
picker.timeChangedListeners = listeners
|
||||||
picker.propertyChangedEvent(tag)
|
picker.propertyChangedEvent(tag)
|
||||||
|
|
@ -306,7 +307,7 @@ func (picker *timePickerData) handleCommand(self View, command string, data Data
|
||||||
picker.properties[TimePickerValue] = value
|
picker.properties[TimePickerValue] = value
|
||||||
if value != oldValue {
|
if value != oldValue {
|
||||||
for _, listener := range picker.timeChangedListeners {
|
for _, listener := range picker.timeChangedListeners {
|
||||||
listener(picker, value)
|
listener(picker, value, oldValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -398,6 +399,6 @@ func GetTimePickerValue(view View, subviewID ...string) time.Time {
|
||||||
// GetTimeChangedListeners returns the TimeChangedListener list of an TimePicker subview.
|
// GetTimeChangedListeners returns the TimeChangedListener list of an TimePicker subview.
|
||||||
// If there are no listeners then the empty list is returned
|
// 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.
|
// 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) {
|
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time, time.Time) {
|
||||||
return getEventListeners[TimePicker, time.Time](view, subviewID, TimeChangedEvent)
|
return getEventWithOldListeners[TimePicker, time.Time](view, subviewID, TimeChangedEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,10 @@ func touchEventsHtml(view View, buffer *strings.Builder) {
|
||||||
for tag, js := range touchEvents {
|
for tag, js := range touchEvents {
|
||||||
if value := view.getRaw(tag); value != nil {
|
if value := view.getRaw(tag); value != nil {
|
||||||
if listeners, ok := value.([]func(View, TouchEvent)); ok && len(listeners) > 0 {
|
if listeners, ok := value.([]func(View, TouchEvent)); ok && len(listeners) > 0 {
|
||||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
buffer.WriteString(js.jsEvent)
|
||||||
|
buffer.WriteString(`="`)
|
||||||
|
buffer.WriteString(js.jsFunc)
|
||||||
|
buffer.WriteString(`(this, event)" `)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -140,7 +143,7 @@ func (event *TouchEvent) init(data DataObject) {
|
||||||
|
|
||||||
event.Touches = []Touch{}
|
event.Touches = []Touch{}
|
||||||
event.TimeStamp = getTimeStamp(data)
|
event.TimeStamp = getTimeStamp(data)
|
||||||
if node := data.PropertyWithTag("touches"); node != nil && node.Type() == ArrayNode {
|
if node := data.PropertyByTag("touches"); node != nil && node.Type() == ArrayNode {
|
||||||
for i := 0; i < node.ArraySize(); i++ {
|
for i := 0; i < node.ArraySize(); i++ {
|
||||||
if element := node.ArrayElement(i); element != nil && element.IsObject() {
|
if element := node.ArrayElement(i); element != nil && element.IsObject() {
|
||||||
if obj := element.Object(); obj != nil {
|
if obj := element.Object(); obj != nil {
|
||||||
|
|
|
||||||
30
utils.go
30
utils.go
|
|
@ -1,7 +1,9 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"net"
|
"net"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
@ -76,3 +78,31 @@ func dataFloatProperty(data DataObject, tag string) float64 {
|
||||||
}
|
}
|
||||||
return 0
|
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
73
view.go
|
|
@ -45,7 +45,7 @@ type View interface {
|
||||||
Focusable() bool
|
Focusable() bool
|
||||||
// Frame returns the location and size of the view in pixels
|
// Frame returns the location and size of the view in pixels
|
||||||
Frame() Frame
|
Frame() Frame
|
||||||
// Scroll returns the location size of the scrolable view in pixels
|
// Scroll returns the location size of the scrollable view in pixels
|
||||||
Scroll() Frame
|
Scroll() Frame
|
||||||
// SetAnimated sets the value (second argument) of the property with name defined by the first argument.
|
// 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
|
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
||||||
|
|
@ -175,6 +175,17 @@ func (view *viewData) Focusable() bool {
|
||||||
if focus, ok := boolProperty(view, Focusable, view.session); ok {
|
if focus, ok := boolProperty(view, Focusable, view.session); ok {
|
||||||
return focus
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,6 +198,14 @@ func (view *viewData) remove(tag string) {
|
||||||
case ID:
|
case ID:
|
||||||
view.viewID = ""
|
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:
|
case UserData:
|
||||||
delete(view.properties, tag)
|
delete(view.properties, tag)
|
||||||
|
|
||||||
|
|
@ -312,6 +331,18 @@ func (view *viewData) set(tag string, value any) bool {
|
||||||
}
|
}
|
||||||
view.viewID = text
|
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:
|
case UserData:
|
||||||
view.properties[tag] = value
|
view.properties[tag] = value
|
||||||
|
|
||||||
|
|
@ -381,10 +412,12 @@ func viewPropertyChanged(view *viewData, tag string) {
|
||||||
case Invisible:
|
case Invisible:
|
||||||
session.updateCSSProperty(htmlID, Visibility, "hidden")
|
session.updateCSSProperty(htmlID, Visibility, "hidden")
|
||||||
session.updateCSSProperty(htmlID, "display", "")
|
session.updateCSSProperty(htmlID, "display", "")
|
||||||
|
session.callFunc("hideTooltip")
|
||||||
|
|
||||||
case Gone:
|
case Gone:
|
||||||
session.updateCSSProperty(htmlID, Visibility, "hidden")
|
session.updateCSSProperty(htmlID, Visibility, "hidden")
|
||||||
session.updateCSSProperty(htmlID, "display", "none")
|
session.updateCSSProperty(htmlID, "display", "none")
|
||||||
|
session.callFunc("hideTooltip")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
session.updateCSSProperty(htmlID, Visibility, "visible")
|
session.updateCSSProperty(htmlID, Visibility, "visible")
|
||||||
|
|
@ -577,7 +610,7 @@ func viewPropertyChanged(view *viewData, tag string) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
case ZIndex, TabSize:
|
case ZIndex, Order, TabSize:
|
||||||
if i, ok := intProperty(view, tag, session, 0); ok {
|
if i, ok := intProperty(view, tag, session, 0); ok {
|
||||||
session.updateCSSProperty(htmlID, tag, strconv.Itoa(i))
|
session.updateCSSProperty(htmlID, tag, strconv.Itoa(i))
|
||||||
}
|
}
|
||||||
|
|
@ -606,6 +639,24 @@ func viewPropertyChanged(view *viewData, tag string) {
|
||||||
session.updateCSSProperty(htmlID, "user-select", "")
|
session.updateCSSProperty(htmlID, "user-select", "")
|
||||||
}
|
}
|
||||||
return
|
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 {
|
if cssTag, ok := sizeProperties[tag]; ok {
|
||||||
|
|
@ -758,14 +809,28 @@ func viewHTML(view View, buffer *strings.Builder) {
|
||||||
buffer.WriteRune(' ')
|
buffer.WriteRune(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
if view.Focusable() && !disabled {
|
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() {
|
||||||
buffer.WriteString(`tabindex="0" `)
|
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)" `)
|
buffer.WriteString(`onscroll="scrollEvent(this, event)" `)
|
||||||
|
|
||||||
keyEventsHtml(view, buffer)
|
keyEventsHtml(view, buffer)
|
||||||
mouseEventsHtml(view, buffer)
|
mouseEventsHtml(view, buffer, hasTooltip)
|
||||||
pointerEventsHtml(view, buffer)
|
pointerEventsHtml(view, buffer)
|
||||||
touchEventsHtml(view, buffer)
|
touchEventsHtml(view, buffer)
|
||||||
focusEventsHtml(view, buffer)
|
focusEventsHtml(view, buffer)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ func ViewByID(rootView View, id string) View {
|
||||||
return rootView
|
return rootView
|
||||||
}
|
}
|
||||||
|
|
||||||
if container, ok := rootView.(ParanetView); ok {
|
if container, ok := rootView.(ParentView); ok {
|
||||||
if view := viewByID(container, id); view != nil {
|
if view := viewByID(container, id); view != nil {
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
@ -32,13 +32,13 @@ func ViewByID(rootView View, id string) View {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewByID(rootView ParanetView, id string) View {
|
func viewByID(rootView ParentView, id string) View {
|
||||||
for _, view := range rootView.Views() {
|
for _, view := range rootView.Views() {
|
||||||
if view != nil {
|
if view != nil {
|
||||||
if view.ID() == id {
|
if view.ID() == id {
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
if container, ok := view.(ParanetView); ok {
|
if container, ok := view.(ParentView); ok {
|
||||||
if v := viewByID(container, id); v != nil {
|
if v := viewByID(container, id); v != nil {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ var viewCreators = map[string]func(Session) View{
|
||||||
"ListView": newListView,
|
"ListView": newListView,
|
||||||
"CanvasView": newCanvasView,
|
"CanvasView": newCanvasView,
|
||||||
"ImageView": newImageView,
|
"ImageView": newImageView,
|
||||||
|
"SvgImageView": newSvgImageView,
|
||||||
"TableView": newTableView,
|
"TableView": newTableView,
|
||||||
"AudioPlayer": newAudioPlayer,
|
"AudioPlayer": newAudioPlayer,
|
||||||
"VideoPlayer": newVideoPlayer,
|
"VideoPlayer": newVideoPlayer,
|
||||||
|
|
|
||||||
28
viewStyle.go
28
viewStyle.go
|
|
@ -193,22 +193,26 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
||||||
outline.ViewOutline(session).cssValue(builder, session)
|
outline.ViewOutline(session).cssValue(builder, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
if z, ok := intProperty(style, ZIndex, session, 0); ok {
|
for _, tag := range []string{ZIndex, Order} {
|
||||||
builder.add(ZIndex, strconv.Itoa(z))
|
if value, ok := intProperty(style, tag, session, 0); ok {
|
||||||
|
builder.add(tag, strconv.Itoa(value))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if opacity, ok := floatProperty(style, Opacity, session, 1.0); ok && opacity >= 0 && opacity <= 1 {
|
if opacity, ok := floatProperty(style, Opacity, session, 1.0); ok && opacity >= 0 && opacity <= 1 {
|
||||||
builder.add(Opacity, strconv.FormatFloat(opacity, 'f', 3, 32))
|
builder.add(Opacity, strconv.FormatFloat(opacity, 'f', 3, 32))
|
||||||
}
|
}
|
||||||
|
|
||||||
if n, ok := intProperty(style, ColumnCount, session, 0); ok && n > 0 {
|
for _, tag := range []string{ColumnCount, TabSize} {
|
||||||
builder.add(ColumnCount, strconv.Itoa(n))
|
if value, ok := intProperty(style, tag, session, 0); ok && value > 0 {
|
||||||
|
builder.add(tag, strconv.Itoa(value))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tag := range []string{
|
for _, tag := range []string{
|
||||||
Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight, Left, Right, Top, Bottom,
|
Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight, Left, Right, Top, Bottom,
|
||||||
TextSize, TextIndent, LetterSpacing, WordSpacing, LineHeight, TextLineThickness,
|
TextSize, TextIndent, LetterSpacing, WordSpacing, LineHeight, TextLineThickness,
|
||||||
ListRowGap, ListColumnGap, GridRowGap, GridColumnGap, ColumnGap, ColumnWidth} {
|
ListRowGap, ListColumnGap, GridRowGap, GridColumnGap, ColumnGap, ColumnWidth, OutlineOffset} {
|
||||||
|
|
||||||
if size, ok := sizeProperty(style, tag, session); ok && size.Type != Auto {
|
if size, ok := sizeProperty(style, tag, session); ok && size.Type != Auto {
|
||||||
cssTag, ok := sizeProperties[tag]
|
cssTag, ok := sizeProperties[tag]
|
||||||
|
|
@ -248,7 +252,7 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
||||||
for _, tag := range []string{
|
for _, tag := range []string{
|
||||||
Overflow, TextAlign, TextTransform, TextWeight, TextLineStyle, WritingMode, TextDirection,
|
Overflow, TextAlign, TextTransform, TextWeight, TextLineStyle, WritingMode, TextDirection,
|
||||||
VerticalTextOrientation, CellVerticalAlign, CellHorizontalAlign, GridAutoFlow, Cursor,
|
VerticalTextOrientation, CellVerticalAlign, CellHorizontalAlign, GridAutoFlow, Cursor,
|
||||||
WhiteSpace, WordBreak, TextOverflow, Float, TableVerticalAlign, Resize} {
|
WhiteSpace, WordBreak, TextOverflow, Float, TableVerticalAlign, Resize, MixBlendMode, BackgroundBlendMode} {
|
||||||
|
|
||||||
if data, ok := enumProperties[tag]; ok {
|
if data, ok := enumProperties[tag]; ok {
|
||||||
if tag != VerticalTextOrientation || (writingMode != VerticalLeftToRight && writingMode != VerticalRightToLeft) {
|
if tag != VerticalTextOrientation || (writingMode != VerticalLeftToRight && writingMode != VerticalRightToLeft) {
|
||||||
|
|
@ -279,10 +283,6 @@ 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 != "" {
|
if text := style.cssTextDecoration(session); text != "" {
|
||||||
builder.add("text-decoration", text)
|
builder.add("text-decoration", text)
|
||||||
}
|
}
|
||||||
|
|
@ -453,6 +453,14 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
||||||
builder.add(`animation-play-state`, `running`)
|
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) {
|
func valueToOrientation(value any, session Session) (int, bool) {
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,60 @@ func (style *viewStyle) set(tag string, value any) bool {
|
||||||
case CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft:
|
case CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft:
|
||||||
return style.setBoundsSide(CellPadding, tag, value)
|
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:
|
case Outline:
|
||||||
return style.setOutline(value)
|
return style.setOutline(value)
|
||||||
|
|
||||||
|
|
@ -338,7 +392,7 @@ func (style *viewStyle) set(tag string, value any) bool {
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case DataObject:
|
case DataObject:
|
||||||
if animation := parseAnimation(value); animation.hasAnimatedPropery() {
|
if animation := parseAnimation(value); animation.hasAnimatedProperty() {
|
||||||
style.properties[tag] = []Animation{animation}
|
style.properties[tag] = []Animation{animation}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -348,7 +402,7 @@ func (style *viewStyle) set(tag string, value any) bool {
|
||||||
result := true
|
result := true
|
||||||
for i := 0; i < value.ArraySize(); i++ {
|
for i := 0; i < value.ArraySize(); i++ {
|
||||||
if obj := value.ArrayElement(i).Object(); obj != nil {
|
if obj := value.ArrayElement(i).Object(); obj != nil {
|
||||||
if anim := parseAnimation(obj); anim.hasAnimatedPropery() {
|
if anim := parseAnimation(obj); anim.hasAnimatedProperty() {
|
||||||
animations = append(animations, anim)
|
animations = append(animations, anim)
|
||||||
} else {
|
} else {
|
||||||
result = false
|
result = false
|
||||||
|
|
|
||||||
81
viewUtils.go
81
viewUtils.go
|
|
@ -153,12 +153,37 @@ func GetOverflow(view View, subviewID ...string) int {
|
||||||
return enumStyledProperty(view, subviewID, Overflow, defaultOverflow, false)
|
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.
|
// 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
|
// 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 {
|
func GetZIndex(view View, subviewID ...string) int {
|
||||||
return intStyledProperty(view, subviewID, ZIndex, 0)
|
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.
|
// 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
|
// 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 {
|
func GetWidth(view View, subviewID ...string) SizeUnit {
|
||||||
|
|
@ -296,6 +321,12 @@ func GetOutline(view View, subviewID ...string) ViewOutline {
|
||||||
return ViewOutline{Style: NoneLine, Width: AutoSize(), Color: 0}
|
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.
|
// 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.
|
// 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 {
|
func GetViewShadows(view View, subviewID ...string) []ViewShadow {
|
||||||
|
|
@ -799,9 +830,13 @@ func colorStyledProperty(view View, subviewID []string, tag string, inherit bool
|
||||||
return Color(0)
|
return Color(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FocusView sets focus on the specified View, if it can be focused.
|
// FocusView sets focus on the specified subview, if it can be focused.
|
||||||
// The focused View is the View which will receive keyboard events by default.
|
// The focused View is the View which will receive keyboard events by default.
|
||||||
func FocusView(view View) {
|
// 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])
|
||||||
|
}
|
||||||
if view != nil {
|
if view != nil {
|
||||||
view.Session().callFunc("focus", view.htmlID())
|
view.Session().callFunc("focus", view.htmlID())
|
||||||
}
|
}
|
||||||
|
|
@ -890,3 +925,45 @@ func isUserSelect(view View) (bool, bool) {
|
||||||
|
|
||||||
return false, false
|
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 ""
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package rui
|
||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
type ParanetView interface {
|
type ParentView interface {
|
||||||
// Views return a list of child views
|
// Views return a list of child views
|
||||||
Views() []View
|
Views() []View
|
||||||
}
|
}
|
||||||
|
|
@ -10,13 +10,15 @@ type ParanetView interface {
|
||||||
// ViewsContainer - mutable list-container of Views
|
// ViewsContainer - mutable list-container of Views
|
||||||
type ViewsContainer interface {
|
type ViewsContainer interface {
|
||||||
View
|
View
|
||||||
ParanetView
|
ParentView
|
||||||
// Append appends a view to the end of the list of a view children
|
// Append appends a view to the end of the list of a view children
|
||||||
Append(view View)
|
Append(view View)
|
||||||
// Insert inserts a view to the "index" position in the list of a view children
|
// Insert inserts a view to the "index" position in the list of a view children
|
||||||
Insert(view View, index int)
|
Insert(view View, index int)
|
||||||
// Remove removes a view from the list of a view children and return it
|
// Remove removes a view from the list of a view children and return it
|
||||||
RemoveView(index int) View
|
RemoveView(index int) View
|
||||||
|
// ViewIndex returns the index of view, -1 overwise
|
||||||
|
ViewIndex(view View) int
|
||||||
}
|
}
|
||||||
|
|
||||||
type viewsContainerData struct {
|
type viewsContainerData struct {
|
||||||
|
|
@ -117,6 +119,15 @@ func (container *viewsContainerData) RemoveView(index int) View {
|
||||||
return 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) {
|
func (container *viewsContainerData) cssStyle(self View, builder cssBuilder) {
|
||||||
container.viewData.cssStyle(self, builder)
|
container.viewData.cssStyle(self, builder)
|
||||||
builder.add(`overflow`, `auto`)
|
builder.add(`overflow`, `auto`)
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ func (bridge *wasmBridge) clearAnimation() {
|
||||||
styles.Set("textContent", "")
|
styles.Set("textContent", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wasmBridge) cavnasStart(htmlID string) {
|
func (bridge *wasmBridge) canvasStart(htmlID string) {
|
||||||
if ProtocolInDebugLog {
|
if ProtocolInDebugLog {
|
||||||
DebugLog("const ctx = document.getElementById('" + htmlID + "'elementId').getContext('2d');\nctx.save();")
|
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) cavnasFinish() {
|
func (bridge *wasmBridge) canvasFinish() {
|
||||||
if !bridge.canvas.IsNull() {
|
if !bridge.canvas.IsNull() {
|
||||||
DebugLog("ctx.restore()")
|
DebugLog("ctx.restore()")
|
||||||
bridge.canvas.Call("restore")
|
bridge.canvas.Call("restore")
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ func (bridge *wsBridge) argToString(arg any) (string, bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorLog("Unsupported agument type")
|
ErrorLog("Unsupported argument type")
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,7 +238,7 @@ if (styles) {
|
||||||
}`)
|
}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) cavnasStart(htmlID string) {
|
func (bridge *wsBridge) canvasStart(htmlID string) {
|
||||||
bridge.canvasBuffer.Reset()
|
bridge.canvasBuffer.Reset()
|
||||||
bridge.canvasBuffer.WriteString(`const ctx = getCanvasContext('`)
|
bridge.canvasBuffer.WriteString(`const ctx = getCanvasContext('`)
|
||||||
bridge.canvasBuffer.WriteString(htmlID)
|
bridge.canvasBuffer.WriteString(htmlID)
|
||||||
|
|
@ -328,7 +328,7 @@ func (bridge *wsBridge) callCanvasImageFunc(url string, property string, funcNam
|
||||||
bridge.canvasBuffer.WriteString(");\n}")
|
bridge.canvasBuffer.WriteString(");\n}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) cavnasFinish() {
|
func (bridge *wsBridge) canvasFinish() {
|
||||||
bridge.canvasBuffer.WriteString("\n")
|
bridge.canvasBuffer.WriteString("\n")
|
||||||
script := bridge.canvasBuffer.String()
|
script := bridge.canvasBuffer.String()
|
||||||
if ProtocolInDebugLog {
|
if ProtocolInDebugLog {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue