public string Export(RectangleF parentPadding, float currYPos) { // start by setting our position to our global position, and then we'll translate. float controlLeftPos = TextView.Frame.Left; float controlTopPos = TextView.Frame.Top; // for vertical, it's relative to the control above it, so just make it relative to that IUIControl logicalParent = ParentNote.GetLogicalVerticalParent(this); if (logicalParent != null) { controlTopPos -= logicalParent.GetFrame( ).Bottom; } else { controlTopPos -= currYPos; } // for horizontal, it just needs to remove padding, since it'll be re-applied on load controlLeftPos -= parentPadding.Left; // Add the tag and attribs // Note: remove margin, because the default_style includes it, and that makes no sense when we will visually place it string xml = string.Format("<TI Top=\"{0}\" Height=\"{1}\"", controlTopPos, TextView.Frame.Height); controlLeftPos /= (ParentSize.Width - parentPadding.Left - parentPadding.Right); xml += string.Format(" Left=\"{0:#0.00}%\"", controlLeftPos * 100); xml += ">"; // and the content xml += "</TI>"; return(xml); }
public string Export(RectangleF parentPadding, float currYPos) { // start by setting our position to our global position, and then we'll translate. float controlLeftPos = Frame.Left; float controlTopPos = Frame.Top; // for vertical, it's relative to the control above it, so just make it relative to that IUIControl logicalParent = ParentNote.GetLogicalVerticalParent(this); if (logicalParent != null) { controlTopPos -= logicalParent.GetFrame( ).Bottom; } else { controlTopPos -= currYPos; } // for horizontal, it just needs to remove padding, since it'll be re-applied on load controlLeftPos -= parentPadding.Left; string xml = "<P "; string attributes = ""; controlLeftPos /= (ParentSize.Width - parentPadding.Left - parentPadding.Right); attributes += string.Format("Left=\"{0:#0.00}%\"", controlLeftPos * 100); attributes += string.Format(" Top=\"{0}\"", controlTopPos); if (string.IsNullOrWhiteSpace(ActiveUrl) == false) { attributes += string.Format(" Url=\"{0}\"", HttpUtility.HtmlEncode(ActiveUrl)); } xml += attributes + ">"; foreach (IUIControl child in ChildControls) { IEditableUIControl editableChild = child as IEditableUIControl; if (editableChild != null) { // children of paragraphs cannot set their own position, so pass 0 xml += editableChild.Export(new RectangleF( ), 0); } } xml += "</P>"; return(xml); }
public string Export(RectangleF parentPadding, float currYPos) { // start by setting our position to our global position, and then we'll translate. float controlLeftPos = Frame.Left; float controlTopPos = Frame.Top; // for vertical, it's relative to the control above it, so just make it relative to that IUIControl logicalParent = ParentNote.GetLogicalVerticalParent(this); if (logicalParent != null) { controlTopPos -= logicalParent.GetFrame( ).Bottom; } else { controlTopPos -= currYPos; } // for horizontal, it just needs to remove padding, since it'll be re-applied on load controlLeftPos -= parentPadding.Left; string encodedQuote = HttpUtility.HtmlEncode(QuoteLabel.Text); string encodedCitation = HttpUtility.HtmlEncode(Citation.Text); // Add the tag and attribs // Note: remove margin, because the default_style includes it, and that makes no sense when we will visually place it string xml = string.Format("<Q Margin=\"0\" Citation=\"{0}\" Top=\"{1}\"", encodedCitation, controlTopPos); controlLeftPos /= (ParentSize.Width - parentPadding.Left - parentPadding.Right); xml += string.Format(" Left=\"{0:#0.00}%\"", controlLeftPos * 100); if (string.IsNullOrWhiteSpace(ActiveUrl) == false) { xml += string.Format(" Url=\"{0}\"", HttpUtility.HtmlEncode(ActiveUrl)); } xml += ">"; // and the content xml += encodedQuote + "</Q>"; return(xml); }
public IUIControl GetLogicalVerticalParent(IUIControl sourceControl) { //// DEBUG - SORT SO WE CAN VISUALLY DRAW THINGS //ChildControls.Sort( delegate( IUIControl a, IUIControl b ) //{ // if( a.GetFrame( ).Top < b.GetFrame( ).Top ) // { // return -1; // } // return 1; //}); // // Returns the control that is "truly" above the sourceControl. // This means that its bottom will be ABOVE the top of the sourceControl. // By making controls relative to their logical parent, the spacing remains consistent across // device types IUIControl nearestControl = null; float currMinDeltaY = float.MaxValue; // given a yPos, find the nearest control that is fully "above" this control // and return its bottom position. foreach (IUIControl child in ChildControls) { // skip the control LOOKING for its parent. if (child != sourceControl) { float deltaY = sourceControl.GetFrame( ).Top - child.GetFrame( ).Bottom; if (deltaY >= 0 && deltaY < currMinDeltaY) { currMinDeltaY = deltaY; nearestControl = child; } } } return(nearestControl); }
public static int Sort(IUIControl x, IUIControl y) { RectangleF xFrame = x.GetFrame( ); RectangleF yFrame = y.GetFrame( ); // take the absolute deltas so that when comparing for "exact", // we can allow an error-margin. The reason is certain words might be centered // within the line height, making them a pixel or two off, but they're still effectively // equal with their sibling float deltaY = Math.Abs(yFrame.Y - xFrame.Y); float deltaX = Math.Abs(yFrame.X - xFrame.X); // if Y is the same, check X. if (deltaY < 2) { if (deltaX < 2) { return(0); } return(yFrame.X > xFrame.X ? -1 : 1); } return(yFrame.Y > xFrame.Y ? -1 : 1); }
public Paragraph(CreateParams parentParams, XmlReader reader) { Initialize( ); // Always get our style first mStyle = parentParams.Style; Styles.Style.ParseStyleAttributesWithDefaults(reader, ref mStyle, ref ControlStyles.mParagraph); // check for attributes we support RectangleF bounds = new RectangleF( ); SizeF parentSize = new SizeF(parentParams.Width, parentParams.Height); ParseCommonAttribs(reader, ref parentSize, ref bounds); // Get margins and padding RectangleF padding; RectangleF margin; GetMarginsAndPadding(ref mStyle, ref parentSize, ref bounds, out margin, out padding); // apply margins to as much of the bounds as we can (bottom must be done by our parent container) ApplyImmediateMargins(ref bounds, ref margin, ref parentSize); Margin = margin; // check for border styling int borderPaddingPx = 0; if (mStyle.mBorderColor.HasValue) { BorderView.BorderColor = mStyle.mBorderColor.Value; } if (mStyle.mBorderRadius.HasValue) { BorderView.CornerRadius = mStyle.mBorderRadius.Value; } if (mStyle.mBorderWidth.HasValue) { BorderView.BorderWidth = mStyle.mBorderWidth.Value; borderPaddingPx = (int)Rock.Mobile.Graphics.Util.UnitToPx(mStyle.mBorderWidth.Value + PrivateNoteConfig.BorderPadding); } if (mStyle.mBackgroundColor.HasValue) { BorderView.BackgroundColor = mStyle.mBackgroundColor.Value; } // // now calculate the available width based on padding. (Don't actually change our width) float availableWidth = bounds.Width - padding.Left - padding.Width - (borderPaddingPx * 2); // see if there's a URL we should care about ActiveUrl = reader.GetAttribute("Url"); string urlLaunchesExternalBrowser = reader.GetAttribute("UrlLaunchesExternalBrowser"); if (string.IsNullOrEmpty(urlLaunchesExternalBrowser) == false) { UrlLaunchesExternalBrowser = bool.Parse(urlLaunchesExternalBrowser); } string urlUsesRockImpersonation = reader.GetAttribute("UrlUsesRockImpersonation"); if (string.IsNullOrEmpty(urlUsesRockImpersonation) == false) { UrlUsesRockImpersonation = bool.Parse(urlUsesRockImpersonation); } // now read what our children's alignment should be // check for alignment string result = reader.GetAttribute("ChildAlignment"); if (string.IsNullOrEmpty(result) == false) { switch (result) { case "Left": { ChildHorzAlignment = Alignment.Left; break; } case "Right": { ChildHorzAlignment = Alignment.Right; break; } case "Center": { ChildHorzAlignment = Alignment.Center; break; } default: { ChildHorzAlignment = mStyle.mAlignment.Value; break; } } } else { // if it wasn't specified, use LEFT alignment. ChildHorzAlignment = Alignment.Left; } bool removedLeadingWhitespace = false; bool lastControlWasElement = false; bool finishedReading = false; while (finishedReading == false && reader.Read( )) { switch (reader.NodeType) { case XmlNodeType.Element: { IUIControl control = Parser.TryParseControl(new CreateParams(this, availableWidth, parentParams.Height, ref mStyle), reader); if (control != null) { // if the last control was an element (NoteText or Reveal), then we have two in a row. So place a space between them! if (lastControlWasElement) { NoteText textLabel = Parser.CreateNoteText(new CreateParams(this, availableWidth, parentParams.Height, ref mStyle), " "); ChildControls.Add(textLabel); } // only allow RevealBoxes / NoteText as children. if (control as RevealBox == null && control as NoteText == null) { throw new Exception(String.Format("Paragraph only supports children of type <RevealBox> or <NoteText>. Found <{0}>", control.GetType( ))); } ChildControls.Add(control); // flag that whitespace is removed, because either // this was the first control and we didn't want to, or // it was removed by the first text we created. removedLeadingWhitespace = true; // flag that the last control placed was a reveal, so that // should we come across another one immediately, we know to insert a space // so they don't render concatenated. lastControlWasElement = true; } break; } case XmlNodeType.Text: { // give the text a style that doesn't include things it shouldn't inherit Styles.Style textStyle = mStyle; textStyle.mBorderColor = null; textStyle.mBorderRadius = null; textStyle.mBorderWidth = null; // grab the text. remove any weird characters string text = Regex.Replace(reader.Value, @"\t|\n|\r", ""); if (removedLeadingWhitespace == false) { removedLeadingWhitespace = true; text = text.TrimStart(' '); } // now break it into words so we can do word wrapping string[] words = text.Split(' '); foreach (string word in words) { // create labels out of each one if (string.IsNullOrEmpty(word) == false) { // if the last thing we added was a special control like a reveal box, we // need the first label after that to have a leading space so it doesn't bunch up against // the control string nextWord = word; if (lastControlWasElement) { nextWord = word.Insert(0, " "); lastControlWasElement = false; } NoteText wordLabel = Parser.CreateNoteText(new CreateParams(this, availableWidth, parentParams.Height, ref textStyle), nextWord + " "); ChildControls.Add(wordLabel); } } lastControlWasElement = false; break; } case XmlNodeType.EndElement: { // if we hit the end of our label, we're done. //if( reader.Name == "Paragraph" || reader.Name == "P" ) if (ElementTagMatches(reader.Name)) { finishedReading = true; } break; } } } // should we add a URL Glyph? We're gonna be clever and add it AS a NoteText, so that it integrates with the paragraph nicely. // now add our glyph, if relevant TryAddUrlGlyph(availableWidth, parentParams.Height); // layout all controls // paragraphs are tricky. // We need to lay out controls horizontally and wrap when we run out of room. // To align, we need to keep track of each "row". When the row is full, // we calculate its width, and then adjust each item IN that row so // that the row is centered within the max width of the paragraph. // The max width of the paragraph is defined as the widest row. // maintain a list of all our rows so that once they are all generated, // we can align them based on the widest row. float maxRowWidth = 0; List <List <IUIControl> > rowList = new List <List <IUIControl> >( ); // track where within a row we need to start a control float rowRemainingWidth = availableWidth; float startingX = bounds.X + padding.Left + borderPaddingPx; // always store the last placed control's height so that should // our NEXT control need to wrap, we know how far down to wrap. float yOffset = bounds.Y + padding.Top + borderPaddingPx; float lastControlHeight = 0; float rowWidth = 0; //Create our first row and put it in our list List <IUIControl> currentRow = new List <IUIControl>( ); rowList.Add(currentRow); foreach (IUIControl control in ChildControls) { RectangleF controlFrame = control.GetFrame( ); // if there is NOT enough room on this row for the next control if (rowRemainingWidth < controlFrame.Width) { // since we're advancing to the next row, trim leading white space, which, if we weren't wrapping, // would be a space between words. // note: we can safely cast to a NoteText because that's the only child type we allow. string text = ((NoteText)control).GetText( ).TrimStart(' '); ((NoteText)control).SetText(text); // advance to the next row yOffset += lastControlHeight; // Reset values for the new row rowRemainingWidth = availableWidth; startingX = bounds.X + padding.Left + borderPaddingPx; lastControlHeight = 0; rowWidth = 0; currentRow = new List <IUIControl>( ); rowList.Add(currentRow); } // Add this next control to the current row currentRow.Add(control); // position this control appropriately control.AddOffset(startingX, yOffset); // update so the next child begins beyond this one. // also reduce the available width by this control's. rowWidth += controlFrame.Width; startingX += controlFrame.Width; //Increment startingX so the next control is placed after this one. rowRemainingWidth -= controlFrame.Width; //Reduce the available width by what this control took. lastControlHeight = controlFrame.Height > lastControlHeight ? controlFrame.Height : lastControlHeight; //Store the height of the tallest control on this row. // track the widest row maxRowWidth = rowWidth > maxRowWidth ? rowWidth : maxRowWidth; } // give each row the legal bounds it may work with RectangleF availableBounds = new RectangleF(bounds.X + padding.Left + borderPaddingPx, bounds.Y + borderPaddingPx + padding.Top, availableWidth, bounds.Height); // Now that we know the widest row, align all the rows foreach (List <IUIControl> row in rowList) { AlignRow(availableBounds, row, maxRowWidth); } // Build our final frame that determines our dimensions RectangleF frame = new RectangleF(65000, 65000, -65000, -65000); // for each child control foreach (IUIControl control in ChildControls) { // enlarge our frame by the current frame and the next child frame = Parser.CalcBoundingFrame(frame, control.GetFrame( )); } frame.Y = bounds.Y; frame.X = bounds.X; frame.Height += padding.Height + padding.Top + (borderPaddingPx * 2); //add in padding frame.Width = bounds.Width; // setup our bounding rect for the border frame = new RectangleF(frame.X, frame.Y, frame.Width, frame.Height); // and store that as our bounds BorderView.Frame = frame; Frame = frame; SetDebugFrame(Frame); // sort everything ChildControls.Sort(BaseControl.Sort); }
public Canvas(CreateParams parentParams, XmlReader reader) { Initialize( ); // Always get our style first mStyle = parentParams.Style; Styles.Style.ParseStyleAttributesWithDefaults(reader, ref mStyle, ref ControlStyles.mCanvas); // check for attributes we support RectangleF bounds = new RectangleF( ); SizeF parentSize = new SizeF(parentParams.Width, parentParams.Height); ParseCommonAttribs(reader, ref parentSize, ref bounds); // Get margins and padding RectangleF padding; RectangleF margin; GetMarginsAndPadding(ref mStyle, ref parentSize, ref bounds, out margin, out padding); // apply margins to as much of the bounds as we can (bottom must be done by our parent container) ApplyImmediateMargins(ref bounds, ref margin, ref parentSize); Margin = margin; // check for border styling int borderPaddingPx = 0; if (mStyle.mBorderColor.HasValue) { BorderView.BorderColor = mStyle.mBorderColor.Value; } if (mStyle.mBorderRadius.HasValue) { BorderView.CornerRadius = mStyle.mBorderRadius.Value; } if (mStyle.mBorderWidth.HasValue) { BorderView.BorderWidth = mStyle.mBorderWidth.Value; borderPaddingPx = (int)Rock.Mobile.Graphics.Util.UnitToPx(mStyle.mBorderWidth.Value + PrivateNoteConfig.BorderPadding); } if (mStyle.mBackgroundColor.HasValue) { BorderView.BackgroundColor = mStyle.mBackgroundColor.Value; } // // now calculate the available width based on padding. (Don't actually change our width) float availableWidth = bounds.Width - padding.Left - padding.Width - (borderPaddingPx * 2); // now read what our children's alignment should be // check for alignment string result = reader.GetAttribute("ChildAlignment"); if (string.IsNullOrEmpty(result) == false) { switch (result) { case "Left": ChildHorzAlignment = Alignment.Left; break; case "Right": ChildHorzAlignment = Alignment.Right; break; case "Center": ChildHorzAlignment = Alignment.Center; break; default: ChildHorzAlignment = mStyle.mAlignment.Value; break; } } else { // if it wasn't specified, use OUR alignment. ChildHorzAlignment = mStyle.mAlignment.Value; } // Parse Child Controls bool finishedParsing = false; while (finishedParsing == false && reader.Read( )) { switch (reader.NodeType) { case XmlNodeType.Element: { // let each child have our available width. Style style = new Style( ); style = mStyle; style.mAlignment = ChildHorzAlignment; IUIControl control = Parser.TryParseControl(new CreateParams(this, availableWidth, parentParams.Height, ref style), reader); if (control != null) { ChildControls.Add(control); } break; } case XmlNodeType.EndElement: { // if we hit the end of our label, we're done. //if( reader.Name == "Canvas" || reader.Name == "C" ) if (ElementTagMatches(reader.Name)) { finishedParsing = true; } break; } } } // layout all controls float yOffset = bounds.Y + padding.Top + borderPaddingPx; //vertically they should just stack float height = 0; // now we must center each control within the stack. foreach (IUIControl control in ChildControls) { RectangleF controlFrame = control.GetFrame( ); RectangleF controlMargin = control.GetMargin( ); // horizontally position the controls according to their // requested alignment Alignment controlAlignment = control.GetHorzAlignment( ); // adjust by our position float xAdjust = 0; switch (controlAlignment) { case Alignment.Center: xAdjust = bounds.X + ((availableWidth / 2) - (controlFrame.Width / 2)); break; case Alignment.Right: xAdjust = bounds.X + (availableWidth - (controlFrame.Width + controlMargin.Width)); break; case Alignment.Left: xAdjust = bounds.X; break; } // adjust the next sibling by yOffset control.AddOffset(xAdjust + padding.Left + borderPaddingPx, yOffset); // track the height of the grid by the control lowest control height = (control.GetFrame( ).Bottom + +controlMargin.Height) > height ? (control.GetFrame( ).Bottom + +controlMargin.Height) : height; } // we need to store our bounds. We cannot // calculate them on the fly because we // would lose any control defined offsets, which would throw everything off. bounds.Height = height + padding.Height + borderPaddingPx; // setup our bounding rect for the border bounds = new RectangleF(bounds.X, bounds.Y, bounds.Width, bounds.Height); // and store that as our bounds BorderView.Frame = bounds; Frame = bounds; // store our debug frame SetDebugFrame(Frame); // sort everything ChildControls.Sort(BaseControl.Sort); }
public void HandleCreateControl(Type controlType, PointF mousePos) { do { // first, if we're creating a header, we need to make sure there isn't already one if (typeof(EditableHeader) == controlType) { List <IUIControl> headerControls = new List <IUIControl>( ); GetControlOfType <EditableHeader>(headerControls); // we found a header, so we're done. if (headerControls.Count > 0) { break; } } // now see if any child wants to create it IUIControl newControl = null; foreach (IUIControl control in ChildControls) { IEditableUIControl editableControl = control as IEditableUIControl; if (editableControl != null) { IEditableUIControl containerControl = editableControl.ContainerForControl(controlType, mousePos); if (containerControl != null) { newControl = containerControl.HandleCreateControl(controlType, mousePos); break; } } } // if a child handled it, we're done if (newControl != null) { break; } // it wasn't a header, and a child didn't create it, so we will. float availableWidth = Frame.Width - Padding.Right - Padding.Left; // if the control type is a header, we want to force it to position 0 float workingWidth = availableWidth; if (typeof(EditableHeader) == controlType) { mousePos = PointF.Empty; // and if its allowed, use the full width if (mStyle.mFullWidthHeader == true) { workingWidth = Frame.Width; } } newControl = Parser.CreateEditableControl(controlType, new BaseControl.CreateParams(this, workingWidth, DeviceHeight, ref mStyle)); ChildControls.Add(newControl); // add it to our renderable canvas newControl.AddToView(MasterView); // default it to where the click occurred newControl.AddOffset((float)mousePos.X, (float)mousePos.Y); // if the newly created control is the lower than all others, update the note height. // This lets us continue to build vertically if (newControl.GetFrame( ).Bottom > Frame.Height) { Frame = new RectangleF(Frame.Left, Frame.Top, Frame.Width, newControl.GetFrame( ).Bottom + Padding.Bottom); } }while(0 != 0); }
void ParseNote(XmlReader reader, float parentWidthUnits, float parentHeightUnits) { DeviceHeight = parentHeightUnits; // get the style first Styles.Style.ParseStyleAttributesWithDefaults(reader, ref mStyle, ref ControlStyles.mMainNote); // check for attributes we support RectangleF bounds = new RectangleF( ); SizeF parentSize = new SizeF(parentWidthUnits, parentHeightUnits); Parser.ParseBounds(reader, ref parentSize, ref bounds); // Parent note doesn't support margins. // PADDING float leftPadding = Styles.Style.GetValueForNullable(mStyle.mPaddingLeft, parentWidthUnits, 0); float rightPadding = Styles.Style.GetValueForNullable(mStyle.mPaddingRight, parentWidthUnits, 0); float topPadding = Styles.Style.GetValueForNullable(mStyle.mPaddingTop, parentHeightUnits, 0); float bottomPadding = Styles.Style.GetValueForNullable(mStyle.mPaddingBottom, parentHeightUnits, 0); Padding = new RectangleF(leftPadding, rightPadding, topPadding, bottomPadding); // now calculate the available width based on padding. (Don't actually change our width) float availableWidth = parentWidthUnits - leftPadding - rightPadding; // A "special" (we won't call this a hack) attribute that will enable the user // to have a header container that spans the full width of the note, which allows // it to be unaffected by the padding. string result = reader.GetAttribute("FullWidthHeader"); if (string.IsNullOrEmpty(result) == false) { mStyle.mFullWidthHeader = bool.Parse(result); } // begin reading the xml stream bool finishedReading = false; while (finishedReading == false && reader.Read( )) { switch (reader.NodeType) { case XmlNodeType.Element: { float workingWidth = availableWidth; if (Header.ElementTagMatches(reader.Name) == true && mStyle.mFullWidthHeader == true) { workingWidth = parentWidthUnits; } IUIControl control = Parser.TryParseControl(new BaseControl.CreateParams(this, workingWidth, parentHeightUnits, ref mStyle), reader); ChildControls.Add(control); break; } case XmlNodeType.EndElement: { if (reader.Name == "Note") { finishedReading = true; } break; } } } // lay stuff out vertically. If the notes were built by hand, like a stackPanel. // if not by hand, like a canvas. float noteHeight = bounds.Y + topPadding; foreach (IUIControl control in ChildControls) { RectangleF controlFrame = control.GetFrame( ); RectangleF controlMargin = control.GetMargin( ); // horizontally position the controls according to their // requested alignment Alignment controlAlignment = control.GetHorzAlignment( ); // adjust by our position float xAdjust = 0; switch (controlAlignment) { case Alignment.Center: { xAdjust = bounds.X + ((availableWidth / 2) - (controlFrame.Width / 2)); break; } case Alignment.Right: { xAdjust = bounds.X + (availableWidth - (controlFrame.Width + controlMargin.Width)); break; } case Alignment.Left: { xAdjust = bounds.X; break; } } // place this next control at yOffset. yOffset should be the current noteHeight, which makes each control relative to the one above it. float yOffset = noteHeight; // if it's the header and full width is specified, don't apply padding. if (control as Header != null && mStyle.mFullWidthHeader == true) { control.AddOffset(xAdjust, yOffset); } else { control.AddOffset(xAdjust + leftPadding, yOffset); } // update the note height noteHeight = control.GetFrame( ).Bottom + controlMargin.Height; } bounds.Width = parentWidthUnits; bounds.Height = (noteHeight - bounds.Y) + bottomPadding; Frame = bounds; AddControlsToView( ); }
public List(CreateParams parentParams, XmlReader reader) { Initialize( ); // Always get our style first mStyle = parentParams.Style; Styles.Style.ParseStyleAttributesWithDefaults(reader, ref mStyle, ref ControlStyles.mList); // check for attributes we support RectangleF bounds = new RectangleF( ); SizeF parentSize = new SizeF(parentParams.Width, parentParams.Height); ParseCommonAttribs(reader, ref parentSize, ref bounds); // Get margins and padding RectangleF padding; RectangleF margin; GetMarginsAndPadding(ref mStyle, ref parentSize, ref bounds, out margin, out padding); // apply margins to as much of the bounds as we can (bottom must be done by our parent container) ApplyImmediateMargins(ref bounds, ref margin, ref parentSize); Margin = margin; // check for border styling int borderPaddingPx = 0; if (mStyle.mBorderColor.HasValue) { BorderView.BorderColor = mStyle.mBorderColor.Value; } if (mStyle.mBorderRadius.HasValue) { BorderView.CornerRadius = mStyle.mBorderRadius.Value; } if (mStyle.mBorderWidth.HasValue) { BorderView.BorderWidth = mStyle.mBorderWidth.Value; borderPaddingPx = (int)Rock.Mobile.Graphics.Util.UnitToPx(mStyle.mBorderWidth.Value + PrivateNoteConfig.BorderPadding); } if (mStyle.mBackgroundColor.HasValue) { BorderView.BackgroundColor = mStyle.mBackgroundColor.Value; } // // convert indentation if it's a percentage float listIndentation = mStyle.mListIndention.Value; if (listIndentation < 1) { listIndentation = parentParams.Width * listIndentation; } // now calculate the available width based on padding. (Don't actually change our width) // also consider the indention amount of the list. float availableWidth = bounds.Width - padding.Left - padding.Width - listIndentation - (borderPaddingPx * 2); // parse for the desired list style. Default to Bullet if they didn't put anything. ListType = reader.GetAttribute("Type"); if (string.IsNullOrEmpty(ListType) == true) { ListType = ListTypeBullet; } // Parse Child Controls int numberedCount = 1; // don't force our alignment, borders, bullet style or indentation on children. Style style = new Style( ); style = mStyle; style.mAlignment = null; style.mListIndention = null; style.mListBullet = null; style.mBorderColor = null; style.mBorderRadius = null; style.mBorderWidth = null; bool finishedParsing = false; while (finishedParsing == false && reader.Read( )) { switch (reader.NodeType) { case XmlNodeType.Element: { // Create the prefix for this list item. string listItemPrefixStr = mStyle.mListBullet + " "; if (ListType == ListTypeNumbered) { listItemPrefixStr = numberedCount.ToString() + ". "; } NoteText textLabel = Parser.CreateNoteText(new CreateParams(this, availableWidth, parentParams.Height, ref style), listItemPrefixStr); ChildControls.Add(textLabel); // create our actual child, but throw an exception if it's anything but a ListItem. IUIControl control = Parser.TryParseControl(new CreateParams(this, availableWidth - textLabel.GetFrame().Width, parentParams.Height, ref style), reader); ListItem listItem = control as ListItem; if (listItem == null) { throw new Exception(String.Format("Only a <ListItem> may be a child of a <List>. Found element <{0}>.", control.GetType( ))); } // if it will actually use the bullet point, increment our count. if (listItem.ShouldShowBulletPoint() == true) { numberedCount++; } else { // otherwise give it a blank space, and keep our count the same. textLabel.SetText(" "); } // and finally add the actual list item. ChildControls.Add(control); break; } case XmlNodeType.EndElement: { // if we hit the end of our label, we're done. //if( reader.Name == "List" || reader.Name == "L" ) if (ElementTagMatches(reader.Name)) { finishedParsing = true; } break; } } } // layout all controls float xAdjust = bounds.X + listIndentation; float yOffset = bounds.Y + padding.Top + borderPaddingPx; //vertically they should just stack // we know each child is a NoteText followed by ListItem. So, lay them out // as: * - ListItem // * - ListItem foreach (IUIControl control in ChildControls) { // position the control control.AddOffset(xAdjust + padding.Left + borderPaddingPx, yOffset); RectangleF controlFrame = control.GetFrame( ); RectangleF controlMargin = control.GetMargin( ); // is this the item prefix? if ((control as NoteText) != null) { // and update xAdjust so the actual item starts after. xAdjust += controlFrame.Width; } else { // reset the values for the next line. xAdjust = bounds.X + listIndentation; yOffset = controlFrame.Bottom + controlMargin.Height; } } // we need to store our bounds. We cannot // calculate them on the fly because we // would lose any control defined offsets, which would throw everything off. bounds.Height = (yOffset - bounds.Y) + padding.Height + borderPaddingPx; Frame = bounds; BorderView.Frame = bounds; // store our debug frame SetDebugFrame(Frame); // sort everything ChildControls.Sort(BaseControl.Sort); }