From 27beb1e679b8b8bd76b46b1247367494cd63db44 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko <2277098+anoshenko@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:32:13 +0200 Subject: [PATCH] Added "mask" property --- CHANGELOG.md | 3 + background.go | 221 +++++++++++++++++++++++++++++++++++------- backgroundGradient.go | 13 ++- propertyNames.go | 54 +++++++++++ propertySet.go | 21 +++- view.go | 3 + viewStyle.go | 44 +++------ viewStyleSet.go | 4 +- 8 files changed, 289 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99328b8..b8c6d5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/background.go b/background.go index db18cfb..44a99da 100644 --- a/background.go +++ b/background.go @@ -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) } diff --git a/backgroundGradient.go b/backgroundGradient.go index e672271..0089020 100644 --- a/backgroundGradient.go +++ b/backgroundGradient.go @@ -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 { diff --git a/propertyNames.go b/propertyNames.go index e825ebf..18dd429 100644 --- a/propertyNames.go +++ b/propertyNames.go @@ -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`. diff --git a/propertySet.go b/propertySet.go index 028de6f..41d43b3 100644 --- a/propertySet.go +++ b/propertySet.go @@ -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"}, "", diff --git a/view.go b/view.go index c999658..1131d7b 100644 --- a/view.go +++ b/view.go @@ -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 := "" diff --git a/viewStyle.go b/viewStyle.go index 0976958..5462d2c 100644 --- a/viewStyle.go +++ b/viewStyle.go @@ -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, diff --git a/viewStyleSet.go b/viewStyleSet.go index 942951d..0139e87 100644 --- a/viewStyleSet.go +++ b/viewStyleSet.go @@ -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 {