Added "mask" property

This commit is contained in:
Alexei Anoshenko 2024-11-27 10:32:13 +02:00
parent a5577273e6
commit 27beb1e679
8 changed files with 289 additions and 74 deletions

View File

@ -11,6 +11,9 @@
"push-scale-x", "push-scale-y", "push-scale-z", "push-translate-x", "push-translate-y", "push-translate-z" StackLayout properties.
* Added "show-opacity", "show-transform", "show-duration", and "show-timing" Popup properties.
* Added GetPushTransform, GetPushDuration, GetPushTiming, and IsMoveToFrontAnimation functions.
* Added "mask", "mask-clip", "mask-origin", and "background-origin" properties.
* Added GetBackground, GetMask, GetBackgroundClip,GetBackgroundOrigin, GetMaskClip, and GetMaskOrigin functions.
* Renamed BorderBoxClip, PaddingBoxClip, and ContentBoxClip constants to BorderBox, PaddingBox, and ContentBox.
* Added LineJoin type. Type of constants MiterJoin, RoundJoin, and BevelJoin changed to LineJoin. Type of Canvas.SetLineJoin function argument changed to LineJoin.
* Added LineCap type. Type of constants ButtCap, RoundCap, and SquareCap changed to LineCap. Type of Canvas.SetLineCap function argument changed to LineCap.

View File

@ -12,25 +12,30 @@ const (
// will not necessarily be entirely covered). The position of the non-repeated
// background image is defined by the background-position CSS property.
NoRepeat = 0
// RepeatXY is value of the Repeat property of an background image:
// The image is repeated as much as needed to cover the whole background
// image painting area. The last image will be clipped if it doesn't fit.
RepeatXY = 1
// RepeatX is value of the Repeat property of an background image:
// The image is repeated horizontally as much as needed to cover
// the whole width background image painting area. The image is not repeated vertically.
// The last image will be clipped if it doesn't fit.
RepeatX = 2
// RepeatY is value of the Repeat property of an background image:
// The image is repeated vertically as much as needed to cover
// the whole height background image painting area. The image is not repeated horizontally.
// The last image will be clipped if it doesn't fit.
RepeatY = 3
// RepeatRound is value of the Repeat property of an background image:
// As the allowed space increases in size, the repeated images will stretch (leaving no gaps)
// until there is room (space left >= half of the image width) for another one to be added.
// When the next image is added, all of the current ones compress to allow room.
RepeatRound = 4
// RepeatSpace is value of the Repeat property of an background image:
// The image is repeated as much as possible without clipping. The first and last images
// are pinned to either side of the element, and whitespace is distributed evenly between the images.
@ -40,10 +45,12 @@ const (
// The background is fixed relative to the element itself and does not scroll with its contents.
// (It is effectively attached to the element's border.)
ScrollAttachment = 0
// FixedAttachment is value of the Attachment property of an background image:
// The background is fixed relative to the viewport. Even if an element has
// a scrolling mechanism, the background doesn't move with the element.
FixedAttachment = 1
// LocalAttachment is value of the Attachment property of an background image:
// The background is fixed relative to the element's contents. If the element has a scrolling mechanism,
// the background scrolls with the element's contents, and the background painting area
@ -51,15 +58,38 @@ const (
// rather than to the border framing them.
LocalAttachment = 2
// BorderBoxClip is value of the BackgroundClip property:
// The background extends to the outside edge of the border (but underneath the border in z-ordering).
BorderBoxClip = 0
// PaddingBoxClip is value of the BackgroundClip property:
// The background extends to the outside edge of the padding. No background is drawn beneath the border.
PaddingBoxClip = 1
// ContentBoxClip is value of the BackgroundClip property:
// The background is painted within (clipped to) the content box.
ContentBoxClip = 2
// BorderBox is the value of the following properties:
//
// * BackgroundClip - The background extends to the outside edge of the border (but underneath the border in z-ordering).
//
// * BackgroundOrigin - The background is positioned relative to the border box.
//
// * MaskClip - The painted content is clipped to the border box.
//
// * MaskOrigin - The mask is positioned relative to the border box.
BorderBox = 0
// PaddingBox is value of the BackgroundClip and MaskClip property:
//
// * BackgroundClip - The background extends to the outside edge of the padding. No background is drawn beneath the border.
//
// * BackgroundOrigin - The background is positioned relative to the padding box.
//
// * MaskClip - The painted content is clipped to the padding box.
//
// * MaskOrigin - The mask is positioned relative to the padding box.
PaddingBox = 1
// ContentBox is value of the BackgroundClip and MaskClip property:
//
// * BackgroundClip - The background is painted within (clipped to) the content box.
//
// * BackgroundOrigin - The background is positioned relative to the content box.
//
// * MaskClip - The painted content is clipped to the content box.
//
// * MaskOrigin - The mask is positioned relative to the content box.
ContentBox = 2
)
// BackgroundElement describes the background element
@ -253,75 +283,198 @@ func (image *backgroundImage) String() string {
return runStringWriter(image)
}
func setBackgroundProperty(properties Properties, value any) []PropertyName {
background := []BackgroundElement{}
error := func() []PropertyName {
notCompatibleType(Background, value)
return nil
}
func parseBackgroundValue(value any) []BackgroundElement {
switch value := value.(type) {
case BackgroundElement:
background = []BackgroundElement{value}
return []BackgroundElement{value}
case []BackgroundElement:
background = value
return value
case []DataValue:
background := []BackgroundElement{}
for _, el := range value {
if el.IsObject() {
if element := createBackground(el.Object()); element != nil {
background = append(background, element)
} else {
return error()
return nil
}
} else if obj := ParseDataText(el.Value()); obj != nil {
if element := createBackground(obj); element != nil {
background = append(background, element)
} else {
return error()
return nil
}
} else {
return error()
return nil
}
}
return background
case DataObject:
if element := createBackground(value); element != nil {
background = []BackgroundElement{element}
} else {
return error()
return []BackgroundElement{element}
}
case []DataObject:
background := []BackgroundElement{}
for _, obj := range value {
if element := createBackground(obj); element != nil {
background = append(background, element)
} else {
return error()
return nil
}
}
return background
case string:
if obj := ParseDataText(value); obj != nil {
if element := createBackground(obj); element != nil {
background = []BackgroundElement{element}
} else {
return error()
return []BackgroundElement{element}
}
} else {
return error()
}
}
return nil
}
func setBackgroundProperty(properties Properties, tag PropertyName, value any) []PropertyName {
background := parseBackgroundValue(value)
if background == nil {
notCompatibleType(tag, value)
return nil
}
if len(background) > 0 {
properties.setRaw(Background, background)
} else if properties.getRaw(Background) != nil {
properties.setRaw(Background, nil)
properties.setRaw(tag, background)
} else if properties.getRaw(tag) != nil {
properties.setRaw(tag, nil)
} else {
return []PropertyName{}
}
return []PropertyName{Background}
return []PropertyName{tag}
}
func backgroundCSS(properties Properties, session Session) string {
if value := properties.getRaw(Background); value != nil {
if backgrounds, ok := value.([]BackgroundElement); ok && len(backgrounds) > 0 {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
for _, background := range backgrounds {
if value := background.cssStyle(session); value != "" {
if buffer.Len() > 0 {
buffer.WriteString(", ")
}
buffer.WriteString(value)
}
}
if buffer.Len() > 0 {
backgroundColor, _ := colorProperty(properties, BackgroundColor, session)
if backgroundColor != 0 {
buffer.WriteRune(' ')
buffer.WriteString(backgroundColor.cssString())
}
return buffer.String()
}
}
}
return ""
}
func maskCSS(properties Properties, session Session) string {
if value := properties.getRaw(Mask); value != nil {
if backgrounds, ok := value.([]BackgroundElement); ok && len(backgrounds) > 0 {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
for _, background := range backgrounds {
if value := background.cssStyle(session); value != "" {
if buffer.Len() > 0 {
buffer.WriteString(", ")
}
buffer.WriteString(value)
}
}
return buffer.String()
}
}
return ""
}
func backgroundStyledPropery(view View, subviewID []string, tag PropertyName) []BackgroundElement {
var background []BackgroundElement = nil
if view = getSubview(view, subviewID); view != nil {
if value := view.getRaw(tag); value != nil {
if backgrounds, ok := value.([]BackgroundElement); ok {
background = backgrounds
}
} else if value := valueFromStyle(view, tag); value != nil {
background = parseBackgroundValue(value)
}
}
if count := len(background); count > 0 {
result := make([]BackgroundElement, count)
copy(result, background)
return result
}
return []BackgroundElement{}
}
// GetBackground returns the view background.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetBackground(view View, subviewID ...string) []BackgroundElement {
return backgroundStyledPropery(view, subviewID, Background)
}
// GetMask returns the view mask.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetMask(view View, subviewID ...string) []BackgroundElement {
return backgroundStyledPropery(view, subviewID, Mask)
}
// GetBackgroundClip returns a "background-clip" of the subview. Returns one of next values:
//
// BorderBox (0), PaddingBox (1), ContentBox (2)
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetBackgroundClip(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, BackgroundClip, 0, false)
}
// GetBackgroundOrigin returns a "background-origin" of the subview. Returns one of next values:
//
// BorderBox (0), PaddingBox (1), ContentBox (2)
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetBackgroundOrigin(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, BackgroundOrigin, 0, false)
}
// GetMaskClip returns a "mask-clip" of the subview. Returns one of next values:
//
// BorderBox (0), PaddingBox (1), ContentBox (2)
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetMaskClip(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, MaskClip, 0, false)
}
// GetMaskOrigin returns a "mask-origin" of the subview. Returns one of next values:
//
// BorderBox (0), PaddingBox (1), ContentBox (2)
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetMaskOrigin(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, MaskOrigin, 0, false)
}

View File

@ -220,6 +220,11 @@ func (point *BackgroundGradientPoint) color(session Session) (Color, bool) {
case Color:
return color, true
default:
if n, ok := isInt(point.Color); ok {
return Color(n), true
}
}
}
return 0, false
@ -315,7 +320,7 @@ func (gradient *backgroundGradient) writeGradient(session Session, buffer *strin
func (gradient *backgroundLinearGradient) init() {
gradient.backgroundElement.init()
gradient.set = backgroundLinearGradientSet
gradient.supportedProperties = append(gradient.supportedProperties, Direction)
gradient.supportedProperties = []PropertyName{Direction, Repeating, Gradient}
}
@ -422,9 +427,9 @@ func (gradient *backgroundRadialGradient) init() {
gradient.backgroundElement.init()
gradient.normalize = normalizeRadialGradientTag
gradient.set = backgroundRadialGradientSet
gradient.supportedProperties = append(gradient.supportedProperties, []PropertyName{
RadialGradientRadius, RadialGradientShape, CenterX, CenterY,
}...)
gradient.supportedProperties = []PropertyName{
RadialGradientRadius, RadialGradientShape, CenterX, CenterY, Gradient, Repeating,
}
}
func (gradient *backgroundRadialGradient) Tag() string {

View File

@ -594,6 +594,21 @@ const (
// `string` - must contain text representation of background element(s) like in resource files.
Background PropertyName = "background"
// Mask is the constant for "mask" property tag.
//
// Used by `View`.
// Set one or more images and/or gradients as the view mask.
// As mask is used only alpha channel of images and/or gradients.
//
// Supported types: `BackgroundElement`, `[]BackgroundElement`, `string`.
//
// Internal type is `[]BackgroundElement`, other types converted to it during assignment.
// See `BackgroundElement` description for more details.
//
// Conversion rules:
// `string` - must contain text representation of background element(s) like in resource files.
Mask PropertyName = "mask"
// Cursor is the constant for "cursor" property tag.
//
// Used by `View`.
@ -1825,6 +1840,45 @@ const (
// `2`(`ContentBoxClip`) or "content-box" - The background is painted inside(clipped) of the content box.
BackgroundClip PropertyName = "background-clip"
// BackgroundOrigin is the constant for "background-origin" property tag.
//
// Used by `View`.
// Determines the background's origin: from the border start, inside the border, or inside the padding.
//
// Supported types: `int`, `string`.
//
// Values:
// `0`(`BorderBox`) or "border-box" - The background is positioned relative to the border box.
// `1`(`PaddingBox`) or "padding-box" - The background is positioned relative to the padding box.
// `2`(`ContentBox`) or "content-box" - The background is positioned relative to the content box.
BackgroundOrigin PropertyName = "background-origin"
// MaskClip is the constant for "mask-clip" property tag.
//
// Used by `View`.
// Determines how image/gradient masks will be used below the box borders.
//
// Supported types: `int`, `string`.
//
// Values:
// `0`(`BorderBox`) or "border-box" - The mask extends to the outer edge of the border.
// `1`(`PaddingBox`) or "padding-box" - The mask extends to the outer edge of the padding.
// `2`(`ContentBox`) or "content-box" - The mask is used inside(clipped) of the content box.
MaskClip PropertyName = "mask-clip"
// MaskOrigin is the constant for "mask-origin" property tag.
//
// Used by `View`.
// Determines the mask's origin: from the border start, inside the border, or inside the padding.
//
// Supported types: `int`, `string`.
//
// Values:
// `0`(`BorderBox`) or "border-box" - The mask is positioned relative to the border box.
// `1`(`PaddingBox`) or "padding-box" - The mask is positioned relative to the padding box.
// `2`(`ContentBox`) or "content-box" - The mask is positioned relative to the content box.
MaskOrigin PropertyName = "mask-origin"
// Gradient is the constant for "gradient" property tag.
//
// Used by `BackgroundElement`.

View File

@ -164,11 +164,13 @@ var sizeProperties = map[PropertyName]string{
CenterY: string(CenterX),
}
var enumProperties = map[PropertyName]struct {
type enumPropertyData struct {
values []string
cssTag string
cssValues []string
}{
}
var enumProperties = map[PropertyName]enumPropertyData{
Semantics: {
[]string{"default", "article", "section", "aside", "header", "main", "footer", "navigation", "figure", "figure-caption", "button", "p", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "code"},
"",
@ -409,6 +411,21 @@ var enumProperties = map[PropertyName]struct {
"background-clip",
[]string{"border-box", "padding-box", "content-box"}, // "text"},
},
BackgroundOrigin: {
[]string{"border-box", "padding-box", "content-box"},
"background-origin",
[]string{"border-box", "padding-box", "content-box"},
},
MaskClip: {
[]string{"border-box", "padding-box", "content-box"},
"mask-clip",
[]string{"border-box", "padding-box", "content-box"},
},
MaskOrigin: {
[]string{"border-box", "padding-box", "content-box"},
"background-origin",
[]string{"border-box", "padding-box", "content-box"},
},
Direction: {
[]string{"to-top", "to-right-top", "to-right", "to-right-bottom", "to-bottom", "to-left-bottom", "to-left", "to-left-top"},
"",

View File

@ -491,6 +491,9 @@ func (view *viewData) propertyChanged(tag PropertyName) {
case Background:
session.updateCSSProperty(htmlID, string(Background), backgroundCSS(view, session))
case Mask:
session.updateCSSProperty(htmlID, "mask", maskCSS(view, session))
case Border, BorderLeft, BorderRight, BorderTop, BorderBottom:
cssWidth := ""
cssColor := ""

View File

@ -108,35 +108,6 @@ func split4Values(text string) []string {
return []string{}
}
func backgroundCSS(properties Properties, session Session) string {
if value := properties.getRaw(Background); value != nil {
if backgrounds, ok := value.([]BackgroundElement); ok && len(backgrounds) > 0 {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
for _, background := range backgrounds {
if value := background.cssStyle(session); value != "" {
if buffer.Len() > 0 {
buffer.WriteString(", ")
}
buffer.WriteString(value)
}
}
if buffer.Len() > 0 {
backgroundColor, _ := colorProperty(properties, BackgroundColor, session)
if backgroundColor != 0 {
buffer.WriteRune(' ')
buffer.WriteString(backgroundColor.cssString())
}
return buffer.String()
}
}
}
return ""
}
func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
if visibility, ok := enumProperty(style, Visibility, session, Visible); ok {
@ -217,8 +188,12 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
}
}
if value, ok := enumProperty(style, BackgroundClip, session, 0); ok {
builder.add(string(BackgroundClip), enumProperties[BackgroundClip].values[value])
for _, tag := range []PropertyName{BackgroundClip, BackgroundOrigin, MaskClip, MaskOrigin} {
if value, ok := enumProperty(style, tag, session, 0); ok {
if data, ok := enumProperties[tag]; ok {
builder.add(data.cssTag, data.values[value])
}
}
}
if background := backgroundCSS(style, session); background != "" {
@ -230,6 +205,10 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
}
}
if mask := maskCSS(style, session); mask != "" {
builder.add("mask", mask)
}
if font, ok := stringProperty(style, FontName, session); ok && font != "" {
builder.add(`font-family`, font)
}
@ -870,7 +849,8 @@ func writeViewStyle(name string, view Properties, buffer *strings.Builder, inden
tagOrder := []PropertyName{
ID, Row, Column, Top, Right, Bottom, Left, Semantics, Cursor, Visibility,
Opacity, ZIndex, Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight,
Margin, Padding, BackgroundClip, BackgroundColor, Background, Border, Radius, Outline, Shadow,
Margin, Padding, BackgroundColor, Background, BackgroundClip, BackgroundOrigin,
Mask, MaskClip, MaskOrigin, Border, Radius, Outline, Shadow,
Orientation, ListWrap, VerticalAlign, HorizontalAlign, CellWidth, CellHeight,
CellVerticalAlign, CellHorizontalAlign, ListRowGap, ListColumnGap, GridRowGap, GridColumnGap,
ColumnCount, ColumnWidth, ColumnSeparator, ColumnGap, AvoidBreak,

View File

@ -210,8 +210,8 @@ func viewStyleSet(style Properties, tag PropertyName, value any) []PropertyName
return []PropertyName{tag}
}
case Background:
return setBackgroundProperty(style, value)
case Background, Mask:
return setBackgroundProperty(style, tag, value)
case Border, CellBorder:
if border := newBorderProperty(value); border != nil {