// COPYRIGHT (C) Tom. ALL RIGHTS RESERVED.
// THE AntdUI PROJECT IS AN WINFORM LIBRARY LICENSED UNDER THE Apache-2.0 License.
// LICENSED UNDER THE Apache License, VERSION 2.0 (THE "License")
// YOU MAY NOT USE THIS FILE EXCEPT IN COMPLIANCE WITH THE License.
// YOU MAY OBTAIN A COPY OF THE LICENSE AT
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, SOFTWARE
// DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
// SEE THE LICENSE FOR THE SPECIFIC LANGUAGE GOVERNING PERMISSIONS AND
// LIMITATIONS UNDER THE License.
// GITEE: https://gitee.com/antdui/AntdUI
// GITHUB: https://github.com/AntdUI/AntdUI
// CSDN: https://blog.csdn.net/v_132
// QQ: 17379620
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows.Forms;
namespace AntdUI.Chat
{
///
/// ChatList 气泡聊天列表
///
/// 气泡聊天列表。
[Description("ChatList 气泡聊天列表")]
[ToolboxItem(true)]
public class ChatList : IControl
{
#region 属性
ChatItemCollection? items;
///
/// 数据集合
///
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Description("数据集合"), Category("数据")]
public ChatItemCollection Items
{
get
{
items ??= new ChatItemCollection(this);
return items;
}
set => items = value.BindData(this);
}
[Description("Emoji字体"), Category("外观"), DefaultValue("Segoe UI Emoji")]
public string EmojiFont { get; set; } = "Segoe UI Emoji";
///
/// 滚动条
///
[Browsable(false)]
public ScrollBar ScrollBar;
#endregion
#region 方法
public bool AddToBottom(IChatItem it, bool force = false)
{
if (force)
{
Items.Add(it);
ToBottom();
return true;
}
else
{
bool isbutt = IsBottom;
Items.Add(it);
if (isbutt) ToBottom();
return isbutt;
}
}
public bool IsBottom
{
get
{
if (ScrollBar.Show) return ScrollBar.Value == ScrollBar.VrValueI;
else return true;
}
}
public void ToBottom()
{
ScrollBar.Value = ScrollBar.VrValueI;
}
#endregion
#region 渲染
protected override void OnPaint(PaintEventArgs e)
{
if (items == null || items.Count == 0) return;
var rect = ClientRectangle;
if (rect.Width == 0 || rect.Height == 0) return;
var g = e.Graphics.High();
float sy = ScrollBar.Value, radius = Config.Dpi * 8F;
g.TranslateTransform(0, -sy);
foreach (IChatItem it in items) PaintItem(g, it, rect, sy, radius);
g.ResetTransform();
ScrollBar.Paint(g);
base.OnPaint(e);
}
StringFormat SFL = Helper.SF(tb: StringAlignment.Near);
void PaintItem(Graphics g, IChatItem it, Rectangle rect, float sy, float radius)
{
it.show = it.Show && it.rect.Y > sy - rect.Height - it.rect.Height && it.rect.Bottom < ScrollBar.Value + ScrollBar.ReadSize + it.rect.Height;
if (it.show)
{
if (it is TextChatItem text)
{
using (var path = text.rect_read.RoundPath(radius))
{
using (var brush = new SolidBrush(Style.Db.TextTertiary))
{
g.DrawStr(text.Name, Font, brush, text.rect_name, SFL);
}
if (text.Me)
{
using (var brush = new SolidBrush(Color.FromArgb(0, 153, 255)))
{
g.FillPath(brush, path);
}
if (text.selectionLength > 0)
{
using (var brush = new SolidBrush(Color.FromArgb(0, 134, 224)))
{
g.FillPath(brush, path);
}
}
using (var brush = new SolidBrush(Color.White))
{
PaintItemText(g, text, brush);
}
}
else
{
using (var brush = new SolidBrush(Color.White))
{
g.FillPath(brush, path);
}
if (text.selectionLength > 0)
{
using (var brush = new SolidBrush(Style.Db.FillQuaternary))
{
g.FillPath(brush, path);
}
}
using (var brush = new SolidBrush(Color.Black))
{
PaintItemText(g, text, brush);
}
}
}
if (text.Icon != null) g.PaintImg(text.rect_icon, text.Icon, TFit.Cover, 0, true);
}
}
}
void PaintItemText(Graphics g, TextChatItem text, SolidBrush fore)
{
if (text.selectionLength > 0)
{
int end = text.selectionStartTemp + text.selectionLength - 1;
if (end > text.cache_font.Length - 1) end = text.cache_font.Length - 1;
CacheFont first = text.cache_font[text.selectionStartTemp];
using (var brush = new SolidBrush(Color.FromArgb(text.Me ? 255 : 60, 96, 165, 250)))
{
for (int i = text.selectionStartTemp; i <= end; i++)
{
var last = text.cache_font[i];
if (i == end) g.FillRectangle(brush, new Rectangle(first.rect.X, first.rect.Y, last.rect.Right - first.rect.X, first.rect.Height));
else if (first.rect.Y != last.rect.Y || last.retun)
{
last = text.cache_font[i - 1];
g.FillRectangle(brush, new Rectangle(first.rect.X, first.rect.Y, last.rect.Right - first.rect.X, first.rect.Height));
first = text.cache_font[i];
}
}
}
}
if (text.HasEmoji)
{
using (var font = new Font(EmojiFont, Font.Size))
{
foreach (var it in text.cache_font)
{
switch (it.type)
{
case GraphemeSplitter.STRE_TYPE.STR:
if (it.emoji) g.DrawStr(it.text, font, fore, it.rect, m_sf);
else g.DrawStr(it.text, Font, fore, it.rect, m_sf);
break;
case GraphemeSplitter.STRE_TYPE.SVG:
using (var bmp_svg = SvgExtend.SvgToBmp(it.text))
{
if (bmp_svg != null) g.PaintImg(it.rect, bmp_svg, TFit.Cover, 0, false);
}
break;
case GraphemeSplitter.STRE_TYPE.BASE64IMG:
using (var ms = new MemoryStream(Convert.FromBase64String(it.text.Substring(it.text.IndexOf(";base64,") + 8))))
using (var bmp_base64 = Image.FromStream(ms))
{
g.PaintImg(it.rect, bmp_base64, TFit.Contain, 0, false);
}
break;
}
}
}
}
else
{
foreach (var it in text.cache_font)
{
switch (it.type)
{
case GraphemeSplitter.STRE_TYPE.STR:
g.DrawStr(it.text, Font, fore, it.rect, m_sf);
break;
case GraphemeSplitter.STRE_TYPE.SVG:
using (var bmp_svg = SvgExtend.SvgToBmp(it.text))
{
if (bmp_svg != null) g.PaintImg(it.rect, bmp_svg, TFit.Cover, 0, false);
}
break;
case GraphemeSplitter.STRE_TYPE.BASE64IMG:
using (var ms = new MemoryStream(Convert.FromBase64String(it.text.Substring(it.text.IndexOf(";base64,") + 8))))
using (var bmp_base64 = Image.FromStream(ms))
{
g.PaintImg(it.rect, bmp_base64, TFit.Contain, 0, false);
}
break;
}
}
}
if (text.showlinedot)
{
int size = (int)(2 * Config.Dpi), w = size * 3;
if (text.cache_font.Length > 0)
{
var rect = text.cache_font[text.cache_font.Length - 1].rect;
using (var brush = new SolidBrush(Color.FromArgb(0, 153, 255)))
{
g.FillRectangle(brush, new Rectangle(rect.Right - w / 2, rect.Bottom - size, w, size));
}
}
else
{
using (var brush = new SolidBrush(Color.FromArgb(0, 153, 255)))
{
g.FillRectangle(brush, new Rectangle(text.rect_read.X + (text.rect_read.Width - w) / 2, text.rect_read.Bottom - size, w, size));
}
}
}
}
public ChatList() { ScrollBar = new ScrollBar(this); }
protected override void Dispose(bool disposing)
{
ScrollBar.Dispose();
base.Dispose(disposing);
}
#endregion
#region 鼠标
TextChatItem? mouseDown = null;
Point oldMouseDown;
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (ScrollBar.MouseDown(e.Location))
{
if (items == null || items.Count == 0) return;
Focus();
int scrolly = ScrollBar.Value;
foreach (IChatItem it in Items)
{
if (it.show && it.Contains(e.Location, 0, scrolly))
{
if (it is TextChatItem text)
{
text.SelectionLength = 0;
if (e.Button == MouseButtons.Left && text.ContainsRead(e.Location, 0, scrolly))
{
oldMouseDown = e.Location;
text.SelectionStart = GetCaretPostion(text, e.X, e.Y + scrolly);
mouseDown = text;
}
}
ItemClick?.Invoke(this, new ChatItemEventArgs(it, e));
}
else if (it is TextChatItem text) text.SelectionLength = 0;
}
}
}
bool mouseDownMove = false;
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
int scrolly = ScrollBar.Value;
if (mouseDown != null)
{
mouseDownMove = true;
SetCursor(CursorType.IBeam);
var index = GetCaretPostion(mouseDown, oldMouseDown.X + (e.X - oldMouseDown.X), oldMouseDown.Y + scrolly + (e.Y - oldMouseDown.Y));
mouseDown.SelectionLength = Math.Abs(index - mouseDown.selectionStart);
if (index > mouseDown.selectionStart) mouseDown.selectionStartTemp = mouseDown.selectionStart;
else mouseDown.selectionStartTemp = index;
Invalidate();
}
else if (ScrollBar.MouseMove(e.Location))
{
if (items == null || items.Count == 0) return;
int count = 0, hand = 0, ibeam = 0;
foreach (IChatItem it in Items)
{
if (it.show && it.Contains(e.Location, 0, scrolly))
{
if (it is TextChatItem text)
{
if (text.ContainsRead(e.Location, 0, scrolly)) ibeam++;
}
//if (it.Contains(e.Location, 0, (int)scrollY.Value, out var change))
//{
// hand++;
//}
//if (change) count++;
}
}
if (ibeam > 0) SetCursor(CursorType.IBeam);
else SetCursor(hand > 0);
if (count > 0) Invalidate();
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (mouseDown != null && mouseDownMove)
{
int scrolly = ScrollBar.Value;
var index = GetCaretPostion(mouseDown, e.X, e.Y + scrolly);
if (mouseDown.selectionStart == index) mouseDown.SelectionLength = 0;
else if (index > mouseDown.selectionStart)
{
mouseDown.SelectionLength = Math.Abs(index - mouseDown.selectionStart);
mouseDown.SelectionStart = mouseDown.selectionStart;
}
else
{
mouseDown.SelectionLength = Math.Abs(index - mouseDown.selectionStart);
mouseDown.SelectionStart = index;
}
Invalidate();
}
mouseDown = null;
ScrollBar.MouseUp();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
ScrollBar.Leave();
SetCursor(false);
}
protected override void OnLostFocus(EventArgs e)
{
base.OnLostFocus(e);
ILeave();
}
protected override void OnLeave(EventArgs e)
{
base.OnLeave(e);
ILeave();
}
void ILeave()
{
ScrollBar.Leave();
SetCursor(false);
if (items == null || items.Count == 0) return;
int count = 0;
foreach (IChatItem it in Items)
{
if (it is TextChatItem text) text.SelectionLength = 0;
}
if (count > 0) Invalidate();
}
protected override void OnMouseWheel(MouseEventArgs e)
{
ScrollBar.MouseWheel(e.Delta);
base.OnMouseWheel(e);
}
#endregion
#region 事件
///
/// 单击时发生
///
[Description("单击时发生"), Category("行为")]
public event ClickEventHandler? ItemClick;
#endregion
#region 键盘
protected override bool ProcessCmdKey(ref System.Windows.Forms.Message msg, Keys keyData)
{
switch (keyData)
{
case Keys.Control | Keys.A:
SelectAll();
return true;
case Keys.Control | Keys.C:
Copy();
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
void SelectAll()
{
foreach (IChatItem it in Items)
{
if (it is TextChatItem text && text.SelectionLength > 0)
{
text.SelectionStart = 0;
text.selectionLength = text.cache_font.Length;
return;
}
}
}
void Copy()
{
foreach (IChatItem it in Items)
{
if (it is TextChatItem text && text.SelectionLength > 0)
{
var _text = GetSelectionText(text);
if (_text == null) return;
this.ClipboardSetText(_text);
return;
}
}
}
string? GetSelectionText(TextChatItem text)
{
if (text.cache_font == null) return null;
else
{
if (text.selectionLength > 0)
{
int start = text.selectionStart, end = text.selectionLength;
int end_temp = start + end;
var texts = new List(end);
foreach (var it in text.cache_font)
{
if (it.i >= start && end_temp > it.i) texts.Add(it.text);
}
return string.Join("", texts);
}
return null;
}
}
#endregion
#region 文本
///
/// 通过坐标系查找光标位置
///
int GetCaretPostion(TextChatItem item, int x, int y)
{
foreach (var it in item.cache_font)
{
if (it.rect.X <= x && it.rect.Right >= x && it.rect.Y <= y && it.rect.Bottom >= y)
{
if (x > it.rect.X + it.rect.Width / 2) return it.i + 1;
else return it.i;
}
}
var nearest = FindNearestFont(x, y, item.cache_font);
if (nearest == null)
{
if (x > item.cache_font[item.cache_font.Length - 1].rect.Right) return item.cache_font.Length;
else return 0;
}
else
{
if (x > nearest.rect.X + nearest.rect.Width / 2) return nearest.i + 1;
else return nearest.i;
}
}
///
/// 寻找最近的矩形和距离的辅助方法
///
CacheFont? FindNearestFont(int x, int y, CacheFont[] cache_font)
{
CacheFont first = cache_font[0], last = cache_font[cache_font.Length - 1];
if (x < first.rect.X && y < first.rect.Y) return first;
else if (x > last.rect.X && y > last.rect.Y) return last;
double minDistance = int.MaxValue;
CacheFont? result = null;
for (int i = 0; i < cache_font.Length; i++)
{
var it = cache_font[i];
// 计算点到矩形四个边的最近距离,取最小值作为当前矩形的最近距离
int distanceToLeft = Math.Abs(x - (it.rect.Left + it.rect.Width / 2)),
distanceToTop = Math.Abs(y - (it.rect.Top + it.rect.Height / 2));
double currentMinDistance = new int[] { distanceToLeft, distanceToTop }.Average();
// 如果当前矩形的最近距离比之前找到的最近距离小,更新最近距离和最近矩形信息
if (currentMinDistance < minDistance)
{
minDistance = currentMinDistance;
result = it;
}
}
return result;
}
#endregion
#region 布局
protected override void OnFontChanged(EventArgs e)
{
var rect = ChangeList();
ScrollBar.SizeChange(rect);
base.OnFontChanged(e);
}
protected override void OnSizeChanged(EventArgs e)
{
var rect = ChangeList();
ScrollBar.SizeChange(rect);
base.OnSizeChanged(e);
}
StringFormat m_sf = Helper.SF_MEASURE_FONT();
internal Rectangle ChangeList()
{
var rect = ClientRectangle;
if (items == null || items.Count == 0) return rect;
if (rect.Width == 0 || rect.Height == 0) return rect;
int y = 0;
Helper.GDI(g =>
{
var size = (int)Math.Ceiling(g.MeasureString(Config.NullText, Font).Height);
int item_height = (int)Math.Ceiling(size * 1.714),
gap = (int)Math.Round(item_height * 0.75),
spilt = item_height - gap, spilt2 = spilt * 2, max_width = (int)(rect.Width * 0.8F) - item_height;
y = spilt;
foreach (IChatItem it in items)
{
it.PARENT = this;
if (it is TextChatItem text)
{
y += text.SetRect(rect, y, g, Font, FixFontWidth(g, Font, text, max_width, spilt2), size, spilt, spilt2, item_height) + gap;
}
}
});
ScrollBar.SetVrSize(y);
return rect;
}
#region 字体
internal Size FixFontWidth(Graphics g, Font Font, TextChatItem item, int max_width, int spilt)
{
item.HasEmoji = false;
int font_height = 0;
var font_widths = new List(item.Text.Length);
GraphemeSplitter.EachT(item.Text, 0, (str, type, nStart, nLen) =>
{
string it = str.Substring(nStart, nLen);
switch (type)
{
case GraphemeSplitter.STRE_TYPE.BASE64IMG:
using (var ms = new MemoryStream(Convert.FromBase64String(it.Substring(it.IndexOf(";base64,") + 8))))
using (var image = Image.FromStream(ms))
{
int imgWidth = image.Width;
int imgHeight = image.Height;
if (imgWidth > max_width)
{
float scaleRatio = (float)max_width / imgWidth;
imgWidth = max_width;
imgHeight = (int)(imgHeight * scaleRatio);
}
font_widths.Add(new CacheFont(it, false, imgWidth, type)
{
imageHeight = imgHeight
});
}
break;
case GraphemeSplitter.STRE_TYPE.SVG:
using (var svgImage = SvgExtend.SvgToBmp(it, font_height, font_height, null))
{
if (svgImage != null)
{
int svgWidth = svgImage.Width;
int svgHeight = svgImage.Height;
font_widths.Add(new CacheFont(it, false, svgWidth, type)
{
imageHeight = svgHeight
});
}
}
break;
default:
var unicodeInfo = CharUnicodeInfo.GetUnicodeCategory(it[0]);
if (IsEmoji(unicodeInfo))
{
item.HasEmoji = true;
font_widths.Add(new CacheFont(it, true, 0, type));
}
else
{
if (it == "\t" || it == "\n" || it == "\r\n")
{
var sizefont = g.MeasureString(" ", Font, 10000, m_sf);
if (font_height < sizefont.Height) font_height = (int)Math.Ceiling(sizefont.Height);
font_widths.Add(new CacheFont(it, false, (int)Math.Ceiling(sizefont.Width * 8F), type));
}
else
{
var sizefont = g.MeasureString(it, Font, 10000, m_sf);
if (font_height < sizefont.Height) font_height = (int)Math.Ceiling(sizefont.Height);
font_widths.Add(new CacheFont(it, false, (int)Math.Ceiling(sizefont.Width), type));
}
}
break;
}
return true;
});
if (item.HasEmoji)
{
using (var font = new Font(EmojiFont, Font.Size))
{
foreach (var it in font_widths)
{
if (it.emoji)
{
var sizefont = g.MeasureString(it.text, font, 10000, m_sf);
if (font_height < sizefont.Height) font_height = (int)Math.Ceiling(sizefont.Height);
it.width = (int)Math.Ceiling(sizefont.Width);
}
}
}
}
for (int j = 0; j < font_widths.Count; j++) { font_widths[j].i = j; }
item.cache_font = font_widths.ToArray();
int usex = 0, usey = 0, maxx = 0, maxy = 0;
foreach (var it in item.cache_font)
{
if (it.text == "\r")
{
it.retun = true;
continue;
}
if (it.text == "\n" || it.text == "\r\n")
{
it.retun = true;
usey += font_height;
usex = 0;
continue;
}
else if (usex + it.width > max_width)
{
usey += font_height;
usex = 0;
}
if (it.imageHeight.HasValue) it.rect = new Rectangle(usex, usey, it.width, it.imageHeight.Value);
else it.rect = new Rectangle(usex, usey, it.width, font_height);
if (maxx < it.rect.Right) maxx = it.rect.Right;
if (maxy < it.rect.Bottom) maxy = it.rect.Bottom;
usex += it.width;
}
return new Size(maxx + spilt, maxy + spilt);
}
bool IsEmoji(UnicodeCategory unicodeInfo)
{
//return unicodeInfo == UnicodeCategory.Surrogate;
return unicodeInfo == UnicodeCategory.Surrogate || unicodeInfo == UnicodeCategory.OtherSymbol ||
unicodeInfo == UnicodeCategory.MathSymbol ||
unicodeInfo == UnicodeCategory.EnclosingMark ||
unicodeInfo == UnicodeCategory.NonSpacingMark ||
unicodeInfo == UnicodeCategory.ModifierLetter;
}
#endregion
#endregion
}
public class ChatItemCollection : iCollection
{
public ChatItemCollection(ChatList it)
{
BindData(it);
}
internal ChatItemCollection BindData(ChatList it)
{
action = render =>
{
if (render) it.ChangeList();
it.Invalidate();
};
return this;
}
}
public class TextChatItem : IChatItem
{
public TextChatItem(string text)
{
_text = text;
}
public TextChatItem(string text, Bitmap? icon)
{
_text = text;
_icon = icon;
}
public TextChatItem(string text, Bitmap? icon, string name)
{
_text = text;
_name = name;
_icon = icon;
}
///
/// 本人
///
[Description("本人"), Category("行为"), DefaultValue(false)]
public bool Me { get; set; }
Image? _icon = null;
///
/// 图标
///
[Description("图标"), Category("外观"), DefaultValue(null)]
public Image? Icon
{
get => _icon;
set
{
if (_icon == value) return;
_icon = value;
OnPropertyChanged("Icon");
}
}
string? _name;
///
/// 名称
///
[Description("名称"), Category("外观"), DefaultValue(null)]
public string? Name
{
get => _name;
set
{
if (_name == value) return;
_name = value;
OnPropertyChanged("Name");
}
}
string _text;
///
/// 文本
///
[Description("文本"), Category("外观")]
public string Text
{
get => _text;
set
{
if (_text == value) return;
_text = value;
Invalidates();
}
}
ITask? task;
internal bool showlinedot = false;
bool loading;
public bool Loading
{
get => loading;
set
{
if (loading == value) return;
loading = value;
task?.Dispose();
task = null;
showlinedot = false;
if (value && PARENT != null)
{
task = new ITask(PARENT, () =>
{
showlinedot = !showlinedot;
Invalidate();
return loading;
}, 200);
}
else Invalidate();
}
}
internal int SetRect(Rectangle _rect, int y, Graphics g, Font font, Size msglen, int gap, int spilt, int spilt2, int image_size)
{
if (string.IsNullOrEmpty(_name))
{
rect = new Rectangle(_rect.X, _rect.Y + y, _rect.Width, msglen.Height);
if (Me)
{
rect_icon = new Rectangle(rect.Right - gap - image_size, rect.Y, image_size, image_size);
rect_read = new Rectangle(rect_icon.X - spilt - msglen.Width, rect_icon.Y, msglen.Width, msglen.Height);
}
else
{
rect_icon = new Rectangle(rect.X + gap, rect.Y, image_size, image_size);
rect_read = new Rectangle(rect_icon.Right + spilt, rect_icon.Y, msglen.Width, msglen.Height);
}
}
else
{
rect = new Rectangle(_rect.X, _rect.Y + y, _rect.Width, msglen.Height + gap);
var size_name = (int)Math.Ceiling(g.MeasureString(_name, font).Width);
if (Me)
{
rect_icon = new Rectangle(rect.Right - gap - image_size, rect.Y, image_size, image_size);
rect_name = new Rectangle(rect_icon.X - spilt - msglen.Width + msglen.Width - size_name, rect_icon.Y, size_name, gap);
rect_read = new Rectangle(rect_icon.X - spilt - msglen.Width, rect_name.Bottom, msglen.Width, msglen.Height);
}
else
{
rect_icon = new Rectangle(rect.X + gap, rect.Y, image_size, image_size);
rect_name = new Rectangle(rect_icon.Right + spilt, rect_icon.Y, size_name, gap);
rect_read = new Rectangle(rect_name.X, rect_name.Bottom, msglen.Width, msglen.Height);
}
}
rect_text = new Rectangle(rect_read.X + spilt, rect_read.Y + spilt, msglen.Width - spilt2, msglen.Height - spilt2);
foreach (var it in cache_font)
{
it.SetOffset(rect_text.Location);
}
Show = true;
return rect.Height;
}
internal Rectangle rect_read { get; set; }
internal Rectangle rect_name { get; set; }
internal Rectangle rect_text { get; set; }
internal Rectangle rect_icon { get; set; }
internal bool ContainsRead(Point point, int x, int y)
{
return rect_text.Contains(new Point(point.X + x, point.Y + y));
}
#region 字体相关
internal bool HasEmoji = false;
internal CacheFont[] cache_font = new CacheFont[0];
internal int selectionStart = 0, selectionStartTemp = 0, selectionLength = 0;
///
/// 所选文本的起点
///
[Browsable(false), DefaultValue(0)]
public int SelectionStart
{
get => selectionStart;
set
{
if (value < 0) value = 0;
else if (value > 0)
{
if (cache_font == null) value = 0;
else if (value > cache_font.Length) value = cache_font.Length;
}
if (selectionStart == value) return;
selectionStart = selectionStartTemp = value;
Invalidate();
}
}
///
/// 所选文本的长度
///
[Browsable(false), DefaultValue(0)]
public int SelectionLength
{
get => selectionLength;
set
{
if (selectionLength == value) return;
selectionLength = value;
Invalidate();
}
}
#endregion
}
public class IChatItem : NotifyProperty
{
public IChatItem() { }
///
/// 用户定义数据
///
[Description("用户定义数据"), Category("数据"), DefaultValue(null)]
public object? Tag { get; set; }
internal void Invalidate()
{
PARENT?.Invalidate();
}
internal void Invalidates()
{
if (PARENT == null) return;
PARENT.ChangeList();
PARENT.Invalidate();
}
internal bool show { get; set; }
internal bool Show { get; set; }
internal ChatList? PARENT { get; set; }
internal Rectangle rect { get; set; }
internal bool Contains(Point point, int x, int y)
{
return rect.Contains(new Point(point.X + x, point.Y + y));
}
}
internal class CacheFont
{
public CacheFont(string _text, bool _emoji, int _width, GraphemeSplitter.STRE_TYPE Type)
{
text = _text;
emoji = _emoji;
width = _width;
type = Type;
}
public int i { get; set; }
public string text { get; set; }
Rectangle _rect;
public Rectangle rect
{
get => _rect;
set { _rect = value; }
}
internal void SetOffset(Point point)
{
_rect.Offset(point);
}
public bool emoji { get; set; }
public bool retun { get; set; }
public int width { get; set; }
public GraphemeSplitter.STRE_TYPE type { get; set; }
public int? imageHeight { get; set; }
}
}