/// <summary>Finds the index of the nearest character to x pixels.</summary> /// <param name="x">The number of pixels from the left edge of this text element.</param> /// <returns>The index of the nearest letter.</returns> public int LetterIndex(int x) { if (ChildNodes == null) { return(0); } int widthSoFar = 0; int lettersSoFar = 0; for (int i = 0; i < ChildNodes.Count; i++) { WordElement word = (WordElement)ChildNodes[i]; // Grab the words computed style: Css.ComputedStyle wordStyle = word.Style.Computed; // Bump up the current width: widthSoFar += wordStyle.PixelWidth; if (x <= widthSoFar) { int localWidthOffset = x - (widthSoFar - wordStyle.PixelWidth); lettersSoFar += wordStyle.Text.LetterIndex(localWidthOffset); break; } else { lettersSoFar += word.LetterCount(); } } return(lettersSoFar); }
public override void Apply(ComputedStyle style,Value value){ // Is it lit? bool lit=(value!=null && value.Boolean); if(!lit && style.Shading==null){ // Essentially ignore it anyway. return; } if(style.Shading==null){ // It's lit: style.RequireShading().Lit=true; }else{ // It's not lit: style.Shading.Lit=false; // Optimise - might no longer need the shading info: style.Shading.Optimise(); } // Request a layout now: style.RequestLayout(); }
/// <summary>Calculates where the transformation origin should go in screen space.</summary> /// <param name="relativeTo">The computed style of the element that the origin will be /// relative to if the origin position is 'Relative'</param> private void CalculateOrigin(ComputedStyle relativeTo){ // We need to figure out where the origin is and then apply the parent transformation to it. _Origin=_OriginOffset; if(_OriginOffsetPercX){ _Origin.x*=relativeTo.PixelWidth; } if(_OriginOffsetPercY){ _Origin.y*=relativeTo.PixelHeight; } if(_OriginPosition==PositionType.Relative){ _Origin.x+=relativeTo.OffsetLeft; _Origin.y+=relativeTo.OffsetTop; } // Map origin to world space: Renderman renderer=relativeTo.Element.Document.Renderer; _Origin=renderer.PixelToWorldUnit(_Origin.x,_Origin.y,relativeTo.ZIndex); if(Parent!=null){ _Origin=Parent.Apply(_Origin); } }
public override void Apply(ComputedStyle style,Value value){ // Got text at all?: if(GetText(style)==null){ return; } // Apply the property: if(value==null || value.Text=="none"){ // Clear the shadow: style.TextShadow=null; }else{ // The glow properties: int blur=0; Color colour=Color.black; ShadowData data=style.TextShadow; if(data==null){ data=new ShadowData(); style.TextShadow=data; } data.HOffset=value[0].PX; data.VOffset=value[1].PX; // Grab the blur: Value innerValue=value[2]; if(innerValue.Type==ValueType.Color){ colour=innerValue.ToColor(); }else{ blur=innerValue.PX; // Grab the colour: innerValue=value[3]; if(innerValue.Type==ValueType.Color){ colour=innerValue.ToColor(); } } if(colour.a==1f){ // Default transparency: colour.a=0.8f; } data.Colour=colour; data.Blur=blur; } // Apply the changes - doesn't change anything about the actual text, so we just want a layout: style.RequestLayout(); }
public RoundedCorners(BorderProperty border){ Border=border; // Grab the renderer: Renderer=Border.Element.Document.Renderer; // Grab the computed style: Computed=border.Element.style.Computed; // Create the inverse border set: InverseBorder=new RoundBorderInverseProperty(border.Element); }
/// <summary>Call this if the current property requires a background image object.</summary> public BackgroundImage GetBackground(ComputedStyle style){ BackgroundImage image=style.BGImage; if(image==null){ style.BGImage=image=new BackgroundImage(style.Element); style.EnforceNoInline(); } // Flag it as having a change: image.Changed=true; return image; }
/// <summary>Call this if the current property requies a border object.</summary> public BorderProperty GetBorder(ComputedStyle style){ BorderProperty border=style.Border; if(border==null){ style.Border=border=new BorderProperty(style.Element); style.EnforceNoInline(); } // Flag it as having a change: border.Changed=true; return border; }
/// <summary>Finds the index of the nearest character to x pixels.</summary> /// <param name="x">The number of pixels from the left edge of this text element.</param> /// <param name="y">The number of pixels from the top edge of this text element.</param> /// <returns>The index of the nearest letter.</returns> public int LetterIndex(int x, int y) { if (ChildNodes == null) { return(0); } int widthSoFar = 0; int lettersSoFar = 0; for (int i = 0; i < ChildNodes.Count; i++) { WordElement word = (WordElement)ChildNodes[i]; // Grab the words computed style: Css.ComputedStyle wordStyle = word.Style.Computed; // Find where the bottom of the line is at: int lineBottom = wordStyle.PixelHeight + wordStyle.ParentOffsetTop; if (lineBottom < y) { // Not at the right line yet. lettersSoFar += word.LetterCount(); continue; } // Crank over the width: widthSoFar += wordStyle.PixelWidth; if (x <= widthSoFar) { int localWidthOffset = x - (widthSoFar - wordStyle.PixelWidth); lettersSoFar += wordStyle.Text.LetterIndex(localWidthOffset); break; } else { lettersSoFar += word.LetterCount(); } } return(lettersSoFar); }
public override void Apply(ComputedStyle style,Value value){ ShaderSet family=null; // Apply: if(value!=null){ // Lowercase so we can have the best chance at spotting the standard set (which is optimised for). string familyLC=value.Text.ToLower(); if(familyLC!="standardui" && familyLC!="standard" && familyLC!="" && familyLC!="none"){ // Get the family: family=ShaderSet.Get(value.Text); } } // Apply it here: if(style.Shading!=null){ // Update it: style.Shading.Shaders=family; if(family==null){ // Check if the shading data is no longer in use: style.Shading.Optimise(); } }else if(family!=null){ style.RequireShading().Shaders=family; } // Request a layout now: style.RequestLayout(); }
/// <summary>Gets the relative position in pixels of the letter at the given index.</summary> /// <param name="index">The index of the letter in this text element.</param> /// <returns>The number of pixels from the left and top edges of this text element the letter is as a vector.</returns> public Vector2 GetPosition(ref int index) { if (index == 0 || ChildNodes == null) { return(Vector2.zero); } int localOffset; WordElement word = GetWordWithLetter(index, out localOffset); if (word == null) { index -= localOffset; return(Vector2.zero); } Css.ComputedStyle computed = word.Style.Computed; float left = computed.ParentOffsetLeft + computed.Text.LocalPositionOf(localOffset); float top = computed.ParentOffsetTop; return(new Vector2(left, top)); }
/// <summary>Gets the fontsize for the given computed style.</summary> /// <param name="parentStyle">The style to get the fontsize from. Used for em calculations.</param> private int ParentFontSize(ComputedStyle style) { if (style == null) { return(12); } if (style.Text != null) { return((int)style.Text.FontSize); } // Note that most of the following is actually the namespace; this is just a single static var. Value fontSize = style[Css.Properties.FontSize.GlobalProperty]; if (fontSize == null) { return(12); } else { return(fontSize.PX); } }
//--------------------------------------
/// <summary>Horizontally aligns a line based on alignment settings in the given computed style.</summary> /// <param name="first">The style of the first element on the line.</param> /// <param name="last">The style of the last element on the line.</param> /// <param name="lineSpace">The amount of space available to the line.</param> /// <param name="elementCount">The number of elements on this line.</param> /// <param name="lineLength">The width of the line in pixels.</param> /// <param name="parent">The style which defines the alignment.</param> private void AlignLine(ComputedStyle first,ComputedStyle last,int lineSpace,int elementCount,int lineLength,ComputedStyle parent){ if(elementCount==0){ return; } // Is this the last line? bool lastLine=(last.NextPacked==null || last.NextPacked.Display==DisplayType.Block); HorizontalAlignType align=parent.HorizontalAlign; if(lastLine){ align=parent.HorizontalAlignLast; if(align==HorizontalAlignType.Auto){ // Pick an alignment based on parent's HorizontalAlign and GoingLeft. align=parent.HorizontalAlign; if(align==HorizontalAlignType.Justify){ // Left or right: align=HorizontalAlignType.Auto; } } } if(align==HorizontalAlignType.Auto){ if(GoingLeftwards){ align=HorizontalAlignType.Right; }else{ align=HorizontalAlignType.Left; } } if(align!=HorizontalAlignType.Left){ // Does the last element on the line end with a space? If so, act like the space isn't there by reducing line length by it. lineLength-=last.EndSpaceSize; } // How many pixels each element will be moved over: float offsetBy=0f; // True if the text is going to be justified. bool justify=false; // How many pixels we add to offsetBy each time we shift an element over: float justifyDelta=0f; if(align==HorizontalAlignType.Center){ // We're centering - shift by half the 'spare' pixels on this row. // How many pixels of space this line has left / 2: offsetBy=(float)(lineSpace-lineLength)/2f; }else if(align==HorizontalAlignType.Right){ // How many pixels of space this line has left: offsetBy=(float)(lineSpace-lineLength); }else if(align==HorizontalAlignType.Justify){ // Justify. This is where the total spare space on the line gets shared out evenly // between the elements on this line. // So, we take the spare space and divide it up by the elements on this line: justifyDelta=(float)(lineSpace-lineLength)/(float)elementCount; if(GoingLeftwards){ // Make sure the first word starts in the correct spot if we're going leftwards: lineLength=lineSpace; // And also we actually want to be taking a little less each time, so invert justifyDelta: justifyDelta=-justifyDelta; } justify=true; } if(GoingLeftwards){ // Everything is locally positioned off to the left. // Because of this, we need to shift them over the entire size of the row: offsetBy+=lineLength; // In this case it can also be left aligned. } ComputedStyle current=first; int counter=0; while(current!=null&&counter<elementCount){ if(current.Float==FloatType.None){ // Shift the element over by the offset. current.ParentOffsetLeft+=(int)offsetBy; if(justify){ offsetBy+=justifyDelta; } } counter++; current=current.NextPacked; } }
/// <summary>Recomputes the space size and inner height of the parent element.</summary> public void SetDimensions() { if (FontToDraw == null || Characters == null) { return; } float width = 0f; float size = FontSize; float screenHeight = FontToDraw.GetHeight(size) * (1f + LineGap); Ascender = FontToDraw.GetAscend(size) + (LineGap * size / 2f); ScaleFactor = size / Fonts.Rasteriser.ScalarX; ComputedStyle computed = Element.Style.Computed; computed.FixedHeight = true; if (SpaceSize == 0f) { SpaceSize = StandardSpaceSize(); } for (int i = 0; i < Characters.Length; i++) { Glyph dChar = Characters[i]; if (dChar == null) { continue; } if (dChar.Image != null) { if (CharacterProviders.FixHeight) { if (dChar.Height > screenHeight) { screenHeight = dChar.Height; } width += dChar.Width; } else { width += FontSize; } } else if (dChar.Space) { width += SpaceSize; } else { width += dChar.AdvanceWidth * size; } width += LetterSpacing; } computed.InnerHeight = (int)screenHeight; computed.InnerWidth = (int)width; computed.FixedWidth = true; computed.SetSize(); }
protected override void Layout() { if (Characters == null || FontToDraw == null || Characters.Length == 0) { return; } // The blocks we allocate here come from FontToDraw. // They use the same renderer and same layout service, but just a different mesh. // This is to enable potentially very large font atlases with multiple fonts. ComputedStyle computed = Element.Style.Computed; Renderman renderer = Element.Document.Renderer; float top = computed.OffsetTop + computed.StyleOffsetTop; float left = computed.OffsetLeft + computed.StyleOffsetLeft; // Should we auto-alias the text? // Note that this property "drags" to following elements which is correct. // We don't really want to break batching chains for aliasing. if (Alias == float.MaxValue) { // Yep! Note all values here are const. float aliasing = Fonts.AutoAliasOffset - ((FontSize - Fonts.AutoAliasRelative) * Fonts.AutoAliasRamp); if (aliasing > 0.1f) { renderer.FontAliasing = aliasing; } } else { // Write aliasing: renderer.FontAliasing = Alias; } if (Extrude != 0f) { // Compute the extrude now: if (Text3D == null) { Text3D = Get3D(FontSize, FontColour, ref left, ref top); } else { // Update it. } return; } else { Text3D = null; } if (!AllWhitespace) { // Firstly, make sure the batch is using the right font texture. // This may generate a new batch if the font doesn't match the previous or existing font. // Get the full shape of the element: int width = computed.PaddedWidth; int height = computed.PaddedHeight; int minY = computed.OffsetTop + computed.BorderTop; int minX = computed.OffsetLeft + computed.BorderLeft; BoxRegion boundary = new BoxRegion(minX, minY, width, height); if (!boundary.Overlaps(renderer.ClippingBoundary)) { if (Visible) { SetVisibility(false); } return; } else if (!Visible) { // ImageLocation will allocate here if it's needed. SetVisibility(true); } } float zIndex = computed.ZIndex; BoxRegion screenRegion = new BoxRegion(); // First up, underline. if (TextLine != null) { // We have one. Locate it next. float lineWeight = (FontToDraw.StrikeSize * FontSize); float yOffset = 0f; switch (TextLine.Type) { case TextLineType.Underline: yOffset = Ascender + lineWeight; break; case TextLineType.StrikeThrough: yOffset = (FontToDraw.StrikeOffset * FontSize); yOffset = Ascender - yOffset; break; case TextLineType.Overline: yOffset = (lineWeight * 2f); break; } Color lineColour = FontColour; if (TextLine.ColourOverride) { lineColour = TextLine.Colour; } screenRegion.Set(left, top + yOffset, computed.PixelWidth, lineWeight); if (screenRegion.Overlaps(renderer.ClippingBoundary)) { // Ensure we have a batch: SetupBatch(null, null); // This region is visible. Clip it: screenRegion.ClipBy(renderer.ClippingBoundary); // And get our block ready: MeshBlock block = Add(); // Set the UV to that of the solid block colour pixel: block.SetSolidColourUV(); // Set the colour: block.SetColour(lineColour); block.SetClipped(renderer.ClippingBoundary, screenRegion, renderer, zIndex); } } // Next, render the characters. // If we're rendering from right to left, flip the punctuation over. // Is the word itself rightwards? bool rightwardWord = false; if (StartPunctuationCount < Characters.Length) { // Is the first actual character a rightwards one? Glyph firstChar = Characters[StartPunctuationCount]; if (firstChar != null) { rightwardWord = firstChar.Rightwards; } } // Right to left (e.g. arabic): if (computed.DrawDirection == DirectionType.RTL) { int end = Characters.Length - EndPunctuationCount; // Draw the punctuation from the end of the string first, backwards: if (EndPunctuationCount > 0) { for (int i = Characters.Length - 1; i >= end; i--) { DrawInvertCharacter(i, ref left, top, renderer, zIndex, screenRegion); } } if (rightwardWord) { // Render the word itself backwards. for (int i = end - 1; i >= StartPunctuationCount; i--) { DrawCharacter(i, ref left, top, renderer, zIndex, screenRegion); } } else { // Draw the middle characters: for (int i = StartPunctuationCount; i < end; i++) { DrawCharacter(i, ref left, top, renderer, zIndex, screenRegion); } } // Draw the punctuation from the start of the string last, backwards: if (StartPunctuationCount > 0) { for (int i = StartPunctuationCount - 1; i >= 0; i--) { DrawInvertCharacter(i, ref left, top, renderer, zIndex, screenRegion); } } } else if (rightwardWord) { // Render the word itself backwards. for (int i = Characters.Length - 1; i >= 0; i--) { DrawCharacter(i, ref left, top, renderer, zIndex, screenRegion); } } else { // Draw it as is. for (int i = 0; i < Characters.Length; i++) { DrawCharacter(i, ref left, top, renderer, zIndex, screenRegion); } } }
protected override void Layout() { if (Image == null || !Image.Loaded()) { return; } if (Clipping == BackgroundClipping.Text) { return; } Renderman renderer = Element.Document.Renderer; if (Image.Animated || Image.IsDynamic || renderer.RenderMode == RenderMode.NoAtlas || Filtering != FilterMode.Point || ForcedIsolate) { // SPA is an animation format, so we need a custom texture atlas to deal with it. // This is because the frames of any animation would quickly exhaust our global texture atlas. // So to get a custom atlas, we must isolate this property. Isolate(); } else if (Image.IsVideo) { // Similarly with a video, we need to isolate it aswell. Isolate(); #if !MOBILE if (!Image.Video.isPlaying && Element["autoplay"] != null) { // Play now: Image.Video.Play(); // Fire an onplay event: Element.Run("onplay"); // Clear: Element["autoplay"] = null; } #endif } else { // Reverse isolation, if we are isolated already: Include(); } ComputedStyle computed = Element.Style.Computed; // Get the full shape of the element: int width = computed.PaddedWidth; int height = computed.PaddedHeight; int minY = computed.OffsetTop + computed.BorderTop; int minX = computed.OffsetLeft + computed.BorderLeft; if (width == 0 || height == 0) { if (Visible) { SetVisibility(false); } return; } BoxRegion boundary = new BoxRegion(minX, minY, width, height); if (!boundary.Overlaps(renderer.ClippingBoundary)) { if (Visible) { SetVisibility(false); } return; } else if (!Visible) { // ImageLocation will allocate here if it's needed. SetVisibility(true); } boundary.ClipBy(renderer.ClippingBoundary); // Texture time - get it's location on that atlas: AtlasLocation locatedAt = ImageLocation; if (locatedAt == null) { // We're not using the atlas here. if (!Isolated) { Isolate(); } int imgWidth = Image.Width(); int imgHeight = Image.Height(); locatedAt = new AtlasLocation(0, 0, imgWidth, imgHeight, imgWidth, imgHeight); } // Isolation is all done - safe to setup the batch now: SetupBatch(locatedAt.Atlas, null); // Great - Use locatedAt.Width/locatedAt.Height - this removes any risk of overflowing into some other image. int imageCountX = 1; int imageCountY = 1; int trueImageWidth = locatedAt.Width; int trueImageHeight = locatedAt.Height; int imageWidth = trueImageWidth; int imageHeight = trueImageHeight; bool autoX = false; bool autoY = false; if (Image.PixelPerfect) { imageWidth = (int)(imageWidth * ScreenInfo.ResolutionScale); imageHeight = (int)(imageWidth * ScreenInfo.ResolutionScale); } if (SizeX != null) { if (SizeX.Single != 0f) { imageWidth = (int)(width * SizeX.Single); } else if (SizeX.PX != 0) { imageWidth = SizeX.PX; } else if (SizeX.IsAuto()) { autoX = true; } } if (SizeY != null) { if (SizeY.Single != 0f) { imageHeight = (int)(height * SizeY.Single); } else if (SizeY.PX != 0) { imageHeight = SizeY.PX; } else if (SizeY.IsAuto()) { autoY = true; } } if (autoX) { imageWidth = imageHeight * trueImageWidth / trueImageHeight; } else if (autoY) { imageHeight = imageWidth * trueImageHeight / trueImageWidth; } // offsetX and offsetY are the images position offset from where it should be (e.g. x of -200 means it's 200px left) // Resolve the true offset values: int offsetX = 0; int offsetY = 0; if (OffsetX != null) { // Resolve a potential mixed % and px: offsetX = OffsetX.GetMixed(width - imageWidth); } if (OffsetY != null) { // Resolve a potential mixed % and px: offsetY = OffsetY.GetMixed(height - imageHeight); } if (RepeatX) { // Get the rounded up number of images: imageCountX = (width - 1) / imageWidth + 1; if (offsetX != 0) { // If we have an offset, another image is introduced. imageCountX++; } } if (RepeatY) { // Get the rounded up number of images: imageCountY = (height - 1) / imageHeight + 1; if (offsetY != 0) { // If we have an offset, another image is introduced. imageCountY++; } } int blockX = minX + offsetX; int blockY = minY + offsetY; if (RepeatX && offsetX > 0) { // We're repeating and the image is offset by a +ve number. // This means a small gap, OffsetX px wide, is open on this left side. // So to fill it, we need to offset this first image by a much bigger number - the value imageWidth-OffsetX. blockX -= (imageWidth - offsetX); // This results in the first image having OffsetX pixels exposed in the box - this is what we want. } if (RepeatY && offsetY > 0) { // Similar thing to above: blockY -= (imageHeight - offsetY); } BoxRegion screenRegion = new BoxRegion(); bool first = true; int startX = blockX; Color colour = computed.ColorOverlay; float zIndex = (computed.ZIndex - 0.003f); for (int y = 0; y < imageCountY; y++) { for (int x = 0; x < imageCountX; x++) { // Draw at blockX/blockY. screenRegion.Set(blockX, blockY, imageWidth, imageHeight); if (screenRegion.Overlaps(boundary)) { // If the two overlap, this means it's actually visible. MeshBlock block = Add(); if (Image.Animated && first) { first = false; // Make sure we have an instance: Image.GoingOnDisplay(); block.ParentMesh.SetMaterial(Image.Animation.AnimatedMaterial); } else if (Image.IsVideo && first) { first = false; block.ParentMesh.SetMaterial(Image.VideoMaterial); } else if (Isolated && first) { first = false; block.ParentMesh.SetMaterial(Image.ImageMaterial); } // Set it's colour: block.SetColour(colour); // And clip our meshblock to fit within boundary: block.TextUV = null; block.ImageUV = block.SetClipped(boundary, screenRegion, renderer, zIndex, locatedAt, block.ImageUV); } blockX += imageWidth; } blockX = startX; blockY += imageHeight; } }
/// <summary>Gets the fontsize for the given computed style.</summary> /// <param name="parentStyle">The style to get the fontsize from. Used for em calculations.</param> private int ParentFontSize(ComputedStyle style){ if(style==null){ return 12; } if(style.Text!=null){ return (int)style.Text.FontSize; } // Note that most of the following is actually the namespace; this is just a single static var. Value fontSize=style[Css.Properties.FontSize.GlobalProperty]; if(fontSize==null){ return 12; }else{ return fontSize.PX; } }
/// <summary>Apply this CSS style to the given computed style. /// Note that you can grab the element from the computed style if you need that.</summary> /// <param name="style">The computed style to apply the property to.</param> /// <param name="value">The new value being applied.</param> public virtual void Apply(ComputedStyle style, Value value) { }
/// <summary>Lets the renderer know the current line doesn't fit anymore elements /// and has been finished.</summary> /// <param name="parentStyle">The computed style of the element holding this line.</param> public void CompleteLine(ComputedStyle parentStyle){ if(PenX>LargestLineWidth){ LargestLineWidth=PenX; } // Compute some alignment next. // Firstly, place the Pen on the line: PenY+=LineHeight; // Next, align the elements and apply their top offset. ComputedStyle current=FirstOnLine; while(current!=null){ // Calculate the offset to where the top left corner is: if(current.Float==FloatType.None){ current.ParentOffsetTop=PenY+Baseline-current.PixelHeight; }else{ current.ParentOffsetTop=PenY+Baseline-LineHeight; } current=current.NextOnLine; } if(ActiveFloats!=null){ // Are any now going to be "deactivated"? for(int i=ActiveFloats.Count-1;i>=0;i--){ // Grab the style: ComputedStyle activeFloat=ActiveFloats[i]; // Is the current render point now higher than this floating object? // If so, we must reduce LineStart/ increase MaxX depending on which type of float it is. if(PenY>=(activeFloat.ParentOffsetTop + activeFloat.PixelHeight)){ // Yep! Deactivate and reduce our size: if(activeFloat.Float==FloatType.Right){ if(GoingLeftwards){ // Decrease LineStart: LineStart-=activeFloat.PixelWidth; }else{ // Increase max x: MaxX+=activeFloat.PixelWidth; } }else{ if(GoingLeftwards){ // Increase max x: MaxX+=activeFloat.PixelWidth; }else{ // Decrease LineStart: LineStart-=activeFloat.PixelWidth; } } // Remove it as an active float: ActiveFloats.RemoveAt(i); } } } FirstOnLine=null; LastOnLine=null; LineHeight=0; Baseline=0; PenX=LineStart; }
/// <summary>Creates a new element style for the given element.</summary> /// <param name="element">The element that this will be the style for.</param> public ElementStyle(Element element) : base(element) { Computed = new ComputedStyle(element); }
/// <summary>Creates a new element style for the given element.</summary> /// <param name="element">The element that this will be the style for.</param> public ElementStyle(Element element):base(element){ Computed=new ComputedStyle(element); }
protected override void Layout() { if (Corners != null) { Corners.PreLayout(); } ComputedStyle computed = Element.Style.Computed; // Find the zIndex: // NB: At same depth as BGColour - right at the back. float zIndex = (computed.ZIndex - 0.006f); // Get the co-ord of the top edge: int top = computed.OffsetTop; int left = computed.OffsetLeft; // And the dimensions of the lines: // Note: boxwidth doesn't include the left/right widths to prevent overlapping. int boxWidth = computed.PaddedWidth; int boxHeight = computed.PaddedHeight + WidthTop + WidthBottom; BoxRegion screenRegion = new BoxRegion(); Renderman renderer = Element.Document.Renderer; // Get the default colour - that's the same as the text colour: Color colour = Color.black; // Is the border multicoloured? bool multiColour = false; // Does this border have a colour? if (Colour == null) { // Grab the text colour if there is one: if (computed.Text != null) { // It's the same as the font colour: colour = computed.Text.FontColour; } else { // Nope - We need to set alpha: colour.a = computed.ColorOverlay.a; } } else if (Colour.Length == 1) { colour = Colour[0]; } else { multiColour = true; } for (int i = 0; i < 4; i++) { int lineHeight = 0; int lineWidth = 0; // Co-ords of the top-left corner for our box: int cornerY = top; int cornerX = left; if (i == 0 || i == 2) { // Top or bottom: lineWidth = boxWidth; lineHeight = BorderWidth(i); } else { lineWidth = BorderWidth(i); lineHeight = boxHeight; } // Does this border have multiple colours? if (multiColour) { colour = Colour[i]; } if (Corners != null) { Corners.Layout(i, ref cornerX, ref cornerY, ref lineWidth, ref lineHeight); } else { switch (i) { case 0: // Top: cornerX += WidthLeft; break; case 1: // Right: cornerX += boxWidth + WidthLeft; break; case 2: // Bottom: cornerY += boxHeight - WidthBottom; cornerX += WidthLeft; break; } } screenRegion.Set(cornerX, cornerY, lineWidth, lineHeight); if (screenRegion.Overlaps(renderer.ClippingBoundary)) { // This region is visible. Clip it: screenRegion.ClipBy(renderer.ClippingBoundary); // Ensure we have a batch (doesn't change graphics or font textures, thus both null): SetupBatch(null, null); // And get our block ready: MeshBlock block = Add(); // Set the UV to that of the solid block colour pixel: block.SetSolidColourUV(); // Set the border colour: block.SetColour(colour); block.SetClipped(renderer.ClippingBoundary, screenRegion, renderer, zIndex); } } }
/// <summary>Adds the given style to the current line.</summary> /// <param name="style">The style to add.</param> private void AddToLine(ComputedStyle style,Element parentNode){ // Don't call with inline elements - block or inline-block only. ComputedStyle parentStyle=(parentNode==null)?null:parentNode.Style.Computed; if( (style.Display==DisplayType.Block && style.Float==FloatType.None) ){ // Doesn't fit here. CompleteLine(parentStyle); }else if((parentStyle==null || parentStyle.WhiteSpace==WhiteSpaceType.Normal) && ((PenX+style.PixelWidth)>MaxX) ){ // Does the last element on the line end with a space? if(LastOnLine!=null && style.Text!=null){ TextRenderingProperty text=LastOnLine.Text; if(text!=null){ // It's a word - does it end with a space? if(text.NoEndingSpace){ // It's a word which does not end with a space. // These two inline words are actually "touching". // So, first pull that last word from the line it's on: if(FirstOnLine!=LastOnLine){ ComputedStyle toMove=LastOnLine; // Must update NextOnLine of the previous element. ComputedStyle beforePrevious=FirstOnLine; toMove=FirstOnLine; // Note that there may actually be more than 2 in a row. // So, we'll go hunting for the first element that is before a sequence of // inline elements with text that does not end with a space. while(true){ ComputedStyle next=toMove.NextOnLine; if(next==LastOnLine){ break; } if(next.Text!=null && next.Text.NoEndingSpace){ // Just advance toMove: toMove=next; continue; } // Advance both: beforePrevious=next; toMove=next; } // Update toMove: toMove=beforePrevious.NextOnLine; // Update before previous: beforePrevious.NextOnLine=null; LastOnLine=beforePrevious; // Complete the line: CompleteLine(parentStyle); // Re-add each one: while(toMove!=null){ ComputedStyle next=toMove.NextOnLine; AddToLine(toMove,parentNode); toMove=next; } } // Add this word to the current line: goto AddNow; } } } // Doesn't fit here. CompleteLine(parentStyle); } AddNow: style.NextPacked=null; style.NextOnLine=null; if(style.Float==FloatType.Right){ if(GoingLeftwards){ style.ParentOffsetLeft=LineStart; PenX+=style.PixelWidth; }else{ style.ParentOffsetLeft=MaxX-style.PixelWidth; } if(ActiveFloats==null){ ActiveFloats=new List<ComputedStyle>(1); } ActiveFloats.Add(style); }else if(style.Float==FloatType.Left){ if(GoingLeftwards){ style.ParentOffsetLeft=MaxX-style.PixelWidth; }else{ style.ParentOffsetLeft=LineStart; PenX+=style.PixelWidth; } if(ActiveFloats==null){ ActiveFloats=new List<ComputedStyle>(1); } ActiveFloats.Add(style); }else if(GoingLeftwards){ PenX+=style.PixelWidth; style.ParentOffsetLeft=LineStart*2-PenX; }else{ style.ParentOffsetLeft=PenX; PenX+=style.PixelWidth; } if(style.Float==FloatType.None && ActiveFloats!=null){ if(style.Display==DisplayType.Block || style.FixedWidth){ // Get this elements width value: Css.Value widthValue=style[Css.Properties.Width.GlobalProperty]; // Is it a percentage or not fixed width and block? if(widthValue==null){ if(style.Display==DisplayType.Block){ // Grab it: int parentWidth=parentStyle.InnerWidth; // Overwrite it: parentStyle.InnerWidth=MaxX-LineStart; // Update the size: style.SetSize(); // And bubble upwards: style.Element.SetWidthForKids(style); // Write back: parentStyle.InnerWidth=parentWidth; } }else{ if(widthValue.Type==Css.ValueType.Percentage){ // Yep! We need to update it. // Grab it: int parentWidth=parentStyle.InnerWidth; // Overwrite it: parentStyle.InnerWidth=MaxX-LineStart; // Resolve it again: widthValue.MakeAbsolute(Css.Properties.Width.GlobalProperty,style.Element); // Apply it: style.InnerWidth=widthValue.PX; style.SetSize(); // Update width: style.Element.SetWidthForKids(style); // Write back: parentStyle.InnerWidth=parentWidth; } } } } if(style.Float==FloatType.Left){ if(GoingLeftwards){ // Reduce max: MaxX-=style.PixelWidth; }else{ // Push over where lines start at: LineStart+=style.PixelWidth; } }else if(style.Float==FloatType.Right){ if(GoingLeftwards){ // Push over where lines start at: LineStart+=style.PixelWidth; }else{ // Reduce max: MaxX-=style.PixelWidth; } }else if(style.PixelHeight>LineHeight){ LineHeight=style.PixelHeight; } if(style.Baseline>Baseline){ Baseline=style.Baseline; } if(FirstPacked==null){ FirstPacked=LastPacked=style; }else{ LastPacked=LastPacked.NextPacked=style; } if(FirstOnLine==null){ FirstOnLine=LastOnLine=style; }else{ if(style.Float==FloatType.Left){ // Push over all the elements before this on the line. ComputedStyle currentLine=FirstOnLine; while(currentLine!=null){ if(currentLine.Float==FloatType.None){ // Move it: currentLine.ParentOffsetLeft+=style.PixelWidth; } // Next one: currentLine=currentLine.NextOnLine; } } LastOnLine=LastOnLine.NextOnLine=style; } if(style.Display==DisplayType.Block && style.Float==FloatType.None){ // A second newline after the block too. CompleteLine(parentStyle); } }
/// <summary>Converts this relative value (such as a percentage or em) into a fixed one.</summary> /// <param name="property">The property that this value represents and is being made absolute.</param> /// <param name="element">The element holding all the values that represent 100%.</param> public void MakeAbsolute(CssProperty property, Element element) { if (element.ParentNode == null) { PX = 0; return; } ComputedStyle parentStyle = element.ParentNode.Style.Computed; switch (Type) { case ValueType.Em: BakePX(ParentFontSize(parentStyle)); break; case ValueType.Percentage: ComputedStyle computed = element.Style.Computed; // Is this along x? if (property.IsXProperty) { // Yep! BakePX(parentStyle.InnerWidth - computed.PaddingLeft - computed.PaddingRight); } else { // Nope! BakePX(parentStyle.InnerHeight - computed.PaddingTop - computed.PaddingBottom); } break; case ValueType.Inherit: InheritFrom(parentStyle[property]); break; case ValueType.Calc: computed = element.Style.Computed; int size = 0; // Is this along x? if (property.IsXProperty) { // Yep! size = parentStyle.InnerWidth - computed.PaddingLeft - computed.PaddingRight; } else { // Nope! size = parentStyle.InnerHeight - computed.PaddingTop - computed.PaddingBottom; } PX = Calculation.Run(size); break; default: computed = element.Style.Computed; // It's a box or point - compute all values [y,x,y,x] // Both will have the first two values: bool useWidth = (Type == ValueType.Point); //x is first for a point. // Don't include padding in the value. int paddingWidth = computed.PaddingLeft + computed.PaddingRight; int paddingHeight = computed.PaddingTop + computed.PaddingBottom; // The cached fontsize if any of these use EM; Chances are more than one will. int parentFontSize = -1; for (int i = 0; i < InnerValues.Length; i++) { Value innerValue = InnerValues[i]; if (innerValue.Type == ValueType.Em) { if (parentFontSize == -1) { parentFontSize = ParentFontSize(parentStyle); } innerValue.BakePX(parentFontSize); } else { // Whats the block size? if (useWidth) { innerValue.BakePX(parentStyle.InnerWidth - paddingWidth); } else { innerValue.BakePX(parentStyle.InnerHeight - paddingHeight); } } // And flip useWidth: useWidth = !useWidth; } break; } }
/// <summary>Resolves any percentage heights for all child elements using the given parent element.</summary> /// <param name="parent">The computed style to base percentages on.</param> public void SetHeightForKids(ComputedStyle parent){ SetDimensionForKids(parent,false); }
/// <summary>Apply this CSS style to the given computed style. /// Note that you can grab the element from the computed style if you need that.</summary> /// <param name="style">The computed style to apply the property to.</param> /// <param name="value">The new value being applied.</param> public virtual void Apply(ComputedStyle style,Value value){}
/// <summary>Tells the UI the mouse was clicked or released.</summary> /// <param name="x">The x coordinate of the mouse in pixels from the left of the screen.</param> /// <param name="y">The y coordinate of the mouse in pixels from the top of the screen.</param> /// <param name="mouseDown">True if the button is now down.</param> /// <returns>True if the mouse was on the UI.</returns> private static bool RunClick(int x, int y, bool mouseDown, out UIEvent uiEvent) { uiEvent = new UIEvent(x, y, mouseDown); // Which button? if (Event.current != null) { uiEvent.keyCode = Event.current.button; } if (UI.document == null || UI.document.html == null) { return(false); } int invertedY = ScreenInfo.ScreenY - 1 - y; bool result = false; if (Mode == InputMode.Screen) { result = UI.document.html.RunClickOnKids(uiEvent); } else if (Mode == InputMode.Physics) { // Screen physics cast here. RaycastHit uiHit; if (Physics.Raycast(UI.GUICamera.ScreenPointToRay(new Vector2(x, invertedY)), out uiHit)) { // Did it hit the main UI? HitResult hit = HandleUIHit(uiHit); result = hit.Success; if (result) { // Yes - As this is the main UI, We must have a HitElement available. All we need to do is ClickOn it! ClickOn(hit.HitElement, uiEvent); } } } if (!result && WorldInputMode != InputMode.None) { // Didn't hit the main UI - handle clicks on WorldUI's. RaycastHit worldUIHit; if (CameraFor3DInput == null) { CameraFor3DInput = Camera.main; } bool hitSuccess = false; if (OnResolve3D != null) { hitSuccess = OnResolve3D(out worldUIHit, uiEvent); } else { hitSuccess = Physics.Raycast(CameraFor3DInput.ScreenPointToRay(new Vector2(x, invertedY)), out worldUIHit); } if (hitSuccess) { // Did it hit a worldUI? HitResult hit = HandleWorldUIHit(worldUIHit); result = hit.Success; if (result) { // Yes it did. result = hit.RunClick(uiEvent); } } } // Clear any LastMouseDown entries: if (!mouseDown) { // Clear their active state: for (int i = LastMouseDown.Count - 1; i >= 0; i--) { // Get the element: Element element = LastMouseDown[i]; // Get computed style: Css.ComputedStyle computed = element.Style.Computed; // Clear active: computed.UnsetModifier("active"); // Still got the mouse over it? if (element.MousedOver != MouseOverState.Out) { // Yep! Re-apply hover: computed.Hover(); } } // Clear the set: LastMouseDown.Clear(); } return(result); }
/// <summary>Sets up this renderer so that it's ready to start packing child elements of /// a given element into lines.</summary> /// <param name="element">The parent element whose children will be packed.</param> public void BeginLinePack(Element element){ ComputedStyle computed=element.Style.Computed; if(computed.Display==DisplayType.Block||computed.FixedWidth){ // Block elements are 100% wide unless stated otherwise (ie with a fixed width). MaxX=computed.InnerWidth; if(element.VScrollbar){ MaxX-=14; if(MaxX<0){ MaxX=0; } } }else if(computed.Display==DisplayType.InlineBlock || computed.Display==DisplayType.TableCell){ // An inline block element uses it's parents size as the maximum. // If it exceeds the space left on a line it will jump to the next line anyway. if(element.parentNode!=null){ MaxX=element.parentNode.Style.Computed.InnerWidth; } } // Kids of elements that don't line pack are packed into the lines of the first parent which does. PenX=0; PenY=0; LineStart=0; LargestLineWidth=0; computed.ContentWidth=0; computed.ContentHeight=0; FirstPacked=LastPacked=null; GoingLeftwards=(computed.DrawDirection==DirectionType.RTL); }
/// <summary>Resolves any percentage widths for all child elements using the given parent element.</summary> /// <param name="parent">The computed style to base percentages on.</param> public void SetWidthForKids(ComputedStyle parent){ SetDimensionForKids(parent,true); }
/// <summary>Call this if the current property requires a text object. NOTE: This one may be null.</summary> public TextRenderingProperty GetText(ComputedStyle style){ // Grab it: TextRenderingProperty text=style.Text; if(text!=null){ // Flag the change: text.Changed=true; } return text; }
/// <summary>Resolves any percentages for all child elements using the given parent element.</summary> /// <param name="parent">The computed style to base percentages on.</param> /// <param name="isWidth">True if we should use the width of the parent; false for height.</param> private void SetDimensionForKids(ComputedStyle parent,bool isWidth){ int dimension=isWidth?parent.InnerWidth:parent.InnerHeight; if(ChildNodes!=null){ for(int i=0;i<ChildNodes.Count;i++){ ChildNodes[i].Style.Computed.SetParentDimension(dimension,isWidth,parent); } } if(HScrollbar){ HorizontalScrollbar.Element.Style.Computed.SetParentDimension(dimension,isWidth,parent); } if(VScrollbar){ VerticalScrollbar.Element.Style.Computed.SetParentDimension(dimension,isWidth,parent); } }
public override void Apply(ComputedStyle style,Value value){ // Got any text at all?: if(GetText(style)==null){ return; } // Apply the property: if(value==null || value.Text=="none"){ // Clear the stroke: style.TextStroke=null; }else{ // The stroke properties: int blur=0; Color colour=Color.black; int thickness=value[0].PX; if(thickness==0){ style.TextStroke=null; }else{ StrokeData data=style.TextStroke; if(data==null){ data=new StrokeData(); style.TextStroke=data; } data.Thickness=thickness; // Grab the blur: Value innerValue=value[1]; if(innerValue.Type==ValueType.Color){ colour=innerValue.ToColor(); }else{ blur=innerValue.PX; // Grab the colour: innerValue=value[2]; if(innerValue.Type==ValueType.Color){ colour=innerValue.ToColor(); } } data.Colour=colour; data.Blur=blur; } } // Apply the changes - doesn't change anything about the actual text, so we just want a layout: style.RequestLayout(); }
/// <summary>Sets the clipping boundary from the given computed style.</summary> /// <param name="style">The computed style to find the clipping boundary from.</param> public void SetBoundary(ComputedStyle style){ bool visibleX=(style.OverflowX==OverflowType.Visible); bool visibleY=(style.OverflowY==OverflowType.Visible); if(visibleX && visibleY){ return; } BoxRegion newBoundary=null; if(visibleX){ newBoundary=new BoxRegion(ClippingBoundary.X,style.OffsetTop+style.StyleOffsetTop+style.ScrollTop,ClippingBoundary.Width,style.InnerHeight); }else if(visibleY){ newBoundary=new BoxRegion(style.OffsetLeft+style.StyleOffsetLeft+style.ScrollLeft,ClippingBoundary.Y,style.InnerWidth,ClippingBoundary.Height); }else{ newBoundary=new BoxRegion(style.OffsetLeft+style.StyleOffsetLeft+style.ScrollLeft,style.OffsetTop+style.StyleOffsetTop+style.ScrollTop,style.InnerWidth,style.InnerHeight); } if(style.Clip){ newBoundary.ClipBy(ClippingBoundary); } ClippingBoundary=newBoundary; }
/// <summary>Recalculates the matrices if this transformation has changed.</summary> public void RecalculateMatrix(ComputedStyle style){ if(Changed){ CalculateOrigin(style); _Changed=false; _LocalMatrix=Matrix4x4.TRS(_Origin,Quaternion.identity,Vector3.one); // Skew: if(HasSkew){ _LocalMatrix*=_Skew; } _LocalMatrix*=Matrix4x4.TRS(_Translate,_Rotation,_Scale); _LocalMatrix*=Matrix4x4.TRS(-_Origin,Quaternion.identity,Vector3.one); } if(Parent!=null){ _Matrix=Parent.Matrix*_LocalMatrix; }else{ _Matrix=_LocalMatrix; } }