rui_orig/imageView.go

447 lines
13 KiB
Go
Raw Normal View History

2021-09-07 17:36:50 +03:00
package rui
import (
2022-11-02 20:08:02 +03:00
"encoding/base64"
2021-09-07 17:36:50 +03:00
"fmt"
2022-11-02 20:08:02 +03:00
"path/filepath"
"runtime"
2021-09-07 17:36:50 +03:00
"strings"
)
const (
// LoadedEvent is the constant for the "loaded-event" property tag.
// The "loaded-event" event occurs event occurs when the image has been loaded.
LoadedEvent = "loaded-event"
// ErrorEvent is the constant for the "error-event" property tag.
// The "error-event" event occurs event occurs when the image loading failed.
ErrorEvent = "error-event"
2021-09-07 17:36:50 +03:00
// NoneFit - value of the "object-fit" property of an ImageView. The replaced content is not resized
NoneFit = 0
// ContainFit - value of the "object-fit" property of an ImageView. The replaced content
// is scaled to maintain its aspect ratio while fitting within the elements content box.
// The entire object is made to fill the box, while preserving its aspect ratio, so the object
// will be "letterboxed" if its aspect ratio does not match the aspect ratio of the box.
ContainFit = 1
// CoverFit - value of the "object-fit" property of an ImageView. The replaced content
// is sized to maintain its aspect ratio while filling the elements entire content box.
// If the object's aspect ratio does not match the aspect ratio of its box, then the object will be clipped to fit.
CoverFit = 2
// FillFit - value of the "object-fit" property of an ImageView. The replaced content is sized
// to fill the elements content box. The entire object will completely fill the box.
// If the object's aspect ratio does not match the aspect ratio of its box, then the object will be stretched to fit.
FillFit = 3
// ScaleDownFit - value of the "object-fit" property of an ImageView. The content is sized as
// if NoneFit or ContainFit were specified, whichever would result in a smaller concrete object size.
ScaleDownFit = 4
)
// ImageView - image View
type ImageView interface {
View
// NaturalSize returns the intrinsic, density-corrected size (width, height) of the image in pixels.
// If the image hasn't been loaded yet or an load error has occurred, then (0, 0) is returned.
NaturalSize() (float64, float64)
// CurrentSource() return the full URL of the image currently visible in the ImageView.
// If the image hasn't been loaded yet or an load error has occurred, then "" is returned.
CurrentSource() string
2021-09-07 17:36:50 +03:00
}
type imageViewData struct {
viewData
naturalWidth float64
naturalHeight float64
currentSrc string
2021-09-07 17:36:50 +03:00
}
// NewImageView create new ImageView object and return it
func NewImageView(session Session, params Params) ImageView {
view := new(imageViewData)
view.init(session)
2021-09-07 17:36:50 +03:00
setInitParams(view, params)
return view
}
func newImageView(session Session) View {
return NewImageView(session, nil)
}
// Init initialize fields of imageView by default values
func (imageView *imageViewData) init(session Session) {
imageView.viewData.init(session)
2021-09-07 17:36:50 +03:00
imageView.tag = "ImageView"
//imageView.systemClass = "ruiImageView"
}
2022-05-22 12:54:02 +03:00
func (imageView *imageViewData) String() string {
return getViewString(imageView)
}
2021-09-07 17:36:50 +03:00
func (imageView *imageViewData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag {
case "source":
tag = Source
2022-11-11 12:55:58 +03:00
case "src-set", "source-set":
tag = SrcSet
2021-09-07 17:36:50 +03:00
case VerticalAlign:
tag = ImageVerticalAlign
case HorizontalAlign:
tag = ImageHorizontalAlign
case altTag:
2021-09-07 17:36:50 +03:00
tag = AltText
}
return tag
}
func (imageView *imageViewData) Remove(tag string) {
imageView.remove(imageView.normalizeTag(tag))
}
func (imageView *imageViewData) remove(tag string) {
imageView.viewData.remove(tag)
if imageView.created {
switch tag {
case Source:
2022-10-30 17:22:33 +03:00
imageView.session.updateProperty(imageView.htmlID(), "src", "")
imageView.session.removeProperty(imageView.htmlID(), "srcset")
2021-09-07 17:36:50 +03:00
case AltText:
updateInnerHTML(imageView.htmlID(), imageView.session)
2021-09-07 17:36:50 +03:00
case ImageVerticalAlign, ImageHorizontalAlign:
updateCSSStyle(imageView.htmlID(), imageView.session)
}
2021-09-07 17:36:50 +03:00
}
}
2022-07-26 18:36:00 +03:00
func (imageView *imageViewData) Set(tag string, value any) bool {
2021-09-07 17:36:50 +03:00
return imageView.set(imageView.normalizeTag(tag), value)
}
2022-07-26 18:36:00 +03:00
func (imageView *imageViewData) set(tag string, value any) bool {
2021-09-07 17:36:50 +03:00
if value == nil {
imageView.remove(tag)
return true
}
switch tag {
case Source:
if text, ok := value.(string); ok {
2022-11-11 12:55:58 +03:00
imageView.properties[tag] = text
if imageView.created {
2022-11-02 20:08:02 +03:00
src, srcset := imageView.src(text)
2022-10-30 17:22:33 +03:00
imageView.session.updateProperty(imageView.htmlID(), "src", src)
2022-11-02 20:08:02 +03:00
if srcset != "" {
2022-10-30 17:22:33 +03:00
imageView.session.updateProperty(imageView.htmlID(), "srcset", srcset)
} else {
2022-10-30 17:22:33 +03:00
imageView.session.removeProperty(imageView.htmlID(), "srcset")
}
2021-09-07 17:36:50 +03:00
}
imageView.propertyChangedEvent(Source)
2021-09-07 17:36:50 +03:00
return true
}
notCompatibleType(Source, value)
2021-09-07 17:36:50 +03:00
2022-11-11 12:55:58 +03:00
case SrcSet:
if text, ok := value.(string); ok {
if text == "" {
delete(imageView.properties, tag)
} else {
imageView.properties[tag] = text
}
if imageView.created {
_, srcset := imageView.src(text)
if srcset != "" {
imageView.session.updateProperty(imageView.htmlID(), "srcset", srcset)
} else {
imageView.session.removeProperty(imageView.htmlID(), "srcset")
}
}
imageView.propertyChangedEvent(Source)
return true
}
notCompatibleType(Source, value)
2021-09-07 17:36:50 +03:00
case AltText:
if text, ok := value.(string); ok {
imageView.properties[AltText] = text
if imageView.created {
updateInnerHTML(imageView.htmlID(), imageView.session)
}
imageView.propertyChangedEvent(Source)
2021-09-07 17:36:50 +03:00
return true
}
notCompatibleType(tag, value)
case LoadedEvent, ErrorEvent:
2022-07-27 20:31:57 +03:00
if listeners, ok := valueToNoParamListeners[ImageView](value); ok {
if listeners == nil {
delete(imageView.properties, tag)
} else {
imageView.properties[tag] = listeners
}
return true
}
2021-09-07 17:36:50 +03:00
default:
if imageView.viewData.set(tag, value) {
if imageView.created {
switch tag {
case ImageVerticalAlign, ImageHorizontalAlign:
updateCSSStyle(imageView.htmlID(), imageView.session)
}
2021-09-07 17:36:50 +03:00
}
return true
}
}
return false
}
2022-07-26 18:36:00 +03:00
func (imageView *imageViewData) Get(tag string) any {
2021-09-07 17:36:50 +03:00
return imageView.viewData.get(imageView.normalizeTag(tag))
}
func (imageView *imageViewData) imageListeners(tag string) []func(ImageView) {
if value := imageView.getRaw(tag); value != nil {
if listeners, ok := value.([]func(ImageView)); ok {
return listeners
}
}
return []func(ImageView){}
}
2021-09-07 17:36:50 +03:00
func (imageView *imageViewData) srcSet(path string) string {
2022-11-11 12:55:58 +03:00
if value := imageView.getRaw(SrcSet); value != nil {
if text, ok := value.(string); ok {
srcset := strings.Split(text, ",")
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
for i, src := range srcset {
if i > 0 {
buffer.WriteString(", ")
}
src = strings.Trim(src, " \t\n")
buffer.WriteString(src)
if index := strings.LastIndex(src, "@"); index > 0 {
if ext := strings.LastIndex(src, "."); ext > index {
buffer.WriteRune(' ')
buffer.WriteString(src[index+1 : ext])
}
} else {
buffer.WriteString(" 1x")
}
}
return buffer.String()
}
}
2021-09-07 17:36:50 +03:00
if srcset, ok := resources.imageSrcSets[path]; ok {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
for i, src := range srcset {
if i > 0 {
buffer.WriteString(", ")
}
buffer.WriteString(src.path)
buffer.WriteString(fmt.Sprintf(" %gx", src.scale))
}
return buffer.String()
}
return ""
}
func (imageView *imageViewData) htmlTag() string {
return "img"
}
2022-11-02 20:08:02 +03:00
func (imageView *imageViewData) src(src string) (string, string) {
if src != "" && src[0] == '@' {
if image, ok := imageView.Session().ImageConstant(src[1:]); ok {
src = image
} else {
src = ""
}
}
if src != "" {
srcset := imageView.srcSet(src)
2022-11-08 16:31:21 +03:00
if runtime.GOOS == "js" && wasmMediaResources {
2022-11-02 20:08:02 +03:00
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 "", ""
2021-09-07 17:36:50 +03:00
}
func (imageView *imageViewData) htmlProperties(self View, buffer *strings.Builder) {
2022-11-02 20:08:02 +03:00
2021-09-07 17:36:50 +03:00
imageView.viewData.htmlProperties(self, buffer)
2022-04-23 18:13:35 +03:00
if imageResource, ok := imageProperty(imageView, Source, imageView.Session()); ok && imageResource != "" {
2022-11-02 20:08:02 +03:00
if src, srcset := imageView.src(imageResource); src != "" {
2022-04-23 18:13:35 +03:00
buffer.WriteString(` src="`)
2022-11-02 20:08:02 +03:00
buffer.WriteString(src)
2021-09-07 17:36:50 +03:00
buffer.WriteString(`"`)
2022-11-02 20:08:02 +03:00
if srcset != "" {
2022-04-23 18:13:35 +03:00
buffer.WriteString(` srcset="`)
buffer.WriteString(srcset)
buffer.WriteString(`"`)
}
2021-09-07 17:36:50 +03:00
}
}
2022-04-17 11:21:01 +03:00
if text := GetImageViewAltText(imageView); text != "" {
2022-04-17 11:21:01 +03:00
buffer.WriteString(` alt="`)
2022-10-29 21:08:51 +03:00
buffer.WriteString(text)
2022-04-17 11:21:01 +03:00
buffer.WriteString(`"`)
}
buffer.WriteString(` onload="imageLoaded(this, event)"`)
if len(imageView.imageListeners(ErrorEvent)) > 0 {
buffer.WriteString(` onerror="imageError(this, event)"`)
}
2021-09-07 17:36:50 +03:00
}
func (imageView *imageViewData) cssStyle(self View, builder cssBuilder) {
imageView.viewData.cssStyle(self, builder)
if value, ok := enumProperty(imageView, Fit, imageView.session, 0); ok {
builder.add("object-fit", enumProperties[Fit].cssValues[value])
} else {
builder.add("object-fit", "none")
}
vAlign := GetImageViewVerticalAlign(imageView)
hAlign := GetImageViewHorizontalAlign(imageView)
2021-09-07 17:36:50 +03:00
if vAlign != CenterAlign || hAlign != CenterAlign {
var position string
switch hAlign {
case LeftAlign:
position = "left"
case RightAlign:
position = "right"
default:
position = "center"
}
switch vAlign {
case TopAlign:
position += " top"
case BottomAlign:
position += " bottom"
default:
position += " center"
}
builder.add("object-position", position)
}
}
func (imageView *imageViewData) handleCommand(self View, command string, data DataObject) bool {
switch command {
case "imageViewError":
for _, listener := range imageView.imageListeners(ErrorEvent) {
listener(imageView)
}
case "imageViewLoaded":
imageView.naturalWidth = dataFloatProperty(data, "natural-width")
imageView.naturalHeight = dataFloatProperty(data, "natural-height")
imageView.currentSrc, _ = data.PropertyValue("current-src")
for _, listener := range imageView.imageListeners(LoadedEvent) {
listener(imageView)
}
default:
return imageView.viewData.handleCommand(self, command, data)
}
return true
}
func (imageView *imageViewData) NaturalSize() (float64, float64) {
return imageView.naturalWidth, imageView.naturalHeight
}
func (imageView *imageViewData) CurrentSource() string {
return imageView.currentSrc
}
2021-09-07 17:36:50 +03:00
// GetImageViewSource returns the image URL of an ImageView subview.
// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned
func GetImageViewSource(view View, subviewID ...string) string {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
2021-09-07 17:36:50 +03:00
}
2022-04-23 18:13:35 +03:00
if view != nil {
if image, ok := imageProperty(view, Source, view.Session()); ok {
return image
}
}
2021-09-07 17:36:50 +03:00
return ""
}
// GetImageViewAltText returns an alternative text description of an ImageView subview.
// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned
func GetImageViewAltText(view View, subviewID ...string) string {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
2022-05-17 10:46:00 +03:00
}
if view != nil {
if value := view.getRaw(AltText); value != nil {
if text, ok := value.(string); ok {
text, _ = view.Session().GetString(text)
return text
}
}
2021-09-07 17:36:50 +03:00
}
return ""
}
// GetImageViewFit returns how the content of a replaced ImageView subview:
// NoneFit (0), ContainFit (1), CoverFit (2), FillFit (3), or ScaleDownFit (4).
// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned
func GetImageViewFit(view View, subviewID ...string) int {
2022-07-28 12:41:50 +03:00
return enumStyledProperty(view, subviewID, Fit, NoneFit, false)
2021-09-07 17:36:50 +03:00
}
// GetImageViewVerticalAlign return the vertical align of an ImageView 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 GetImageViewVerticalAlign(view View, subviewID ...string) int {
2022-07-28 12:41:50 +03:00
return enumStyledProperty(view, subviewID, ImageVerticalAlign, LeftAlign, false)
2021-09-07 17:36:50 +03:00
}
// GetImageViewHorizontalAlign return the vertical align of an ImageView 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 GetImageViewHorizontalAlign(view View, subviewID ...string) int {
2022-07-28 12:41:50 +03:00
return enumStyledProperty(view, subviewID, ImageHorizontalAlign, LeftAlign, false)
2021-09-07 17:36:50 +03:00
}