forked from mbk-lab/rui_orig
Compare commits
No commits in common. "main" and "0.10" have entirely different histories.
33
CHANGELOG.md
33
CHANGELOG.md
|
|
@ -1,35 +1,6 @@
|
|||
# v0.13.0
|
||||
|
||||
* Added SetHotKey function to Session interface
|
||||
* Added ViewIndex function to ViewsContainer interface
|
||||
* Added ReloadCell function to TableView interface
|
||||
* Added ReloadTableViewCell function
|
||||
* Added "tooltip" property and GetTooltip function
|
||||
* Added "outline-offset" property and GetOutlineOffset function
|
||||
* Changed the main event listener format for "drop-down-event", "edit-text-changed",
|
||||
"color-changed", "number-changed", "date-changed", and "time-changed" events.
|
||||
Old format is "<listener>(<view>, <new value>)", new format is "<listener>(<view>, <new value>, <old value>)"
|
||||
* Changed FocusView function
|
||||
* Added support for height and width range in media styles.
|
||||
Changed MediaStyle, SetMediaStyle, and MediaStyles functions of Theme interface
|
||||
* Bug fixing
|
||||
|
||||
# v0.12.0
|
||||
|
||||
* Added SvgImageView
|
||||
* Added InlineImageFromResource function
|
||||
|
||||
# v0.11.0
|
||||
|
||||
* Added "tabindex", "order", "column-fill", "column-span-all", "background-blend-mode", and "mix-blend-mode" properties
|
||||
* Added GetTabIndex, GetOrder, GetColumnFill, IsColumnSpanAll, GetBackgroundBlendMode, and GetMixBlendMode functions
|
||||
* ClientItem, SetClientItem, and RemoveAllClientItems method added to Session interface
|
||||
* PropertyWithTag method of DataObject interface renamed to PropertyByTag
|
||||
|
||||
# v0.10.0
|
||||
# v.10.0
|
||||
|
||||
* The Canvas.TextWidth method replaced by Canvas.TextMetrics
|
||||
* Added support of WebAssembly
|
||||
|
||||
# v0.9.0
|
||||
|
||||
|
|
@ -101,7 +72,7 @@ Changed MediaStyle, SetMediaStyle, and MediaStyles functions of Theme interface
|
|||
# v0.2.0
|
||||
|
||||
* Added "animation" and "transition" properties, Animation interface, animation events
|
||||
* Renamed ColorProperty constant to ColorTag
|
||||
* Renamed ColorPropery constant to ColorTag
|
||||
* Updated readme
|
||||
* Added the Animation example to the demo
|
||||
* Bug fixing
|
||||
|
|
|
|||
1101
README-ru.md
1101
README-ru.md
File diff suppressed because it is too large
Load Diff
384
README.md
384
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 |
|
||||
| "width" | Width | SizeUnit | Border line width |
|
||||
| "color" | ColorTag | Color | Border line color |
|
||||
| "color" | Color | Color | Border line color |
|
||||
|
||||
Example
|
||||
|
||||
|
|
@ -767,9 +767,9 @@ Example
|
|||
equivalent to
|
||||
|
||||
view.Set(rui.Border, NewBorder(rui.Params{
|
||||
rui.Style : rui.SolidBorder,
|
||||
rui.Width : rui.Px(1),
|
||||
rui.ColorTag: rui.Black,
|
||||
rui.Style: rui.SolidBorder,
|
||||
rui.Width: rui.Px(1),
|
||||
rui.ColorProperty: rui.Black,
|
||||
}))
|
||||
|
||||
The BorderProperty interface can be converted to a ViewBorders structure using the Border function.
|
||||
|
|
@ -831,53 +831,11 @@ Example
|
|||
equivalent to
|
||||
|
||||
view.Set(rui.Border, NewBorder(rui.Params{
|
||||
rui.Style : rui.SolidBorder,
|
||||
rui.Width : rui.Px(1),
|
||||
rui.ColorTag: rui.Black,
|
||||
rui.Style: rui.SolidBorder,
|
||||
rui.Width: rui.Px(1),
|
||||
rui.ColorProperty: rui.Black,
|
||||
}))
|
||||
|
||||
### "outline" and "outline-offset" properties
|
||||
|
||||
The "outline" property defines the border outside the View.
|
||||
A frame line is described by three attributes: line style, thickness, and color.
|
||||
|
||||
The "outline" property is similar to the "border" property. The three main differences between an "outline" frame and a "border" frame are:
|
||||
|
||||
1) the "border" frame is always located inside the boundaries of the View, and the "outline" frame can be located both inside the View and outside it;
|
||||
|
||||
2) all sides of the "outline" frame are the same, while the sides of the "border" frame can have different color, style and thickness.
|
||||
|
||||
3) "border" thickness is added to "padding" and "outline" thickness does not affect "padding".
|
||||
|
||||
The value of the "border" property is stored as an OutlineProperty interface that implements the Properties interface (see above).
|
||||
OutlineProperty can contain the following properties:
|
||||
|
||||
| Property | Constant | Type | Description |
|
||||
|-----------|----------|----------|---------------------|
|
||||
| "style" | Style | int | Border line style |
|
||||
| "width" | Width | SizeUnit | Border line width |
|
||||
| "color" | ColorTag | Color | Border line color |
|
||||
|
||||
Line style can take the following values:
|
||||
|
||||
| Value | Constant | Name | Description |
|
||||
|:-----:|------------|----------|---------------------|
|
||||
| 0 | NoneLine | "none" | No frame |
|
||||
| 1 | SolidLine | "solid" | Solid line |
|
||||
| 2 | DashedLine | "dashed" | Dashed line |
|
||||
| 3 | DottedLine | "dotted" | Dotted line |
|
||||
| 4 | DoubleLine | "double" | Double solid line |
|
||||
|
||||
All other style values are ignored.
|
||||
|
||||
The NewOutline function is used to create the OutlineProperty interface.
|
||||
|
||||
By default, the inner border of the "outline" border is the same as the border of the View (i.e. the border is drawn around the View).
|
||||
To change this behavior, use the "outline-offset" SizeUnit property (OutlineOffset constant).
|
||||
This property defines the distance between the inner border of the frame and the border of the View.
|
||||
A positive value moves the frame away from the View's boundaries, while a negative value forces the frame to be inside the View
|
||||
(in this case, the frame is drawn on top of the contents of the View).
|
||||
|
||||
### "radius" property
|
||||
|
||||
The "radius" property sets the elliptical corner radius of the View. Radii are specified by the RadiusProperty
|
||||
|
|
@ -1012,7 +970,7 @@ The shadow has the following properties:
|
|||
|
||||
| Property | Constant | Type | Description |
|
||||
|-----------------|---------------|----------|-----------------------------------------------------------------------|
|
||||
| "color" | ColorTag | Color | Shadow color |
|
||||
| "color" | ColorProperty | Color | Shadow color |
|
||||
| "inset" | Inset | bool | true - the shadow inside the View, false - outside |
|
||||
| "x-offset" | XOffset | SizeUnit | Offset the shadow along the X axis |
|
||||
| "y-offset" | YOffset | SizeUnit | Offset the shadow along the Y axis |
|
||||
|
|
@ -1031,9 +989,9 @@ The NewShadowWithParams function is used when constants must be used as paramete
|
|||
For example:
|
||||
|
||||
shadow := NewShadowWithParams(rui.Params{
|
||||
rui.ColorTag : "@shadowColor",
|
||||
rui.BlurRadius: 8.0,
|
||||
rui.Dilation : 16.0,
|
||||
rui.ColorProperty : "@shadowColor",
|
||||
rui.BlurRadius : 8.0,
|
||||
rui.Dilation : 16.0,
|
||||
})
|
||||
|
||||
ViewShadow, ViewShadow array, and ViewShadow textual representation can be assigned as a value to the "shadow" property.
|
||||
|
|
@ -1237,47 +1195,6 @@ Can be one of the following int values:
|
|||
|
||||
* ImageVerticalAlign,
|
||||
|
||||
### "background-blend-mode" property
|
||||
|
||||
The "background-blend-mode" int property (BackgroundBlendMode constant)sets how an view's background images should blend
|
||||
with each other and with the view's background color.
|
||||
|
||||
Can take one of the following values:
|
||||
|
||||
| Constant | Value | Name | Description |
|
||||
|-----------------|:-----:|--------------|------------------------------------------------------------------|
|
||||
| BlendNormal | 0 | "normal" | The final color is the top color, regardless of what the bottom color is. The effect is like two opaque pieces of paper overlapping. |
|
||||
| BlendMultiply | 1 | "multiply" | The final color is the result of multiplying the top and bottom colors. A black layer leads to a black final layer, and a white layer leads to no change. The effect is like two images printed on transparent film overlapping. |
|
||||
| BlendScreen | 2 | "screen" | The final color is the result of inverting the colors, multiplying them, and inverting that value. A black layer leads to no change, and a white layer leads to a white final layer. The effect is like two images shone onto a projection screen. |
|
||||
| BlendOverlay | 3 | "overlay" | The final color is the result of multiply if the bottom color is darker, or screen if the bottom color is lighter. This blend mode is equivalent to hard-light but with the layers swapped. |
|
||||
| BlendDarken | 4 | "darken" | The final color is composed of the darkest values of each color channel. |
|
||||
| BlendLighten | 5 | "lighten" | The final color is composed of the lightest values of each color channel. |
|
||||
| BlendColorDodge | 6 | "color-dodge"| The final color is the result of dividing the bottom color by the inverse of the top color. A black foreground leads to no change. A foreground with the inverse color of the backdrop leads to a fully lit color. This blend mode is similar to screen, but the foreground need only be as light as the inverse of the backdrop to create a fully lit color. |
|
||||
| BlendColorBurn | 7 | "color-burn" | The final color is the result of inverting the bottom color, dividing the value by the top color, and inverting that value. A white foreground leads to no change. A foreground with the inverse color of the backdrop leads to a black final image. This blend mode is similar to multiply, but the foreground need only be as dark as the inverse of the backdrop to make the final image black. |
|
||||
| BlendHardLight | 8 | "hard-light" | The final color is the result of multiply if the top color is darker, or screen if the top color is lighter. This blend mode is equivalent to overlay but with the layers swapped. The effect is similar to shining a harsh spotlight on the backdrop. |
|
||||
| BlendSoftLight | 9 | "soft-light" | The final color is similar to hard-light, but softer. This blend mode behaves similar to hard-light. The effect is similar to shining a diffused spotlight on the backdrop*.* |
|
||||
| BlendDifference | 10 | "difference" | The final color is the result of subtracting the darker of the two colors from the lighter one. A black layer has no effect, while a white layer inverts the other layer's color. |
|
||||
| BlendExclusion | 11 | "exclusion" | The final color is similar to difference, but with less contrast. As with difference, a black layer has no effect, while a white layer inverts the other layer's color. |
|
||||
| BlendHue | 12 | "hue" | The final color has the hue of the top color, while using the saturation and luminosity of the bottom color. |
|
||||
| BlendSaturation | 13 | "saturation" | The final color has the saturation of the top color, while using the hue and luminosity of the bottom color. A pure gray backdrop, having no saturation, will have no effect. |
|
||||
| BlendColor | 14 | "color" | The final color has the hue and saturation of the top color, while using the luminosity of the bottom color. The effect preserves gray levels and can be used to colorize the foreground. |
|
||||
| BlendLuminosity | 15 | "luminosity" | The final color has the luminosity of the top color, while using the hue and saturation of the bottom color. This blend mode is equivalent to BlendColor, but with the layers swapped. |
|
||||
|
||||
You can get the value of this property using the function
|
||||
|
||||
func GetBackgroundBlendMode(view View, subviewID ...string) int
|
||||
|
||||
### "mix-blend-mode" property
|
||||
|
||||
The "mix-blend-mode" int property (MixBlendMode constant) sets how a view's content should blend
|
||||
with the content of the view's parent and the view's background.
|
||||
|
||||
Possible values of this property are similar to the values of the "background-blend-mode" property (see above)
|
||||
|
||||
You can get the value of this property using the function
|
||||
|
||||
func GetMixBlendMode(view View, subviewID ...string) int
|
||||
|
||||
### "clip" property
|
||||
|
||||
The "clip" property (Clip constant) of the ClipShape type specifies the crop area.
|
||||
|
|
@ -1340,29 +1257,13 @@ The textual description of the polygonal cropping area is in the following forma
|
|||
|
||||
### "opacity" property
|
||||
|
||||
The "opacity" property (Opacity constant) of the float64 type sets the transparency of the View. Valid values are from 0 to 1.
|
||||
The "opacity" property (constant Opacity) of the float64 type sets the transparency of the View. Valid values are from 0 to 1.
|
||||
Where 1 - View is fully opaque, 0 - fully transparent.
|
||||
|
||||
You can get the value of this property using the function
|
||||
|
||||
func GetOpacity(view View, subviewID ...string) float64
|
||||
|
||||
### "tabindex" property
|
||||
|
||||
The "tabindex" int property (TabIndex constant) determines whether this View should participate in sequential navigation
|
||||
throughout the page using the keyboard and in what order. It can take one of the following types of values:
|
||||
|
||||
* negative value - View can be selected with the mouse or touch, but does not participate in sequential navigation;
|
||||
|
||||
* 0 - View can be selected and reached using sequential navigation, the order of navigation is determined by the browser (usually in order of addition);
|
||||
|
||||
* positive value - the element will be reached (and selected) using sequential navigation, and navigation is performed by ascending "tabindex" value.
|
||||
If multiple elements contain the same "tabindex" value, navigation is done in the order in which they were added.
|
||||
|
||||
You can get the value of this property using the function
|
||||
|
||||
func GetTabIndex(viewView, subviewID ...string) int
|
||||
|
||||
### "z-index" property
|
||||
|
||||
The "z-index" property (constant ZIndex) of type int defines the position of the element and its children along the z-axis.
|
||||
|
|
@ -1453,12 +1354,6 @@ It also helps to voice the interface to systems for people with disabilities:
|
|||
| 18 | "blockquote" | Quote. Changes the style of the text |
|
||||
| 19 | "code" | Program code. Changes the style of the text |
|
||||
|
||||
### "tooltip" property
|
||||
|
||||
The "tooltip" string property (Tooltip constant) specifies the tooltip text.
|
||||
Tooltip pops up when hovering the mouse cursor.
|
||||
You can use html tags when formatting the tooltip text.
|
||||
|
||||
### Text properties
|
||||
|
||||
All properties listed in this section are inherited, i.e. the property will apply
|
||||
|
|
@ -1623,7 +1518,7 @@ You can get the value of this property using the function
|
|||
|
||||
#### "text-weight" property
|
||||
|
||||
The "text-weight" int property (TextWeight constant) sets the font style. Valid values:
|
||||
Свойство "text-weight" (константа TextWeight) - свойство типа int устанавливает начертание шрифта. Допустимые значения:
|
||||
|
||||
| Value | Constant | Common name of the face |
|
||||
|:-----:|----------------|---------------------------|
|
||||
|
|
@ -1658,8 +1553,8 @@ To create a ViewShadow for the text shadow, the following functions are used:
|
|||
The NewShadowWithParams function is used when constants must be used as parameters. For example:
|
||||
|
||||
shadow := NewShadowWithParams(rui.Params{
|
||||
rui.ColorTag : "@shadowColor",
|
||||
rui.BlurRadius: 8.0,
|
||||
rui.ColorProperty : "@shadowColor",
|
||||
rui.BlurRadius : 8.0,
|
||||
})
|
||||
|
||||
ViewShadow, ViewShadow array, ViewShadow textual representation can be assigned as a value to the "text-shadow" property (see above, section "The 'shadow' property").
|
||||
|
|
@ -2276,15 +2171,6 @@ If index is less than 0, then to the beginning of the list.
|
|||
This function removes the View from the given position and returns it.
|
||||
If index points outside the bounds of the list, then nothing is removed, and the function returns nil.
|
||||
|
||||
ViewIndex(view View) int
|
||||
|
||||
This function returns the index of the child View, or -1 if there is no such View in the container.
|
||||
It is often used in conjunction with RemoveView if the index of the child View is unknown:
|
||||
|
||||
if index := container.ViewIndex(view); index >= 0 {
|
||||
container.RemoveView(index)
|
||||
}
|
||||
|
||||
## ListLayout
|
||||
|
||||
ListLayout is a container that implements the ViewsContainer interface. To create it, use the function
|
||||
|
|
@ -2352,17 +2238,6 @@ alignment of items in the list. Valid values:
|
|||
The "list-row-gap" and "list-column-gap" SizeUnit properties (ListRowGap and ListColumnGap constants)
|
||||
allow you to set the distance between the rows and columns of the container, respectively. The default is 0px.
|
||||
|
||||
### "order"
|
||||
|
||||
The "order" property (Order constant) of type int is used by Views placed in a ListLayout or GridLayout container (see below),
|
||||
to change its position in the container.
|
||||
The "order" property defines the order used to place the View in the container. The elements are arranged in ascending order by their order value.
|
||||
Elements with the same order value are placed in the order in which they were added to the container.
|
||||
|
||||
The default value is 0. Therefore, negative values of the "order" property must be used to place the View at the beginning.
|
||||
|
||||
Note: The "order" property only affects the visual order of the elements, not the logical order or tabs.
|
||||
|
||||
## GridLayout
|
||||
|
||||
GridLayout is a container that implements the ViewsContainer interface. To create it, use the function
|
||||
|
|
@ -2544,16 +2419,16 @@ The separator line is described by three attributes: line style, thickness, and
|
|||
The value of the "column-separator" property is stored as the ColumnSeparatorProperty interface,
|
||||
which implements the Properties interface (see above). ColumnSeparatorProperty can contain the following properties:
|
||||
|
||||
| Property | Constant | Type | Description |
|
||||
|----------|-----------|----------|----------------|
|
||||
| "style" | Style | int | Line style |
|
||||
| "width" | Width | SizeUnit | Line thickness |
|
||||
| "color" | ColorTag | Color | Line color |
|
||||
| Property | Constant | Type | Description |
|
||||
|----------|---------------|----------|----------------|
|
||||
| "style" | Style | int | Line style |
|
||||
| "width" | Width | SizeUnit | Line thickness |
|
||||
| "color" | ColorProperty | Color | Line color |
|
||||
|
||||
Line style can take the following values:
|
||||
|
||||
| Value | Constant | Name | Description |
|
||||
|:-----:|------------|----------|-------------------|
|
||||
|:-----:|------------|----------| ------------------|
|
||||
| 0 | NoneLine | "none" | No frame |
|
||||
| 1 | SolidLine | "solid" | Solid line |
|
||||
| 2 | DashedLine | "dashed" | Dashed line |
|
||||
|
|
@ -2602,25 +2477,11 @@ For example
|
|||
equivalent to
|
||||
|
||||
view.Set(rui.ColumnSeparator, ColumnSeparatorProperty(rui.Params{
|
||||
rui.Style : rui.SolidBorder,
|
||||
rui.Width : rui.Px(1),
|
||||
rui.ColorTag: rui.Black,
|
||||
rui.Style: rui.SolidBorder,
|
||||
rui.Width: rui.Px(1),
|
||||
rui.ColorProperty: rui.Black,
|
||||
}))
|
||||
|
||||
### "column-fill" property
|
||||
|
||||
The "column-fill" int property (ColumnFill constant) controls how an ColumnLayout's contents are balanced when broken into columns.
|
||||
Valid values:
|
||||
|
||||
| Value | Constant | Name | Description |
|
||||
|:-----:|-------------------|-----------|------------------------------------------------------------|
|
||||
| 0 | ColumnFillBalance | "balance" | Content is equally divided between columns (default value) |
|
||||
| 1 | ColumnFillAuto | "auto" | Columns are filled sequentially. Content takes up only the room it needs, possibly resulting in some columns remaining empty |
|
||||
|
||||
You can get the value of this property using the function
|
||||
|
||||
func GetColumnFill(view View, subviewID ...string) int
|
||||
|
||||
### "avoid-break" property
|
||||
|
||||
When forming columns, ColumnLayout can break some types of View, so that the beginning
|
||||
|
|
@ -2636,20 +2497,6 @@ You can get the value of this property using the function
|
|||
|
||||
func GetAvoidBreak(view View, subviewID ...string) bool
|
||||
|
||||
### "column-span-all" property
|
||||
|
||||
The "column-span-all" bool property (ColumnSpanAll constant) is set for Views placed in the ColumnLayout.
|
||||
If this property is set to true, then the View expands to the full width of the ColumnLayout, covering all columns.
|
||||
Such a View will, as it were, break the container.
|
||||
|
||||
Typically, this property is used for headers.
|
||||
|
||||
The default value is "false".
|
||||
|
||||
You can get the value of this property using the function
|
||||
|
||||
func IsColumnSpanAll(view View, subviewID ...string) bool
|
||||
|
||||
## StackLayout
|
||||
|
||||
StackLayout is a container that implements the ViewsContainer interface.
|
||||
|
|
@ -2892,7 +2739,7 @@ It determines how the text is cut if it goes out of bounds.
|
|||
This property of type int can take the following values
|
||||
|
||||
| Value | Constant | Name | Cropping Text |
|
||||
|:-----:|----------------------|------------|-------------------------------------------------------------|
|
||||
|:-----:|----------------------| -----------|-------------------------------------------------------------|
|
||||
| 0 | TextOverflowClip | "clip" | Text is clipped at the border (default) |
|
||||
| 1 | TextOverflowEllipsis | "ellipsis" | At the end of the visible part of the text '…' is displayed |
|
||||
|
||||
|
|
@ -2905,25 +2752,7 @@ To create an ImageView function is used:
|
|||
func NewImageView(session Session, params Params) ImageView
|
||||
|
||||
The displayed image is specified by the string property "src" (Source constant).
|
||||
As a value, this property is assigned either the name of the image in the "images" folder of the resources, or the url of the image, or inline-image.
|
||||
|
||||
An inline-image is the content of an image file encoded in base64 format.
|
||||
To get an inline-image from the application resources, use the function
|
||||
|
||||
func InlineImageFromResource(filename string) (string, bool)
|
||||
|
||||
Inline-images must be used in WebAssembly applications
|
||||
if you want to host images in resources rather than on an external server.
|
||||
Inline-images can cause app freezes in Safari and should be avoided.
|
||||
Example
|
||||
|
||||
if runtime.GOOS == "js" {
|
||||
if image, ok := rui.InlineImageFromResource("image.png"); ok {
|
||||
view.Set(rui.Source, image)
|
||||
}
|
||||
} else {
|
||||
view.Set(rui.Source, "image.png")
|
||||
}
|
||||
As a value, this property is assigned either the name of the image in the "images" folder of the resources, or the url of the image.
|
||||
|
||||
ImageView allows you to display different images depending on screen density
|
||||
(See section "Images for screens with different pixel densities").
|
||||
|
|
@ -2989,39 +2818,6 @@ The following functions can be used to retrieve ImageView property values:
|
|||
func GetImageViewVerticalAlign(view View, subviewID ...string) int
|
||||
func GetImageViewHorizontalAlign(view View, subviewID ...string) int
|
||||
|
||||
## SvgImageView
|
||||
|
||||
The SvgImageView element extending the View interface is designed to display svg images.
|
||||
|
||||
To create an SvgImageView function is used:
|
||||
|
||||
func NewSvgImageView(session Session, params Params) ImageView
|
||||
|
||||
The image to be displayed is specified by the string property "content" (constant Content).
|
||||
The value of this property can be assigned
|
||||
* the image file name in the images folder of the resources;
|
||||
* image url;
|
||||
* content of the svg image.
|
||||
|
||||
Examples
|
||||
|
||||
rui.Set(rootView, "iconView", rui.Content, "icon.svg")
|
||||
|
||||
rui.Set(rootView, "iconView", rui.Content, `<svg width="32" height="32" version="1.1" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(-499.08 -247.12)">
|
||||
<path d="m508.08 249.12 14 14-14 14" fill="none" stroke="#0f0" stroke-linecap="round" stroke-width="1px"/>
|
||||
</g>
|
||||
</svg>`)
|
||||
|
||||
Regardless of how you determined the property of "Content" to the client is always transmitted the contents of the SVG image. For example, if you set the image as follows
|
||||
|
||||
rui.Set(rootView, "iconView", rui.Content, "icon.svg")
|
||||
|
||||
then the program will first upload the contents of the "icon.svg" file to the memory,
|
||||
and then transmit this contents to the client as the value of the "content" property.
|
||||
|
||||
This allows you to include SVG images in the resources of a WebAssembly application.
|
||||
|
||||
## EditView
|
||||
|
||||
The EditView element is a test editor and extends the View interface.
|
||||
|
|
@ -3086,21 +2882,13 @@ The following functions can be used to get the values of the properties of an Ed
|
|||
The "edit-text-changed" event (EditTextChangedEvent constant) is used to track changes to the text.
|
||||
The main event listener has the following format:
|
||||
|
||||
func(EditView, string, string)
|
||||
func(EditView, string)
|
||||
|
||||
where the second argument is the new text value, the third argument is the previous text value.
|
||||
|
||||
Additional event listeners can have the following format
|
||||
|
||||
func(EditView, newText string)
|
||||
func(newText, oldText string)
|
||||
func(newText string)
|
||||
func(EditView)
|
||||
func()
|
||||
where the second argument is the new text value
|
||||
|
||||
You can get the current list of text change listeners using the function
|
||||
|
||||
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string, string)
|
||||
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string)
|
||||
|
||||
## NumberPicker
|
||||
|
||||
|
|
@ -3156,21 +2944,13 @@ You can read the values of these properties using the functions:
|
|||
The "number-changed" event (NumberChangedEvent constant) is used to track the change in the entered value.
|
||||
The main event listener has the following format:
|
||||
|
||||
func(picker NumberPicker, newValue, oldValue float64)
|
||||
|
||||
where the second argument is the new value, the third argument is the previous value.
|
||||
|
||||
Additional event listeners can have the following format
|
||||
|
||||
func(picker NumberPicker, newValue float64)
|
||||
func(newValue, oldValue float64)
|
||||
func(newValue float64)
|
||||
func(picker NumberPicker)
|
||||
func()
|
||||
|
||||
where the second argument is the new value
|
||||
|
||||
You can get the current list of value change listeners using the function
|
||||
|
||||
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64, float64)
|
||||
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64)
|
||||
|
||||
## DatePicker
|
||||
|
||||
|
|
@ -3211,21 +2991,13 @@ You can read the values of these properties using the functions:
|
|||
The "date-changed" event (DateChangedEvent constant) is used to track the change in the entered value.
|
||||
The main event listener has the following format:
|
||||
|
||||
func(picker DatePicker, newDate, oldDate time.Time)
|
||||
|
||||
where the second argument is the new date value, the third argument is the previous date value.
|
||||
|
||||
Additional event listeners can have the following format
|
||||
|
||||
func(picker DatePicker, newDate time.Time)
|
||||
func(newDate, oldDate time.Time)
|
||||
func(newDate time.Time)
|
||||
func(picker DatePicker)
|
||||
func()
|
||||
|
||||
where the second argument is the new date value
|
||||
|
||||
You can get the current list of date change listeners using the function
|
||||
|
||||
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time, time.Time)
|
||||
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time)
|
||||
|
||||
## TimePicker
|
||||
|
||||
|
|
@ -3266,21 +3038,13 @@ You can read the values of these properties using the functions:
|
|||
The "time-changed" event (TimeChangedEvent constant) is used to track the change in the entered value.
|
||||
The main event listener has the following format:
|
||||
|
||||
func(picker TimePicker, newTime, oldTime time.Time)
|
||||
|
||||
where the second argument is the new time value, the third argument is the previous time value.
|
||||
|
||||
Additional event listeners can have the following format
|
||||
|
||||
func(picker TimePicker, newTime time.Time)
|
||||
func(newTime, oldTime time.Time)
|
||||
func(newTime time.Time)
|
||||
func(picker TimePicker)
|
||||
func()
|
||||
|
||||
where the second argument is the new time value
|
||||
|
||||
You can get the current list of date change listeners using the function
|
||||
|
||||
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time, time.Time)
|
||||
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time)
|
||||
|
||||
## ColorPicker
|
||||
|
||||
|
|
@ -3304,21 +3068,13 @@ The value of the property "color-picker-value" can also be read using the functi
|
|||
The "color-changed" event (ColorChangedEvent constant) is used to track the change in the selected color.
|
||||
The main event listener has the following format:
|
||||
|
||||
func(picker ColorPicker, newColor, oldColor Color)
|
||||
func(picker ColorPicker, newColor Color)
|
||||
|
||||
where the second argument is the new color value, the third argument is the previous color value.
|
||||
|
||||
Additional event listeners can have the following format
|
||||
|
||||
func(picker ColorPicker, newColor string)
|
||||
func(newColor, oldColor string)
|
||||
func(newColor string)
|
||||
func(picker ColorPicker)
|
||||
func()
|
||||
where the second argument is the new color value
|
||||
|
||||
You can get the current list of date change listeners using the function
|
||||
|
||||
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color, Color)
|
||||
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color)
|
||||
|
||||
## FilePicker
|
||||
|
||||
|
|
@ -3448,19 +3204,11 @@ The main event listener has the following format:
|
|||
|
||||
func(list DropDownList, newCurrent int)
|
||||
|
||||
where the second argument is the index of the selected item, the third argument is the previous index value.
|
||||
|
||||
Additional event listeners can have the following format
|
||||
|
||||
func(list DropDownList, newCurrent int)
|
||||
func(newCurrent, oldCurrent int)
|
||||
func(newCurrent int)
|
||||
func(list DropDownList)
|
||||
func()
|
||||
where the second argument is the index of the selected item
|
||||
|
||||
You can get the current list of date change listeners using the function
|
||||
|
||||
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int, int)
|
||||
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int)
|
||||
|
||||
## ProgressBar
|
||||
|
||||
|
|
@ -3763,18 +3511,6 @@ The "content" property can also be assigned the following data types
|
|||
|
||||
[][]any and [][]string are converted to a TableAdapter when assigned.
|
||||
|
||||
If the elements of the table change during operation, then to update the contents of the table,
|
||||
you must call one of the two methods of the TableView interface
|
||||
|
||||
* ReloadTableData()
|
||||
* ReloadCell(row, column int)
|
||||
|
||||
The ReloadTableData method updates the entire table, while ReloadCell updates the contents of only a specific table cell.
|
||||
Global functions can be used to call the ReloadTableData and ReloadCell methods
|
||||
|
||||
func ReloadTableViewData(view View, subviewID ...string) bool
|
||||
func ReloadTableViewCell(row, column int, view View, subviewID ...string) bool
|
||||
|
||||
### "cell-style" property
|
||||
|
||||
The "cell-style" property (CellStyle constant) is used to customize the appearance of a table cell.
|
||||
|
|
@ -4400,7 +4136,7 @@ AudioPlayer and VideoPlayer are elements for audio and video playback.
|
|||
Both elements implement the MediaPlayer interface. Most of the properties and all events
|
||||
of AudioPlayer and VideoPlayer are implemented through the MediaPlayer.
|
||||
|
||||
### "src" property
|
||||
### Свойство "src"
|
||||
|
||||
The "src" property (Source constant) specifies one or more media sources. The "src" property can take on the following types:
|
||||
|
||||
|
|
@ -5171,9 +4907,6 @@ It is used when the client needs to transfer a file from the server.
|
|||
* DownloadFileData(filename string, data [] byte) downloads (saves) on the client side a file
|
||||
with a specified name and specified content. Typically used to transfer a file generated in server memory.
|
||||
|
||||
* SetHotKey(keyCode KeyCode, controlKeys ControlKeyMask, fn func(Session)) - sets the function that will be called
|
||||
when the given hotkey is pressed.
|
||||
|
||||
## Resource description format
|
||||
|
||||
Application resources (themes, views, translations) can be described as text (utf-8).
|
||||
|
|
@ -5461,17 +5194,9 @@ In addition to general styles, you can add styles for specific work modes. To do
|
|||
* ":portrait" or ":landscape" are respectively styles for portrait or landscape mode of the program.
|
||||
Attention means the aspect ratio of the program window, not the screen.
|
||||
|
||||
* ":width<min-width>-<max-width>" - styles for a screen whose width is in the range specified in logical pixels.
|
||||
* ":width< size >" are styles for a screen whose width does not exceed the specified size in logical pixels.
|
||||
|
||||
* ":width<max-width>" - styles for a screen whose width does not exceed the specified value in logical pixels.
|
||||
|
||||
* ":width<min-width>-" - styles for a screen whose width is greater than the specified value in logical pixels.
|
||||
|
||||
* ":height<min-height>-<max-height>" - styles for a screen whose height is in the range specified in logical pixels.
|
||||
|
||||
* ":height<max-height>" - styles for a screen whose height does not exceed the specified value in logical pixels.
|
||||
|
||||
* ":height<minimum-height>-" - styles for a screen whose height is greater than the specified value in logical pixels.
|
||||
* ":height< size >" are styles for a screen whose height does not exceed the specified size in logical pixels.
|
||||
|
||||
For example
|
||||
|
||||
|
|
@ -5503,19 +5228,7 @@ For example
|
|||
width = 100%,
|
||||
height = 50%,
|
||||
},
|
||||
],
|
||||
styles:portrait:width320-640 = [
|
||||
samplePage {
|
||||
width = 90%,
|
||||
height = 60%,
|
||||
},
|
||||
],
|
||||
styles:portrait:width640- = [
|
||||
samplePage {
|
||||
width = 80%,
|
||||
height = 70%,
|
||||
},
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
## Standard constants and styles
|
||||
|
|
@ -5583,9 +5296,6 @@ System color constants that you can override:
|
|||
| ruiPopupTextColor | Popup text color |
|
||||
| ruiPopupTitleColor | Popup title background color |
|
||||
| ruiPopupTitleTextColor | Popup Title Text Color |
|
||||
| ruiTooltipBackground | Tooltip background color |
|
||||
| ruiTooltipTextColor | Tooltip text color |
|
||||
| ruiTooltipShadowColor | Tooltip shadow color |
|
||||
|
||||
Constants that you can override:
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ const (
|
|||
LinearTiming = "linear"
|
||||
)
|
||||
|
||||
// StepsTiming return a timing function along stepCount stops along the transition, displaying each stop for equal lengths of time
|
||||
// StepsTiming return a timing function along stepCount stops along the transition, diplaying each stop for equal lengths of time
|
||||
func StepsTiming(stepCount int) string {
|
||||
return "steps(" + strconv.Itoa(stepCount) + ")"
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ type Animation interface {
|
|||
writeTransitionString(tag string, buffer *strings.Builder)
|
||||
animationCSS(session Session) string
|
||||
transitionCSS(buffer *strings.Builder, session Session)
|
||||
hasAnimatedProperty() bool
|
||||
hasAnimatedPropery() bool
|
||||
animationName() string
|
||||
}
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ func NewAnimation(params Params) Animation {
|
|||
return animation
|
||||
}
|
||||
|
||||
func (animation *animationData) hasAnimatedProperty() bool {
|
||||
func (animation *animationData) hasAnimatedPropery() bool {
|
||||
props := animation.getRaw(PropertyTag)
|
||||
if props == nil {
|
||||
ErrorLog("There are no animated properties.")
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const (
|
|||
AnimationStartEvent = "animation-start-event"
|
||||
|
||||
// AnimationEndEvent is the constant for "animation-end-event" property tag.
|
||||
// The "animation-end-event" is fired when an animation has completed.
|
||||
// The "animation-end-event" is fired when aт фnimation has completed.
|
||||
// If the animation aborts before reaching completion, such as if the element is removed
|
||||
// or the animation is removed from the element, the "animation-end-event" is not fired.
|
||||
AnimationEndEvent = "animation-end-event"
|
||||
|
|
@ -46,7 +46,7 @@ const (
|
|||
|
||||
// AnimationIterationEvent is the constant for "animation-iteration-event" property tag.
|
||||
// The "animation-iteration-event" is fired when an iteration of an animation ends,
|
||||
// and another one begins. This event does not occur at the same time as the animation end event,
|
||||
// and another one begins. This event does not occur at the same time as the animationend event,
|
||||
// and therefore does not occur for animations with an "iteration-count" of one.
|
||||
AnimationIterationEvent = "animation-iteration-event"
|
||||
)
|
||||
|
|
@ -91,10 +91,7 @@ func transitionEventsHtml(view View, buffer *strings.Builder) {
|
|||
for tag, js := range transitionEvents {
|
||||
if value := view.getRaw(tag); value != nil {
|
||||
if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
|
||||
buffer.WriteString(js.jsEvent)
|
||||
buffer.WriteString(`="`)
|
||||
buffer.WriteString(js.jsFunc)
|
||||
buffer.WriteString(`(this, event)" `)
|
||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -160,10 +157,7 @@ func animationEventsHtml(view View, buffer *strings.Builder) {
|
|||
for tag, js := range animationEvents {
|
||||
if value := view.getRaw(tag); value != nil {
|
||||
if listeners, ok := value.([]func(View)); ok && len(listeners) > 0 {
|
||||
buffer.WriteString(js.jsEvent)
|
||||
buffer.WriteString(`="`)
|
||||
buffer.WriteString(js.jsFunc)
|
||||
buffer.WriteString(`(this, event)" `)
|
||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
appServer.go
11
appServer.go
|
|
@ -156,7 +156,14 @@ func (app *application) socketReader(bridge webBridge) {
|
|||
ErrorLog(`"session" key not found`)
|
||||
}
|
||||
|
||||
bridge.writeMessage("restartSession();")
|
||||
answer := ""
|
||||
if session, answer = app.startSession(obj, events, bridge); session != nil {
|
||||
if !bridge.writeMessage(answer) {
|
||||
return
|
||||
}
|
||||
session.onStart()
|
||||
go sessionEventHandler(session, events, bridge)
|
||||
}
|
||||
|
||||
case "answer":
|
||||
session.handleAnswer(obj)
|
||||
|
|
@ -288,7 +295,7 @@ func OpenBrowser(url string) bool {
|
|||
case "linux":
|
||||
for _, provider := range []string{"xdg-open", "x-www-browser", "www-browser"} {
|
||||
if _, err = exec.LookPath(provider); err == nil {
|
||||
if err = exec.Command(provider, url).Start(); err == nil {
|
||||
if exec.Command(provider, url).Start(); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@ func (app *wasmApp) init(params AppParams) {
|
|||
div = document.Call("createElement", "div")
|
||||
div.Set("className", "ruiPopupLayer")
|
||||
div.Set("id", "ruiPopupLayer")
|
||||
div.Set("onclick", "clickOutsidePopup(event)")
|
||||
div.Set("style", "visibility: hidden;")
|
||||
body.Call("appendChild", div)
|
||||
|
||||
|
|
|
|||
372
app_scripts.js
372
app_scripts.js
|
|
@ -52,31 +52,9 @@ function sessionInfo() {
|
|||
message += ",pixel-ratio=" + pixelRatio;
|
||||
}
|
||||
|
||||
if (localStorage.length > 0) {
|
||||
message += ",storage="
|
||||
lead = "_{"
|
||||
for (var i = 0; i < localStorage.length; i++) {
|
||||
var key = localStorage.key(i)
|
||||
var value = localStorage.getItem(key)
|
||||
key = key.replaceAll(/\\/g, "\\\\")
|
||||
key = key.replaceAll(/\"/g, "\\\"")
|
||||
key = key.replaceAll(/\'/g, "\\\'")
|
||||
value = value.replaceAll(/\\/g, "\\\\")
|
||||
value = value.replaceAll(/\"/g, "\\\"")
|
||||
value = value.replaceAll(/\'/g, "\\\'")
|
||||
message += lead + "\"" + key + "\"=\"" + value + "\""
|
||||
lead = ","
|
||||
}
|
||||
message += "}"
|
||||
}
|
||||
|
||||
return message + "}";
|
||||
}
|
||||
|
||||
function restartSession() {
|
||||
sendMessage( sessionInfo() );
|
||||
}
|
||||
|
||||
function getIntAttribute(element, tag) {
|
||||
let value = element.getAttribute(tag);
|
||||
if (value) {
|
||||
|
|
@ -246,26 +224,22 @@ function enterOrSpaceKeyClickEvent(event) {
|
|||
function activateTab(layoutId, tabNumber) {
|
||||
var element = document.getElementById(layoutId);
|
||||
if (element) {
|
||||
var currentNumber = element.getAttribute("data-current");
|
||||
if (currentNumber != tabNumber) {
|
||||
function setTab(number, styleProperty, display) {
|
||||
var tab = document.getElementById(layoutId + '-' + number);
|
||||
var currentTabId = element.getAttribute("data-current");
|
||||
var newTabId = layoutId + '-' + tabNumber;
|
||||
if (currentTabId != newTabId) {
|
||||
function setTab(tabId, styleProperty, display) {
|
||||
var tab = document.getElementById(tabId);
|
||||
if (tab) {
|
||||
tab.className = element.getAttribute(styleProperty);
|
||||
var page = document.getElementById(tab.getAttribute("data-view"));
|
||||
if (page) {
|
||||
page.style.display = display;
|
||||
}
|
||||
return
|
||||
}
|
||||
var page = document.getElementById(layoutId + "-page" + number);
|
||||
if (page) {
|
||||
page.style.display = display;
|
||||
}
|
||||
}
|
||||
setTab(currentNumber, "data-inactiveTabStyle", "none")
|
||||
setTab(tabNumber, "data-activeTabStyle", "");
|
||||
element.setAttribute("data-current", tabNumber);
|
||||
setTab(currentTabId, "data-inactiveTabStyle", "none")
|
||||
setTab(newTabId, "data-activeTabStyle", "");
|
||||
element.setAttribute("data-current", newTabId);
|
||||
scanElementsSize()
|
||||
}
|
||||
}
|
||||
|
|
@ -311,18 +285,7 @@ function keyEvent(element, event, tag) {
|
|||
message += ",timeStamp=" + event.timeStamp;
|
||||
}
|
||||
if (event.key) {
|
||||
switch (event.key) {
|
||||
case '"':
|
||||
message += ",key=`\"`";
|
||||
break
|
||||
|
||||
case '\\':
|
||||
message += ",key=`\\`";
|
||||
break
|
||||
|
||||
default:
|
||||
message += ",key=\"" + event.key + "\"";
|
||||
}
|
||||
message += ",key=\"" + event.key + "\"";
|
||||
}
|
||||
if (event.code) {
|
||||
message += ",code=\"" + event.code + "\"";
|
||||
|
|
@ -445,7 +408,6 @@ function mouseOutEvent(element, event) {
|
|||
function clickEvent(element, event) {
|
||||
mouseEvent(element, event, "click-event")
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function doubleClickEvent(element, event) {
|
||||
|
|
@ -609,10 +571,6 @@ function selectDropDownListItem(elementId, number) {
|
|||
function listItemClickEvent(element, event) {
|
||||
event.stopPropagation();
|
||||
|
||||
if (element.getAttribute("data-disabled") == "1") {
|
||||
return
|
||||
}
|
||||
|
||||
var selected = false;
|
||||
if (element.classList) {
|
||||
const focusStyle = getListFocusedItemStyle(element);
|
||||
|
|
@ -730,9 +688,6 @@ function findRightListItem(list, x, y) {
|
|||
var count = list.childNodes.length;
|
||||
for (var i = 0; i < count; i++) {
|
||||
var item = list.childNodes[i];
|
||||
if (item.getAttribute("data-disabled") == "1") {
|
||||
continue;
|
||||
}
|
||||
if (item.offsetLeft >= x) {
|
||||
if (result) {
|
||||
var result_dy = Math.abs(result.offsetTop - y);
|
||||
|
|
@ -754,9 +709,6 @@ function findLeftListItem(list, x, y) {
|
|||
var count = list.childNodes.length;
|
||||
for (var i = 0; i < count; i++) {
|
||||
var item = list.childNodes[i];
|
||||
if (item.getAttribute("data-disabled") == "1") {
|
||||
continue;
|
||||
}
|
||||
if (item.offsetLeft < x) {
|
||||
if (result) {
|
||||
var result_dy = Math.abs(result.offsetTop - y);
|
||||
|
|
@ -778,9 +730,6 @@ function findTopListItem(list, x, y) {
|
|||
var count = list.childNodes.length;
|
||||
for (var i = 0; i < count; i++) {
|
||||
var item = list.childNodes[i];
|
||||
if (item.getAttribute("data-disabled") == "1") {
|
||||
continue;
|
||||
}
|
||||
if (item.offsetTop < y) {
|
||||
if (result) {
|
||||
var result_dx = Math.abs(result.offsetLeft - x);
|
||||
|
|
@ -802,9 +751,6 @@ function findBottomListItem(list, x, y) {
|
|||
var count = list.childNodes.length;
|
||||
for (var i = 0; i < count; i++) {
|
||||
var item = list.childNodes[i];
|
||||
if (item.getAttribute("data-disabled") == "1") {
|
||||
continue;
|
||||
}
|
||||
if (item.offsetTop >= y) {
|
||||
if (result) {
|
||||
var result_dx = Math.abs(result.offsetLeft - x);
|
||||
|
|
@ -853,77 +799,50 @@ function listViewKeyDownEvent(element, event) {
|
|||
if (current) {
|
||||
var item
|
||||
switch (key) {
|
||||
case " ":
|
||||
case "Enter":
|
||||
var message = "itemClick{session=" + sessionID + ",id=" + element.id + "}";
|
||||
sendMessage(message);
|
||||
break;
|
||||
case " ":
|
||||
case "Enter":
|
||||
var message = "itemClick{session=" + sessionID + ",id=" + element.id + "}";
|
||||
sendMessage(message);
|
||||
break;
|
||||
|
||||
case "ArrowLeft":
|
||||
item = findLeftListItem(element, current.offsetLeft, current.offsetTop);
|
||||
break;
|
||||
|
||||
case "ArrowRight":
|
||||
item = findRightListItem(element, current.offsetLeft + current.offsetWidth, current.offsetTop);
|
||||
break;
|
||||
|
||||
case "ArrowDown":
|
||||
item = findBottomListItem(element, current.offsetLeft, current.offsetTop + current.offsetHeight);
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
item = findLeftListItem(element, current.offsetLeft, current.offsetTop);
|
||||
break;
|
||||
|
||||
case "ArrowRight":
|
||||
item = findRightListItem(element, current.offsetLeft + current.offsetWidth, current.offsetTop);
|
||||
break;
|
||||
|
||||
case "ArrowDown":
|
||||
item = findBottomListItem(element, current.offsetLeft, current.offsetTop + current.offsetHeight);
|
||||
break;
|
||||
|
||||
case "ArrowUp":
|
||||
item = findTopListItem(element, current.offsetLeft, current.offsetTop);
|
||||
break;
|
||||
case "ArrowUp":
|
||||
item = findTopListItem(element, current.offsetLeft, current.offsetTop);
|
||||
break;
|
||||
|
||||
case "Home":
|
||||
item = element.childNodes[0];
|
||||
break;
|
||||
case "Home":
|
||||
item = element.childNodes[0];
|
||||
break;
|
||||
|
||||
case "End":
|
||||
item = element.childNodes[element.childNodes.length - 1];
|
||||
break;
|
||||
case "End":
|
||||
item = element.childNodes[element.childNodes.length - 1];
|
||||
break;
|
||||
|
||||
case "PageUp":
|
||||
// TODO
|
||||
break;
|
||||
case "PageUp":
|
||||
// TODO
|
||||
break;
|
||||
|
||||
case "PageDown":
|
||||
// TODO
|
||||
break;
|
||||
case "PageDown":
|
||||
// TODO
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
if (item && item !== current) {
|
||||
selectListItem(element, item, true);
|
||||
}
|
||||
} else {
|
||||
switch (key) {
|
||||
case " ":
|
||||
case "Enter":
|
||||
case "ArrowLeft":
|
||||
case "ArrowUp":
|
||||
case "ArrowRight":
|
||||
case "ArrowDown":
|
||||
case "Home":
|
||||
case "End":
|
||||
case "PageUp":
|
||||
case "PageDown":
|
||||
var list = element.childNodes[0];
|
||||
var count = list.childNodes.length;
|
||||
for (var i = 0; i < count; i++) {
|
||||
var item = list.childNodes[i];
|
||||
if (item.getAttribute("data-disabled") == "1") {
|
||||
continue;
|
||||
}
|
||||
selectListItem(element, item, true);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1021,8 +940,8 @@ function radioButtonKeyClickEvent(element, event) {
|
|||
|
||||
function editViewInputEvent(element) {
|
||||
var text = element.value
|
||||
text = text.replaceAll(/\\/g, "\\\\")
|
||||
text = text.replaceAll(/\"/g, "\\\"")
|
||||
text = text.replace(/\\/g, "\\\\")
|
||||
text = text.replace(/\"/g, "\\\"")
|
||||
var message = "textChanged{session=" + sessionID + ",id=" + element.id + ",text=\"" + text + "\"}"
|
||||
sendMessage(message);
|
||||
}
|
||||
|
|
@ -1228,7 +1147,7 @@ function loadImage(url) {
|
|||
img.addEventListener("error", function(event) {
|
||||
var message = "imageError{session=" + sessionID + ",url=\"" + url + "\"";
|
||||
if (event && event.message) {
|
||||
var text = event.message.replaceAll(new RegExp("\"", 'g'), "\\\"")
|
||||
var text = event.message.replace(new RegExp("\"", 'g'), "\\\"")
|
||||
message += ",message=\"" + text + "\"";
|
||||
}
|
||||
sendMessage(message + "}")
|
||||
|
|
@ -1259,7 +1178,7 @@ function loadInlineImage(url, content) {
|
|||
img.addEventListener("error", function(event) {
|
||||
var message = "imageError{session=" + sessionID + ",url=\"" + url + "\"";
|
||||
if (event && event.message) {
|
||||
var text = event.message.replaceAll(new RegExp("\"", 'g'), "\\\"")
|
||||
var text = event.message.replace(new RegExp("\"", 'g'), "\\\"")
|
||||
message += ",message=\"" + text + "\"";
|
||||
}
|
||||
sendMessage(message + "}")
|
||||
|
|
@ -1268,6 +1187,11 @@ function loadInlineImage(url, content) {
|
|||
img.src = content;
|
||||
}
|
||||
|
||||
function clickOutsidePopup(e) {
|
||||
sendMessage("clickOutsidePopup{session=" + sessionID + "}")
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
function clickClosePopup(element, e) {
|
||||
var popupId = element.getAttribute("data-popupId");
|
||||
sendMessage("clickClosePopup{session=" + sessionID + ",id=" + popupId + "}")
|
||||
|
|
@ -1415,7 +1339,7 @@ function mediaSetVolume(elementId, volume) {
|
|||
}
|
||||
}
|
||||
|
||||
function startDownload(url, filename) {
|
||||
function startDowndload(url, filename) {
|
||||
var element = document.getElementById("ruiDownloader");
|
||||
if (element) {
|
||||
element.href = url;
|
||||
|
|
@ -1484,24 +1408,6 @@ function tableViewBlurEvent(element, event) {
|
|||
}
|
||||
}
|
||||
|
||||
function setTableCellCursorByID(tableID, row, column) {
|
||||
var table = document.getElementById(tableID);
|
||||
if (table) {
|
||||
if (!setTableCellCursor(table, row, column)) {
|
||||
const focusStyle = getTableFocusedItemStyle(table);
|
||||
const oldCellID = table.getAttribute("data-current");
|
||||
if (oldCellID) {
|
||||
const oldCell = document.getElementById(oldCellID);
|
||||
if (oldCell && oldCell.classList) {
|
||||
oldCell.classList.remove(focusStyle);
|
||||
oldCell.classList.remove(getTableSelectedItemStyle(table));
|
||||
}
|
||||
table.removeAttribute("data-current");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setTableCellCursor(element, row, column) {
|
||||
const cellID = element.id + "-" + row + "-" + column;
|
||||
var cell = document.getElementById(cellID);
|
||||
|
|
@ -1589,8 +1495,6 @@ function tableViewCellKeyDownEvent(element, event) {
|
|||
case "End":
|
||||
case "PageUp":
|
||||
case "PageDown":
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
const rows = element.getAttribute("data-rows");
|
||||
const columns = element.getAttribute("data-columns");
|
||||
if (rows && columns) {
|
||||
|
|
@ -1601,6 +1505,8 @@ function tableViewCellKeyDownEvent(element, event) {
|
|||
column = 0;
|
||||
while (columns < columnCount) {
|
||||
if (setTableCellCursor(element, row, column)) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
column++;
|
||||
|
|
@ -1608,6 +1514,8 @@ function tableViewCellKeyDownEvent(element, event) {
|
|||
row++;
|
||||
}
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
|
@ -1675,24 +1583,6 @@ function tableViewCellKeyDownEvent(element, event) {
|
|||
}
|
||||
}
|
||||
|
||||
function setTableRowCursorByID(tableID, row) {
|
||||
var table = document.getElementById(tableID);
|
||||
if (table) {
|
||||
if (!setTableRowCursor(table, row)) {
|
||||
const focusStyle = getTableFocusedItemStyle(table);
|
||||
const oldRowID = table.getAttribute("data-current");
|
||||
if (oldRowID) {
|
||||
const oldRow = document.getElementById(oldRowID);
|
||||
if (oldRow && oldRow.classList) {
|
||||
oldRow.classList.remove(focusStyle);
|
||||
oldRow.classList.remove(getTableSelectedItemStyle(table));
|
||||
}
|
||||
table.removeAttribute("data-current");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setTableRowCursor(element, row) {
|
||||
const tableRowID = element.id + "-" + row;
|
||||
var tableRow = document.getElementById(tableRowID);
|
||||
|
|
@ -1707,6 +1597,7 @@ function setTableRowCursor(element, row) {
|
|||
if (oldRow && oldRow.classList) {
|
||||
oldRow.classList.remove(focusStyle);
|
||||
oldRow.classList.remove(getTableSelectedItemStyle(element));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1951,152 +1842,3 @@ function getCanvasContext(elementId) {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function localStorageSet(key, value) {
|
||||
try {
|
||||
localStorage.setItem(key, value)
|
||||
} catch (err) {
|
||||
sendMessage("storageError{session=" + sessionID + ", error=`" + err + "`}")
|
||||
}
|
||||
}
|
||||
|
||||
function localStorageClear() {
|
||||
try {
|
||||
localStorage.setItem(key, value)
|
||||
} catch (err) {
|
||||
sendMessage("storageError{session=" + sessionID + ", error=`" + err + "`}")
|
||||
}
|
||||
}
|
||||
|
||||
function showTooltip(element, tooltip) {
|
||||
const layer = document.getElementById("ruiTooltipLayer");
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
layer.style.left = "0px";
|
||||
layer.style.right = "0px";
|
||||
|
||||
var tooltipBox = document.getElementById("ruiTooltipText");
|
||||
if (tooltipBox) {
|
||||
tooltipBox.innerHTML = tooltip;
|
||||
}
|
||||
|
||||
var left = element.offsetLeft;
|
||||
var top = element.offsetTop;
|
||||
var width = element.offsetWidth;
|
||||
var height = element.offsetHeight;
|
||||
var parent = element.offsetParent;
|
||||
|
||||
while (parent) {
|
||||
left += parent.offsetLeft;
|
||||
top += parent.offsetTop;
|
||||
width = parent.offsetWidth;
|
||||
height = parent.offsetHeight;
|
||||
parent = parent.offsetParent;
|
||||
}
|
||||
|
||||
if (element.offsetWidth >= tooltipBox.offsetWidth) {
|
||||
layer.style.left = left + "px";
|
||||
layer.style.justifyItems = "start";
|
||||
} else {
|
||||
const rightOff = width - (left + element.offsetWidth);
|
||||
if (left > rightOff) {
|
||||
if (width - left < tooltipBox.offsetWidth) {
|
||||
layer.style.right = rightOff + "px";
|
||||
layer.style.justifyItems = "end";
|
||||
} else {
|
||||
layer.style.left = (left - rightOff) + "px";
|
||||
layer.style.right = "0px";
|
||||
layer.style.justifyItems = "center";
|
||||
}
|
||||
} else {
|
||||
if (width - rightOff < tooltipBox.offsetWidth) {
|
||||
layer.style.left = left + "px";
|
||||
layer.style.justifyItems = "start";
|
||||
} else {
|
||||
layer.style.right = (rightOff - left) + "px";
|
||||
layer.style.justifyItems = "center";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bottomOff = height - (top + element.offsetHeight);
|
||||
var arrow = document.getElementById("ruiTooltipTopArrow");
|
||||
|
||||
if (bottomOff < arrow.offsetHeight + tooltipBox.offsetHeight) {
|
||||
if (arrow) {
|
||||
arrow.style.visibility = "hidden";
|
||||
}
|
||||
|
||||
arrow = document.getElementById("ruiTooltipBottomArrow");
|
||||
if (arrow) {
|
||||
arrow.style.visibility = "visible";
|
||||
}
|
||||
|
||||
layer.style.top = "0px";
|
||||
layer.style.bottom = height - top - arrow.offsetHeight / 2 + "px";
|
||||
layer.style.gridTemplateRows = "1fr auto auto"
|
||||
|
||||
} else {
|
||||
if (arrow) {
|
||||
arrow.style.visibility = "visible";
|
||||
}
|
||||
|
||||
layer.style.top = top + element.offsetHeight - arrow.offsetHeight / 2 + "px";
|
||||
layer.style.bottom = "0px";
|
||||
layer.style.gridTemplateRows = "auto auto 1fr"
|
||||
|
||||
arrow = document.getElementById("ruiTooltipBottomArrow");
|
||||
if (arrow) {
|
||||
arrow.style.visibility = "hidden";
|
||||
}
|
||||
}
|
||||
|
||||
layer.style.visibility = "visible";
|
||||
layer.style.opacity = 1;
|
||||
}
|
||||
|
||||
function mouseEnterEvent(element, event) {
|
||||
event.stopPropagation();
|
||||
|
||||
let tooltip = element.getAttribute("data-tooltip");
|
||||
if (tooltip) {
|
||||
showTooltip(element, tooltip);
|
||||
}
|
||||
|
||||
sendMessage("mouse-enter{session=" + sessionID + ",id=" + element.id + mouseEventData(element, event) + "}");
|
||||
}
|
||||
|
||||
function mouseLeaveEvent(element, event) {
|
||||
event.stopPropagation();
|
||||
|
||||
if (element.getAttribute("data-tooltip")) {
|
||||
const layer = document.getElementById("ruiTooltipLayer");
|
||||
if (layer) {
|
||||
layer.style.opacity = 0;
|
||||
layer.style.visibility = "hidden";
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage("mouse-leave{session=" + sessionID + ",id=" + element.id + mouseEventData(element, event) + "}");
|
||||
}
|
||||
|
||||
function hideTooltip() {
|
||||
const layer = document.getElementById("ruiTooltipLayer");
|
||||
if (layer) {
|
||||
layer.style.opacity = 0;
|
||||
layer.style.visibility = "hidden";
|
||||
}
|
||||
}
|
||||
|
||||
function stopEventPropagation(element, event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function setCssVar(tag, value) {
|
||||
const root = document.querySelector(':root');
|
||||
if (root) {
|
||||
root.style.setProperty(tag, value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,6 @@
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
:root {
|
||||
--tooltip-arrow-size: 6px;
|
||||
--tooltip-background: white;
|
||||
--tooltip-text-color: black;
|
||||
--tooltip-shadow-color: gray;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
|
|
@ -83,54 +76,7 @@ ul:focus {
|
|||
left: 0px;
|
||||
}
|
||||
|
||||
.ruiTooltipLayer {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto 1fr;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
left: 0px;
|
||||
transition: opacity 0.5s ease-out;
|
||||
filter: drop-shadow(0px 0px 2px var(--tooltip-shadow-color));
|
||||
}
|
||||
|
||||
.ruiTooltipTopArrow {
|
||||
grid-row-start: 1;
|
||||
grid-row-end: 2;
|
||||
border-width: var(--tooltip-arrow-size);
|
||||
border-style: solid;
|
||||
border-color: transparent transparent var(--tooltip-background) transparent;
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.ruiTooltipBottomArrow {
|
||||
grid-row-start: 3;
|
||||
grid-row-end: 4;
|
||||
border-width: var(--tooltip-arrow-size);
|
||||
border-style: solid;
|
||||
border-color: var(--tooltip-background) transparent transparent transparent;
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.ruiTooltipText {
|
||||
grid-row-start: 2;
|
||||
grid-row-end: 3;
|
||||
padding: 4px 8px 4px 8px;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
background-color: var(--tooltip-background);
|
||||
color: var(--tooltip-text-color);
|
||||
/*box-shadow: 0px 0px 4px 2px #8888;*/
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ruiView {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ruiAbsoluteLayout {
|
||||
|
|
@ -166,17 +112,16 @@ ul:focus {
|
|||
}
|
||||
|
||||
.ruiImageView {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ruiSvgImageView {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.ruiListView {
|
||||
overflow: auto;
|
||||
/*
|
||||
display: flex;
|
||||
align-content: stretch;
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
var wasmMediaResources = false
|
||||
|
||||
//go:embed app_scripts.js
|
||||
var defaultScripts string
|
||||
|
||||
|
|
@ -73,15 +75,9 @@ func getStartPage(buffer *strings.Builder, params AppParams, addScripts string)
|
|||
buffer.WriteString(addScripts)
|
||||
buffer.WriteString(`</script>
|
||||
</head>
|
||||
<body id="body" onkeydown="keyDownEvent(this, event)">
|
||||
<body>
|
||||
<div class="ruiRoot" id="ruiRootView"></div>
|
||||
<div class="ruiPopupLayer" id="ruiPopupLayer" style="visibility: hidden; isolation: isolate;"></div>
|
||||
<div class="ruiTooltipLayer" id="ruiTooltipLayer" style="visibility: hidden; opacity: 0;">
|
||||
<div id="ruiTooltipText" class="ruiTooltipText"></div>
|
||||
<div id="ruiTooltipTopArrow" class="ruiTooltipTopArrow"></div>
|
||||
<div id="ruiTooltipBottomArrow" class="ruiTooltipBottomArrow"></div>
|
||||
</div>
|
||||
|
||||
<div class="ruiPopupLayer" id="ruiPopupLayer" style="visibility: hidden;" onclick="clickOutsidePopup(event)"></div>
|
||||
<a id="ruiDownloader" download style="display: none;"></a>
|
||||
</body>`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ func (gradient *backgroundConicGradient) parseGradientText(value string) []Backg
|
|||
for i, element := range elements {
|
||||
var ok bool
|
||||
if vector[i], ok = gradient.stringToGradientPoint(strings.Trim(element, " ")); !ok {
|
||||
ErrorLogF(`Invalid %d element of the conic gradient: "%s"`, i, element)
|
||||
ErrorLogF(`Ivalid %d element of the conic gradient: "%s"`, i, element)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -240,7 +240,7 @@ func (gradient *backgroundConicGradient) setGradient(value any) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
ErrorLogF(`Invalid conic gradient: "%s"`, value)
|
||||
ErrorLogF(`Ivalid conic gradient: "%s"`, value)
|
||||
return false
|
||||
|
||||
case []BackgroundGradientAngle:
|
||||
|
|
@ -252,7 +252,7 @@ func (gradient *backgroundConicGradient) setGradient(value any) bool {
|
|||
|
||||
for i, point := range value {
|
||||
if point.Color == nil {
|
||||
ErrorLogF("Invalid %d element of the conic gradient: Color is nil", i)
|
||||
ErrorLogF("Ivalid %d element of the conic gradient: Color is nil", i)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ func (gradient *backgroundGradient) parseGradientText(value string) []Background
|
|||
points := make([]BackgroundGradientPoint, count)
|
||||
for i, element := range elements {
|
||||
if !points[i].setValue(element) {
|
||||
ErrorLogF(`Invalid %d element of the conic gradient: "%s"`, i, element)
|
||||
ErrorLogF(`Ivalid %d element of the conic gradient: "%s"`, i, element)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ func (border *borderProperty) setBorderObject(obj DataObject) bool {
|
|||
result := true
|
||||
|
||||
for _, side := range []string{Top, Right, Bottom, Left} {
|
||||
if node := obj.PropertyByTag(side); node != nil {
|
||||
if node := obj.PropertyWithTag(side); node != nil {
|
||||
if node.Type() == ObjectNode {
|
||||
if !border.setSingleBorderObject(side, node.Object()) {
|
||||
result = false
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ func (button *buttonData) CreateSuperView(session Session) View {
|
|||
HorizontalAlign: CenterAlign,
|
||||
VerticalAlign: CenterAlign,
|
||||
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)
|
||||
|
||||
// SetImageFillStyle set the image as the filling pattern.
|
||||
// repeat - indicating how to repeat the pattern's image. Possible values are:
|
||||
// repeate - indicating how to repeat the pattern's image. Possible values are:
|
||||
// NoRepeat (0) - neither direction,
|
||||
// RepeatXY (1) - both directions,
|
||||
// RepeatX (2) - horizontal only,
|
||||
|
|
@ -286,7 +286,7 @@ type Canvas interface {
|
|||
DrawImage(x, y float64, image Image)
|
||||
// DrawImageInRect draws the image in the rectangle (x, y, width, height), scaling in height and width if necessary
|
||||
DrawImageInRect(x, y, width, height float64, image Image)
|
||||
// DrawImageFragment draws the fragment (described by srcX, srcY, srcWidth, srcHeight) of image
|
||||
// DrawImageFragment draws the frament (described by srcX, srcY, srcWidth, srcHeight) of image
|
||||
// in the rectangle (dstX, dstY, dstWidth, dstHeight), scaling in height and width if necessary
|
||||
DrawImageFragment(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight float64, image Image)
|
||||
|
||||
|
|
@ -302,12 +302,12 @@ func newCanvas(view CanvasView) Canvas {
|
|||
canvas := new(canvasData)
|
||||
canvas.view = view
|
||||
canvas.session = view.Session()
|
||||
canvas.session.canvasStart(view.htmlID())
|
||||
canvas.session.cavnasStart(view.htmlID())
|
||||
return canvas
|
||||
}
|
||||
|
||||
func (canvas *canvasData) finishDraw() {
|
||||
canvas.session.canvasFinish()
|
||||
canvas.session.cavnasFinish()
|
||||
}
|
||||
|
||||
func (canvas *canvasData) View() CanvasView {
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ const (
|
|||
BlueViolet Color = 0xff8a2be2
|
||||
// Brown color constant
|
||||
Brown Color = 0xffa52a2a
|
||||
// BurlyWood color constant
|
||||
BurlyWood Color = 0xffdeb887
|
||||
// Burlywood color constant
|
||||
Burlywood Color = 0xffdeb887
|
||||
// CadetBlue color constant
|
||||
CadetBlue Color = 0xff5f9ea0
|
||||
// Chartreuse color constant
|
||||
|
|
@ -67,8 +67,8 @@ const (
|
|||
Coral Color = 0xffff7f50
|
||||
// CornflowerBlue color constant
|
||||
CornflowerBlue Color = 0xff6495ed
|
||||
// CornSilk color constant
|
||||
CornSilk Color = 0xfffff8dc
|
||||
// Cornsilk color constant
|
||||
Cornsilk Color = 0xfffff8dc
|
||||
// Crimson color constant
|
||||
Crimson Color = 0xffdc143c
|
||||
// Cyan color constant
|
||||
|
|
@ -105,8 +105,8 @@ const (
|
|||
DarkSlateBlue Color = 0xff483d8b
|
||||
// DarkSlateGray color constant
|
||||
DarkSlateGray Color = 0xff2f4f4f
|
||||
// DarkSlateGrey color constant
|
||||
DarkSlateGrey Color = 0xff2f4f4f
|
||||
// Darkslategrey color constant
|
||||
Darkslategrey Color = 0xff2f4f4f
|
||||
// DarkTurquoise color constant
|
||||
DarkTurquoise Color = 0xff00ced1
|
||||
// DarkViolet color constant
|
||||
|
|
@ -135,8 +135,8 @@ const (
|
|||
Gold Color = 0xffffd700
|
||||
// GoldenRod color constant
|
||||
GoldenRod Color = 0xffdaa520
|
||||
// GreenYellow color constant
|
||||
GreenYellow Color = 0xffadff2f
|
||||
// GreenyEllow color constant
|
||||
GreenyEllow Color = 0xffadff2f
|
||||
// Grey color constant
|
||||
Grey Color = 0xff808080
|
||||
// Honeydew color constant
|
||||
|
|
@ -293,8 +293,8 @@ const (
|
|||
Violet Color = 0xffee82ee
|
||||
// Wheat color constant
|
||||
Wheat Color = 0xfff5deb3
|
||||
// WhiteSmoke color constant
|
||||
WhiteSmoke Color = 0xfff5f5f5
|
||||
// Whitesmoke color constant
|
||||
Whitesmoke Color = 0xfff5f5f5
|
||||
// YellowGreen color constant
|
||||
YellowGreen Color = 0xff9acd32
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ type ColorPicker interface {
|
|||
|
||||
type colorPickerData struct {
|
||||
viewData
|
||||
colorChangedListeners []func(ColorPicker, Color, Color)
|
||||
colorChangedListeners []func(ColorPicker, Color)
|
||||
}
|
||||
|
||||
// NewColorPicker create new ColorPicker object and return it
|
||||
|
|
@ -34,7 +34,7 @@ func newColorPicker(session Session) View {
|
|||
func (picker *colorPickerData) init(session Session) {
|
||||
picker.viewData.init(session)
|
||||
picker.tag = "ColorPicker"
|
||||
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
|
||||
picker.colorChangedListeners = []func(ColorPicker, Color){}
|
||||
picker.properties[Padding] = Px(0)
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ func (picker *colorPickerData) remove(tag string) {
|
|||
switch tag {
|
||||
case ColorChangedEvent:
|
||||
if len(picker.colorChangedListeners) > 0 {
|
||||
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
|
||||
picker.colorChangedListeners = []func(ColorPicker, Color){}
|
||||
picker.propertyChangedEvent(tag)
|
||||
}
|
||||
|
||||
|
|
@ -86,12 +86,12 @@ func (picker *colorPickerData) set(tag string, value any) bool {
|
|||
|
||||
switch tag {
|
||||
case ColorChangedEvent:
|
||||
listeners, ok := valueToEventWithOldListeners[ColorPicker, Color](value)
|
||||
listeners, ok := valueToEventListeners[ColorPicker, Color](value)
|
||||
if !ok {
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
} else if listeners == nil {
|
||||
listeners = []func(ColorPicker, Color, Color){}
|
||||
listeners = []func(ColorPicker, Color){}
|
||||
}
|
||||
picker.colorChangedListeners = listeners
|
||||
picker.propertyChangedEvent(tag)
|
||||
|
|
@ -116,7 +116,7 @@ func (picker *colorPickerData) colorChanged(oldColor Color) {
|
|||
picker.session.callFunc("setInputValue", picker.htmlID(), newColor.rgbString())
|
||||
}
|
||||
for _, listener := range picker.colorChangedListeners {
|
||||
listener(picker, newColor, oldColor)
|
||||
listener(picker, newColor)
|
||||
}
|
||||
picker.propertyChangedEvent(ColorTag)
|
||||
}
|
||||
|
|
@ -169,7 +169,7 @@ func (picker *colorPickerData) handleCommand(self View, command string, data Dat
|
|||
picker.properties[ColorPickerValue] = color
|
||||
if color != oldColor {
|
||||
for _, listener := range picker.colorChangedListeners {
|
||||
listener(picker, color, oldColor)
|
||||
listener(picker, color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -204,6 +204,6 @@ func GetColorPickerValue(view View, subviewID ...string) Color {
|
|||
// GetColorChangedListeners returns the ColorChangedListener list of an ColorPicker subview.
|
||||
// If there are no listeners then the empty list is returned
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color, Color) {
|
||||
return getEventWithOldListeners[ColorPicker, Color](view, subviewID, ColorChangedEvent)
|
||||
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color) {
|
||||
return getEventListeners[ColorPicker, Color](view, subviewID, ColorChangedEvent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,45 +11,28 @@ const (
|
|||
// Values less than zero are not valid. if the "column-count" property value is 0 then
|
||||
// the number of columns is calculated based on the "column-width" property
|
||||
ColumnCount = "column-count"
|
||||
|
||||
// ColumnWidth is the constant for the "column-width" property tag.
|
||||
// The "column-width" SizeUnit property specifies the width of each column.
|
||||
ColumnWidth = "column-width"
|
||||
|
||||
// ColumnGap is the constant for the "column-gap" property tag.
|
||||
// The "column-width" SizeUnit property sets the size of the gap (gutter) between columns.
|
||||
ColumnGap = "column-gap"
|
||||
|
||||
// ColumnSeparator is the constant for the "column-separator" property tag.
|
||||
// The "column-separator" property specifies the line drawn between columns in a multi-column layout.
|
||||
ColumnSeparator = "column-separator"
|
||||
|
||||
// ColumnSeparatorStyle is the constant for the "column-separator-style" property tag.
|
||||
// The "column-separator-style" int property sets the style of the line drawn between
|
||||
// columns in a multi-column layout.
|
||||
// Valid values are NoneLine (0), SolidLine (1), DashedLine (2), DottedLine (3), and DoubleLine (4).
|
||||
ColumnSeparatorStyle = "column-separator-style"
|
||||
|
||||
// ColumnSeparatorWidth is the constant for the "column-separator-width" property tag.
|
||||
// The "column-separator-width" SizeUnit property sets the width of the line drawn between
|
||||
// columns in a multi-column layout.
|
||||
ColumnSeparatorWidth = "column-separator-width"
|
||||
|
||||
// ColumnSeparatorColor is the constant for the "column-separator-color" property tag.
|
||||
// The "column-separator-color" Color property sets the color of the line drawn between
|
||||
// columns in a multi-column layout.
|
||||
ColumnSeparatorColor = "column-separator-color"
|
||||
|
||||
// ColumnFill is the constant for the "column-fill" property tag.
|
||||
// The "column-fill" int property controls how an ColumnLayout's contents are balanced when broken into columns.
|
||||
// Valid values are
|
||||
// * ColumnFillBalance (0) - Content is equally divided between columns (default value);
|
||||
// * ColumnFillAuto (1) - Columns are filled sequentially. Content takes up only the room it needs, possibly resulting in some columns remaining empty.
|
||||
ColumnFill = "column-fill"
|
||||
|
||||
// ColumnSpanAll is the constant for the "column-span-all" property tag.
|
||||
// The "column-span-all" bool property makes it possible for a view to span across all columns when its value is set to true.
|
||||
ColumnSpanAll = "column-span-all"
|
||||
)
|
||||
|
||||
// ColumnLayout - grid-container of View
|
||||
|
|
@ -223,16 +206,3 @@ func GetColumnSeparatorColor(view View, subviewID ...string) Color {
|
|||
border := getColumnSeparator(view, subviewID)
|
||||
return border.Color
|
||||
}
|
||||
|
||||
// GetColumnFill returns a "column-fill" property value of the subview.
|
||||
// Returns one of next values: ColumnFillBalance (0) or ColumnFillAuto (1)
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||
func GetColumnFill(view View, subviewID ...string) int {
|
||||
return enumStyledProperty(view, subviewID, ColumnFill, ColumnFillBalance, true)
|
||||
}
|
||||
|
||||
// IsColumnSpanAll returns a "column-span-all" property value of the subview.
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||
func IsColumnSpanAll(view View, subviewID ...string) bool {
|
||||
return boolStyledProperty(view, subviewID, ColumnSpanAll, false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -243,17 +243,8 @@ func (customView *CustomViewData) RemoveView(index int) View {
|
|||
return container.RemoveView(index)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes a view from the list of a view children and return it
|
||||
func (customView *CustomViewData) ViewIndex(view View) int {
|
||||
if customView.superView != nil {
|
||||
if container, ok := customView.superView.(ViewsContainer); ok {
|
||||
return container.ViewIndex(view)
|
||||
}
|
||||
}
|
||||
return -1
|
||||
return nil
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) String() string {
|
||||
|
|
|
|||
66
data.go
66
data.go
|
|
@ -18,12 +18,11 @@ type DataObject interface {
|
|||
Tag() string
|
||||
PropertyCount() int
|
||||
Property(index int) DataNode
|
||||
PropertyByTag(tag string) DataNode
|
||||
PropertyWithTag(tag string) DataNode
|
||||
PropertyValue(tag string) (string, bool)
|
||||
PropertyObject(tag string) DataObject
|
||||
SetPropertyValue(tag, value string)
|
||||
SetPropertyObject(tag string, object DataObject)
|
||||
ToParams() Params
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
@ -44,7 +43,6 @@ type DataNode interface {
|
|||
ArraySize() int
|
||||
ArrayElement(index int) DataValue
|
||||
ArrayElements() []DataValue
|
||||
ArrayAsParams() []Params
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
@ -108,7 +106,7 @@ func (object *dataObject) Property(index int) DataNode {
|
|||
return object.property[index]
|
||||
}
|
||||
|
||||
func (object *dataObject) PropertyByTag(tag string) DataNode {
|
||||
func (object *dataObject) PropertyWithTag(tag string) DataNode {
|
||||
if object.property != nil {
|
||||
for _, node := range object.property {
|
||||
if node.Tag() == tag {
|
||||
|
|
@ -120,14 +118,14 @@ func (object *dataObject) PropertyByTag(tag string) DataNode {
|
|||
}
|
||||
|
||||
func (object *dataObject) PropertyValue(tag string) (string, bool) {
|
||||
if node := object.PropertyByTag(tag); node != nil && node.Type() == TextNode {
|
||||
if node := object.PropertyWithTag(tag); node != nil && node.Type() == TextNode {
|
||||
return node.Text(), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (object *dataObject) PropertyObject(tag string) DataObject {
|
||||
if node := object.PropertyByTag(tag); node != nil && node.Type() == ObjectNode {
|
||||
if node := object.PropertyWithTag(tag); node != nil && node.Type() == ObjectNode {
|
||||
return node.Object()
|
||||
}
|
||||
return nil
|
||||
|
|
@ -167,42 +165,6 @@ func (object *dataObject) SetPropertyObject(tag string, obj DataObject) {
|
|||
object.setNode(node)
|
||||
}
|
||||
|
||||
func (object *dataObject) ToParams() Params {
|
||||
params := Params{}
|
||||
for _, node := range object.property {
|
||||
switch node.Type() {
|
||||
case TextNode:
|
||||
if text := node.Text(); text != "" {
|
||||
params[node.Tag()] = text
|
||||
}
|
||||
|
||||
case ObjectNode:
|
||||
if obj := node.Object(); obj != nil {
|
||||
params[node.Tag()] = node.Object()
|
||||
}
|
||||
|
||||
case ArrayNode:
|
||||
array := []any{}
|
||||
for i := 0; i < node.ArraySize(); i++ {
|
||||
if data := node.ArrayElement(i); data != nil {
|
||||
if data.IsObject() {
|
||||
if obj := data.Object(); obj != nil {
|
||||
array = append(array, obj)
|
||||
}
|
||||
} else if text := data.Value(); text != "" {
|
||||
array = append(array, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(array) > 0 {
|
||||
params[node.Tag()] = array
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
type dataNode struct {
|
||||
tag string
|
||||
|
|
@ -259,22 +221,6 @@ func (node *dataNode) ArrayElements() []DataValue {
|
|||
return []DataValue{}
|
||||
}
|
||||
|
||||
func (node *dataNode) ArrayAsParams() []Params {
|
||||
result := []Params{}
|
||||
if node.array != nil {
|
||||
for _, data := range node.array {
|
||||
if data.IsObject() {
|
||||
if obj := data.Object(); obj != nil {
|
||||
if params := obj.ToParams(); len(params) > 0 {
|
||||
result = append(result, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ParseDataText - parse text and return DataNode
|
||||
func ParseDataText(text string) DataObject {
|
||||
|
||||
|
|
@ -494,8 +440,8 @@ func ParseDataText(text string) DataObject {
|
|||
endPos := pos
|
||||
skipSpaces(false)
|
||||
if startPos == endPos {
|
||||
//ErrorLog("empty tag")
|
||||
return "", true
|
||||
ErrorLog("empty tag")
|
||||
return "", false
|
||||
}
|
||||
return string(data[startPos:endPos]), true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ type DatePicker interface {
|
|||
|
||||
type datePickerData struct {
|
||||
viewData
|
||||
dateChangedListeners []func(DatePicker, time.Time, time.Time)
|
||||
dateChangedListeners []func(DatePicker, time.Time)
|
||||
}
|
||||
|
||||
// NewDatePicker create new DatePicker object and return it
|
||||
|
|
@ -40,7 +40,7 @@ func newDatePicker(session Session) View {
|
|||
func (picker *datePickerData) init(session Session) {
|
||||
picker.viewData.init(session)
|
||||
picker.tag = "DatePicker"
|
||||
picker.dateChangedListeners = []func(DatePicker, time.Time, time.Time){}
|
||||
picker.dateChangedListeners = []func(DatePicker, time.Time){}
|
||||
}
|
||||
|
||||
func (picker *datePickerData) String() string {
|
||||
|
|
@ -69,7 +69,7 @@ func (picker *datePickerData) remove(tag string) {
|
|||
switch tag {
|
||||
case DateChangedEvent:
|
||||
if len(picker.dateChangedListeners) > 0 {
|
||||
picker.dateChangedListeners = []func(DatePicker, time.Time, time.Time){}
|
||||
picker.dateChangedListeners = []func(DatePicker, time.Time){}
|
||||
picker.propertyChangedEvent(tag)
|
||||
}
|
||||
return
|
||||
|
|
@ -94,14 +94,13 @@ func (picker *datePickerData) remove(tag string) {
|
|||
|
||||
case DatePickerValue:
|
||||
if _, ok := picker.properties[DatePickerValue]; ok {
|
||||
oldDate := GetDatePickerValue(picker)
|
||||
delete(picker.properties, DatePickerValue)
|
||||
date := GetDatePickerValue(picker)
|
||||
if picker.created {
|
||||
picker.session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
|
||||
}
|
||||
for _, listener := range picker.dateChangedListeners {
|
||||
listener(picker, date, oldDate)
|
||||
listener(picker, date)
|
||||
}
|
||||
} else {
|
||||
return
|
||||
|
|
@ -227,7 +226,7 @@ func (picker *datePickerData) set(tag string, value any) bool {
|
|||
picker.session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
|
||||
}
|
||||
for _, listener := range picker.dateChangedListeners {
|
||||
listener(picker, date, oldDate)
|
||||
listener(picker, date)
|
||||
}
|
||||
picker.propertyChangedEvent(tag)
|
||||
}
|
||||
|
|
@ -235,12 +234,12 @@ func (picker *datePickerData) set(tag string, value any) bool {
|
|||
}
|
||||
|
||||
case DateChangedEvent:
|
||||
listeners, ok := valueToEventWithOldListeners[DatePicker, time.Time](value)
|
||||
listeners, ok := valueToEventListeners[DatePicker, time.Time](value)
|
||||
if !ok {
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
} else if listeners == nil {
|
||||
listeners = []func(DatePicker, time.Time, time.Time){}
|
||||
listeners = []func(DatePicker, time.Time){}
|
||||
}
|
||||
picker.dateChangedListeners = listeners
|
||||
picker.propertyChangedEvent(tag)
|
||||
|
|
@ -319,7 +318,7 @@ func (picker *datePickerData) handleCommand(self View, command string, data Data
|
|||
picker.properties[DatePickerValue] = value
|
||||
if value != oldValue {
|
||||
for _, listener := range picker.dateChangedListeners {
|
||||
listener(picker, value, oldValue)
|
||||
listener(picker, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -411,6 +410,6 @@ func GetDatePickerValue(view View, subviewID ...string) time.Time {
|
|||
// GetDateChangedListeners returns the DateChangedListener list of an DatePicker subview.
|
||||
// If there are no listeners then the empty list is returned
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time, time.Time) {
|
||||
return getEventWithOldListeners[DatePicker, time.Time](view, subviewID, DateChangedEvent)
|
||||
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time) {
|
||||
return getEventListeners[DatePicker, time.Time](view, subviewID, DateChangedEvent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ theme {
|
|||
ruiTabTextColor = #FF404040,
|
||||
ruiCurrentTabColor = #FFFFFFFF,
|
||||
ruiCurrentTabTextColor = #FF000000,
|
||||
ruiTooltipBackground = #FFFFFFFF,
|
||||
ruiTooltipTextColor = #FF000000,
|
||||
ruiTooltipShadowColor = #FF808080,
|
||||
},
|
||||
colors:dark = _{
|
||||
ruiTextColor = #FFE0E0E0,
|
||||
|
|
@ -46,9 +43,6 @@ theme {
|
|||
ruiTabTextColor = #FFE0E0E0,
|
||||
ruiCurrentTabColor = #FF000000,
|
||||
ruiCurrentTabTextColor = #FFFFFFFF,
|
||||
ruiTooltipBackground = #FF303030,
|
||||
ruiTooltipTextColor = #FFDDDDDD,
|
||||
ruiTooltipShadowColor = #FFDDDDDD,
|
||||
},
|
||||
constants = _{
|
||||
ruiButtonHorizontalPadding = 16px,
|
||||
|
|
@ -110,26 +104,6 @@ theme {
|
|||
ruiButton:active {
|
||||
background-color = @ruiButtonActiveColor
|
||||
},
|
||||
ruiDefaultButton {
|
||||
align = center,
|
||||
padding = "@ruiButtonVerticalPadding, @ruiButtonHorizontalPadding, @ruiButtonVerticalPadding, @ruiButtonHorizontalPadding",
|
||||
margin = @ruiButtonMargin,
|
||||
radius = @ruiButtonRadius,
|
||||
background-color = @ruiButtonColor,
|
||||
text-color = @ruiButtonTextColor,
|
||||
text-weight = bold,
|
||||
border = _{width = 1px, style = solid, color = @ruiButtonTextColor}
|
||||
},
|
||||
ruiDefaultButton:hover {
|
||||
text-color = @ruiTextColor,
|
||||
background-color = @ruiBackgroundColor,
|
||||
},
|
||||
ruiDefaultButton:focus {
|
||||
shadow = _{spread-radius = @ruiButtonHighlightDilation, blur = @ruiButtonHighlightBlur, color = @ruiHighlightColor },
|
||||
},
|
||||
ruiDefaultButton:active {
|
||||
background-color = @ruiButtonActiveColor
|
||||
},
|
||||
ruiCheckbox {
|
||||
radius = 2px,
|
||||
padding = 1px,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ func (session *sessionData) startDownload(file downloadFile) {
|
|||
currentDownloadId++
|
||||
id := strconv.Itoa(currentDownloadId)
|
||||
downloadFiles[id] = file
|
||||
session.callFunc("startDownload", id, file.filename)
|
||||
session.callFunc("startDowndload", id, file.filename)
|
||||
}
|
||||
|
||||
func serveDownloadFile(id string, w http.ResponseWriter, r *http.Request) bool {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ type dropDownListData struct {
|
|||
viewData
|
||||
items []string
|
||||
disabledItems []any
|
||||
dropDownListener []func(DropDownList, int, int)
|
||||
dropDownListener []func(DropDownList, int)
|
||||
}
|
||||
|
||||
// NewDropDownList create new DropDownList object and return it
|
||||
|
|
@ -41,7 +41,7 @@ func (list *dropDownListData) init(session Session) {
|
|||
list.tag = "DropDownList"
|
||||
list.items = []string{}
|
||||
list.disabledItems = []any{}
|
||||
list.dropDownListener = []func(DropDownList, int, int){}
|
||||
list.dropDownListener = []func(DropDownList, int){}
|
||||
}
|
||||
|
||||
func (list *dropDownListData) String() string {
|
||||
|
|
@ -78,7 +78,7 @@ func (list *dropDownListData) remove(tag string) {
|
|||
|
||||
case DropDownEvent:
|
||||
if len(list.dropDownListener) > 0 {
|
||||
list.dropDownListener = []func(DropDownList, int, int){}
|
||||
list.dropDownListener = []func(DropDownList, int){}
|
||||
list.propertyChangedEvent(tag)
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ func (list *dropDownListData) remove(tag string) {
|
|||
if list.created {
|
||||
list.session.callFunc("selectDropDownListItem", list.htmlID(), 0)
|
||||
}
|
||||
list.onSelectedItemChanged(0, oldCurrent)
|
||||
list.onSelectedItemChanged(0)
|
||||
}
|
||||
|
||||
default:
|
||||
|
|
@ -116,12 +116,12 @@ func (list *dropDownListData) set(tag string, value any) bool {
|
|||
return list.setDisabledItems(value)
|
||||
|
||||
case DropDownEvent:
|
||||
listeners, ok := valueToEventWithOldListeners[DropDownList, int](value)
|
||||
listeners, ok := valueToEventListeners[DropDownList, int](value)
|
||||
if !ok {
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
} else if listeners == nil {
|
||||
listeners = []func(DropDownList, int, int){}
|
||||
listeners = []func(DropDownList, int){}
|
||||
}
|
||||
list.dropDownListener = listeners
|
||||
list.propertyChangedEvent(tag)
|
||||
|
|
@ -137,7 +137,7 @@ func (list *dropDownListData) set(tag string, value any) bool {
|
|||
if list.created {
|
||||
list.session.callFunc("selectDropDownListItem", list.htmlID(), current)
|
||||
}
|
||||
list.onSelectedItemChanged(current, oldCurrent)
|
||||
list.onSelectedItemChanged(current)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -377,9 +377,9 @@ func (list *dropDownListData) htmlDisabledProperties(self View, buffer *strings.
|
|||
}
|
||||
}
|
||||
|
||||
func (list *dropDownListData) onSelectedItemChanged(number, old int) {
|
||||
func (list *dropDownListData) onSelectedItemChanged(number int) {
|
||||
for _, listener := range list.dropDownListener {
|
||||
listener(list, number, old)
|
||||
listener(list, number)
|
||||
}
|
||||
list.propertyChangedEvent(Current)
|
||||
}
|
||||
|
|
@ -390,9 +390,8 @@ func (list *dropDownListData) handleCommand(self View, command string, data Data
|
|||
if text, ok := data.PropertyValue("number"); ok {
|
||||
if number, err := strconv.Atoi(text); err == nil {
|
||||
if GetCurrent(list) != number && number >= 0 && number < len(list.items) {
|
||||
old := GetCurrent(list)
|
||||
list.properties[Current] = number
|
||||
list.onSelectedItemChanged(number, old)
|
||||
list.onSelectedItemChanged(number)
|
||||
}
|
||||
} else {
|
||||
ErrorLog(err.Error())
|
||||
|
|
@ -407,8 +406,8 @@ func (list *dropDownListData) handleCommand(self View, command string, data Data
|
|||
|
||||
// GetDropDownListeners returns the "drop-down-event" listener list. If there are no listeners then the empty list is returned.
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int, int) {
|
||||
return getEventWithOldListeners[DropDownList, int](view, subviewID, DropDownEvent)
|
||||
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int) {
|
||||
return getEventListeners[DropDownList, int](view, subviewID, DropDownEvent)
|
||||
}
|
||||
|
||||
// GetDropDownItems return the DropDownList items list.
|
||||
|
|
|
|||
33
editView.go
33
editView.go
|
|
@ -23,7 +23,7 @@ const (
|
|||
PasswordText = 1
|
||||
// EmailText - e-mail type of EditView. Allows to enter one email
|
||||
EmailText = 2
|
||||
// EmailsText - e-mail type of EditView. Allows to enter multiple emails separated by comma
|
||||
// EmailsText - e-mail type of EditView. Allows to enter multiple emails separeted by comma
|
||||
EmailsText = 3
|
||||
// URLText - url type of EditView. Allows to enter one url
|
||||
URLText = 4
|
||||
|
|
@ -41,7 +41,7 @@ type EditView interface {
|
|||
|
||||
type editViewData struct {
|
||||
viewData
|
||||
textChangeListeners []func(EditView, string, string)
|
||||
textChangeListeners []func(EditView, string)
|
||||
}
|
||||
|
||||
// NewEditView create new EditView object and return it
|
||||
|
|
@ -58,7 +58,7 @@ func newEditView(session Session) View {
|
|||
|
||||
func (edit *editViewData) init(session Session) {
|
||||
edit.viewData.init(session)
|
||||
edit.textChangeListeners = []func(EditView, string, string){}
|
||||
edit.textChangeListeners = []func(EditView, string){}
|
||||
edit.tag = "EditView"
|
||||
}
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ func (edit *editViewData) remove(tag string) {
|
|||
|
||||
case EditTextChangedEvent:
|
||||
if len(edit.textChangeListeners) > 0 {
|
||||
edit.textChangeListeners = []func(EditView, string, string){}
|
||||
edit.textChangeListeners = []func(EditView, string){}
|
||||
edit.propertyChangedEvent(tag)
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ func (edit *editViewData) remove(tag string) {
|
|||
oldText := GetText(edit)
|
||||
delete(edit.properties, tag)
|
||||
if oldText != "" {
|
||||
edit.textChanged("", oldText)
|
||||
edit.textChanged("")
|
||||
if edit.created {
|
||||
edit.session.callFunc("setInputValue", edit.htmlID(), "")
|
||||
}
|
||||
|
|
@ -205,7 +205,7 @@ func (edit *editViewData) set(tag string, value any) bool {
|
|||
if text, ok := value.(string); ok {
|
||||
edit.properties[Text] = text
|
||||
if text = GetText(edit); oldText != text {
|
||||
edit.textChanged(text, oldText)
|
||||
edit.textChanged(text)
|
||||
if edit.created {
|
||||
if GetEditViewType(edit) == MultiLineText {
|
||||
updateInnerHTML(edit.htmlID(), edit.Session())
|
||||
|
|
@ -328,12 +328,12 @@ func (edit *editViewData) set(tag string, value any) bool {
|
|||
return false
|
||||
|
||||
case EditTextChangedEvent:
|
||||
listeners, ok := valueToEventWithOldListeners[EditView, string](value)
|
||||
listeners, ok := valueToEventListeners[EditView, string](value)
|
||||
if !ok {
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
} else if listeners == nil {
|
||||
listeners = []func(EditView, string, string){}
|
||||
listeners = []func(EditView, string){}
|
||||
}
|
||||
edit.textChangeListeners = listeners
|
||||
edit.propertyChangedEvent(tag)
|
||||
|
|
@ -358,11 +358,10 @@ func (edit *editViewData) AppendText(text string) {
|
|||
if GetEditViewType(edit) == MultiLineText {
|
||||
if value := edit.getRaw(Text); value != nil {
|
||||
if textValue, ok := value.(string); ok {
|
||||
oldText := textValue
|
||||
textValue += text
|
||||
edit.properties[Text] = textValue
|
||||
edit.session.callFunc("appendToInnerHTML", edit.htmlID(), text)
|
||||
edit.textChanged(textValue, oldText)
|
||||
edit.textChanged(textValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -372,9 +371,9 @@ func (edit *editViewData) AppendText(text string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (edit *editViewData) textChanged(newText, oldText string) {
|
||||
func (edit *editViewData) textChanged(newText string) {
|
||||
for _, listener := range edit.textChangeListeners {
|
||||
listener(edit, newText, oldText)
|
||||
listener(edit, newText)
|
||||
}
|
||||
edit.propertyChangedEvent(Text)
|
||||
}
|
||||
|
|
@ -486,7 +485,7 @@ func (edit *editViewData) handleCommand(self View, command string, data DataObje
|
|||
if text, ok := data.PropertyValue("text"); ok {
|
||||
edit.properties[Text] = text
|
||||
if text := GetText(edit); text != oldText {
|
||||
edit.textChanged(text, oldText)
|
||||
edit.textChanged(text)
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
|
@ -532,7 +531,7 @@ func GetHint(view View, subviewID ...string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// GetMaxLength returns a maximal length of EditView. If a maximal length is not limited then 0 is returned
|
||||
// GetMaxLength returns a maximal lenght of EditView. If a maximal lenght is not limited then 0 is returned
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value of the first argument (view) is returned.
|
||||
func GetMaxLength(view View, subviewID ...string) int {
|
||||
return intStyledProperty(view, subviewID, MaxLength, 0)
|
||||
|
|
@ -553,8 +552,8 @@ func IsSpellcheck(view View, subviewID ...string) bool {
|
|||
// GetTextChangedListeners returns the TextChangedListener list of an EditView or MultiLineEditView subview.
|
||||
// If there are no listeners then the empty list is returned
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string, string) {
|
||||
return getEventWithOldListeners[EditView, string](view, subviewID, EditTextChangedEvent)
|
||||
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string) {
|
||||
return getEventListeners[EditView, string](view, subviewID, EditTextChangedEvent)
|
||||
}
|
||||
|
||||
// GetEditViewType returns a value of the Type property of EditView.
|
||||
|
|
@ -605,7 +604,7 @@ func AppendEditText(view View, subviewID string, text string) {
|
|||
}
|
||||
}
|
||||
|
||||
// GetCaretColor returns the color of the text input caret.
|
||||
// GetCaretColor returns the color of the text input carret.
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||
func GetCaretColor(view View, subviewID ...string) Color {
|
||||
return colorStyledProperty(view, subviewID, CaretColor, false)
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ func (picker *filePickerData) htmlDisabledProperties(self View, buffer *strings.
|
|||
func (picker *filePickerData) handleCommand(self View, command string, data DataObject) bool {
|
||||
switch command {
|
||||
case "fileSelected":
|
||||
if node := data.PropertyByTag("files"); node != nil && node.Type() == ArrayNode {
|
||||
if node := data.PropertyWithTag("files"); node != nil && node.Type() == ArrayNode {
|
||||
count := node.ArraySize()
|
||||
files := make([]FileInfo, count)
|
||||
for i := 0; i < count; i++ {
|
||||
|
|
|
|||
|
|
@ -143,10 +143,7 @@ func getFocusListeners(view View, subviewID []string, tag string) []func(View) {
|
|||
func focusEventsHtml(view View, buffer *strings.Builder) {
|
||||
if view.Focusable() {
|
||||
for _, js := range focusEvents {
|
||||
buffer.WriteString(js.jsEvent)
|
||||
buffer.WriteString(`="`)
|
||||
buffer.WriteString(js.jsFunc)
|
||||
buffer.WriteString(`(this, event)" `)
|
||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
go.mod
8
go.mod
|
|
@ -1,7 +1,5 @@
|
|||
module git.mbk-lab.ru/mbk-lab/rui
|
||||
module github.com/anoshenko/rui
|
||||
|
||||
go 1.20
|
||||
go 1.18
|
||||
|
||||
require github.com/gorilla/websocket v1.5.1
|
||||
|
||||
require golang.org/x/net v0.17.0 // indirect
|
||||
require github.com/gorilla/websocket v1.5.0
|
||||
|
|
|
|||
6
go.sum
6
go.sum
|
|
@ -1,4 +1,2 @@
|
|||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type httpHandler struct {
|
||||
app *application
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
switch req.Method {
|
||||
case http.MethodGet:
|
||||
path := `/` + strings.TrimPrefix(req.URL.Path, `/`)
|
||||
req.URL.Path = `/` + strings.TrimPrefix(strings.TrimPrefix(path, h.prefix), `/`)
|
||||
|
||||
h.app.ServeHTTP(w, req)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
NewHandler используется для встраивания приложения rui в сторонние WEB-фреймворки (net/http, gin, echo...).
|
||||
Пример для echo:
|
||||
|
||||
e := echo.New()
|
||||
e.Any(`/ui/*`, func()echo.HandlerFunc{
|
||||
rui.AddEmbedResources(&resources)
|
||||
|
||||
h := rui.NewHandler("/ui", CreateSessionContent, rui.AppParams{
|
||||
Title: `Awesome app`,
|
||||
Icon: `favicon.png`,
|
||||
})
|
||||
|
||||
return func(c echo.Context) error {
|
||||
h.ServeHTTP(c.Response(), c.Request())
|
||||
return nil
|
||||
}
|
||||
})
|
||||
*/
|
||||
func NewHandler(urlPrefix string, createContentFunc func(Session) SessionContent, params AppParams) *httpHandler {
|
||||
app := new(application)
|
||||
app.params = params
|
||||
app.sessions = map[int]Session{}
|
||||
app.createContentFunc = createContentFunc
|
||||
apps = append(apps, app)
|
||||
|
||||
h := &httpHandler{
|
||||
app: app,
|
||||
prefix: `/` + strings.Trim(urlPrefix, `/`),
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
25
image.go
25
image.go
|
|
@ -1,7 +1,11 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -79,6 +83,27 @@ func (manager *imageManager) loadImage(url string, onLoaded func(Image), session
|
|||
image.loadingStatus = ImageLoading
|
||||
manager.images[url] = image
|
||||
|
||||
if runtime.GOOS == "js" && wasmMediaResources {
|
||||
if file, ok := resources.images[url]; ok && file.fs != nil {
|
||||
|
||||
dataType := map[string]string{
|
||||
".svg": "data:image/svg+xml",
|
||||
".png": "data:image/png",
|
||||
".jpg": "data:image/jpg",
|
||||
".jpeg": "data:image/jpg",
|
||||
".gif": "data:image/gif",
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(url))
|
||||
if prefix, ok := dataType[ext]; ok {
|
||||
if data, err := file.fs.ReadFile(file.path); err == nil {
|
||||
session.callFunc("loadInlineImage", url, prefix+";base64,"+base64.StdEncoding.EncodeToString(data))
|
||||
return image
|
||||
} else {
|
||||
DebugLog(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
session.callFunc("loadImage", url)
|
||||
return image
|
||||
}
|
||||
|
|
|
|||
28
imageView.go
28
imageView.go
|
|
@ -1,7 +1,10 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -67,7 +70,8 @@ func newImageView(session Session) View {
|
|||
func (imageView *imageViewData) init(session Session) {
|
||||
imageView.viewData.init(session)
|
||||
imageView.tag = "ImageView"
|
||||
imageView.systemClass = "ruiImageView"
|
||||
//imageView.systemClass = "ruiImageView"
|
||||
|
||||
}
|
||||
|
||||
func (imageView *imageViewData) String() string {
|
||||
|
|
@ -268,7 +272,27 @@ func (imageView *imageViewData) src(src string) (string, string) {
|
|||
}
|
||||
|
||||
if src != "" {
|
||||
return src, imageView.srcSet(src)
|
||||
srcset := imageView.srcSet(src)
|
||||
if runtime.GOOS == "js" && wasmMediaResources {
|
||||
if image, ok := resources.images[src]; ok && image.fs != nil {
|
||||
dataType := map[string]string{
|
||||
".svg": "data:image/svg+xml",
|
||||
".png": "data:image/png",
|
||||
".jpg": "data:image/jpg",
|
||||
".jpeg": "data:image/jpg",
|
||||
".gif": "data:image/gif",
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(src))
|
||||
if prefix, ok := dataType[ext]; ok {
|
||||
if data, err := image.fs.ReadFile(image.path); err == nil {
|
||||
return prefix + ";base64," + base64.StdEncoding.EncodeToString(data), ""
|
||||
} else {
|
||||
DebugLog(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return src, srcset
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
|
|
|||
395
keyEvents.go
395
keyEvents.go
|
|
@ -20,127 +20,6 @@ const (
|
|||
KeyUpEvent = "key-up-event"
|
||||
)
|
||||
|
||||
type ControlKeyMask int
|
||||
type KeyCode string
|
||||
|
||||
const (
|
||||
// AltKey is the mask of the "alt" key
|
||||
AltKey ControlKeyMask = 1
|
||||
// CtrlKey is the mask of the "ctrl" key
|
||||
CtrlKey ControlKeyMask = 2
|
||||
// ShiftKey is the mask of the "shift" key
|
||||
ShiftKey ControlKeyMask = 4
|
||||
// MetaKey is the mask of the "meta" key
|
||||
MetaKey ControlKeyMask = 8
|
||||
|
||||
KeyA KeyCode = "KeyA"
|
||||
KeyB KeyCode = "KeyB"
|
||||
KeyC KeyCode = "KeyC"
|
||||
KeyD KeyCode = "KeyD"
|
||||
KeyE KeyCode = "KeyE"
|
||||
KeyF KeyCode = "KeyF"
|
||||
KeyG KeyCode = "KeyG"
|
||||
KeyH KeyCode = "KeyH"
|
||||
KeyI KeyCode = "KeyI"
|
||||
KeyJ KeyCode = "KeyJ"
|
||||
KeyK KeyCode = "KeyK"
|
||||
KeyL KeyCode = "KeyL"
|
||||
KeyM KeyCode = "KeyM"
|
||||
KeyN KeyCode = "KeyN"
|
||||
KeyO KeyCode = "KeyO"
|
||||
KeyP KeyCode = "KeyP"
|
||||
KeyQ KeyCode = "KeyQ"
|
||||
KeyR KeyCode = "KeyR"
|
||||
KeyS KeyCode = "KeyS"
|
||||
KeyT KeyCode = "KeyT"
|
||||
KeyU KeyCode = "KeyU"
|
||||
KeyV KeyCode = "KeyV"
|
||||
KeyW KeyCode = "KeyW"
|
||||
KeyX KeyCode = "KeyX"
|
||||
KeyY KeyCode = "KeyY"
|
||||
KeyZ KeyCode = "KeyZ"
|
||||
Digit0Key KeyCode = "Digit0"
|
||||
Digit1Key KeyCode = "Digit1"
|
||||
Digit2Key KeyCode = "Digit2"
|
||||
Digit3Key KeyCode = "Digit3"
|
||||
Digit4Key KeyCode = "Digit4"
|
||||
Digit5Key KeyCode = "Digit5"
|
||||
Digit6Key KeyCode = "Digit6"
|
||||
Digit7Key KeyCode = "Digit7"
|
||||
Digit8Key KeyCode = "Digit8"
|
||||
Digit9Key KeyCode = "Digit9"
|
||||
SpaceKey KeyCode = "Space"
|
||||
MinusKey KeyCode = "Minus"
|
||||
EqualKey KeyCode = "Equal"
|
||||
IntlBackslashKey KeyCode = "IntlBackslash"
|
||||
BracketLeftKey KeyCode = "BracketLeft"
|
||||
BracketRightKey KeyCode = "BracketRight"
|
||||
SemicolonKey KeyCode = "Semicolon"
|
||||
CommaKey KeyCode = "Comma"
|
||||
PeriodKey KeyCode = "Period"
|
||||
QuoteKey KeyCode = "Quote"
|
||||
BackquoteKey KeyCode = "Backquote"
|
||||
SlashKey KeyCode = "Slash"
|
||||
EscapeKey KeyCode = "Escape"
|
||||
EnterKey KeyCode = "Enter"
|
||||
TabKey KeyCode = "Tab"
|
||||
CapsLockKey KeyCode = "CapsLock"
|
||||
DeleteKey KeyCode = "Delete"
|
||||
InsertKey KeyCode = "Insert"
|
||||
HelpKey KeyCode = "Help"
|
||||
BackspaceKey KeyCode = "Backspace"
|
||||
PrintScreenKey KeyCode = "PrintScreen"
|
||||
ScrollLockKey KeyCode = "ScrollLock"
|
||||
PauseKey KeyCode = "Pause"
|
||||
ContextMenuKey KeyCode = "ContextMenu"
|
||||
ArrowLeftKey KeyCode = "ArrowLeft"
|
||||
ArrowRightKey KeyCode = "ArrowRight"
|
||||
ArrowUpKey KeyCode = "ArrowUp"
|
||||
ArrowDownKey KeyCode = "ArrowDown"
|
||||
HomeKey KeyCode = "Home"
|
||||
EndKey KeyCode = "End"
|
||||
PageUpKey KeyCode = "PageUp"
|
||||
PageDownKey KeyCode = "PageDown"
|
||||
F1Key KeyCode = "F1"
|
||||
F2Key KeyCode = "F2"
|
||||
F3Key KeyCode = "F3"
|
||||
F4Key KeyCode = "F4"
|
||||
F5Key KeyCode = "F5"
|
||||
F6Key KeyCode = "F6"
|
||||
F7Key KeyCode = "F7"
|
||||
F8Key KeyCode = "F8"
|
||||
F9Key KeyCode = "F9"
|
||||
F10Key KeyCode = "F10"
|
||||
F11Key KeyCode = "F11"
|
||||
F12Key KeyCode = "F12"
|
||||
F13Key KeyCode = "F13"
|
||||
NumLockKey KeyCode = "NumLock"
|
||||
NumpadKey0 KeyCode = "Numpad0"
|
||||
NumpadKey1 KeyCode = "Numpad1"
|
||||
NumpadKey2 KeyCode = "Numpad2"
|
||||
NumpadKey3 KeyCode = "Numpad3"
|
||||
NumpadKey4 KeyCode = "Numpad4"
|
||||
NumpadKey5 KeyCode = "Numpad5"
|
||||
NumpadKey6 KeyCode = "Numpad6"
|
||||
NumpadKey7 KeyCode = "Numpad7"
|
||||
NumpadKey8 KeyCode = "Numpad8"
|
||||
NumpadKey9 KeyCode = "Numpad9"
|
||||
NumpadDecimalKey KeyCode = "NumpadDecimal"
|
||||
NumpadEnterKey KeyCode = "NumpadEnter"
|
||||
NumpadAddKey KeyCode = "NumpadAdd"
|
||||
NumpadSubtractKey KeyCode = "NumpadSubtract"
|
||||
NumpadMultiplyKey KeyCode = "NumpadMultiply"
|
||||
NumpadDivideKey KeyCode = "NumpadDivide"
|
||||
ShiftLeftKey KeyCode = "ShiftLeft"
|
||||
ShiftRightKey KeyCode = "ShiftRight"
|
||||
ControlLeftKey KeyCode = "ControlLeft"
|
||||
ControlRightKey KeyCode = "ControlRight"
|
||||
AltLeftKey KeyCode = "AltLeft"
|
||||
AltRightKey KeyCode = "AltRight"
|
||||
MetaLeftKey KeyCode = "MetaLeft"
|
||||
MetaRightKey KeyCode = "MetaRight"
|
||||
)
|
||||
|
||||
type KeyEvent struct {
|
||||
// TimeStamp is the time at which the event was created (in milliseconds).
|
||||
// This value is time since epoch—but in reality, browsers' definitions vary.
|
||||
|
|
@ -153,7 +32,7 @@ type KeyEvent struct {
|
|||
|
||||
// Code holds a string that identifies the physical key being pressed. The value is not affected
|
||||
// by the current keyboard layout or modifier state, so a particular key will always return the same value.
|
||||
Code KeyCode
|
||||
Code string
|
||||
|
||||
// Repeat == true if a key has been depressed long enough to trigger key repetition, otherwise false.
|
||||
Repeat bool
|
||||
|
|
@ -180,8 +59,7 @@ func (event *KeyEvent) init(data DataObject) {
|
|||
}
|
||||
|
||||
event.Key, _ = data.PropertyValue("key")
|
||||
code, _ := data.PropertyValue("code")
|
||||
event.Code = KeyCode(code)
|
||||
event.Code, _ = data.PropertyValue("code")
|
||||
event.TimeStamp = getTimeStamp(data)
|
||||
event.Repeat = getBool("repeat")
|
||||
event.CtrlKey = getBool("ctrlKey")
|
||||
|
|
@ -315,183 +193,9 @@ func valueToEventListeners[V View, E any](value any) ([]func(V, E), bool) {
|
|||
return nil, false
|
||||
}
|
||||
|
||||
func valueToEventWithOldListeners[V View, E any](value any) ([]func(V, E, E), bool) {
|
||||
if value == nil {
|
||||
return nil, true
|
||||
}
|
||||
|
||||
switch value := value.(type) {
|
||||
case func(V, E, E):
|
||||
return []func(V, E, E){value}, true
|
||||
|
||||
case func(V, E):
|
||||
fn := func(v V, val, _ E) {
|
||||
value(v, val)
|
||||
}
|
||||
return []func(V, E, E){fn}, true
|
||||
|
||||
case func(E, E):
|
||||
fn := func(_ V, val, old E) {
|
||||
value(val, old)
|
||||
}
|
||||
return []func(V, E, E){fn}, true
|
||||
|
||||
case func(E):
|
||||
fn := func(_ V, val, _ E) {
|
||||
value(val)
|
||||
}
|
||||
return []func(V, E, E){fn}, true
|
||||
|
||||
case func(V):
|
||||
fn := func(v V, _, _ E) {
|
||||
value(v)
|
||||
}
|
||||
return []func(V, E, E){fn}, true
|
||||
|
||||
case func():
|
||||
fn := func(V, E, E) {
|
||||
value()
|
||||
}
|
||||
return []func(V, E, E){fn}, true
|
||||
|
||||
case []func(V, E, E):
|
||||
if len(value) == 0 {
|
||||
return nil, true
|
||||
}
|
||||
for _, fn := range value {
|
||||
if fn == nil {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return value, true
|
||||
|
||||
case []func(V, E):
|
||||
count := len(value)
|
||||
if count == 0 {
|
||||
return nil, true
|
||||
}
|
||||
listeners := make([]func(V, E, E), count)
|
||||
for i, fn := range value {
|
||||
if fn == nil {
|
||||
return nil, false
|
||||
}
|
||||
listeners[i] = func(view V, val, _ E) {
|
||||
fn(view, val)
|
||||
}
|
||||
}
|
||||
return listeners, true
|
||||
|
||||
case []func(E):
|
||||
count := len(value)
|
||||
if count == 0 {
|
||||
return nil, true
|
||||
}
|
||||
listeners := make([]func(V, E, E), count)
|
||||
for i, fn := range value {
|
||||
if fn == nil {
|
||||
return nil, false
|
||||
}
|
||||
listeners[i] = func(_ V, val, _ E) {
|
||||
fn(val)
|
||||
}
|
||||
}
|
||||
return listeners, true
|
||||
|
||||
case []func(E, E):
|
||||
count := len(value)
|
||||
if count == 0 {
|
||||
return nil, true
|
||||
}
|
||||
listeners := make([]func(V, E, E), count)
|
||||
for i, fn := range value {
|
||||
if fn == nil {
|
||||
return nil, false
|
||||
}
|
||||
listeners[i] = func(_ V, val, old E) {
|
||||
fn(val, old)
|
||||
}
|
||||
}
|
||||
return listeners, true
|
||||
|
||||
case []func(V):
|
||||
count := len(value)
|
||||
if count == 0 {
|
||||
return nil, true
|
||||
}
|
||||
listeners := make([]func(V, E, E), count)
|
||||
for i, fn := range value {
|
||||
if fn == nil {
|
||||
return nil, false
|
||||
}
|
||||
listeners[i] = func(view V, _, _ E) {
|
||||
fn(view)
|
||||
}
|
||||
}
|
||||
return listeners, true
|
||||
|
||||
case []func():
|
||||
count := len(value)
|
||||
if count == 0 {
|
||||
return nil, true
|
||||
}
|
||||
listeners := make([]func(V, E, E), count)
|
||||
for i, fn := range value {
|
||||
if fn == nil {
|
||||
return nil, false
|
||||
}
|
||||
listeners[i] = func(V, E, E) {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
return listeners, true
|
||||
|
||||
case []any:
|
||||
count := len(value)
|
||||
if count == 0 {
|
||||
return nil, true
|
||||
}
|
||||
listeners := make([]func(V, E, E), count)
|
||||
for i, v := range value {
|
||||
if v == nil {
|
||||
return nil, false
|
||||
}
|
||||
switch fn := v.(type) {
|
||||
case func(V, E, E):
|
||||
listeners[i] = fn
|
||||
|
||||
case func(V, E):
|
||||
listeners[i] = func(view V, val, _ E) {
|
||||
fn(view, val)
|
||||
}
|
||||
|
||||
case func(E, E):
|
||||
listeners[i] = func(_ V, val, old E) {
|
||||
fn(val, old)
|
||||
}
|
||||
|
||||
case func(E):
|
||||
listeners[i] = func(_ V, val, _ E) {
|
||||
fn(val)
|
||||
}
|
||||
|
||||
case func(V):
|
||||
listeners[i] = func(view V, _, _ E) {
|
||||
fn(view)
|
||||
}
|
||||
|
||||
case func():
|
||||
listeners[i] = func(V, E, E) {
|
||||
fn()
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return listeners, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
var keyEvents = map[string]struct{ jsEvent, jsFunc string }{
|
||||
KeyDownEvent: {jsEvent: "onkeydown", jsFunc: "keyDownEvent"},
|
||||
KeyUpEvent: {jsEvent: "onkeyup", jsFunc: "keyUpEvent"},
|
||||
}
|
||||
|
||||
func (view *viewData) setKeyListener(tag string, value any) bool {
|
||||
|
|
@ -503,57 +207,26 @@ func (view *viewData) setKeyListener(tag string, value any) bool {
|
|||
|
||||
if listeners == nil {
|
||||
view.removeKeyListener(tag)
|
||||
} else {
|
||||
switch tag {
|
||||
case KeyDownEvent:
|
||||
view.properties[tag] = listeners
|
||||
if view.created {
|
||||
view.session.updateProperty(view.htmlID(), "onkeydown", "keyDownEvent(this, event)")
|
||||
}
|
||||
|
||||
case KeyUpEvent:
|
||||
view.properties[tag] = listeners
|
||||
if view.created {
|
||||
view.session.updateProperty(view.htmlID(), "onkeyup", "keyUpEvent(this, event)")
|
||||
}
|
||||
|
||||
default:
|
||||
return false
|
||||
} else if js, ok := keyEvents[tag]; ok {
|
||||
view.properties[tag] = listeners
|
||||
if view.created {
|
||||
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (view *viewData) removeKeyListener(tag string) {
|
||||
delete(view.properties, tag)
|
||||
if view.created {
|
||||
switch tag {
|
||||
case KeyDownEvent:
|
||||
if !view.Focusable() {
|
||||
view.session.removeProperty(view.htmlID(), "onkeydown")
|
||||
}
|
||||
|
||||
case KeyUpEvent:
|
||||
view.session.removeProperty(view.htmlID(), "onkeyup")
|
||||
if js, ok := keyEvents[tag]; ok {
|
||||
view.session.removeProperty(view.htmlID(), js.jsEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getEventWithOldListeners[V View, E any](view View, subviewID []string, tag string) []func(V, E, E) {
|
||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
||||
view = ViewByID(view, subviewID[0])
|
||||
}
|
||||
if view != nil {
|
||||
if value := view.Get(tag); value != nil {
|
||||
if result, ok := value.([]func(V, E, E)); ok {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return []func(V, E, E){}
|
||||
}
|
||||
|
||||
func getEventListeners[V View, E any](view View, subviewID []string, tag string) []func(V, E) {
|
||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
||||
view = ViewByID(view, subviewID[0])
|
||||
|
|
@ -569,52 +242,22 @@ func getEventListeners[V View, E any](view View, subviewID []string, tag string)
|
|||
}
|
||||
|
||||
func keyEventsHtml(view View, buffer *strings.Builder) {
|
||||
if len(getEventListeners[View, KeyEvent](view, nil, KeyDownEvent)) > 0 {
|
||||
buffer.WriteString(`onkeydown="keyDownEvent(this, event)" `)
|
||||
} else if view.Focusable() {
|
||||
if len(getEventListeners[View, MouseEvent](view, nil, ClickEvent)) > 0 {
|
||||
buffer.WriteString(`onkeydown="keyDownEvent(this, event)" `)
|
||||
for tag, js := range keyEvents {
|
||||
if listeners := getEventListeners[View, KeyEvent](view, nil, tag); len(listeners) > 0 {
|
||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
||||
}
|
||||
}
|
||||
|
||||
if listeners := getEventListeners[View, KeyEvent](view, nil, KeyUpEvent); len(listeners) > 0 {
|
||||
buffer.WriteString(`onkeyup="keyUpEvent(this, event)" `)
|
||||
}
|
||||
}
|
||||
|
||||
func handleKeyEvents(view View, tag string, data DataObject) {
|
||||
var event KeyEvent
|
||||
event.init(data)
|
||||
listeners := getEventListeners[View, KeyEvent](view, nil, tag)
|
||||
|
||||
if len(listeners) > 0 {
|
||||
var event KeyEvent
|
||||
event.init(data)
|
||||
|
||||
for _, listener := range listeners {
|
||||
listener(view, event)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if tag == KeyDownEvent && view.Focusable() && (event.Key == " " || event.Key == "Enter") && !IsDisabled(view) {
|
||||
if listeners := getEventListeners[View, MouseEvent](view, nil, ClickEvent); len(listeners) > 0 {
|
||||
clickEvent := MouseEvent{
|
||||
TimeStamp: event.TimeStamp,
|
||||
Button: PrimaryMouseButton,
|
||||
Buttons: PrimaryMouseMask,
|
||||
CtrlKey: event.CtrlKey,
|
||||
AltKey: event.AltKey,
|
||||
ShiftKey: event.ShiftKey,
|
||||
MetaKey: event.MetaKey,
|
||||
ClientX: view.Frame().Width / 2,
|
||||
ClientY: view.Frame().Height / 2,
|
||||
X: view.Frame().Width / 2,
|
||||
Y: view.Frame().Height / 2,
|
||||
ScreenX: view.Frame().Left + view.Frame().Width/2,
|
||||
ScreenY: view.Frame().Top + view.Frame().Height/2,
|
||||
}
|
||||
for _, listener := range listeners {
|
||||
listener(view, clickEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
137
listView.go
137
listView.go
|
|
@ -47,7 +47,7 @@ const (
|
|||
// ListView - the list view interface
|
||||
type ListView interface {
|
||||
View
|
||||
ParentView
|
||||
ParanetView
|
||||
// ReloadListViewData updates ListView content
|
||||
ReloadListViewData()
|
||||
|
||||
|
|
@ -129,92 +129,78 @@ func (listView *listViewData) remove(tag string) {
|
|||
case Gap:
|
||||
listView.remove(ListRowGap)
|
||||
listView.remove(ListColumnGap)
|
||||
return
|
||||
|
||||
case Checked:
|
||||
if len(listView.checkedItem) == 0 {
|
||||
return
|
||||
}
|
||||
listView.checkedItem = []int{}
|
||||
if listView.created {
|
||||
updateInnerHTML(listView.htmlID(), listView.session)
|
||||
if len(listView.checkedItem) > 0 {
|
||||
listView.checkedItem = []int{}
|
||||
if listView.created {
|
||||
updateInnerHTML(listView.htmlID(), listView.session)
|
||||
}
|
||||
listView.propertyChangedEvent(tag)
|
||||
}
|
||||
|
||||
case Items:
|
||||
if listView.adapter == nil {
|
||||
return
|
||||
}
|
||||
listView.adapter = nil
|
||||
if listView.created {
|
||||
updateInnerHTML(listView.htmlID(), listView.session)
|
||||
if listView.adapter != nil {
|
||||
listView.adapter = nil
|
||||
if listView.created {
|
||||
updateInnerHTML(listView.htmlID(), listView.session)
|
||||
}
|
||||
listView.propertyChangedEvent(tag)
|
||||
}
|
||||
|
||||
case Orientation, ListWrap:
|
||||
if _, ok := listView.properties[tag]; !ok {
|
||||
return
|
||||
}
|
||||
delete(listView.properties, tag)
|
||||
if listView.created {
|
||||
updateCSSStyle(listView.htmlID(), listView.session)
|
||||
if _, ok := listView.properties[tag]; ok {
|
||||
delete(listView.properties, tag)
|
||||
if listView.created {
|
||||
updateCSSStyle(listView.htmlID(), listView.session)
|
||||
}
|
||||
listView.propertyChangedEvent(tag)
|
||||
}
|
||||
|
||||
case Current:
|
||||
current := GetCurrent(listView)
|
||||
if current == -1 {
|
||||
return
|
||||
}
|
||||
delete(listView.properties, tag)
|
||||
if listView.created {
|
||||
htmlID := listView.htmlID()
|
||||
session := listView.session
|
||||
session.removeProperty(htmlID, "data-current")
|
||||
updateInnerHTML(htmlID, session)
|
||||
updateInnerHTML(listView.htmlID(), listView.session)
|
||||
}
|
||||
if current != -1 {
|
||||
for _, listener := range listView.selectedListeners {
|
||||
listener(listView, -1)
|
||||
}
|
||||
listView.propertyChangedEvent(tag)
|
||||
}
|
||||
|
||||
case ItemWidth, ItemHeight, ItemHorizontalAlign, ItemVerticalAlign, ItemCheckbox,
|
||||
CheckboxHorizontalAlign, CheckboxVerticalAlign:
|
||||
if _, ok := listView.properties[tag]; !ok {
|
||||
return
|
||||
}
|
||||
delete(listView.properties, tag)
|
||||
if listView.created {
|
||||
updateInnerHTML(listView.htmlID(), listView.session)
|
||||
}
|
||||
|
||||
case ListItemStyle, CurrentStyle, CurrentInactiveStyle:
|
||||
if !listView.setItemStyle(tag, "") {
|
||||
return
|
||||
CheckboxHorizontalAlign, CheckboxVerticalAlign, ListItemStyle, CurrentStyle, CurrentInactiveStyle:
|
||||
if _, ok := listView.properties[tag]; ok {
|
||||
delete(listView.properties, tag)
|
||||
if listView.created {
|
||||
updateInnerHTML(listView.htmlID(), listView.session)
|
||||
}
|
||||
listView.propertyChangedEvent(tag)
|
||||
}
|
||||
|
||||
case ListItemClickedEvent:
|
||||
if len(listView.clickedListeners) == 0 {
|
||||
return
|
||||
if len(listView.clickedListeners) > 0 {
|
||||
listView.clickedListeners = []func(ListView, int){}
|
||||
listView.propertyChangedEvent(tag)
|
||||
}
|
||||
listView.clickedListeners = []func(ListView, int){}
|
||||
|
||||
case ListItemSelectedEvent:
|
||||
if len(listView.selectedListeners) == 0 {
|
||||
return
|
||||
if len(listView.selectedListeners) > 0 {
|
||||
listView.selectedListeners = []func(ListView, int){}
|
||||
listView.propertyChangedEvent(tag)
|
||||
}
|
||||
listView.selectedListeners = []func(ListView, int){}
|
||||
|
||||
case ListItemCheckedEvent:
|
||||
if len(listView.checkedListeners) == 0 {
|
||||
return
|
||||
if len(listView.checkedListeners) > 0 {
|
||||
listView.checkedListeners = []func(ListView, []int){}
|
||||
listView.propertyChangedEvent(tag)
|
||||
}
|
||||
listView.checkedListeners = []func(ListView, []int){}
|
||||
|
||||
default:
|
||||
listView.viewData.remove(tag)
|
||||
return
|
||||
}
|
||||
|
||||
listView.propertyChangedEvent(tag)
|
||||
}
|
||||
|
||||
func (listView *listViewData) Set(tag string, value any) bool {
|
||||
|
|
@ -282,20 +268,11 @@ func (listView *listViewData) set(tag string, value any) bool {
|
|||
if !listView.setIntProperty(Current, value) {
|
||||
return false
|
||||
}
|
||||
|
||||
current := GetCurrent(listView)
|
||||
if oldCurrent == current {
|
||||
return true
|
||||
}
|
||||
|
||||
if listView.created {
|
||||
htmlID := listView.htmlID()
|
||||
if current >= 0 {
|
||||
listView.session.updateProperty(htmlID, "data-current", fmt.Sprintf("%s-%d", htmlID, current))
|
||||
} else {
|
||||
listView.session.removeProperty(htmlID, "data-current")
|
||||
}
|
||||
}
|
||||
for _, listener := range listView.selectedListeners {
|
||||
listener(listView, current)
|
||||
}
|
||||
|
|
@ -313,7 +290,12 @@ func (listView *listViewData) set(tag string, value any) bool {
|
|||
}
|
||||
|
||||
case ListItemStyle, CurrentStyle, CurrentInactiveStyle:
|
||||
if !listView.setItemStyle(tag, value) {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
listView.properties[tag] = value
|
||||
|
||||
default:
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -328,33 +310,6 @@ func (listView *listViewData) set(tag string, value any) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (listView *listViewData) setItemStyle(tag string, value any) bool {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
if value == "" {
|
||||
delete(listView.properties, tag)
|
||||
} else {
|
||||
listView.properties[tag] = value
|
||||
}
|
||||
|
||||
default:
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
}
|
||||
|
||||
if listView.created {
|
||||
switch tag {
|
||||
case CurrentStyle:
|
||||
listView.session.updateProperty(listView.htmlID(), "data-focusitemstyle", listView.currentStyle())
|
||||
|
||||
case CurrentInactiveStyle:
|
||||
listView.session.updateProperty(listView.htmlID(), "data-bluritemstyle", listView.currentInactiveStyle())
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (listView *listViewData) Get(tag string) any {
|
||||
return listView.get(listView.normalizeTag(tag))
|
||||
}
|
||||
|
|
@ -785,9 +740,6 @@ func (listView *listViewData) checkboxSubviews(self View, buffer *strings.Builde
|
|||
}
|
||||
buffer.WriteString(`" onclick="listItemClickEvent(this, event)" data-left="0" data-top="0" data-width="0" data-height="0" style="display: grid; justify-items: stretch; align-items: stretch;`)
|
||||
listView.itemSize(self, buffer)
|
||||
if !listView.adapter.IsListItemEnabled(i) {
|
||||
buffer.WriteString(`" data-disabled="1`)
|
||||
}
|
||||
buffer.WriteString(`">`)
|
||||
buffer.WriteString(itemDiv)
|
||||
|
||||
|
|
@ -844,9 +796,6 @@ func (listView *listViewData) noneCheckboxSubviews(self View, buffer *strings.Bu
|
|||
}
|
||||
buffer.WriteString(`" `)
|
||||
buffer.WriteString(itemStyle)
|
||||
if !listView.adapter.IsListItemEnabled(i) {
|
||||
buffer.WriteString(` data-disabled="1"`)
|
||||
}
|
||||
buffer.WriteString(`>`)
|
||||
|
||||
if view := listView.getItemView(i); view != nil {
|
||||
|
|
|
|||
|
|
@ -184,22 +184,14 @@ func (view *viewData) removeMouseListener(tag string) {
|
|||
}
|
||||
}
|
||||
|
||||
func mouseEventsHtml(view View, buffer *strings.Builder, hasTooltip bool) {
|
||||
func mouseEventsHtml(view View, buffer *strings.Builder) {
|
||||
for tag, js := range mouseEvents {
|
||||
if value := view.getRaw(tag); value != nil {
|
||||
if listeners, ok := value.([]func(View, MouseEvent)); ok && len(listeners) > 0 {
|
||||
buffer.WriteString(js.jsEvent)
|
||||
buffer.WriteString(`="`)
|
||||
buffer.WriteString(js.jsFunc)
|
||||
buffer.WriteString(`(this, event)" `)
|
||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasTooltip {
|
||||
buffer.WriteString(`onmouseenter="mouseEnterEvent(this, event)" `)
|
||||
buffer.WriteString(`onmouseleave="mouseLeaveEvent(this, event)" `)
|
||||
}
|
||||
}
|
||||
|
||||
func getTimeStamp(data DataObject) uint64 {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ type NumberPicker interface {
|
|||
|
||||
type numberPickerData struct {
|
||||
viewData
|
||||
numberChangedListeners []func(NumberPicker, float64, float64)
|
||||
numberChangedListeners []func(NumberPicker, float64)
|
||||
}
|
||||
|
||||
// NewNumberPicker create new NumberPicker object and return it
|
||||
|
|
@ -47,7 +47,7 @@ func newNumberPicker(session Session) View {
|
|||
func (picker *numberPickerData) init(session Session) {
|
||||
picker.viewData.init(session)
|
||||
picker.tag = "NumberPicker"
|
||||
picker.numberChangedListeners = []func(NumberPicker, float64, float64){}
|
||||
picker.numberChangedListeners = []func(NumberPicker, float64){}
|
||||
}
|
||||
|
||||
func (picker *numberPickerData) String() string {
|
||||
|
|
@ -76,20 +76,7 @@ func (picker *numberPickerData) remove(tag string) {
|
|||
switch tag {
|
||||
case NumberChangedEvent:
|
||||
if len(picker.numberChangedListeners) > 0 {
|
||||
picker.numberChangedListeners = []func(NumberPicker, float64, float64){}
|
||||
picker.propertyChangedEvent(tag)
|
||||
}
|
||||
|
||||
case NumberPickerValue:
|
||||
oldValue := GetNumberPickerValue(picker)
|
||||
picker.viewData.remove(tag)
|
||||
if oldValue != 0 {
|
||||
if picker.created {
|
||||
picker.session.callFunc("setInputValue", picker.htmlID(), 0)
|
||||
}
|
||||
for _, listener := range picker.numberChangedListeners {
|
||||
listener(picker, 0, oldValue)
|
||||
}
|
||||
picker.numberChangedListeners = []func(NumberPicker, float64){}
|
||||
picker.propertyChangedEvent(tag)
|
||||
}
|
||||
|
||||
|
|
@ -111,12 +98,12 @@ func (picker *numberPickerData) set(tag string, value any) bool {
|
|||
|
||||
switch tag {
|
||||
case NumberChangedEvent:
|
||||
listeners, ok := valueToEventWithOldListeners[NumberPicker, float64](value)
|
||||
listeners, ok := valueToEventListeners[NumberPicker, float64](value)
|
||||
if !ok {
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
} else if listeners == nil {
|
||||
listeners = []func(NumberPicker, float64, float64){}
|
||||
listeners = []func(NumberPicker, float64){}
|
||||
}
|
||||
picker.numberChangedListeners = listeners
|
||||
picker.propertyChangedEvent(tag)
|
||||
|
|
@ -132,7 +119,7 @@ func (picker *numberPickerData) set(tag string, value any) bool {
|
|||
picker.session.callFunc("setInputValue", picker.htmlID(), newValue)
|
||||
}
|
||||
for _, listener := range picker.numberChangedListeners {
|
||||
listener(picker, f, oldValue)
|
||||
listener(picker, f)
|
||||
}
|
||||
picker.propertyChangedEvent(tag)
|
||||
}
|
||||
|
|
@ -172,6 +159,13 @@ func (picker *numberPickerData) propertyChanged(tag string) {
|
|||
} else {
|
||||
picker.session.updateProperty(picker.htmlID(), Step, "any")
|
||||
}
|
||||
|
||||
case NumberPickerValue:
|
||||
value := GetNumberPickerValue(picker)
|
||||
picker.session.callFunc("setInputValue", picker.htmlID(), value)
|
||||
for _, listener := range picker.numberChangedListeners {
|
||||
listener(picker, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -248,7 +242,7 @@ func (picker *numberPickerData) handleCommand(self View, command string, data Da
|
|||
picker.properties[NumberPickerValue] = text
|
||||
if value != oldValue {
|
||||
for _, listener := range picker.numberChangedListeners {
|
||||
listener(picker, value, oldValue)
|
||||
listener(picker, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -329,6 +323,6 @@ func GetNumberPickerValue(view View, subviewID ...string) float64 {
|
|||
// GetNumberChangedListeners returns the NumberChangedListener list of an NumberPicker subview.
|
||||
// If there are no listeners then the empty list is returned
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64, float64) {
|
||||
return getEventWithOldListeners[NumberPicker, float64](view, subviewID, NumberChangedEvent)
|
||||
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64) {
|
||||
return getEventListeners[NumberPicker, float64](view, subviewID, NumberChangedEvent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,10 +129,7 @@ func pointerEventsHtml(view View, buffer *strings.Builder) {
|
|||
for tag, js := range pointerEvents {
|
||||
if value := view.getRaw(tag); value != nil {
|
||||
if listeners, ok := value.([]func(View, PointerEvent)); ok && len(listeners) > 0 {
|
||||
buffer.WriteString(js.jsEvent)
|
||||
buffer.WriteString(`="`)
|
||||
buffer.WriteString(js.jsFunc)
|
||||
buffer.WriteString(`(this, event)" `)
|
||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
100
popup.go
100
popup.go
|
|
@ -78,21 +78,11 @@ const (
|
|||
// LeftArrow is value of the popup "arrow" property:
|
||||
// Arrow on the left side of the pop-up window
|
||||
LeftArrow = 4
|
||||
|
||||
// NormalButton is the constant of the popup button type: the normal button
|
||||
NormalButton PopupButtonType = 0
|
||||
// DefaultButton is the constant of the popup button type: button that fires when the "Enter" key is pressed
|
||||
DefaultButton PopupButtonType = 1
|
||||
// CancelButton is the constant of the popup button type: button that fires when the "Escape" key is pressed
|
||||
CancelButton PopupButtonType = 2
|
||||
)
|
||||
|
||||
type PopupButtonType int
|
||||
|
||||
// PopupButton describes a button that will be placed at the bottom of the window.
|
||||
type PopupButton struct {
|
||||
Title string
|
||||
Type PopupButtonType
|
||||
OnClick func(Popup)
|
||||
}
|
||||
|
||||
|
|
@ -105,14 +95,11 @@ type Popup interface {
|
|||
onDismiss()
|
||||
html(buffer *strings.Builder)
|
||||
viewByHTMLID(id string) View
|
||||
keyEvent(event KeyEvent) bool
|
||||
}
|
||||
|
||||
type popupData struct {
|
||||
layerView View
|
||||
view View
|
||||
buttons []PopupButton
|
||||
cancelable bool
|
||||
dismissListener []func(Popup)
|
||||
}
|
||||
|
||||
|
|
@ -286,7 +273,6 @@ func (arrow *popupArrow) createView(popupView View) View {
|
|||
|
||||
func (popup *popupData) init(view View, popupParams Params) {
|
||||
popup.view = view
|
||||
popup.cancelable = false
|
||||
session := view.Session()
|
||||
|
||||
columnCount := 3
|
||||
|
|
@ -406,15 +392,13 @@ func (popup *popupData) init(view View, popupParams Params) {
|
|||
TextSize: Px(20),
|
||||
Content: "✕",
|
||||
NotTranslate: true,
|
||||
ClickEvent: popup.cancel,
|
||||
ClickEvent: func(View) {
|
||||
popup.Dismiss()
|
||||
},
|
||||
})
|
||||
popup.cancelable = true
|
||||
|
||||
case OutsideClose:
|
||||
outsideClose, _ = boolProperty(popupParams, OutsideClose, session)
|
||||
if outsideClose {
|
||||
popup.cancelable = true
|
||||
}
|
||||
|
||||
case Buttons:
|
||||
switch value := value.(type) {
|
||||
|
|
@ -510,7 +494,6 @@ func (popup *popupData) init(view View, popupParams Params) {
|
|||
view.Set(Row, viewRow)
|
||||
popupView.Append(view)
|
||||
|
||||
popup.buttons = buttons
|
||||
if buttonCount := len(buttons); buttonCount > 0 {
|
||||
buttonsAlign, _ := enumProperty(params, ButtonsAlign, session, RightAlign)
|
||||
popupCellHeight = append(popupCellHeight, AutoSize())
|
||||
|
|
@ -528,31 +511,21 @@ func (popup *popupData) init(view View, popupParams Params) {
|
|||
buttonsPanel.Set(Margin, gap)
|
||||
}
|
||||
|
||||
for i, button := range buttons {
|
||||
title := button.Title
|
||||
if title == "" && button.Type == CancelButton {
|
||||
title = "Cancel"
|
||||
}
|
||||
|
||||
buttonView := NewButton(session, Params{
|
||||
Column: i,
|
||||
Content: title,
|
||||
createButton := func(n int, button PopupButton) Button {
|
||||
return NewButton(session, Params{
|
||||
Column: n,
|
||||
Content: button.Title,
|
||||
ClickEvent: func() {
|
||||
if button.OnClick != nil {
|
||||
button.OnClick(popup)
|
||||
} else {
|
||||
popup.Dismiss()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
for i, button := range buttons {
|
||||
buttonsPanel.Append(createButton(i, button))
|
||||
}
|
||||
|
||||
popupView.Append(NewGridLayout(session, Params{
|
||||
|
|
@ -571,8 +544,11 @@ func (popup *popupData) init(view View, popupParams Params) {
|
|||
}
|
||||
|
||||
popup.layerView = NewGridLayout(session, layerParams)
|
||||
|
||||
if outsideClose {
|
||||
popup.layerView.Set(ClickEvent, popup.cancel)
|
||||
popup.layerView.Set(ClickEvent, func(View) {
|
||||
popup.Dismiss()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -584,21 +560,12 @@ func (popup *popupData) Session() Session {
|
|||
return popup.view.Session()
|
||||
}
|
||||
|
||||
func (popup *popupData) cancel() {
|
||||
for _, button := range popup.buttons {
|
||||
if button.Type == CancelButton && button.OnClick != nil {
|
||||
button.OnClick(popup)
|
||||
return
|
||||
}
|
||||
}
|
||||
popup.Dismiss()
|
||||
}
|
||||
|
||||
func (popup *popupData) Dismiss() {
|
||||
popup.Session().popupManager().dismissPopup(popup)
|
||||
for _, listener := range popup.dismissListener {
|
||||
listener(popup)
|
||||
}
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (popup *popupData) Show() {
|
||||
|
|
@ -620,27 +587,6 @@ func (popup *popupData) onDismiss() {
|
|||
}
|
||||
}
|
||||
|
||||
func (popup *popupData) keyEvent(event KeyEvent) bool {
|
||||
if !event.AltKey && !event.CtrlKey && !event.ShiftKey && !event.MetaKey {
|
||||
switch event.Code {
|
||||
case EnterKey:
|
||||
for _, button := range popup.buttons {
|
||||
if button.Type == DefaultButton && button.OnClick != nil {
|
||||
button.OnClick(popup)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
case EscapeKey:
|
||||
if popup.cancelable {
|
||||
popup.Dismiss()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NewPopup creates a new Popup
|
||||
func NewPopup(view View, param Params) Popup {
|
||||
if view == nil {
|
||||
|
|
@ -691,8 +637,6 @@ func (manager *popupManager) showPopup(popup Popup) {
|
|||
|
||||
session.callFunc("blurCurrent")
|
||||
manager.updatePopupLayerInnerHTML(session)
|
||||
session.updateCSSProperty("ruiTooltipLayer", "visibility", "hidden")
|
||||
session.updateCSSProperty("ruiTooltipLayer", "opacity", "0")
|
||||
session.updateCSSProperty("ruiPopupLayer", "visibility", "visible")
|
||||
session.updateCSSProperty("ruiRoot", "pointer-events", "none")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,19 +28,8 @@ func ShowQuestion(title, text string, session Session, onYes func(), onNo func()
|
|||
CloseButton: false,
|
||||
OutsideClose: false,
|
||||
Buttons: []PopupButton{
|
||||
{
|
||||
Title: "Yes",
|
||||
Type: DefaultButton,
|
||||
OnClick: func(popup Popup) {
|
||||
popup.Dismiss()
|
||||
if onYes != nil {
|
||||
onYes()
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "No",
|
||||
Type: CancelButton,
|
||||
OnClick: func(popup Popup) {
|
||||
popup.Dismiss()
|
||||
if onNo != nil {
|
||||
|
|
@ -48,6 +37,15 @@ func ShowQuestion(title, text string, session Session, onYes func(), onNo func()
|
|||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Yes",
|
||||
OnClick: func(popup Popup) {
|
||||
popup.Dismiss()
|
||||
if onYes != nil {
|
||||
onYes()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if title != "" {
|
||||
|
|
@ -70,12 +68,11 @@ func ShowCancellableQuestion(title, text string, session Session, onYes func(),
|
|||
OutsideClose: false,
|
||||
Buttons: []PopupButton{
|
||||
{
|
||||
Title: "Yes",
|
||||
Type: DefaultButton,
|
||||
Title: "Cancel",
|
||||
OnClick: func(popup Popup) {
|
||||
popup.Dismiss()
|
||||
if onYes != nil {
|
||||
onYes()
|
||||
if onCancel != nil {
|
||||
onCancel()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -89,12 +86,11 @@ func ShowCancellableQuestion(title, text string, session Session, onYes func(),
|
|||
},
|
||||
},
|
||||
{
|
||||
Title: "Cancel",
|
||||
Type: CancelButton,
|
||||
Title: "Yes",
|
||||
OnClick: func(popup Popup) {
|
||||
popup.Dismiss()
|
||||
if onCancel != nil {
|
||||
onCancel()
|
||||
if onYes != nil {
|
||||
onYes()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -131,14 +127,10 @@ func (popup *popupMenuData) ListSize() int {
|
|||
}
|
||||
|
||||
func (popup *popupMenuData) ListItem(index int, session Session) View {
|
||||
view := NewTextView(popup.session, Params{
|
||||
return NewTextView(popup.session, Params{
|
||||
Text: popup.items[index],
|
||||
Style: "ruiPopupMenuItem",
|
||||
})
|
||||
if !popup.IsListItemEnabled(index) {
|
||||
view.Set(TextColor, "@ruiDisabledTextColor")
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
func (popup *popupMenuData) IsListItemEnabled(index int) bool {
|
||||
|
|
|
|||
|
|
@ -277,10 +277,6 @@ const (
|
|||
// The "outline-width" SizeUnit property sets the width of an view's outline.
|
||||
OutlineWidth = "outline-width"
|
||||
|
||||
// OutlineWidth is the constant for the "outline-offset" property tag.
|
||||
// The "outline-offset" SizeUnit property sets the amount of space between an outline and the edge or border of an element..
|
||||
OutlineOffset = "outline-offset"
|
||||
|
||||
// Shadow is the constant for the "shadow" property tag.
|
||||
// The "shadow" property adds shadow effects around a view's frame. A shadow is described
|
||||
// by X and Y offsets relative to the element, blur and spread radius, and color.
|
||||
|
|
@ -425,7 +421,7 @@ const (
|
|||
// This is an inherited property, i.e. if it is not defined, then the value of the parent view is used.
|
||||
VerticalTextOrientation = "vertical-text-orientation"
|
||||
|
||||
// TextOverflow is the constant for the "text-overflow" property tag.
|
||||
// TextTverflow is the constant for the "text-overflow" property tag.
|
||||
// The "text-overflow" int property sets how hidden overflow content is signaled to users.
|
||||
// It can be clipped or display an ellipsis ('…'). Valid values are
|
||||
TextOverflow = "text-overflow"
|
||||
|
|
@ -542,7 +538,7 @@ const (
|
|||
|
||||
// AvoidBreak is the constant for the "avoid-break" property tag.
|
||||
// The "avoid-break" bool property sets how region breaks should behave inside a generated box.
|
||||
// If the property value is "true" then avoids any break from being inserted within the principal box.
|
||||
// If the property value is "true" then fvoids any break from being inserted within the principal box.
|
||||
// If the property value is "false" then allows, but does not force, any break to be inserted within
|
||||
// the principal box.
|
||||
AvoidBreak = "avoid-break"
|
||||
|
|
@ -660,7 +656,7 @@ const (
|
|||
// allowing text and inline Views to wrap around it.
|
||||
Float = "float"
|
||||
|
||||
// UserData is the constant for the "user-data" property tag.
|
||||
// UsetData is the constant for the "user-data" property tag.
|
||||
// The "user-data" property can contain any user data
|
||||
UserData = "user-data"
|
||||
|
||||
|
|
@ -673,35 +669,4 @@ const (
|
|||
// The "user-select" bool property controls whether the user can select text.
|
||||
// This is an inherited property, i.e. if it is not defined, then the value of the parent view is used.
|
||||
UserSelect = "user-select"
|
||||
|
||||
// Order is the constant for the "Order" property tag.
|
||||
// The "Order" int property sets the order to layout an item in a ListLayout or GridLayout container.
|
||||
// Items in a container are sorted by ascending order value and then by their source code order.
|
||||
Order = "Order"
|
||||
|
||||
// BackgroundBlendMode is the constant for the "background-blend-mode" property tag.
|
||||
// The "background-blend-mode" int property sets how an view's background images should blend
|
||||
// with each other and with the view's background color.
|
||||
// Valid values are "normal" (0), "multiply" (1), "screen" (2), "overlay" (3), "darken" (4), "lighten" (5),
|
||||
// "color-dodge" (6), "color-burn" (7), "hard-light" (8), "soft-light" (9), "difference" (10),
|
||||
// "exclusion" (11), "hue" (12), "saturation" (13), "color" (14), "luminosity" (15).
|
||||
BackgroundBlendMode = "background-blend-mode"
|
||||
|
||||
// MixBlendMode is the constant for the "mix-blend-mode" property tag.
|
||||
// The "mix-blend-mode" int property sets how a view's content should blend
|
||||
// with the content of the view's parent and the view's background.
|
||||
// Valid values are "normal" (0), "multiply" (1), "screen" (2), "overlay" (3), "darken" (4), "lighten" (5),
|
||||
// "color-dodge" (6), "color-burn" (7), "hard-light" (8), "soft-light" (9), "difference" (10),
|
||||
// "exclusion" (11), "hue" (12), "saturation" (13), "color" (14), "luminosity" (15).
|
||||
MixBlendMode = "mix-blend-mode"
|
||||
|
||||
// TabIndex is the constant for the "tabindex" property tag.
|
||||
// The "tabindex" int property indicates that View can be focused, and where it participates in sequential keyboard navigation
|
||||
// (usually with the Tab key, hence the name).
|
||||
// * A negative value means that View is not reachable via sequential keyboard navigation, but could be focused by clicking with the mouse or touching.
|
||||
// * tabindex="0" means that View should be focusable in sequential keyboard navigation, after any positive tabindex values and its order is defined in order of its addition.
|
||||
// * A positive value means View should be focusable in sequential keyboard navigation, with its order defined by the value of the number.
|
||||
TabIndex = "tabindex"
|
||||
|
||||
Tooltip = "tooltip"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ var boolProperties = []string{
|
|||
TabCloseButton,
|
||||
Repeating,
|
||||
UserSelect,
|
||||
ColumnSpanAll,
|
||||
}
|
||||
|
||||
var intProperties = []string{
|
||||
|
|
@ -74,8 +73,6 @@ var intProperties = []string{
|
|||
RowSpan,
|
||||
ColumnSpan,
|
||||
ColumnCount,
|
||||
Order,
|
||||
TabIndex,
|
||||
}
|
||||
|
||||
var floatProperties = map[string]struct{ min, max float64 }{
|
||||
|
|
@ -136,7 +133,6 @@ var sizeProperties = map[string]string{
|
|||
BorderTopWidth: BorderTopWidth,
|
||||
BorderBottomWidth: BorderBottomWidth,
|
||||
OutlineWidth: OutlineWidth,
|
||||
OutlineOffset: OutlineOffset,
|
||||
XOffset: XOffset,
|
||||
YOffset: YOffset,
|
||||
BlurRadius: BlurRadius,
|
||||
|
|
@ -451,21 +447,6 @@ var enumProperties = map[string]struct {
|
|||
"",
|
||||
[]string{"none", "top", "right", "bottom", "left"},
|
||||
},
|
||||
MixBlendMode: {
|
||||
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
|
||||
MixBlendMode,
|
||||
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
|
||||
},
|
||||
BackgroundBlendMode: {
|
||||
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
|
||||
BackgroundBlendMode,
|
||||
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
|
||||
},
|
||||
ColumnFill: {
|
||||
[]string{"balance", "auto"},
|
||||
ColumnFill,
|
||||
[]string{"balance", "auto"},
|
||||
},
|
||||
}
|
||||
|
||||
func notCompatibleType(tag string, value any) {
|
||||
|
|
|
|||
|
|
@ -307,92 +307,4 @@ const (
|
|||
// "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later.
|
||||
// This may cause views to appear out-of-order, when doing so would fill in holes left by larger views.
|
||||
ColumnDenseAutoFlow = 3
|
||||
|
||||
// BlendNormal - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color is the top color, regardless of what the bottom color is.
|
||||
// The effect is like two opaque pieces of paper overlapping.
|
||||
BlendNormal = 0
|
||||
|
||||
// BlendMultiply - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color is the result of multiplying the top and bottom colors.
|
||||
// A black layer leads to a black final layer, and a white layer leads to no change.
|
||||
// The effect is like two images printed on transparent film overlapping.
|
||||
BlendMultiply = 1
|
||||
|
||||
// BlendScreen - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color is the result of inverting the colors, multiplying them, and inverting that value.
|
||||
// A black layer leads to no change, and a white layer leads to a white final layer.
|
||||
// The effect is like two images shone onto a projection screen.
|
||||
BlendScreen = 2
|
||||
|
||||
// BlendOverlay - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color is the result of multiply if the bottom color is darker, or screen if the bottom color is lighter.
|
||||
// This blend mode is equivalent to hard-light but with the layers swapped.
|
||||
BlendOverlay = 3
|
||||
|
||||
// BlendDarken - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color is composed of the darkest values of each color channel.
|
||||
BlendDarken = 4
|
||||
|
||||
// BlendLighten - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color is composed of the lightest values of each color channel.
|
||||
BlendLighten = 5
|
||||
|
||||
// BlendColorDodge - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color is the result of dividing the bottom color by the inverse of the top color.
|
||||
// A black foreground leads to no change. A foreground with the inverse color of the backdrop leads to a fully lit color.
|
||||
// This blend mode is similar to screen, but the foreground need only be as light as the inverse of the backdrop to create a fully lit color.
|
||||
BlendColorDodge = 6
|
||||
|
||||
// BlendColorBurn - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color is the result of inverting the bottom color, dividing the value by the top color, and inverting that value.
|
||||
// A white foreground leads to no change. A foreground with the inverse color of the backdrop leads to a black final image.
|
||||
// This blend mode is similar to multiply, but the foreground need only be as dark as the inverse of the backdrop to make the final image black.
|
||||
BlendColorBurn = 7
|
||||
|
||||
// BlendHardLight - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color is the result of multiply if the top color is darker, or screen if the top color is lighter.
|
||||
// This blend mode is equivalent to overlay but with the layers swapped. The effect is similar to shining a harsh spotlight on the backdrop.
|
||||
BlendHardLight = 8
|
||||
|
||||
// BlendSoftLight - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color is similar to hard-light, but softer. This blend mode behaves similar to hard-light.
|
||||
// The effect is similar to shining a diffused spotlight on the backdrop*.*
|
||||
BlendSoftLight = 9
|
||||
|
||||
// BlendDifference - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color is the result of subtracting the darker of the two colors from the lighter one.
|
||||
// A black layer has no effect, while a white layer inverts the other layer's color.
|
||||
BlendDifference = 10
|
||||
|
||||
// BlendExclusion - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color is similar to difference, but with less contrast.
|
||||
// As with difference, a black layer has no effect, while a white layer inverts the other layer's color.
|
||||
BlendExclusion = 11
|
||||
|
||||
// BlendHue - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color has the hue of the top color, while using the saturation and luminosity of the bottom color.
|
||||
BlendHue = 12
|
||||
|
||||
// BlendSaturation - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color has the saturation of the top color, while using the hue and luminosity of the bottom color.
|
||||
// A pure gray backdrop, having no saturation, will have no effect.
|
||||
BlendSaturation = 13
|
||||
|
||||
// BlendColor - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color has the hue and saturation of the top color, while using the luminosity of the bottom color.
|
||||
// The effect preserves gray levels and can be used to colorize the foreground.
|
||||
BlendColor = 14
|
||||
|
||||
// BlendLuminosity - value of the "mix-blend-mode" and "background-blend-mode" property:
|
||||
// The final color has the luminosity of the top color, while using the hue and saturation of the bottom color.
|
||||
// This blend mode is equivalent to BlendColor, but with the layers swapped.
|
||||
BlendLuminosity = 15
|
||||
|
||||
// ColumnFillBalance - value of the "column-fill" property: content is equally divided between columns.
|
||||
ColumnFillBalance = 0
|
||||
|
||||
// ColumnFillAuto - value of the "column-fill" property:
|
||||
// Columns are filled sequentially. Content takes up only the room it needs, possibly resulting in some columns remaining empty.
|
||||
ColumnFillAuto = 1
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const (
|
|||
// Resizable - grid-container of View
|
||||
type Resizable interface {
|
||||
View
|
||||
ParentView
|
||||
ParanetView
|
||||
}
|
||||
|
||||
type resizableData struct {
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ func scanEmbedImagesDir(fs *embed.FS, dir, prefix string) {
|
|||
} else {
|
||||
ext := strings.ToLower(filepath.Ext(name))
|
||||
switch ext {
|
||||
case ".png", ".jpg", ".jpeg", ".svg", ".gif", ".bmp", ".webp":
|
||||
case ".png", ".jpg", ".jpeg", ".svg", ".gif", ".bmp":
|
||||
registerImage(fs, path, prefix+name)
|
||||
}
|
||||
}
|
||||
|
|
@ -272,8 +272,8 @@ func serveResourceFile(filename string, w http.ResponseWriter, r *http.Request)
|
|||
if serveEmbed(fs, dir+"/"+filename) {
|
||||
return true
|
||||
}
|
||||
if subDirs, err := fs.ReadDir(dir); err == nil {
|
||||
for _, subdir := range subDirs {
|
||||
if subdirs, err := fs.ReadDir(dir); err == nil {
|
||||
for _, subdir := range subdirs {
|
||||
if subdir.IsDir() {
|
||||
if serveEmbed(fs, dir+"/"+subdir.Name()+"/"+filename) {
|
||||
return true
|
||||
|
|
|
|||
158
session.go
158
session.go
|
|
@ -20,13 +20,13 @@ type webBridge interface {
|
|||
writeMessage(text string) bool
|
||||
addAnimationCSS(css string)
|
||||
clearAnimation()
|
||||
canvasStart(htmlID string)
|
||||
cavnasStart(htmlID string)
|
||||
callCanvasFunc(funcName string, args ...any)
|
||||
callCanvasVarFunc(v any, funcName string, args ...any)
|
||||
callCanvasImageFunc(url string, property string, funcName string, args ...any)
|
||||
createCanvasVar(funcName string, args ...any) any
|
||||
updateCanvasProperty(property string, value any)
|
||||
canvasFinish()
|
||||
cavnasFinish()
|
||||
canvasTextMetrics(htmlID, font, text string) TextMetrics
|
||||
htmlPropertyValue(htmlID, name string) string
|
||||
answerReceived(answer DataObject)
|
||||
|
|
@ -100,17 +100,6 @@ type Session interface {
|
|||
// OpenURL opens the url in the new browser tab
|
||||
OpenURL(url string)
|
||||
|
||||
// ClientItem reads value by key from the client-side storage
|
||||
ClientItem(key string) (string, bool)
|
||||
// SetClientItem stores a key-value pair in the client-side storage
|
||||
SetClientItem(key, value string)
|
||||
// RemoveAllClientItems removes all key-value pair from the client-side storage
|
||||
RemoveAllClientItems()
|
||||
|
||||
// SetHotKey sets the function that will be called when the given hotkey is pressed.
|
||||
// Invoke SetHotKey(..., ..., nil) for remove hotkey function.
|
||||
SetHotKey(keyCode KeyCode, controlKeys ControlKeyMask, fn func(Session))
|
||||
|
||||
getCurrentTheme() Theme
|
||||
registerAnimation(props []AnimatedProperty) string
|
||||
|
||||
|
|
@ -136,13 +125,13 @@ type Session interface {
|
|||
finishUpdateScript(htmlID string)
|
||||
addAnimationCSS(css string)
|
||||
clearAnimation()
|
||||
canvasStart(htmlID string)
|
||||
cavnasStart(htmlID string)
|
||||
callCanvasFunc(funcName string, args ...any)
|
||||
createCanvasVar(funcName string, args ...any) any
|
||||
callCanvasVarFunc(v any, funcName string, args ...any)
|
||||
callCanvasImageFunc(url string, property string, funcName string, args ...any)
|
||||
updateCanvasProperty(property string, value any)
|
||||
canvasFinish()
|
||||
cavnasFinish()
|
||||
canvasTextMetrics(htmlID, font, text string) TextMetrics
|
||||
htmlPropertyValue(htmlID, name string) string
|
||||
handleAnswer(data DataObject)
|
||||
|
|
@ -194,8 +183,6 @@ type sessionData struct {
|
|||
animationCounter int
|
||||
animationCSS string
|
||||
updateScripts map[string]*strings.Builder
|
||||
clientStorage map[string]string
|
||||
hotkeys map[string]func(Session)
|
||||
}
|
||||
|
||||
func newSession(app Application, id int, customTheme string, params DataObject) Session {
|
||||
|
|
@ -212,8 +199,6 @@ func newSession(app Application, id int, customTheme string, params DataObject)
|
|||
session.animationCounter = 0
|
||||
session.animationCSS = ""
|
||||
session.updateScripts = map[string]*strings.Builder{}
|
||||
session.clientStorage = map[string]string{}
|
||||
session.hotkeys = map[string]func(Session){}
|
||||
|
||||
if customTheme != "" {
|
||||
if theme, ok := CreateThemeFromText(customTheme); ok {
|
||||
|
|
@ -305,28 +290,9 @@ func (session *sessionData) writeInitScript(writer *strings.Builder) {
|
|||
|
||||
if session.rootView != nil {
|
||||
writer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
viewHTML(session.rootView, buffer)
|
||||
text := strings.ReplaceAll(buffer.String(), "'", `\'`)
|
||||
writer.WriteString(text)
|
||||
viewHTML(session.rootView, writer)
|
||||
writer.WriteString("';\nscanElementsSize();")
|
||||
}
|
||||
|
||||
session.updateTooltipConstants()
|
||||
}
|
||||
|
||||
func (session *sessionData) updateTooltipConstants() {
|
||||
if color, ok := session.Color("ruiTooltipBackground"); ok {
|
||||
session.bridge.callFunc("setCssVar", "--tooltip-background", color.cssString())
|
||||
}
|
||||
if color, ok := session.Color("ruiTooltipTextColor"); ok {
|
||||
session.bridge.callFunc("setCssVar", "--tooltip-text-color", color.cssString())
|
||||
}
|
||||
if color, ok := session.Color("ruiTooltipShadowColor"); ok {
|
||||
session.bridge.callFunc("setCssVar", "--tooltip-shadow-color", color.cssString())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (session *sessionData) reload() {
|
||||
|
|
@ -347,7 +313,6 @@ func (session *sessionData) reload() {
|
|||
}
|
||||
|
||||
session.bridge.writeMessage(buffer.String())
|
||||
session.updateTooltipConstants()
|
||||
}
|
||||
|
||||
func (session *sessionData) ignoreViewUpdates() bool {
|
||||
|
|
@ -459,9 +424,9 @@ func (session *sessionData) clearAnimation() {
|
|||
}
|
||||
}
|
||||
|
||||
func (session *sessionData) canvasStart(htmlID string) {
|
||||
func (session *sessionData) cavnasStart(htmlID string) {
|
||||
if session.bridge != nil {
|
||||
session.bridge.canvasStart(htmlID)
|
||||
session.bridge.cavnasStart(htmlID)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -496,9 +461,9 @@ func (session *sessionData) callCanvasImageFunc(url string, property string, fun
|
|||
}
|
||||
}
|
||||
|
||||
func (session *sessionData) canvasFinish() {
|
||||
func (session *sessionData) cavnasFinish() {
|
||||
if session.bridge != nil {
|
||||
session.bridge.canvasFinish()
|
||||
session.bridge.cavnasFinish()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -547,7 +512,7 @@ func (session *sessionData) handleRootSize(data DataObject) {
|
|||
}
|
||||
|
||||
func (session *sessionData) handleResize(data DataObject) {
|
||||
if node := data.PropertyByTag("views"); node != nil && node.Type() == ArrayNode {
|
||||
if node := data.PropertyWithTag("views"); node != nil && node.Type() == ArrayNode {
|
||||
for _, el := range node.ArrayElements() {
|
||||
if el.IsObject() {
|
||||
obj := el.Object()
|
||||
|
|
@ -622,16 +587,6 @@ func (session *sessionData) handleSessionInfo(params DataObject) {
|
|||
session.pixelRatio = f
|
||||
}
|
||||
}
|
||||
|
||||
if node := params.PropertyByTag("storage"); node != nil && node.Type() == ObjectNode {
|
||||
if obj := node.Object(); obj != nil {
|
||||
for i := 0; i < obj.PropertyCount(); i++ {
|
||||
if element := obj.Property(i); element.Type() == TextNode {
|
||||
session.clientStorage[element.Tag()] = element.Text()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (session *sessionData) handleEvent(command string, data DataObject) {
|
||||
|
|
@ -651,89 +606,17 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
|
|||
case "sessionInfo":
|
||||
session.handleSessionInfo(data)
|
||||
|
||||
case "storageError":
|
||||
if text, ok := data.PropertyValue("error"); ok {
|
||||
ErrorLog(text)
|
||||
}
|
||||
|
||||
default:
|
||||
if viewID, ok := data.PropertyValue("id"); ok {
|
||||
if viewID != "body" {
|
||||
if view := session.viewByHTMLID(viewID); view != nil {
|
||||
view.handleCommand(view, command, data)
|
||||
}
|
||||
if view := session.viewByHTMLID(viewID); view != nil {
|
||||
view.handleCommand(view, command, data)
|
||||
}
|
||||
if command == KeyDownEvent {
|
||||
var event KeyEvent
|
||||
event.init(data)
|
||||
session.hotKey(event)
|
||||
}
|
||||
} else {
|
||||
} else if command != "clickOutsidePopup" {
|
||||
ErrorLog(`"id" property not found. Event: ` + command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (session *sessionData) hotKey(event KeyEvent) {
|
||||
popups := session.popupManager().popups
|
||||
if count := len(popups); count > 0 {
|
||||
if popups[count-1].keyEvent(event) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var controlKeys ControlKeyMask = 0
|
||||
if event.AltKey {
|
||||
controlKeys |= AltKey
|
||||
}
|
||||
if event.CtrlKey {
|
||||
controlKeys |= CtrlKey
|
||||
}
|
||||
if event.MetaKey {
|
||||
controlKeys |= MetaKey
|
||||
}
|
||||
if event.ShiftKey {
|
||||
controlKeys |= ShiftKey
|
||||
}
|
||||
|
||||
key := hotkeyCode(KeyCode(event.Code), controlKeys)
|
||||
if fn, ok := session.hotkeys[key]; ok && fn != nil {
|
||||
fn(session)
|
||||
}
|
||||
}
|
||||
|
||||
func hotkeyCode(keyCode KeyCode, controlKeys ControlKeyMask) string {
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
buffer.WriteString(strings.ToLower(string(keyCode)))
|
||||
if controlKeys != 0 {
|
||||
buffer.WriteRune('-')
|
||||
if controlKeys&AltKey != 0 {
|
||||
buffer.WriteRune('a')
|
||||
}
|
||||
if controlKeys&CtrlKey != 0 {
|
||||
buffer.WriteRune('c')
|
||||
}
|
||||
if controlKeys&MetaKey != 0 {
|
||||
buffer.WriteRune('m')
|
||||
}
|
||||
if controlKeys&ShiftKey != 0 {
|
||||
buffer.WriteRune('s')
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (session *sessionData) SetHotKey(keyCode KeyCode, controlKeys ControlKeyMask, fn func(Session)) {
|
||||
hotkey := hotkeyCode(keyCode, controlKeys)
|
||||
if fn == nil {
|
||||
delete(session.hotkeys, hotkey)
|
||||
} else {
|
||||
session.hotkeys[hotkey] = fn
|
||||
}
|
||||
}
|
||||
|
||||
func (session *sessionData) SetTitle(title string) {
|
||||
title, _ = session.GetString(title)
|
||||
session.callFunc("setTitle", title)
|
||||
|
|
@ -754,18 +637,3 @@ func (session *sessionData) OpenURL(urlStr string) {
|
|||
}
|
||||
session.callFunc("openURL", urlStr)
|
||||
}
|
||||
|
||||
func (session *sessionData) ClientItem(key string) (string, bool) {
|
||||
value, ok := session.clientStorage[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (session *sessionData) SetClientItem(key, value string) {
|
||||
session.clientStorage[key] = value
|
||||
session.bridge.callFunc("localStorageSet", key, value)
|
||||
}
|
||||
|
||||
func (session *sessionData) RemoveAllClientItems() {
|
||||
session.clientStorage = map[string]string{}
|
||||
session.bridge.callFunc("localStorageClear")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ func updateInnerHTML(htmlID string, session Session) {
|
|||
view = session.viewByHTMLID(htmlID)
|
||||
}
|
||||
if view != nil {
|
||||
session.callFunc("hideTooltip")
|
||||
|
||||
script := allocStringBuilder()
|
||||
defer freeStringBuilder(script)
|
||||
|
||||
|
|
@ -44,7 +42,7 @@ func viewByHTMLID(id string, startView View) View {
|
|||
if startView.htmlID() == id {
|
||||
return startView
|
||||
}
|
||||
if container, ok := startView.(ParentView); ok {
|
||||
if container, ok := startView.(ParanetView); ok {
|
||||
for _, view := range container.Views() {
|
||||
if view != nil {
|
||||
if v := viewByHTMLID(id, view); v != nil {
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ func (data *sizeFuncData) parseArgs(args []any, allowNumber bool) bool {
|
|||
}
|
||||
}
|
||||
|
||||
ErrorLogF(`The %s function argument can't be a number`, data.tag)
|
||||
ErrorLogF(`The %s function argument cann't be a number`, data.tag)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
|||
161
svgImageView.go
161
svgImageView.go
|
|
@ -1,161 +0,0 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SvgImageView - image View
|
||||
type SvgImageView interface {
|
||||
View
|
||||
}
|
||||
|
||||
type svgImageViewData struct {
|
||||
viewData
|
||||
}
|
||||
|
||||
// NewSvgImageView create new SvgImageView object and return it
|
||||
func NewSvgImageView(session Session, params Params) SvgImageView {
|
||||
view := new(svgImageViewData)
|
||||
view.init(session)
|
||||
setInitParams(view, params)
|
||||
return view
|
||||
}
|
||||
|
||||
func newSvgImageView(session Session) View {
|
||||
return NewSvgImageView(session, nil)
|
||||
}
|
||||
|
||||
// Init initialize fields of imageView by default values
|
||||
func (imageView *svgImageViewData) init(session Session) {
|
||||
imageView.viewData.init(session)
|
||||
imageView.tag = "SvgImageView"
|
||||
imageView.systemClass = "ruiSvgImageView"
|
||||
}
|
||||
|
||||
func (imageView *svgImageViewData) String() string {
|
||||
return getViewString(imageView)
|
||||
}
|
||||
|
||||
func (imageView *svgImageViewData) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
case Source, "source":
|
||||
tag = Content
|
||||
|
||||
case VerticalAlign:
|
||||
tag = CellVerticalAlign
|
||||
|
||||
case HorizontalAlign:
|
||||
tag = CellHorizontalAlign
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func (imageView *svgImageViewData) Remove(tag string) {
|
||||
imageView.remove(imageView.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (imageView *svgImageViewData) remove(tag string) {
|
||||
imageView.viewData.remove(tag)
|
||||
|
||||
if imageView.created {
|
||||
switch tag {
|
||||
case Content:
|
||||
updateInnerHTML(imageView.htmlID(), imageView.session)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (imageView *svgImageViewData) Set(tag string, value any) bool {
|
||||
return imageView.set(imageView.normalizeTag(tag), value)
|
||||
}
|
||||
|
||||
func (imageView *svgImageViewData) set(tag string, value any) bool {
|
||||
if value == nil {
|
||||
imageView.remove(tag)
|
||||
return true
|
||||
}
|
||||
|
||||
switch tag {
|
||||
case Content:
|
||||
if text, ok := value.(string); ok {
|
||||
imageView.properties[Content] = text
|
||||
if imageView.created {
|
||||
updateInnerHTML(imageView.htmlID(), imageView.session)
|
||||
}
|
||||
imageView.propertyChangedEvent(Content)
|
||||
return true
|
||||
}
|
||||
notCompatibleType(Source, value)
|
||||
return false
|
||||
|
||||
default:
|
||||
return imageView.viewData.set(tag, value)
|
||||
}
|
||||
}
|
||||
|
||||
func (imageView *svgImageViewData) Get(tag string) any {
|
||||
return imageView.viewData.get(imageView.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (imageView *svgImageViewData) htmlTag() string {
|
||||
return "div"
|
||||
}
|
||||
|
||||
func (imageView *svgImageViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||
if value := imageView.getRaw(Content); value != nil {
|
||||
if content, ok := value.(string); ok && content != "" {
|
||||
if strings.HasPrefix(content, "@") {
|
||||
if name, ok := imageView.session.ImageConstant(content[1:]); ok {
|
||||
content = name
|
||||
}
|
||||
}
|
||||
|
||||
if image, ok := resources.images[content]; ok {
|
||||
if image.fs != nil {
|
||||
if data, err := image.fs.ReadFile(image.path); err == nil {
|
||||
buffer.WriteString(string(data))
|
||||
return
|
||||
} else {
|
||||
DebugLog(err.Error())
|
||||
}
|
||||
} else if data, err := os.ReadFile(image.path); err == nil {
|
||||
buffer.WriteString(string(data))
|
||||
return
|
||||
} else {
|
||||
DebugLog(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(content, "http://") || strings.HasPrefix(content, "https://") {
|
||||
resp, err := http.Get(content)
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
if body, err := io.ReadAll(resp.Body); err == nil {
|
||||
buffer.WriteString(string(body))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
DebugLog(err.Error())
|
||||
}
|
||||
|
||||
buffer.WriteString(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetSvgImageViewVerticalAlign return the vertical align of an SvgImageView subview: TopAlign (0), BottomAlign (1), CenterAlign (2)
|
||||
// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned
|
||||
func GetSvgImageViewVerticalAlign(view View, subviewID ...string) int {
|
||||
return enumStyledProperty(view, subviewID, CellVerticalAlign, LeftAlign, false)
|
||||
}
|
||||
|
||||
// GetSvgImageViewHorizontalAlign return the vertical align of an SvgImageView subview: LeftAlign (0), RightAlign (1), CenterAlign (2)
|
||||
// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned
|
||||
func GetSvgImageViewHorizontalAlign(view View, subviewID ...string) int {
|
||||
return enumStyledProperty(view, subviewID, CellHorizontalAlign, LeftAlign, false)
|
||||
}
|
||||
130
tableAdapter.go
130
tableAdapter.go
|
|
@ -228,21 +228,11 @@ func (adapter *textTableAdapter) Cell(row, column int) any {
|
|||
return nil
|
||||
}
|
||||
|
||||
type simpleTableLineStyle struct {
|
||||
type simpleTableRowStyle struct {
|
||||
params []Params
|
||||
}
|
||||
|
||||
func (style *simpleTableLineStyle) ColumnStyle(column int) Params {
|
||||
if column < len(style.params) {
|
||||
params := style.params[column]
|
||||
if len(params) > 0 {
|
||||
return params
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (style *simpleTableLineStyle) RowStyle(row int) Params {
|
||||
func (style *simpleTableRowStyle) RowStyle(row int) Params {
|
||||
if row < len(style.params) {
|
||||
params := style.params[row]
|
||||
if len(params) > 0 {
|
||||
|
|
@ -252,24 +242,50 @@ func (style *simpleTableLineStyle) RowStyle(row int) Params {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (table *tableViewData) setLineStyle(tag string, value any) bool {
|
||||
func (table *tableViewData) setRowStyle(value any) bool {
|
||||
newSimpleTableRowStyle := func(params []Params) TableRowStyle {
|
||||
if len(params) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := new(simpleTableRowStyle)
|
||||
result.params = params
|
||||
return result
|
||||
}
|
||||
|
||||
switch value := value.(type) {
|
||||
case TableRowStyle:
|
||||
table.properties[RowStyle] = value
|
||||
|
||||
case []Params:
|
||||
if len(value) > 0 {
|
||||
style := new(simpleTableLineStyle)
|
||||
style.params = value
|
||||
table.properties[tag] = style
|
||||
if style := newSimpleTableRowStyle(value); style != nil {
|
||||
table.properties[RowStyle] = style
|
||||
} else {
|
||||
delete(table.properties, tag)
|
||||
delete(table.properties, RowStyle)
|
||||
}
|
||||
|
||||
case DataNode:
|
||||
if params := value.ArrayAsParams(); len(params) > 0 {
|
||||
style := new(simpleTableLineStyle)
|
||||
style.params = params
|
||||
table.properties[tag] = style
|
||||
if value.Type() == ArrayNode {
|
||||
params := make([]Params, value.ArraySize())
|
||||
for i, element := range value.ArrayElements() {
|
||||
params[i] = Params{}
|
||||
if element.IsObject() {
|
||||
obj := element.Object()
|
||||
for k := 0; k < obj.PropertyCount(); k++ {
|
||||
if prop := obj.Property(k); prop != nil && prop.Type() == TextNode {
|
||||
params[i][prop.Tag()] = prop.Text()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
params[i][Style] = element.Value()
|
||||
}
|
||||
}
|
||||
if style := newSimpleTableRowStyle(params); style != nil {
|
||||
table.properties[RowStyle] = style
|
||||
} else {
|
||||
delete(table.properties, RowStyle)
|
||||
}
|
||||
} else {
|
||||
delete(table.properties, tag)
|
||||
return false
|
||||
}
|
||||
|
||||
default:
|
||||
|
|
@ -278,14 +294,6 @@ func (table *tableViewData) setLineStyle(tag string, value any) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (table *tableViewData) setRowStyle(value any) bool {
|
||||
switch value := value.(type) {
|
||||
case TableRowStyle:
|
||||
table.properties[RowStyle] = value
|
||||
}
|
||||
return table.setLineStyle(RowStyle, value)
|
||||
}
|
||||
|
||||
func (table *tableViewData) getRowStyle() TableRowStyle {
|
||||
for _, tag := range []string{RowStyle, Content} {
|
||||
if value := table.getRaw(tag); value != nil {
|
||||
|
|
@ -297,12 +305,70 @@ func (table *tableViewData) getRowStyle() TableRowStyle {
|
|||
return nil
|
||||
}
|
||||
|
||||
type simpleTableColumnStyle struct {
|
||||
params []Params
|
||||
}
|
||||
|
||||
func (style *simpleTableColumnStyle) ColumnStyle(row int) Params {
|
||||
if row < len(style.params) {
|
||||
params := style.params[row]
|
||||
if len(params) > 0 {
|
||||
return params
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (table *tableViewData) setColumnStyle(value any) bool {
|
||||
newSimpleTableColumnStyle := func(params []Params) TableColumnStyle {
|
||||
if len(params) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := new(simpleTableColumnStyle)
|
||||
result.params = params
|
||||
return result
|
||||
}
|
||||
|
||||
switch value := value.(type) {
|
||||
case TableColumnStyle:
|
||||
table.properties[ColumnStyle] = value
|
||||
|
||||
case []Params:
|
||||
if style := newSimpleTableColumnStyle(value); style != nil {
|
||||
table.properties[ColumnStyle] = style
|
||||
} else {
|
||||
delete(table.properties, ColumnStyle)
|
||||
}
|
||||
|
||||
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 table.setLineStyle(ColumnStyle, value)
|
||||
return true
|
||||
}
|
||||
|
||||
func (table *tableViewData) getColumnStyle() TableColumnStyle {
|
||||
|
|
|
|||
216
tableView.go
216
tableView.go
|
|
@ -218,9 +218,8 @@ type CellIndex struct {
|
|||
// TableView - text View
|
||||
type TableView interface {
|
||||
View
|
||||
ParentView
|
||||
ParanetView
|
||||
ReloadTableData()
|
||||
ReloadCell(row, column int)
|
||||
CellFrame(row, column int) Frame
|
||||
|
||||
content() TableAdapter
|
||||
|
|
@ -525,6 +524,10 @@ func (table *tableViewData) set(tag string, value any) bool {
|
|||
|
||||
case Current:
|
||||
switch value := value.(type) {
|
||||
case int:
|
||||
table.current.Row = value
|
||||
table.current.Column = -1
|
||||
|
||||
case CellIndex:
|
||||
table.current = value
|
||||
|
||||
|
|
@ -551,7 +554,6 @@ func (table *tableViewData) set(tag string, value any) bool {
|
|||
table.current.Column = n[1]
|
||||
} else {
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
n, err := strconv.Atoi(value)
|
||||
|
|
@ -564,13 +566,8 @@ func (table *tableViewData) set(tag string, value any) bool {
|
|||
}
|
||||
|
||||
default:
|
||||
if n, ok := isInt(value); ok {
|
||||
table.current.Row = n
|
||||
table.current.Column = -1
|
||||
} else {
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
}
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
}
|
||||
|
||||
default:
|
||||
|
|
@ -588,18 +585,9 @@ func (table *tableViewData) propertyChanged(tag string) {
|
|||
CellBorder, HeadHeight, HeadStyle, FootHeight, FootStyle,
|
||||
CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft,
|
||||
TableCellClickedEvent, TableCellSelectedEvent, TableRowClickedEvent,
|
||||
TableRowSelectedEvent, AllowSelection:
|
||||
TableRowSelectedEvent, AllowSelection, Current:
|
||||
table.ReloadTableData()
|
||||
|
||||
case Current:
|
||||
switch GetTableSelectionMode(table) {
|
||||
case CellSelection:
|
||||
table.session.callFunc("setTableCellCursorByID", table.htmlID(), table.current.Row, table.current.Column)
|
||||
|
||||
case RowSelection:
|
||||
table.session.callFunc("setTableRowCursorByID", table.htmlID(), table.current.Row)
|
||||
}
|
||||
|
||||
case Gap:
|
||||
htmlID := table.htmlID()
|
||||
session := table.Session()
|
||||
|
|
@ -618,8 +606,7 @@ func (table *tableViewData) propertyChanged(tag string) {
|
|||
|
||||
switch GetTableSelectionMode(table) {
|
||||
case CellSelection:
|
||||
tabIndex, _ := intProperty(table, TabIndex, session, 0)
|
||||
session.updateProperty(htmlID, "tabindex", tabIndex)
|
||||
session.updateProperty(htmlID, "tabindex", "0")
|
||||
session.updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)")
|
||||
session.updateProperty(htmlID, "onblur", "tableViewBlurEvent(this, event)")
|
||||
session.updateProperty(htmlID, "data-selection", "cell")
|
||||
|
|
@ -634,8 +621,7 @@ func (table *tableViewData) propertyChanged(tag string) {
|
|||
session.updateProperty(htmlID, "onkeydown", "tableViewCellKeyDownEvent(this, event)")
|
||||
|
||||
case RowSelection:
|
||||
tabIndex, _ := intProperty(table, TabIndex, session, 0)
|
||||
session.updateProperty(htmlID, "tabindex", tabIndex)
|
||||
session.updateProperty(htmlID, "tabindex", "0")
|
||||
session.updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)")
|
||||
session.updateProperty(htmlID, "onblur", "tableViewBlurEvent(this, event)")
|
||||
session.updateProperty(htmlID, "data-selection", "row")
|
||||
|
|
@ -650,11 +636,7 @@ func (table *tableViewData) propertyChanged(tag string) {
|
|||
session.updateProperty(htmlID, "onkeydown", "tableViewRowKeyDownEvent(this, event)")
|
||||
|
||||
default: // NoneSelection
|
||||
if tabIndex, ok := intProperty(table, TabIndex, session, -1); !ok || tabIndex < 0 {
|
||||
session.removeProperty(htmlID, "tabindex")
|
||||
}
|
||||
|
||||
for _, prop := range []string{"data-current", "onfocus", "onblur", "onkeydown", "data-selection"} {
|
||||
for _, prop := range []string{"tabindex", "data-current", "onfocus", "onblur", "onkeydown", "data-selection"} {
|
||||
session.removeProperty(htmlID, prop)
|
||||
}
|
||||
}
|
||||
|
|
@ -849,7 +831,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
|||
var view tableCellView
|
||||
view.init(session)
|
||||
|
||||
ignoreCells := []struct{ row, column int }{}
|
||||
ignorCells := []struct{ row, column int }{}
|
||||
selectionMode := GetTableSelectionMode(table)
|
||||
|
||||
var allowCellSelection TableAllowCellSelection = nil
|
||||
|
|
@ -881,7 +863,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
|||
vAlign := vAlignCss[vAlignValue]
|
||||
|
||||
tableCSS := func(startRow, endRow int, cellTag string, cellBorder BorderProperty, cellPadding BoundsProperty) {
|
||||
//var namedColors []NamedColor = nil
|
||||
var namedColors []NamedColor = nil
|
||||
|
||||
for row := startRow; row < endRow; row++ {
|
||||
cssBuilder.buffer.Reset()
|
||||
|
|
@ -926,7 +908,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
|||
|
||||
for column := 0; column < columnCount; column++ {
|
||||
ignore := false
|
||||
for _, cell := range ignoreCells {
|
||||
for _, cell := range ignorCells {
|
||||
if cell.row == row && cell.column == column {
|
||||
ignore = true
|
||||
break
|
||||
|
|
@ -1012,7 +994,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
|||
buffer.WriteString(strconv.Itoa(columnSpan))
|
||||
buffer.WriteRune('"')
|
||||
for c := column + 1; c < column+columnSpan; c++ {
|
||||
ignoreCells = append(ignoreCells, struct {
|
||||
ignorCells = append(ignorCells, struct {
|
||||
row int
|
||||
column int
|
||||
}{row: row, column: c})
|
||||
|
|
@ -1028,7 +1010,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
|||
}
|
||||
for r := row + 1; r < row+rowSpan; r++ {
|
||||
for c := column; c < column+columnSpan; c++ {
|
||||
ignoreCells = append(ignoreCells, struct {
|
||||
ignorCells = append(ignorCells, struct {
|
||||
row int
|
||||
column int
|
||||
}{row: r, column: c})
|
||||
|
|
@ -1043,60 +1025,57 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
|||
}
|
||||
buffer.WriteRune('>')
|
||||
|
||||
table.writeCellHtml(adapter, row, column, buffer)
|
||||
/*
|
||||
switch value := adapter.Cell(row, column).(type) {
|
||||
case string:
|
||||
buffer.WriteString(value)
|
||||
switch value := adapter.Cell(row, column).(type) {
|
||||
case string:
|
||||
buffer.WriteString(value)
|
||||
|
||||
case View:
|
||||
viewHTML(value, buffer)
|
||||
table.cellViews = append(table.cellViews, 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())
|
||||
if namedColors == nil {
|
||||
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(session.checkboxOnImage())
|
||||
} else {
|
||||
buffer.WriteString(session.checkboxOffImage())
|
||||
}
|
||||
|
||||
default:
|
||||
if n, ok := isInt(value); ok {
|
||||
buffer.WriteString(fmt.Sprintf("%d", n))
|
||||
} else {
|
||||
buffer.WriteString("<Unsupported value>")
|
||||
case Color:
|
||||
buffer.WriteString(`<div style="display: inline; height: 1em; background-color: `)
|
||||
buffer.WriteString(value.cssString())
|
||||
buffer.WriteString(`"> </div> `)
|
||||
buffer.WriteString(value.String())
|
||||
if namedColors == nil {
|
||||
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(session.checkboxOnImage())
|
||||
} else {
|
||||
buffer.WriteString(session.checkboxOffImage())
|
||||
}
|
||||
|
||||
default:
|
||||
if n, ok := isInt(value); ok {
|
||||
buffer.WriteString(fmt.Sprintf("%d", n))
|
||||
} else {
|
||||
buffer.WriteString("<Unsupported value>")
|
||||
}
|
||||
}
|
||||
|
||||
buffer.WriteString(`</`)
|
||||
buffer.WriteString(cellTag)
|
||||
|
|
@ -1135,8 +1114,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
|||
footHeight := GetTableFootHeight(table)
|
||||
cellBorder := table.getCellBorder()
|
||||
cellPadding := table.boundsProperty(CellPadding)
|
||||
if cellPadding == nil || len(cellPadding.AllTags()) == 0 {
|
||||
cellPadding = nil
|
||||
if cellPadding == nil {
|
||||
if style, ok := stringProperty(table, Style, table.Session()); ok {
|
||||
if style, ok := table.Session().resolveConstants(style); ok {
|
||||
cellPadding = table.cellPaddingFromStyle(style)
|
||||
|
|
@ -1312,59 +1290,6 @@ func (table *tableViewData) cellPaddingFromStyle(style string) BoundsProperty {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (table *tableViewData) writeCellHtml(adapter TableAdapter, row, column int, buffer *strings.Builder) {
|
||||
switch value := adapter.Cell(row, column).(type) {
|
||||
case string:
|
||||
buffer.WriteString(value)
|
||||
|
||||
case View:
|
||||
viewHTML(value, buffer)
|
||||
table.cellViews = append(table.cellViews, value)
|
||||
|
||||
case Color:
|
||||
buffer.WriteString(`<div style="display: inline; height: 1em; background-color: `)
|
||||
buffer.WriteString(value.cssString())
|
||||
buffer.WriteString(`"> </div> `)
|
||||
buffer.WriteString(value.String())
|
||||
|
||||
namedColors := NamedColors()
|
||||
for _, namedColor := range namedColors {
|
||||
if namedColor.Color == value {
|
||||
buffer.WriteString(" (")
|
||||
buffer.WriteString(namedColor.Name)
|
||||
buffer.WriteRune(')')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case fmt.Stringer:
|
||||
buffer.WriteString(value.String())
|
||||
|
||||
case rune:
|
||||
buffer.WriteString(string(value))
|
||||
|
||||
case float32:
|
||||
buffer.WriteString(fmt.Sprintf("%g", float64(value)))
|
||||
|
||||
case float64:
|
||||
buffer.WriteString(fmt.Sprintf("%g", value))
|
||||
|
||||
case bool:
|
||||
if value {
|
||||
buffer.WriteString(table.Session().checkboxOnImage())
|
||||
} else {
|
||||
buffer.WriteString(table.Session().checkboxOffImage())
|
||||
}
|
||||
|
||||
default:
|
||||
if n, ok := isInt(value); ok {
|
||||
buffer.WriteString(fmt.Sprintf("%d", n))
|
||||
} else {
|
||||
buffer.WriteString("<Unsupported value>")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (table *tableViewData) cellBorderFromStyle(style string) BorderProperty {
|
||||
if value := table.Session().styleProperty(style, CellBorder); value != nil {
|
||||
if border, ok := value.(BorderProperty); ok {
|
||||
|
|
@ -1452,19 +1377,6 @@ func (table *tableViewData) CellFrame(row, column int) Frame {
|
|||
return Frame{}
|
||||
}
|
||||
|
||||
func (table *tableViewData) ReloadCell(row, column int) {
|
||||
adapter := table.content()
|
||||
if adapter == nil {
|
||||
return
|
||||
}
|
||||
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
table.writeCellHtml(adapter, row, column, buffer)
|
||||
table.session.updateInnerHTML(table.cellID(row, column), buffer.String())
|
||||
}
|
||||
|
||||
func (table *tableViewData) Views() []View {
|
||||
return table.cellViews
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ func GetTableSelectionMode(view View, subviewID ...string) int {
|
|||
return enumStyledProperty(view, subviewID, SelectionMode, NoneSelection, false)
|
||||
}
|
||||
|
||||
// GetTableVerticalAlign returns a vertical align in a TableView cell. Returns one of next values:
|
||||
// GetTableVerticalAlign returns a vertical align in a TavleView cell. Returns one of next values:
|
||||
// TopAlign (0), BottomAlign (1), CenterAlign (2), and BaselineAlign (3)
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||
func GetTableVerticalAlign(view View, subviewID ...string) int {
|
||||
|
|
@ -182,7 +182,6 @@ func GetTableRowSelectedListeners(view View, subviewID ...string) []func(TableVi
|
|||
}
|
||||
|
||||
// ReloadTableViewData updates TableView
|
||||
// If the second argument (subviewID) is not specified or it is "" then updates the first argument (TableView).
|
||||
func ReloadTableViewData(view View, subviewID ...string) bool {
|
||||
var tableView TableView
|
||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
||||
|
|
@ -199,22 +198,3 @@ func ReloadTableViewData(view View, subviewID ...string) bool {
|
|||
tableView.ReloadTableData()
|
||||
return true
|
||||
}
|
||||
|
||||
// ReloadTableViewCell updates the given table cell.
|
||||
// If the last argument (subviewID) is not specified or it is "" then updates the cell of the first argument (TableView).
|
||||
func ReloadTableViewCell(row, column int, view View, subviewID ...string) bool {
|
||||
var tableView TableView
|
||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
||||
if tableView = TableViewByID(view, subviewID[0]); tableView == nil {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
var ok bool
|
||||
if tableView, ok = view.(TableView); !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
tableView.ReloadCell(row, column)
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
|
@ -566,22 +567,7 @@ func (tabsLayout *tabsLayoutData) IsListItemEnabled(index int) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (tabsLayout *tabsLayoutData) updateTitle(view View, tag string) {
|
||||
session := tabsLayout.session
|
||||
title, _ := stringProperty(view, Title, session)
|
||||
if !GetNotTranslate(tabsLayout) {
|
||||
title, _ = session.GetString(title)
|
||||
}
|
||||
session.updateInnerHTML(view.htmlID()+"-title", title)
|
||||
}
|
||||
|
||||
func (tabsLayout *tabsLayoutData) updateIcon(view View, tag string) {
|
||||
session := tabsLayout.session
|
||||
icon, _ := stringProperty(view, Icon, session)
|
||||
session.updateProperty(view.htmlID()+"-icon", "src", icon)
|
||||
}
|
||||
|
||||
func (tabsLayout *tabsLayoutData) updateTabCloseButton(view View, tag string) {
|
||||
func (tabsLayout *tabsLayoutData) updateContent(view View, tag string) {
|
||||
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
|
||||
}
|
||||
|
||||
|
|
@ -592,9 +578,9 @@ func (tabsLayout *tabsLayoutData) Append(view View) {
|
|||
}
|
||||
if view != nil {
|
||||
tabsLayout.viewsContainerData.Append(view)
|
||||
view.SetChangeListener(Title, tabsLayout.updateTitle)
|
||||
view.SetChangeListener(Icon, tabsLayout.updateIcon)
|
||||
view.SetChangeListener(TabCloseButton, tabsLayout.updateTabCloseButton)
|
||||
view.SetChangeListener(Title, tabsLayout.updateContent)
|
||||
view.SetChangeListener(Icon, tabsLayout.updateContent)
|
||||
view.SetChangeListener(TabCloseButton, tabsLayout.updateContent)
|
||||
if len(tabsLayout.views) == 1 {
|
||||
tabsLayout.properties[Current] = 0
|
||||
for _, listener := range tabsLayout.tabListener {
|
||||
|
|
@ -616,9 +602,9 @@ func (tabsLayout *tabsLayoutData) Insert(view View, index int) {
|
|||
defer tabsLayout.propertyChangedEvent(Current)
|
||||
}
|
||||
tabsLayout.viewsContainerData.Insert(view, index)
|
||||
view.SetChangeListener(Title, tabsLayout.updateTitle)
|
||||
view.SetChangeListener(Icon, tabsLayout.updateIcon)
|
||||
view.SetChangeListener(TabCloseButton, tabsLayout.updateTabCloseButton)
|
||||
view.SetChangeListener(Title, tabsLayout.updateContent)
|
||||
view.SetChangeListener(Icon, tabsLayout.updateContent)
|
||||
view.SetChangeListener(TabCloseButton, tabsLayout.updateContent)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -664,6 +650,10 @@ func (tabsLayout *tabsLayoutData) RemoveView(index int) View {
|
|||
return view
|
||||
}
|
||||
|
||||
func (tabsLayout *tabsLayoutData) currentID() string {
|
||||
return fmt.Sprintf("%s-%d", tabsLayout.htmlID(), tabsLayout.currentItem(0))
|
||||
}
|
||||
|
||||
func (tabsLayout *tabsLayoutData) htmlProperties(self View, buffer *strings.Builder) {
|
||||
tabsLayout.viewsContainerData.htmlProperties(self, buffer)
|
||||
buffer.WriteString(` data-inactiveTabStyle="`)
|
||||
|
|
@ -671,7 +661,7 @@ func (tabsLayout *tabsLayoutData) htmlProperties(self View, buffer *strings.Buil
|
|||
buffer.WriteString(`" data-activeTabStyle="`)
|
||||
buffer.WriteString(tabsLayout.activeTabStyle())
|
||||
buffer.WriteString(`" data-current="`)
|
||||
buffer.WriteString(strconv.Itoa(tabsLayout.currentItem(0)))
|
||||
buffer.WriteString(tabsLayout.currentID())
|
||||
buffer.WriteRune('"')
|
||||
}
|
||||
|
||||
|
|
@ -736,7 +726,7 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
|
|||
notTranslate := GetNotTranslate(tabsLayout)
|
||||
closeButton, _ := boolProperty(tabsLayout, TabCloseButton, tabsLayout.session)
|
||||
|
||||
var tabStyle, titleStyle string
|
||||
var tabStyle, titleDiv string
|
||||
switch location {
|
||||
case LeftTabs, RightTabs:
|
||||
tabStyle = `display: grid; grid-template-rows: auto 1fr auto; align-items: center; justify-items: center; grid-row-gap: 8px;`
|
||||
|
|
@ -750,13 +740,13 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
|
|||
|
||||
switch location {
|
||||
case LeftTabs:
|
||||
titleStyle = ` style="writing-mode: vertical-lr; transform: rotate(180deg); grid-row-start: 2; grid-row-end: 3; grid-column-start: 1; grid-column-end: 2;">`
|
||||
titleDiv = `<div style="writing-mode: vertical-lr; transform: rotate(180deg); grid-row-start: 2; grid-row-end: 3; grid-column-start: 1; grid-column-end: 2;">`
|
||||
|
||||
case RightTabs:
|
||||
titleStyle = ` style="writing-mode: vertical-lr; grid-row-start: 2; grid-row-end: 3; grid-column-start: 1; grid-column-end: 2;">`
|
||||
titleDiv = `<div style="writing-mode: vertical-lr; grid-row-start: 2; grid-row-end: 3; grid-column-start: 1; grid-column-end: 2;">`
|
||||
|
||||
default:
|
||||
titleStyle = ` style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 2; grid-column-end: 3;">`
|
||||
titleDiv = `<div style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 2; grid-column-end: 3;">`
|
||||
}
|
||||
|
||||
for n, view := range tabsLayout.views {
|
||||
|
|
@ -795,26 +785,21 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
|
|||
buffer.WriteString(`">`)
|
||||
|
||||
if icon != "" {
|
||||
buffer.WriteString(`<img id="`)
|
||||
buffer.WriteString(view.htmlID())
|
||||
switch location {
|
||||
case LeftTabs:
|
||||
buffer.WriteString(`-icon" style="grid-row-start: 3; grid-row-end: 4; grid-column-start: 1; grid-column-end: 2;" src="`)
|
||||
buffer.WriteString(`<img style="grid-row-start: 3; grid-row-end: 4; grid-column-start: 1; grid-column-end: 2;" src="`)
|
||||
|
||||
case RightTabs:
|
||||
buffer.WriteString(`-icon" style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
|
||||
buffer.WriteString(`<img style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
|
||||
|
||||
default:
|
||||
buffer.WriteString(`-icon" style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
|
||||
buffer.WriteString(`<img style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
|
||||
}
|
||||
buffer.WriteString(icon)
|
||||
buffer.WriteString(`">`)
|
||||
}
|
||||
|
||||
buffer.WriteString(`<div id="`)
|
||||
buffer.WriteString(view.htmlID())
|
||||
buffer.WriteString(`-title"`)
|
||||
buffer.WriteString(titleStyle)
|
||||
buffer.WriteString(titleDiv)
|
||||
buffer.WriteString(title)
|
||||
buffer.WriteString(`</div>`)
|
||||
|
||||
|
|
@ -831,7 +816,7 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
|
|||
buffer.WriteString(tabsLayoutID)
|
||||
buffer.WriteString(`', `)
|
||||
buffer.WriteString(strconv.Itoa(n))
|
||||
buffer.WriteString(`, event)" style="display: grid; `)
|
||||
buffer.WriteString(`, event)" style="display: grid; `)
|
||||
|
||||
switch location {
|
||||
case LeftTabs:
|
||||
|
|
|
|||
337
theme.go
337
theme.go
|
|
@ -13,17 +13,11 @@ const (
|
|||
LandscapeMedia = 2
|
||||
)
|
||||
|
||||
type MediaStyleParams struct {
|
||||
Orientation int
|
||||
MinWidth int
|
||||
MaxWidth int
|
||||
MinHeight int
|
||||
MaxHeight int
|
||||
}
|
||||
|
||||
type mediaStyle struct {
|
||||
MediaStyleParams
|
||||
styles map[string]ViewStyle
|
||||
orientation int
|
||||
maxWidth int
|
||||
maxHeight int
|
||||
styles map[string]ViewStyle
|
||||
}
|
||||
|
||||
type theme struct {
|
||||
|
|
@ -55,13 +49,12 @@ type Theme interface {
|
|||
ImageConstantTags() []string
|
||||
Style(tag string) ViewStyle
|
||||
SetStyle(tag string, style ViewStyle)
|
||||
RemoveStyle(tag string)
|
||||
MediaStyle(tag string, params MediaStyleParams) ViewStyle
|
||||
SetMediaStyle(tag string, params MediaStyleParams, style ViewStyle)
|
||||
MediaStyle(tag string, orientation, maxWidth, maxHeight int) ViewStyle
|
||||
SetMediaStyle(tag string, orientation, maxWidth, maxHeight int, style ViewStyle)
|
||||
StyleTags() []string
|
||||
MediaStyles(tag string) []struct {
|
||||
Selectors string
|
||||
Params MediaStyleParams
|
||||
Selectors string
|
||||
Orientation, MaxWidth, MaxHeight int
|
||||
}
|
||||
Append(anotherTheme Theme)
|
||||
|
||||
|
|
@ -77,7 +70,7 @@ func (rule mediaStyle) cssText() string {
|
|||
builder := allocStringBuilder()
|
||||
defer freeStringBuilder(builder)
|
||||
|
||||
switch rule.Orientation {
|
||||
switch rule.orientation {
|
||||
case PortraitMedia:
|
||||
builder.WriteString(" and (orientation: portrait)")
|
||||
|
||||
|
|
@ -85,108 +78,79 @@ func (rule mediaStyle) cssText() string {
|
|||
builder.WriteString(" and (orientation: landscape)")
|
||||
}
|
||||
|
||||
writeSize := func(tag string, minSize, maxSize int) {
|
||||
if minSize != maxSize {
|
||||
if minSize > 0 {
|
||||
builder.WriteString(fmt.Sprintf(" and (min-%s: %d.001px)", tag, minSize))
|
||||
}
|
||||
if maxSize > 0 {
|
||||
builder.WriteString(fmt.Sprintf(" and (max-%s: %dpx)", tag, maxSize))
|
||||
}
|
||||
} else if minSize > 0 {
|
||||
builder.WriteString(fmt.Sprintf(" and (%s: %dpx)", tag, minSize))
|
||||
}
|
||||
if rule.maxWidth > 0 {
|
||||
builder.WriteString(" and (max-width: ")
|
||||
builder.WriteString(strconv.Itoa(rule.maxWidth))
|
||||
builder.WriteString("px)")
|
||||
}
|
||||
|
||||
writeSize("width", rule.MinWidth, rule.MaxWidth)
|
||||
writeSize("height", rule.MinHeight, rule.MaxHeight)
|
||||
if rule.maxHeight > 0 {
|
||||
builder.WriteString(" and (max-height: ")
|
||||
builder.WriteString(strconv.Itoa(rule.maxHeight))
|
||||
builder.WriteString("px)")
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func parseMediaRule(text string) (mediaStyle, bool) {
|
||||
rule := mediaStyle{
|
||||
styles: map[string]ViewStyle{},
|
||||
orientation: DefaultMedia,
|
||||
maxWidth: 0,
|
||||
maxHeight: 0,
|
||||
styles: map[string]ViewStyle{},
|
||||
}
|
||||
|
||||
elements := strings.Split(text, ":")
|
||||
for i := 1; i < len(elements); i++ {
|
||||
switch element := elements[i]; element {
|
||||
case "portrait":
|
||||
if rule.Orientation != DefaultMedia {
|
||||
if rule.orientation != DefaultMedia {
|
||||
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
|
||||
return rule, false
|
||||
}
|
||||
rule.Orientation = PortraitMedia
|
||||
rule.orientation = PortraitMedia
|
||||
|
||||
case "landscape":
|
||||
if rule.Orientation != DefaultMedia {
|
||||
if rule.orientation != DefaultMedia {
|
||||
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
|
||||
return rule, false
|
||||
}
|
||||
rule.Orientation = LandscapeMedia
|
||||
rule.orientation = LandscapeMedia
|
||||
|
||||
default:
|
||||
elementSize := func(name string) (int, int, bool, error) {
|
||||
elementSize := func(name string) (int, bool) {
|
||||
if strings.HasPrefix(element, name) {
|
||||
var err error = nil
|
||||
min := 0
|
||||
max := 0
|
||||
data := element[len(name):]
|
||||
if pos := strings.Index(data, "-"); pos >= 0 {
|
||||
if pos > 0 {
|
||||
min, err = strconv.Atoi(data[:pos])
|
||||
}
|
||||
if err == nil && pos+1 < len(data) {
|
||||
max, err = strconv.Atoi(data[pos+1:])
|
||||
}
|
||||
} else {
|
||||
max, err = strconv.Atoi(data)
|
||||
size, err := strconv.Atoi(element[len(name):])
|
||||
if err == nil && size > 0 {
|
||||
return size, true
|
||||
}
|
||||
return min, max, true, err
|
||||
ErrorLogF(`Invalid style section name "%s": %s`, text, err.Error())
|
||||
return 0, false
|
||||
}
|
||||
return 0, 0, false, nil
|
||||
return 0, true
|
||||
}
|
||||
|
||||
if min, max, ok, err := elementSize("width"); ok {
|
||||
|
||||
if err != nil {
|
||||
ErrorLogF(`Invalid style section name "%s": %s`, text, err.Error())
|
||||
if size, ok := elementSize("width"); !ok || size > 0 {
|
||||
if !ok {
|
||||
return rule, false
|
||||
}
|
||||
if rule.MinWidth != 0 || rule.MaxWidth != 0 {
|
||||
if rule.maxWidth != 0 {
|
||||
ErrorLog(`Duplicate "width" tag in the style section "` + text + `"`)
|
||||
return rule, false
|
||||
}
|
||||
if min == 0 && max == 0 {
|
||||
ErrorLog(`Invalid arguments of "width" tag in the style section "` + text + `"`)
|
||||
rule.maxWidth = size
|
||||
} else if size, ok := elementSize("height"); !ok || size > 0 {
|
||||
if !ok {
|
||||
return rule, false
|
||||
}
|
||||
|
||||
rule.MinWidth = min
|
||||
rule.MaxWidth = max
|
||||
|
||||
} else if min, max, ok, err := elementSize("height"); ok {
|
||||
|
||||
if err != nil {
|
||||
ErrorLogF(`Invalid style section name "%s": %s`, text, err.Error())
|
||||
return rule, false
|
||||
}
|
||||
if rule.MinHeight != 0 || rule.MaxHeight != 0 {
|
||||
if rule.maxHeight != 0 {
|
||||
ErrorLog(`Duplicate "height" tag in the style section "` + text + `"`)
|
||||
return rule, false
|
||||
}
|
||||
if min == 0 && max == 0 {
|
||||
ErrorLog(`Invalid arguments of "height" tag in the style section "` + text + `"`)
|
||||
return rule, false
|
||||
}
|
||||
|
||||
rule.MinHeight = min
|
||||
rule.MaxHeight = max
|
||||
|
||||
rule.maxHeight = size
|
||||
} else {
|
||||
|
||||
ErrorLogF(`Unknown element "%s" in the style section name "%s"`, element, text)
|
||||
ErrorLogF(`Unknown elemnet "%s" in the style section name "%s"`, element, text)
|
||||
return rule, false
|
||||
}
|
||||
}
|
||||
|
|
@ -300,72 +264,36 @@ func (theme *theme) SetStyle(tag string, style ViewStyle) {
|
|||
}
|
||||
}
|
||||
|
||||
func (theme *theme) RemoveStyle(tag string) {
|
||||
tag2 := tag + ":"
|
||||
remove := func(styles map[string]ViewStyle) {
|
||||
tags := []string{tag}
|
||||
for t := range styles {
|
||||
if strings.HasPrefix(t, tag2) {
|
||||
tags = append(tags, t)
|
||||
}
|
||||
}
|
||||
for _, t := range tags {
|
||||
delete(styles, t)
|
||||
}
|
||||
}
|
||||
remove(theme.styles)
|
||||
for _, mediaStyle := range theme.mediaStyles {
|
||||
remove(mediaStyle.styles)
|
||||
}
|
||||
}
|
||||
|
||||
func (theme *theme) MediaStyle(tag string, params MediaStyleParams) ViewStyle {
|
||||
func (theme *theme) MediaStyle(tag string, orientation, maxWidth, maxHeight int) ViewStyle {
|
||||
for _, styles := range theme.mediaStyles {
|
||||
if styles.Orientation == params.Orientation &&
|
||||
styles.MaxWidth == params.MaxWidth &&
|
||||
styles.MinWidth == params.MinWidth &&
|
||||
styles.MaxHeight == params.MaxHeight &&
|
||||
styles.MinHeight == params.MinHeight {
|
||||
if styles.orientation == orientation && styles.maxWidth == maxWidth && styles.maxHeight == maxHeight {
|
||||
if style, ok := styles.styles[tag]; ok {
|
||||
return style
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if params.Orientation == 0 && params.MaxWidth == 0 && params.MinWidth == 0 &&
|
||||
params.MaxHeight == 0 && params.MinHeight == 0 {
|
||||
if orientation == 0 && maxWidth <= 0 && maxHeight <= 0 {
|
||||
return theme.style(tag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (theme *theme) SetMediaStyle(tag string, params MediaStyleParams, style ViewStyle) {
|
||||
if params.MaxWidth < 0 {
|
||||
params.MaxWidth = 0
|
||||
func (theme *theme) SetMediaStyle(tag string, orientation, maxWidth, maxHeight int, style ViewStyle) {
|
||||
if maxWidth < 0 {
|
||||
maxWidth = 0
|
||||
}
|
||||
if params.MinWidth < 0 {
|
||||
params.MinWidth = 0
|
||||
}
|
||||
if params.MaxHeight < 0 {
|
||||
params.MaxHeight = 0
|
||||
}
|
||||
if params.MinHeight < 0 {
|
||||
params.MinHeight = 0
|
||||
if maxHeight < 0 {
|
||||
maxHeight = 0
|
||||
}
|
||||
|
||||
if params.Orientation == 0 && params.MaxWidth == 0 && params.MinWidth == 0 &&
|
||||
params.MaxHeight == 0 && params.MinHeight == 0 {
|
||||
if orientation == DefaultMedia && maxWidth == 0 && maxHeight == 0 {
|
||||
theme.SetStyle(tag, style)
|
||||
return
|
||||
}
|
||||
|
||||
for i, styles := range theme.mediaStyles {
|
||||
if styles.Orientation == params.Orientation &&
|
||||
styles.MaxWidth == params.MaxWidth &&
|
||||
styles.MinWidth == params.MinWidth &&
|
||||
styles.MaxHeight == params.MaxHeight &&
|
||||
styles.MinHeight == params.MinHeight {
|
||||
if styles.orientation == orientation && styles.maxWidth == maxWidth && styles.maxHeight == maxHeight {
|
||||
if style != nil {
|
||||
theme.mediaStyles[i].styles[tag] = style
|
||||
} else {
|
||||
|
|
@ -377,8 +305,10 @@ func (theme *theme) SetMediaStyle(tag string, params MediaStyleParams, style Vie
|
|||
|
||||
if style != nil {
|
||||
theme.mediaStyles = append(theme.mediaStyles, mediaStyle{
|
||||
MediaStyleParams: params,
|
||||
styles: map[string]ViewStyle{tag: style},
|
||||
orientation: orientation,
|
||||
maxWidth: maxWidth,
|
||||
maxHeight: maxHeight,
|
||||
styles: map[string]ViewStyle{tag: style},
|
||||
})
|
||||
theme.sortMediaStyles()
|
||||
}
|
||||
|
|
@ -476,12 +406,12 @@ func (theme *theme) StyleTags() []string {
|
|||
}
|
||||
|
||||
func (theme *theme) MediaStyles(tag string) []struct {
|
||||
Selectors string
|
||||
Params MediaStyleParams
|
||||
Selectors string
|
||||
Orientation, MaxWidth, MaxHeight int
|
||||
} {
|
||||
result := []struct {
|
||||
Selectors string
|
||||
Params MediaStyleParams
|
||||
Selectors string
|
||||
Orientation, MaxWidth, MaxHeight int
|
||||
}{}
|
||||
|
||||
prefix := tag + ":"
|
||||
|
|
@ -489,11 +419,13 @@ func (theme *theme) MediaStyles(tag string) []struct {
|
|||
for themeTag := range theme.styles {
|
||||
if strings.HasPrefix(themeTag, prefix) {
|
||||
result = append(result, struct {
|
||||
Selectors string
|
||||
Params MediaStyleParams
|
||||
Selectors string
|
||||
Orientation, MaxWidth, MaxHeight int
|
||||
}{
|
||||
Selectors: themeTag[prefixLen:],
|
||||
Params: MediaStyleParams{},
|
||||
Selectors: themeTag[prefixLen:],
|
||||
Orientation: DefaultMedia,
|
||||
MaxWidth: 0,
|
||||
MaxHeight: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -501,21 +433,25 @@ func (theme *theme) MediaStyles(tag string) []struct {
|
|||
for _, media := range theme.mediaStyles {
|
||||
if _, ok := media.styles[tag]; ok {
|
||||
result = append(result, struct {
|
||||
Selectors string
|
||||
Params MediaStyleParams
|
||||
Selectors string
|
||||
Orientation, MaxWidth, MaxHeight int
|
||||
}{
|
||||
Selectors: "",
|
||||
Params: media.MediaStyleParams,
|
||||
Selectors: "",
|
||||
Orientation: media.orientation,
|
||||
MaxWidth: media.maxWidth,
|
||||
MaxHeight: media.maxHeight,
|
||||
})
|
||||
}
|
||||
for themeTag := range media.styles {
|
||||
if strings.HasPrefix(themeTag, prefix) {
|
||||
result = append(result, struct {
|
||||
Selectors string
|
||||
Params MediaStyleParams
|
||||
Selectors string
|
||||
Orientation, MaxWidth, MaxHeight int
|
||||
}{
|
||||
Selectors: themeTag[prefixLen:],
|
||||
Params: media.MediaStyleParams,
|
||||
Selectors: themeTag[prefixLen:],
|
||||
Orientation: media.orientation,
|
||||
MaxWidth: media.maxWidth,
|
||||
MaxHeight: media.maxHeight,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -564,11 +500,9 @@ func (theme *theme) Append(anotherTheme Theme) {
|
|||
for _, anotherMedia := range another.mediaStyles {
|
||||
exists := false
|
||||
for _, media := range theme.mediaStyles {
|
||||
if anotherMedia.MinHeight == media.MinHeight &&
|
||||
anotherMedia.MaxHeight == media.MaxHeight &&
|
||||
anotherMedia.MinWidth == media.MinWidth &&
|
||||
anotherMedia.MaxWidth == media.MaxWidth &&
|
||||
anotherMedia.Orientation == media.Orientation {
|
||||
if anotherMedia.maxHeight == media.maxHeight &&
|
||||
anotherMedia.maxWidth == media.maxWidth &&
|
||||
anotherMedia.orientation == media.orientation {
|
||||
for tag, style := range anotherMedia.styles {
|
||||
media.styles[tag] = style
|
||||
}
|
||||
|
|
@ -591,37 +525,18 @@ func (theme *theme) cssText(session Session) string {
|
|||
var builder cssStyleBuilder
|
||||
builder.init()
|
||||
|
||||
styleList := func(styles map[string]ViewStyle) []string {
|
||||
ruiStyles := []string{}
|
||||
customStyles := []string{}
|
||||
for tag := range styles {
|
||||
if strings.HasPrefix(tag, "rui") {
|
||||
ruiStyles = append(ruiStyles, tag)
|
||||
} else {
|
||||
customStyles = append(customStyles, tag)
|
||||
}
|
||||
}
|
||||
sort.Strings(ruiStyles)
|
||||
sort.Strings(customStyles)
|
||||
return append(ruiStyles, customStyles...)
|
||||
}
|
||||
|
||||
for _, tag := range styleList(theme.styles) {
|
||||
if style := theme.styles[tag]; style != nil {
|
||||
builder.startStyle(tag)
|
||||
style.cssViewStyle(&builder, session)
|
||||
builder.endStyle()
|
||||
}
|
||||
for tag, style := range theme.styles {
|
||||
builder.startStyle(tag)
|
||||
style.cssViewStyle(&builder, session)
|
||||
builder.endStyle()
|
||||
}
|
||||
|
||||
for _, media := range theme.mediaStyles {
|
||||
builder.startMedia(media.cssText())
|
||||
for _, tag := range styleList(media.styles) {
|
||||
if style := media.styles[tag]; style != nil {
|
||||
builder.startStyle(tag)
|
||||
style.cssViewStyle(&builder, session)
|
||||
builder.endStyle()
|
||||
}
|
||||
for tag, style := range media.styles {
|
||||
builder.startStyle(tag)
|
||||
style.cssViewStyle(&builder, session)
|
||||
builder.endStyle()
|
||||
}
|
||||
builder.endMedia()
|
||||
}
|
||||
|
|
@ -772,19 +687,13 @@ func (theme *theme) addText(themeText string) bool {
|
|||
func (theme *theme) sortMediaStyles() {
|
||||
if len(theme.mediaStyles) > 1 {
|
||||
sort.SliceStable(theme.mediaStyles, func(i, j int) bool {
|
||||
if theme.mediaStyles[i].Orientation != theme.mediaStyles[j].Orientation {
|
||||
return theme.mediaStyles[i].Orientation < theme.mediaStyles[j].Orientation
|
||||
if theme.mediaStyles[i].orientation != theme.mediaStyles[j].orientation {
|
||||
return theme.mediaStyles[i].orientation < theme.mediaStyles[j].orientation
|
||||
}
|
||||
if theme.mediaStyles[i].MinWidth != theme.mediaStyles[j].MinWidth {
|
||||
return theme.mediaStyles[i].MinWidth < theme.mediaStyles[j].MinWidth
|
||||
if theme.mediaStyles[i].maxWidth != theme.mediaStyles[j].maxWidth {
|
||||
return theme.mediaStyles[i].maxWidth < theme.mediaStyles[j].maxWidth
|
||||
}
|
||||
if theme.mediaStyles[i].MinHeight != theme.mediaStyles[j].MinHeight {
|
||||
return theme.mediaStyles[i].MinHeight < theme.mediaStyles[j].MinHeight
|
||||
}
|
||||
if theme.mediaStyles[i].MaxWidth != theme.mediaStyles[j].MaxWidth {
|
||||
return theme.mediaStyles[i].MaxWidth < theme.mediaStyles[j].MaxWidth
|
||||
}
|
||||
return theme.mediaStyles[i].MaxHeight < theme.mediaStyles[j].MaxHeight
|
||||
return theme.mediaStyles[i].maxHeight < theme.mediaStyles[j].maxHeight
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -902,7 +811,7 @@ func (theme *theme) String() string {
|
|||
writeConstants("constants", theme.constants)
|
||||
writeConstants("constants:touch", theme.touchConstants)
|
||||
|
||||
writeStyles := func(orientation, maxWidth, maxHeight int, styles map[string]ViewStyle) bool {
|
||||
writeStyles := func(orientation, maxWidth, maxHeihgt int, styles map[string]ViewStyle) bool {
|
||||
count := len(styles)
|
||||
if count == 0 {
|
||||
return false
|
||||
|
|
@ -925,16 +834,16 @@ func (theme *theme) String() string {
|
|||
if maxWidth > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(":width%d", maxWidth))
|
||||
}
|
||||
if maxHeight > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(":height%d", maxHeight))
|
||||
if maxHeihgt > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(":heihgt%d", maxHeihgt))
|
||||
}
|
||||
buffer.WriteString(" = [\n")
|
||||
|
||||
for _, tag := range tags {
|
||||
if style, ok := styles[tag]; ok && len(style.AllTags()) > 0 {
|
||||
if style, ok := styles[tag]; ok {
|
||||
buffer.WriteString("\t\t")
|
||||
writeViewStyle(tag, style, buffer, "\t\t")
|
||||
buffer.WriteString(",\n")
|
||||
buffer.WriteString(",")
|
||||
}
|
||||
}
|
||||
buffer.WriteString("\t],\n")
|
||||
|
|
@ -943,53 +852,7 @@ func (theme *theme) String() string {
|
|||
|
||||
writeStyles(0, 0, 0, theme.styles)
|
||||
for _, media := range theme.mediaStyles {
|
||||
//writeStyles(media.orientation, media.maxWidth, media.maxHeight, media.styles)
|
||||
if count := len(media.styles); count > 0 {
|
||||
|
||||
tags := make([]string, 0, count)
|
||||
for name := range media.styles {
|
||||
tags = append(tags, name)
|
||||
}
|
||||
sort.Strings(tags)
|
||||
|
||||
buffer.WriteString("\tstyles")
|
||||
switch media.Orientation {
|
||||
case PortraitMedia:
|
||||
buffer.WriteString(":portrait")
|
||||
|
||||
case LandscapeMedia:
|
||||
buffer.WriteString(":landscape")
|
||||
}
|
||||
|
||||
if media.MinWidth > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(":width%d-", media.MinWidth))
|
||||
if media.MaxWidth > 0 {
|
||||
buffer.WriteString(strconv.Itoa(media.MaxWidth))
|
||||
}
|
||||
} else if media.MaxWidth > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(":width%d", media.MaxWidth))
|
||||
}
|
||||
|
||||
if media.MinHeight > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(":height%d-", media.MinHeight))
|
||||
if media.MaxHeight > 0 {
|
||||
buffer.WriteString(strconv.Itoa(media.MaxHeight))
|
||||
}
|
||||
} else if media.MaxHeight > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(":height%d", media.MaxHeight))
|
||||
}
|
||||
|
||||
buffer.WriteString(" = [\n")
|
||||
|
||||
for _, tag := range tags {
|
||||
if style, ok := media.styles[tag]; ok && len(style.AllTags()) > 0 {
|
||||
buffer.WriteString("\t\t")
|
||||
writeViewStyle(tag, style, buffer, "\t\t")
|
||||
buffer.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
buffer.WriteString("\t],\n")
|
||||
}
|
||||
writeStyles(media.orientation, media.maxWidth, media.maxHeight, media.styles)
|
||||
}
|
||||
|
||||
buffer.WriteString("}\n")
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ type TimePicker interface {
|
|||
|
||||
type timePickerData struct {
|
||||
viewData
|
||||
timeChangedListeners []func(TimePicker, time.Time, time.Time)
|
||||
timeChangedListeners []func(TimePicker, time.Time)
|
||||
}
|
||||
|
||||
// NewTimePicker create new TimePicker object and return it
|
||||
|
|
@ -40,7 +40,7 @@ func newTimePicker(session Session) View {
|
|||
func (picker *timePickerData) init(session Session) {
|
||||
picker.viewData.init(session)
|
||||
picker.tag = "TimePicker"
|
||||
picker.timeChangedListeners = []func(TimePicker, time.Time, time.Time){}
|
||||
picker.timeChangedListeners = []func(TimePicker, time.Time){}
|
||||
}
|
||||
|
||||
func (picker *timePickerData) String() string {
|
||||
|
|
@ -69,7 +69,7 @@ func (picker *timePickerData) remove(tag string) {
|
|||
switch tag {
|
||||
case TimeChangedEvent:
|
||||
if len(picker.timeChangedListeners) > 0 {
|
||||
picker.timeChangedListeners = []func(TimePicker, time.Time, time.Time){}
|
||||
picker.timeChangedListeners = []func(TimePicker, time.Time){}
|
||||
picker.propertyChangedEvent(tag)
|
||||
}
|
||||
return
|
||||
|
|
@ -94,14 +94,13 @@ func (picker *timePickerData) remove(tag string) {
|
|||
|
||||
case TimePickerValue:
|
||||
if _, ok := picker.properties[TimePickerValue]; ok {
|
||||
oldTime := GetTimePickerValue(picker)
|
||||
delete(picker.properties, TimePickerValue)
|
||||
time := GetTimePickerValue(picker)
|
||||
if picker.created {
|
||||
picker.session.callFunc("setInputValue", picker.htmlID(), time.Format(timeFormat))
|
||||
}
|
||||
for _, listener := range picker.timeChangedListeners {
|
||||
listener(picker, time, oldTime)
|
||||
listener(picker, time)
|
||||
}
|
||||
} else {
|
||||
return
|
||||
|
|
@ -215,7 +214,7 @@ func (picker *timePickerData) set(tag string, value any) bool {
|
|||
picker.session.callFunc("setInputValue", picker.htmlID(), time.Format(timeFormat))
|
||||
}
|
||||
for _, listener := range picker.timeChangedListeners {
|
||||
listener(picker, time, oldTime)
|
||||
listener(picker, time)
|
||||
}
|
||||
picker.propertyChangedEvent(tag)
|
||||
}
|
||||
|
|
@ -223,12 +222,12 @@ func (picker *timePickerData) set(tag string, value any) bool {
|
|||
}
|
||||
|
||||
case TimeChangedEvent:
|
||||
listeners, ok := valueToEventWithOldListeners[TimePicker, time.Time](value)
|
||||
listeners, ok := valueToEventListeners[TimePicker, time.Time](value)
|
||||
if !ok {
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
} else if listeners == nil {
|
||||
listeners = []func(TimePicker, time.Time, time.Time){}
|
||||
listeners = []func(TimePicker, time.Time){}
|
||||
}
|
||||
picker.timeChangedListeners = listeners
|
||||
picker.propertyChangedEvent(tag)
|
||||
|
|
@ -307,7 +306,7 @@ func (picker *timePickerData) handleCommand(self View, command string, data Data
|
|||
picker.properties[TimePickerValue] = value
|
||||
if value != oldValue {
|
||||
for _, listener := range picker.timeChangedListeners {
|
||||
listener(picker, value, oldValue)
|
||||
listener(picker, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -399,6 +398,6 @@ func GetTimePickerValue(view View, subviewID ...string) time.Time {
|
|||
// GetTimeChangedListeners returns the TimeChangedListener list of an TimePicker subview.
|
||||
// If there are no listeners then the empty list is returned
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time, time.Time) {
|
||||
return getEventWithOldListeners[TimePicker, time.Time](view, subviewID, TimeChangedEvent)
|
||||
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time) {
|
||||
return getEventListeners[TimePicker, time.Time](view, subviewID, TimeChangedEvent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,10 +130,7 @@ func touchEventsHtml(view View, buffer *strings.Builder) {
|
|||
for tag, js := range touchEvents {
|
||||
if value := view.getRaw(tag); value != nil {
|
||||
if listeners, ok := value.([]func(View, TouchEvent)); ok && len(listeners) > 0 {
|
||||
buffer.WriteString(js.jsEvent)
|
||||
buffer.WriteString(`="`)
|
||||
buffer.WriteString(js.jsFunc)
|
||||
buffer.WriteString(`(this, event)" `)
|
||||
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +140,7 @@ func (event *TouchEvent) init(data DataObject) {
|
|||
|
||||
event.Touches = []Touch{}
|
||||
event.TimeStamp = getTimeStamp(data)
|
||||
if node := data.PropertyByTag("touches"); node != nil && node.Type() == ArrayNode {
|
||||
if node := data.PropertyWithTag("touches"); node != nil && node.Type() == ArrayNode {
|
||||
for i := 0; i < node.ArraySize(); i++ {
|
||||
if element := node.ArrayElement(i); element != nil && element.IsObject() {
|
||||
if obj := element.Object(); obj != nil {
|
||||
|
|
|
|||
30
utils.go
30
utils.go
|
|
@ -1,9 +1,7 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
|
@ -78,31 +76,3 @@ func dataFloatProperty(data DataObject, tag string) float64 {
|
|||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// InlineImageFromResource reads image from resources and converts it to an inline image.
|
||||
// Supported png, jpeg, gif, and svg files
|
||||
func InlineImageFromResource(filename string) (string, bool) {
|
||||
if image, ok := resources.images[filename]; ok && image.fs != nil {
|
||||
dataType := map[string]string{
|
||||
".svg": "data:image/svg+xml",
|
||||
".png": "data:image/png",
|
||||
".jpg": "data:image/jpg",
|
||||
".jpeg": "data:image/jpg",
|
||||
".gif": "data:image/gif",
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
if prefix, ok := dataType[ext]; ok {
|
||||
if data, err := image.fs.ReadFile(image.path); err == nil {
|
||||
return prefix + ";base64," + base64.StdEncoding.EncodeToString(data), true
|
||||
} else {
|
||||
DebugLog(err.Error())
|
||||
}
|
||||
} else {
|
||||
DebugLogF(`InlineImageFromResource("%s") error: Unsupported file`, filename)
|
||||
}
|
||||
} else {
|
||||
DebugLogF(`The resource image "%s" not found`, filename)
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
|
|
|||
75
view.go
75
view.go
|
|
@ -45,7 +45,7 @@ type View interface {
|
|||
Focusable() bool
|
||||
// Frame returns the location and size of the view in pixels
|
||||
Frame() Frame
|
||||
// Scroll returns the location size of the scrollable view in pixels
|
||||
// Scroll returns the location size of the scrolable view in pixels
|
||||
Scroll() Frame
|
||||
// SetAnimated sets the value (second argument) of the property with name defined by the first argument.
|
||||
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
||||
|
|
@ -175,17 +175,6 @@ func (view *viewData) Focusable() bool {
|
|||
if focus, ok := boolProperty(view, Focusable, view.session); ok {
|
||||
return focus
|
||||
}
|
||||
|
||||
if style, ok := stringProperty(view, Style, view.session); ok {
|
||||
if style, ok := view.session.resolveConstants(style); ok {
|
||||
if value := view.session.styleProperty(style, Focusable); ok {
|
||||
if focus, ok := valueToBool(value, view.Session()); ok {
|
||||
return focus
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -198,14 +187,6 @@ func (view *viewData) remove(tag string) {
|
|||
case ID:
|
||||
view.viewID = ""
|
||||
|
||||
case TabIndex, "tab-index":
|
||||
delete(view.properties, tag)
|
||||
if view.Focusable() {
|
||||
view.session.updateProperty(view.htmlID(), "tabindex", "0")
|
||||
} else {
|
||||
view.session.updateProperty(view.htmlID(), "tabindex", "-1")
|
||||
}
|
||||
|
||||
case UserData:
|
||||
delete(view.properties, tag)
|
||||
|
||||
|
|
@ -331,18 +312,6 @@ func (view *viewData) set(tag string, value any) bool {
|
|||
}
|
||||
view.viewID = text
|
||||
|
||||
case TabIndex, "tab-index":
|
||||
if !view.setIntProperty(tag, value) {
|
||||
return false
|
||||
}
|
||||
if value, ok := intProperty(view, TabIndex, view.Session(), 0); ok {
|
||||
view.session.updateProperty(view.htmlID(), "tabindex", strconv.Itoa(value))
|
||||
} else if view.Focusable() {
|
||||
view.session.updateProperty(view.htmlID(), "tabindex", "0")
|
||||
} else {
|
||||
view.session.updateProperty(view.htmlID(), "tabindex", "-1")
|
||||
}
|
||||
|
||||
case UserData:
|
||||
view.properties[tag] = value
|
||||
|
||||
|
|
@ -412,12 +381,10 @@ func viewPropertyChanged(view *viewData, tag string) {
|
|||
case Invisible:
|
||||
session.updateCSSProperty(htmlID, Visibility, "hidden")
|
||||
session.updateCSSProperty(htmlID, "display", "")
|
||||
session.callFunc("hideTooltip")
|
||||
|
||||
case Gone:
|
||||
session.updateCSSProperty(htmlID, Visibility, "hidden")
|
||||
session.updateCSSProperty(htmlID, "display", "none")
|
||||
session.callFunc("hideTooltip")
|
||||
|
||||
default:
|
||||
session.updateCSSProperty(htmlID, Visibility, "visible")
|
||||
|
|
@ -610,7 +577,7 @@ func viewPropertyChanged(view *viewData, tag string) {
|
|||
}
|
||||
return
|
||||
|
||||
case ZIndex, Order, TabSize:
|
||||
case ZIndex, TabSize:
|
||||
if i, ok := intProperty(view, tag, session, 0); ok {
|
||||
session.updateCSSProperty(htmlID, tag, strconv.Itoa(i))
|
||||
}
|
||||
|
|
@ -639,24 +606,6 @@ func viewPropertyChanged(view *viewData, tag string) {
|
|||
session.updateCSSProperty(htmlID, "user-select", "")
|
||||
}
|
||||
return
|
||||
|
||||
case ColumnSpanAll:
|
||||
if spanAll, ok := boolProperty(view, ColumnSpanAll, session); ok && spanAll {
|
||||
session.updateCSSProperty(htmlID, `column-span`, `all`)
|
||||
} else {
|
||||
session.updateCSSProperty(htmlID, `column-span`, `none`)
|
||||
}
|
||||
return
|
||||
|
||||
case Tooltip:
|
||||
if tooltip := GetTooltip(view); tooltip == "" {
|
||||
session.removeProperty(htmlID, "data-tooltip")
|
||||
} else {
|
||||
session.updateProperty(htmlID, "data-tooltip", tooltip)
|
||||
session.updateProperty(htmlID, "onmouseenter", "mouseEnterEvent(this, event)")
|
||||
session.updateProperty(htmlID, "onmouseleave", "mouseLeaveEvent(this, event)")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if cssTag, ok := sizeProperties[tag]; ok {
|
||||
|
|
@ -809,28 +758,14 @@ func viewHTML(view View, buffer *strings.Builder) {
|
|||
buffer.WriteRune(' ')
|
||||
}
|
||||
|
||||
if !disabled {
|
||||
if value, ok := intProperty(view, TabIndex, view.Session(), -1); ok {
|
||||
buffer.WriteString(`tabindex="`)
|
||||
buffer.WriteString(strconv.Itoa(value))
|
||||
buffer.WriteString(`" `)
|
||||
} else if view.Focusable() {
|
||||
buffer.WriteString(`tabindex="0" `)
|
||||
}
|
||||
}
|
||||
|
||||
hasTooltip := false
|
||||
if tooltip := GetTooltip(view); tooltip != "" {
|
||||
buffer.WriteString(`data-tooltip=" `)
|
||||
buffer.WriteString(tooltip)
|
||||
buffer.WriteString(`" `)
|
||||
hasTooltip = true
|
||||
if view.Focusable() && !disabled {
|
||||
buffer.WriteString(`tabindex="0" `)
|
||||
}
|
||||
|
||||
buffer.WriteString(`onscroll="scrollEvent(this, event)" `)
|
||||
|
||||
keyEventsHtml(view, buffer)
|
||||
mouseEventsHtml(view, buffer, hasTooltip)
|
||||
mouseEventsHtml(view, buffer)
|
||||
pointerEventsHtml(view, buffer)
|
||||
touchEventsHtml(view, buffer)
|
||||
focusEventsHtml(view, buffer)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ func ViewByID(rootView View, id string) View {
|
|||
return rootView
|
||||
}
|
||||
|
||||
if container, ok := rootView.(ParentView); ok {
|
||||
if container, ok := rootView.(ParanetView); ok {
|
||||
if view := viewByID(container, id); view != nil {
|
||||
return view
|
||||
}
|
||||
|
|
@ -32,13 +32,13 @@ func ViewByID(rootView View, id string) View {
|
|||
return nil
|
||||
}
|
||||
|
||||
func viewByID(rootView ParentView, id string) View {
|
||||
func viewByID(rootView ParanetView, id string) View {
|
||||
for _, view := range rootView.Views() {
|
||||
if view != nil {
|
||||
if view.ID() == id {
|
||||
return view
|
||||
}
|
||||
if container, ok := view.(ParentView); ok {
|
||||
if container, ok := view.(ParanetView); ok {
|
||||
if v := viewByID(container, id); v != nil {
|
||||
return v
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ var viewCreators = map[string]func(Session) View{
|
|||
"ListView": newListView,
|
||||
"CanvasView": newCanvasView,
|
||||
"ImageView": newImageView,
|
||||
"SvgImageView": newSvgImageView,
|
||||
"TableView": newTableView,
|
||||
"AudioPlayer": newAudioPlayer,
|
||||
"VideoPlayer": newVideoPlayer,
|
||||
|
|
|
|||
28
viewStyle.go
28
viewStyle.go
|
|
@ -193,26 +193,22 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
|||
outline.ViewOutline(session).cssValue(builder, session)
|
||||
}
|
||||
|
||||
for _, tag := range []string{ZIndex, Order} {
|
||||
if value, ok := intProperty(style, tag, session, 0); ok {
|
||||
builder.add(tag, strconv.Itoa(value))
|
||||
}
|
||||
if z, ok := intProperty(style, ZIndex, session, 0); ok {
|
||||
builder.add(ZIndex, strconv.Itoa(z))
|
||||
}
|
||||
|
||||
if opacity, ok := floatProperty(style, Opacity, session, 1.0); ok && opacity >= 0 && opacity <= 1 {
|
||||
builder.add(Opacity, strconv.FormatFloat(opacity, 'f', 3, 32))
|
||||
}
|
||||
|
||||
for _, tag := range []string{ColumnCount, TabSize} {
|
||||
if value, ok := intProperty(style, tag, session, 0); ok && value > 0 {
|
||||
builder.add(tag, strconv.Itoa(value))
|
||||
}
|
||||
if n, ok := intProperty(style, ColumnCount, session, 0); ok && n > 0 {
|
||||
builder.add(ColumnCount, strconv.Itoa(n))
|
||||
}
|
||||
|
||||
for _, tag := range []string{
|
||||
Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight, Left, Right, Top, Bottom,
|
||||
TextSize, TextIndent, LetterSpacing, WordSpacing, LineHeight, TextLineThickness,
|
||||
ListRowGap, ListColumnGap, GridRowGap, GridColumnGap, ColumnGap, ColumnWidth, OutlineOffset} {
|
||||
ListRowGap, ListColumnGap, GridRowGap, GridColumnGap, ColumnGap, ColumnWidth} {
|
||||
|
||||
if size, ok := sizeProperty(style, tag, session); ok && size.Type != Auto {
|
||||
cssTag, ok := sizeProperties[tag]
|
||||
|
|
@ -252,7 +248,7 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
|||
for _, tag := range []string{
|
||||
Overflow, TextAlign, TextTransform, TextWeight, TextLineStyle, WritingMode, TextDirection,
|
||||
VerticalTextOrientation, CellVerticalAlign, CellHorizontalAlign, GridAutoFlow, Cursor,
|
||||
WhiteSpace, WordBreak, TextOverflow, Float, TableVerticalAlign, Resize, MixBlendMode, BackgroundBlendMode} {
|
||||
WhiteSpace, WordBreak, TextOverflow, Float, TableVerticalAlign, Resize} {
|
||||
|
||||
if data, ok := enumProperties[tag]; ok {
|
||||
if tag != VerticalTextOrientation || (writingMode != VerticalLeftToRight && writingMode != VerticalRightToLeft) {
|
||||
|
|
@ -283,6 +279,10 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
|||
}
|
||||
}
|
||||
|
||||
if tabSize, ok := intProperty(style, TabSize, session, 8); ok && tabSize > 0 {
|
||||
builder.add(TabSize, strconv.Itoa(tabSize))
|
||||
}
|
||||
|
||||
if text := style.cssTextDecoration(session); text != "" {
|
||||
builder.add("text-decoration", text)
|
||||
}
|
||||
|
|
@ -453,14 +453,6 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
|||
builder.add(`animation-play-state`, `running`)
|
||||
}
|
||||
}
|
||||
|
||||
if spanAll, ok := boolProperty(style, ColumnSpanAll, session); ok {
|
||||
if spanAll {
|
||||
builder.add(`column-span`, `all`)
|
||||
} else {
|
||||
builder.add(`column-span`, `none`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func valueToOrientation(value any, session Session) (int, bool) {
|
||||
|
|
|
|||
|
|
@ -202,60 +202,6 @@ func (style *viewStyle) set(tag string, value any) bool {
|
|||
case CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft:
|
||||
return style.setBoundsSide(CellPadding, tag, value)
|
||||
|
||||
case HeadStyle, FootStyle:
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
style.properties[tag] = value
|
||||
return true
|
||||
|
||||
case Params:
|
||||
style.properties[tag] = value
|
||||
return true
|
||||
|
||||
case DataObject:
|
||||
if params := value.ToParams(); len(params) > 0 {
|
||||
style.properties[tag] = params
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
case CellStyle, ColumnStyle, RowStyle:
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
style.properties[tag] = value
|
||||
return true
|
||||
|
||||
case Params:
|
||||
style.properties[tag] = value
|
||||
return true
|
||||
|
||||
case DataObject:
|
||||
if params := value.ToParams(); len(params) > 0 {
|
||||
style.properties[tag] = params
|
||||
}
|
||||
return true
|
||||
|
||||
case DataNode:
|
||||
switch value.Type() {
|
||||
case TextNode:
|
||||
if text := value.Text(); text != "" {
|
||||
style.properties[tag] = text
|
||||
}
|
||||
return true
|
||||
|
||||
case ObjectNode:
|
||||
if obj := value.Object(); obj != nil {
|
||||
if params := obj.ToParams(); len(params) > 0 {
|
||||
style.properties[tag] = params
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
case ArrayNode:
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
case Outline:
|
||||
return style.setOutline(value)
|
||||
|
||||
|
|
@ -392,7 +338,7 @@ func (style *viewStyle) set(tag string, value any) bool {
|
|||
return true
|
||||
|
||||
case DataObject:
|
||||
if animation := parseAnimation(value); animation.hasAnimatedProperty() {
|
||||
if animation := parseAnimation(value); animation.hasAnimatedPropery() {
|
||||
style.properties[tag] = []Animation{animation}
|
||||
return true
|
||||
}
|
||||
|
|
@ -402,7 +348,7 @@ func (style *viewStyle) set(tag string, value any) bool {
|
|||
result := true
|
||||
for i := 0; i < value.ArraySize(); i++ {
|
||||
if obj := value.ArrayElement(i).Object(); obj != nil {
|
||||
if anim := parseAnimation(obj); anim.hasAnimatedProperty() {
|
||||
if anim := parseAnimation(obj); anim.hasAnimatedPropery() {
|
||||
animations = append(animations, anim)
|
||||
} else {
|
||||
result = false
|
||||
|
|
|
|||
81
viewUtils.go
81
viewUtils.go
|
|
@ -153,37 +153,12 @@ func GetOverflow(view View, subviewID ...string) int {
|
|||
return enumStyledProperty(view, subviewID, Overflow, defaultOverflow, false)
|
||||
}
|
||||
|
||||
// GetTabIndex returns the subview tab-index.
|
||||
// If the second argument (subviewID) is not specified or it is "" then a tab-index of the first argument (view) is returned
|
||||
func GetTabIndex(view View, subviewID ...string) int {
|
||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
||||
view = ViewByID(view, subviewID[0])
|
||||
}
|
||||
|
||||
defaultValue := -1
|
||||
if view != nil {
|
||||
if view.Focusable() {
|
||||
defaultValue = 0
|
||||
}
|
||||
if value, ok := intProperty(view, TabIndex, view.Session(), defaultValue); ok {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetZIndex returns the subview z-order.
|
||||
// If the second argument (subviewID) is not specified or it is "" then a z-order of the first argument (view) is returned
|
||||
func GetZIndex(view View, subviewID ...string) int {
|
||||
return intStyledProperty(view, subviewID, ZIndex, 0)
|
||||
}
|
||||
|
||||
// GetOrder returns the subview order to layout an item in a ListLayout or GridLayout container.
|
||||
// If the second argument (subviewID) is not specified or it is "" then an order of the first argument (view) is returned
|
||||
func GetOrder(view View, subviewID ...string) int {
|
||||
return intStyledProperty(view, subviewID, Order, 0)
|
||||
}
|
||||
|
||||
// GetWidth returns the subview width.
|
||||
// If the second argument (subviewID) is not specified or it is "" then a width of the first argument (view) is returned
|
||||
func GetWidth(view View, subviewID ...string) SizeUnit {
|
||||
|
|
@ -321,12 +296,6 @@ func GetOutline(view View, subviewID ...string) ViewOutline {
|
|||
return ViewOutline{Style: NoneLine, Width: AutoSize(), Color: 0}
|
||||
}
|
||||
|
||||
// GetOutlineOffset returns the subview outline offset.
|
||||
// If the second argument (subviewID) is not specified or it is "" then a offset of the first argument (view) is returned
|
||||
func GetOutlineOffset(view View, subviewID ...string) SizeUnit {
|
||||
return sizeStyledProperty(view, subviewID, OutlineOffset, false)
|
||||
}
|
||||
|
||||
// GetViewShadows returns shadows of the subview.
|
||||
// If the second argument (subviewID) is not specified or it is "" then shadows of the first argument (view) is returned.
|
||||
func GetViewShadows(view View, subviewID ...string) []ViewShadow {
|
||||
|
|
@ -830,13 +799,9 @@ func colorStyledProperty(view View, subviewID []string, tag string, inherit bool
|
|||
return Color(0)
|
||||
}
|
||||
|
||||
// FocusView sets focus on the specified subview, if it can be focused.
|
||||
// FocusView sets focus on the specified View, if it can be focused.
|
||||
// The focused View is the View which will receive keyboard events by default.
|
||||
// If the second argument (subviewID) is not specified or it is "" then focus is set on the first argument (view)
|
||||
func FocusView(view View, subviewID ...string) {
|
||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
||||
view = ViewByID(view, subviewID[0])
|
||||
}
|
||||
func FocusView(view View) {
|
||||
if view != nil {
|
||||
view.Session().callFunc("focus", view.htmlID())
|
||||
}
|
||||
|
|
@ -925,45 +890,3 @@ func isUserSelect(view View) (bool, bool) {
|
|||
|
||||
return false, false
|
||||
}
|
||||
|
||||
// GetMixBlendMode returns a "mix-blend-mode" of the subview. Returns one of next values:
|
||||
//
|
||||
// BlendNormal (0), BlendMultiply (1), BlendScreen (2), BlendOverlay (3), BlendDarken (4),
|
||||
// BlendLighten (5), BlendColorDodge (6), BlendColorBurn (7), BlendHardLight (8),
|
||||
// BlendSoftLight (9), BlendDifference (10), BlendExclusion (11), BlendHue (12),
|
||||
// BlendSaturation (13), BlendColor (14), BlendLuminosity (15)
|
||||
//
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||
func GetMixBlendMode(view View, subviewID ...string) int {
|
||||
return enumStyledProperty(view, subviewID, MixBlendMode, BlendNormal, true)
|
||||
}
|
||||
|
||||
// GetBackgroundBlendMode returns a "background-blend-mode" of the subview. Returns one of next values:
|
||||
//
|
||||
// BlendNormal (0), BlendMultiply (1), BlendScreen (2), BlendOverlay (3), BlendDarken (4),
|
||||
// BlendLighten (5), BlendColorDodge (6), BlendColorBurn (7), BlendHardLight (8),
|
||||
// BlendSoftLight (9), BlendDifference (10), BlendExclusion (11), BlendHue (12),
|
||||
// BlendSaturation (13), BlendColor (14), BlendLuminosity (15)
|
||||
//
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||
func GetBackgroundBlendMode(view View, subviewID ...string) int {
|
||||
return enumStyledProperty(view, subviewID, BackgroundBlendMode, BlendNormal, true)
|
||||
}
|
||||
|
||||
// GetTooltip returns a tooltip text of the subview.
|
||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||
func GetTooltip(view View, subviewID ...string) string {
|
||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
||||
view = ViewByID(view, subviewID[0])
|
||||
}
|
||||
|
||||
if view != nil {
|
||||
if value := view.Get(Tooltip); value != nil {
|
||||
if text, ok := value.(string); ok {
|
||||
return text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package rui
|
|||
|
||||
import "strings"
|
||||
|
||||
type ParentView interface {
|
||||
type ParanetView interface {
|
||||
// Views return a list of child views
|
||||
Views() []View
|
||||
}
|
||||
|
|
@ -10,15 +10,13 @@ type ParentView interface {
|
|||
// ViewsContainer - mutable list-container of Views
|
||||
type ViewsContainer interface {
|
||||
View
|
||||
ParentView
|
||||
ParanetView
|
||||
// Append appends a view to the end of the list of a view children
|
||||
Append(view View)
|
||||
// Insert inserts a view to the "index" position in the list of a view children
|
||||
Insert(view View, index int)
|
||||
// Remove removes a view from the list of a view children and return it
|
||||
RemoveView(index int) View
|
||||
// ViewIndex returns the index of view, -1 overwise
|
||||
ViewIndex(view View) int
|
||||
}
|
||||
|
||||
type viewsContainerData struct {
|
||||
|
|
@ -119,15 +117,6 @@ func (container *viewsContainerData) RemoveView(index int) View {
|
|||
return view
|
||||
}
|
||||
|
||||
func (container *viewsContainerData) ViewIndex(view View) int {
|
||||
for index, v := range container.views {
|
||||
if v == view {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (container *viewsContainerData) cssStyle(self View, builder cssBuilder) {
|
||||
container.viewData.cssStyle(self, builder)
|
||||
builder.add(`overflow`, `auto`)
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ func (bridge *wasmBridge) clearAnimation() {
|
|||
styles.Set("textContent", "")
|
||||
}
|
||||
|
||||
func (bridge *wasmBridge) canvasStart(htmlID string) {
|
||||
func (bridge *wasmBridge) cavnasStart(htmlID string) {
|
||||
if ProtocolInDebugLog {
|
||||
DebugLog("const ctx = document.getElementById('" + htmlID + "'elementId').getContext('2d');\nctx.save();")
|
||||
}
|
||||
|
|
@ -204,7 +204,7 @@ func (bridge *wasmBridge) updateCanvasProperty(property string, value any) {
|
|||
}
|
||||
}
|
||||
|
||||
func (bridge *wasmBridge) canvasFinish() {
|
||||
func (bridge *wasmBridge) cavnasFinish() {
|
||||
if !bridge.canvas.IsNull() {
|
||||
DebugLog("ctx.restore()")
|
||||
bridge.canvas.Call("restore")
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ func (bridge *wsBridge) argToString(arg any) (string, bool) {
|
|||
}
|
||||
}
|
||||
|
||||
ErrorLog("Unsupported argument type")
|
||||
ErrorLog("Unsupported agument type")
|
||||
return "", false
|
||||
}
|
||||
|
||||
|
|
@ -238,7 +238,7 @@ if (styles) {
|
|||
}`)
|
||||
}
|
||||
|
||||
func (bridge *wsBridge) canvasStart(htmlID string) {
|
||||
func (bridge *wsBridge) cavnasStart(htmlID string) {
|
||||
bridge.canvasBuffer.Reset()
|
||||
bridge.canvasBuffer.WriteString(`const ctx = getCanvasContext('`)
|
||||
bridge.canvasBuffer.WriteString(htmlID)
|
||||
|
|
@ -328,7 +328,7 @@ func (bridge *wsBridge) callCanvasImageFunc(url string, property string, funcNam
|
|||
bridge.canvasBuffer.WriteString(");\n}")
|
||||
}
|
||||
|
||||
func (bridge *wsBridge) canvasFinish() {
|
||||
func (bridge *wsBridge) cavnasFinish() {
|
||||
bridge.canvasBuffer.WriteString("\n")
|
||||
script := bridge.canvasBuffer.String()
|
||||
if ProtocolInDebugLog {
|
||||
|
|
|
|||
Loading…
Reference in New Issue