mirror of https://github.com/anoshenko/rui.git
358 lines
11 KiB
Go
358 lines
11 KiB
Go
package rui
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
)
|
||
|
||
// Constants for [ViewFilter] specific properties and events
|
||
const (
|
||
// Blur is the constant for "blur" property tag.
|
||
//
|
||
// Used by `ViewFilter`.
|
||
// Applies a Gaussian blur. The value of radius defines the value of the standard deviation to the Gaussian function, or
|
||
// how many pixels on the screen blend into each other, so a larger value will create more blur. The lacuna value for
|
||
// interpolation is 0. The parameter is specified as a length in pixels.
|
||
//
|
||
// Supported types: `float`, `int`, `string`.
|
||
//
|
||
// Internal type is `float`, other types converted to it during assignment.
|
||
Blur PropertyName = "blur"
|
||
|
||
// Brightness is the constant for "brightness" property tag.
|
||
//
|
||
// Used by `ViewFilter`.
|
||
// Applies a linear multiplier to input image, making it appear more or less bright. A value of 0% will create an image
|
||
// that is completely black. A value of 100% leaves the input unchanged. Other values are linear multipliers on the
|
||
// effect. Values of an amount over 100% are allowed, providing brighter results.
|
||
//
|
||
// Supported types: `float`, `int`, `string`.
|
||
//
|
||
// Internal type is `float`, other types converted to it during assignment.
|
||
Brightness PropertyName = "brightness"
|
||
|
||
// Contrast is the constant for "contrast" property tag.
|
||
//
|
||
// Used by `ViewFilter`.
|
||
// Adjusts the contrast of the input. A value of 0% will create an image that is completely black. A value of 100% leaves
|
||
// the input unchanged. Values of amount over 100% are allowed, providing results with less contrast.
|
||
//
|
||
// Supported types: `float`, `int`, `string`.
|
||
//
|
||
// Internal type is `float`, other types converted to it during assignment.
|
||
Contrast PropertyName = "contrast"
|
||
|
||
// DropShadow is the constant for "drop-shadow" property tag.
|
||
//
|
||
// Used by `ViewFilter`.
|
||
// Applies a drop shadow effect to the input image. A drop shadow is effectively a blurred, offset version of the input
|
||
// image's alpha mask drawn in a particular color, composited below the image. Shadow parameters are set using the
|
||
// `ViewShadow` interface.
|
||
//
|
||
// Supported types: `[]ViewShadow`, `ViewShadow`, `string`.
|
||
//
|
||
// Internal type is `[]ViewShadow`, other types converted to it during assignment.
|
||
// See `ViewShadow` description for more details.
|
||
//
|
||
// Conversion rules:
|
||
// `[]ViewShadow` - stored as is, no conversion performed.
|
||
// `ViewShadow` - converted to `[]ViewShadow`.
|
||
// `string` - string representation of `ViewShadow`. Example: "_{blur = 1em, color = black, spread-radius = 0.5em}".
|
||
DropShadow PropertyName = "drop-shadow"
|
||
|
||
// Grayscale is the constant for "grayscale" property tag.
|
||
//
|
||
// Used by `ViewFilter`.
|
||
// Converts the input image to grayscale. The value of ‘amount’ defines the proportion of the conversion. A value of 100%
|
||
// is completely grayscale. A value of 0% leaves the input unchanged. Values between 0% and 100% are linear multipliers on
|
||
// the effect.
|
||
//
|
||
// Supported types: `float`, `int`, `string`.
|
||
//
|
||
// Internal type is `float`, other types converted to it during assignment.
|
||
Grayscale PropertyName = "grayscale"
|
||
|
||
// HueRotate is the constant for "hue-rotate" property tag.
|
||
//
|
||
// Used by `ViewFilter`.
|
||
// Applies a hue rotation on the input image. The value of ‘angle’ defines the number of degrees around the color circle
|
||
// the input samples will be adjusted. A value of 0deg leaves the input unchanged. If the ‘angle’ parameter is missing, a
|
||
// value of 0deg is used. Though there is no maximum value, the effect of values above 360deg wraps around.
|
||
//
|
||
// Supported types: `AngleUnit`, `string`, `float`, `int`.
|
||
//
|
||
// Internal type is `AngleUnit`, other types will be converted to it during assignment.
|
||
// See `AngleUnit` description for more details.
|
||
//
|
||
// Conversion rules:
|
||
// `AngleUnit` - stored as is, no conversion performed.
|
||
// `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created.
|
||
// `float` - a new `AngleUnit` value will be created with `Radian` as a type.
|
||
// `int` - a new `AngleUnit` value will be created with `Radian` as a type.
|
||
HueRotate PropertyName = "hue-rotate"
|
||
|
||
// Invert is the constant for "invert" property tag.
|
||
//
|
||
// Used by `ViewFilter`.
|
||
// Inverts the samples in the input image. The value of ‘amount’ defines the proportion of the conversion. A value of 100%
|
||
// is completely inverted. A value of 0% leaves the input unchanged. Values between 0% and 100% are linear multipliers on
|
||
// the effect.
|
||
//
|
||
// Supported types: `float64`, `int`, `string`.
|
||
//
|
||
// Internal type is `float`, other types converted to it during assignment.
|
||
Invert PropertyName = "invert"
|
||
|
||
// Saturate is the constant for "saturate" property tag.
|
||
//
|
||
// Used by `ViewFilter`.
|
||
// Saturates the input image. The value of ‘amount’ defines the proportion of the conversion. A value of 0% is completely
|
||
// un-saturated. A value of 100% leaves the input unchanged. Other values are linear multipliers on the effect. Values of
|
||
// amount over 100% are allowed, providing super-saturated results.
|
||
//
|
||
// Supported types: `float`, `int`, `string`.
|
||
//
|
||
// Internal type is `float`, other types converted to it during assignment.
|
||
Saturate PropertyName = "saturate"
|
||
|
||
// Sepia is the constant for "sepia" property tag.
|
||
//
|
||
// Used by `ViewFilter`.
|
||
// Converts the input image to sepia. The value of ‘amount’ defines the proportion of the conversion. A value of 100% is
|
||
// completely sepia. A value of 0% leaves the input unchanged. Values between 0% and 100% are linear multipliers on the
|
||
// effect.
|
||
//
|
||
// Supported types: `float`, `int`, `string`.
|
||
//
|
||
// Internal type is `float`, other types converted to it during assignment.
|
||
Sepia PropertyName = "sepia"
|
||
)
|
||
|
||
// ViewFilter defines an applied to a View a graphical effects like blur or color shift.
|
||
// Allowable properties are Blur, Brightness, Contrast, DropShadow, Grayscale, HueRotate, Invert, Opacity, Saturate, and Sepia
|
||
type ViewFilter interface {
|
||
Properties
|
||
fmt.Stringer
|
||
stringWriter
|
||
cssStyle(session Session) string
|
||
}
|
||
|
||
type viewFilter struct {
|
||
dataProperty
|
||
}
|
||
|
||
// NewViewFilter creates the new ViewFilter
|
||
func NewViewFilter(params Params) ViewFilter {
|
||
if len(params) > 0 {
|
||
filter := new(viewFilter)
|
||
filter.init()
|
||
for tag, value := range params {
|
||
if !filter.Set(tag, value) {
|
||
return nil
|
||
}
|
||
}
|
||
return filter
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func newViewFilter(obj DataObject) ViewFilter {
|
||
filter := new(viewFilter)
|
||
filter.init()
|
||
for i := 0; i < obj.PropertyCount(); i++ {
|
||
if node := obj.Property(i); node != nil {
|
||
tag := node.Tag()
|
||
switch node.Type() {
|
||
case TextNode:
|
||
filter.Set(PropertyName(tag), node.Text())
|
||
|
||
case ObjectNode:
|
||
if tag == string(HueRotate) {
|
||
// TODO
|
||
} else {
|
||
ErrorLog(`Invalid value of "` + tag + `"`)
|
||
}
|
||
|
||
default:
|
||
ErrorLog(`Invalid value of "` + tag + `"`)
|
||
}
|
||
}
|
||
}
|
||
|
||
if len(filter.properties) > 0 {
|
||
return filter
|
||
}
|
||
ErrorLog("Empty view filter")
|
||
return nil
|
||
}
|
||
|
||
func (filter *viewFilter) init() {
|
||
filter.dataProperty.init()
|
||
filter.set = viewFilterSet
|
||
filter.supportedProperties = []PropertyName{Blur, Brightness, Contrast, Saturate, Grayscale, Invert, Opacity, Sepia, HueRotate, DropShadow}
|
||
}
|
||
|
||
func viewFilterSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||
switch tag {
|
||
case Blur, Brightness, Contrast, Saturate:
|
||
return setFloatProperty(properties, tag, value, 0, 10000)
|
||
|
||
case Grayscale, Invert, Opacity, Sepia:
|
||
return setFloatProperty(properties, tag, value, 0, 100)
|
||
|
||
case HueRotate:
|
||
return setAngleProperty(properties, tag, value)
|
||
|
||
case DropShadow:
|
||
if setShadowProperty(properties, tag, value) {
|
||
return []PropertyName{tag}
|
||
}
|
||
}
|
||
|
||
ErrorLogF(`"%s" property is not supported by the view filter`, tag)
|
||
return nil
|
||
}
|
||
|
||
func (filter *viewFilter) String() string {
|
||
return runStringWriter(filter)
|
||
}
|
||
|
||
func (filter *viewFilter) writeString(buffer *strings.Builder, indent string) {
|
||
buffer.WriteString("filter { ")
|
||
comma := false
|
||
tags := filter.AllTags()
|
||
for _, tag := range tags {
|
||
if value, ok := filter.properties[tag]; ok {
|
||
if comma {
|
||
buffer.WriteString(", ")
|
||
}
|
||
buffer.WriteString(string(tag))
|
||
buffer.WriteString(" = ")
|
||
writePropertyValue(buffer, tag, value, indent)
|
||
comma = true
|
||
}
|
||
}
|
||
buffer.WriteString(" }")
|
||
}
|
||
|
||
func (filter *viewFilter) cssStyle(session Session) string {
|
||
buffer := allocStringBuilder()
|
||
defer freeStringBuilder(buffer)
|
||
|
||
if value, ok := floatTextProperty(filter, Blur, session, 0); ok {
|
||
buffer.WriteString(string(Blur))
|
||
buffer.WriteRune('(')
|
||
buffer.WriteString(value)
|
||
buffer.WriteString("px)")
|
||
}
|
||
|
||
for _, tag := range []PropertyName{Brightness, Contrast, Saturate, Grayscale, Invert, Opacity, Sepia} {
|
||
if value, ok := floatTextProperty(filter, tag, session, 0); ok {
|
||
if buffer.Len() > 0 {
|
||
buffer.WriteRune(' ')
|
||
}
|
||
buffer.WriteString(string(tag))
|
||
buffer.WriteRune('(')
|
||
buffer.WriteString(value)
|
||
buffer.WriteString("%)")
|
||
}
|
||
}
|
||
|
||
if value, ok := angleProperty(filter, HueRotate, session); ok {
|
||
if buffer.Len() > 0 {
|
||
buffer.WriteRune(' ')
|
||
}
|
||
buffer.WriteString(string(HueRotate))
|
||
buffer.WriteRune('(')
|
||
buffer.WriteString(value.cssString())
|
||
buffer.WriteRune(')')
|
||
}
|
||
|
||
var lead string
|
||
if buffer.Len() > 0 {
|
||
lead = " drop-shadow("
|
||
} else {
|
||
lead = "drop-shadow("
|
||
}
|
||
|
||
for _, shadow := range getShadows(filter, DropShadow) {
|
||
if shadow.cssTextStyle(buffer, session, lead) {
|
||
buffer.WriteRune(')')
|
||
lead = " drop-shadow("
|
||
}
|
||
}
|
||
|
||
return buffer.String()
|
||
}
|
||
|
||
func setFilterProperty(properties Properties, tag PropertyName, value any) []PropertyName {
|
||
switch value := value.(type) {
|
||
case ViewFilter:
|
||
properties.setRaw(tag, value)
|
||
return []PropertyName{tag}
|
||
|
||
case string:
|
||
if obj := NewDataObject(value); obj == nil {
|
||
if filter := newViewFilter(obj); filter != nil {
|
||
properties.setRaw(tag, filter)
|
||
return []PropertyName{tag}
|
||
}
|
||
}
|
||
|
||
case DataObject:
|
||
if filter := newViewFilter(value); filter != nil {
|
||
properties.setRaw(tag, filter)
|
||
return []PropertyName{tag}
|
||
}
|
||
|
||
case DataValue:
|
||
if value.IsObject() {
|
||
if filter := newViewFilter(value.Object()); filter != nil {
|
||
properties.setRaw(tag, filter)
|
||
return []PropertyName{tag}
|
||
}
|
||
}
|
||
}
|
||
|
||
notCompatibleType(tag, value)
|
||
return nil
|
||
}
|
||
|
||
// GetFilter returns a View graphical effects like blur or color shift.
|
||
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
|
||
func GetFilter(view View, subviewID ...string) ViewFilter {
|
||
if view = getSubview(view, subviewID); view != nil {
|
||
if value := view.getRaw(Filter); value != nil {
|
||
if filter, ok := value.(ViewFilter); ok {
|
||
return filter
|
||
}
|
||
}
|
||
if value := valueFromStyle(view, Filter); value != nil {
|
||
if filter, ok := value.(ViewFilter); ok {
|
||
return filter
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetBackdropFilter returns the area behind a View graphical effects like blur or color shift.
|
||
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
|
||
func GetBackdropFilter(view View, subviewID ...string) ViewFilter {
|
||
if view = getSubview(view, subviewID); view != nil {
|
||
if value := view.getRaw(BackdropFilter); value != nil {
|
||
if filter, ok := value.(ViewFilter); ok {
|
||
return filter
|
||
}
|
||
}
|
||
if value := valueFromStyle(view, BackdropFilter); value != nil {
|
||
if filter, ok := value.(ViewFilter); ok {
|
||
return filter
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|