CPF/CPF/LayoutManager.cs
2023-11-24 14:11:17 +08:00

412 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Text;
using CPF.Threading;
using CPF.Drawing;
using System.Linq;
using CPF.Platform;
using CPF.Controls;
namespace CPF
{
public class LayoutManager
{
View root;
internal LayoutManager(View view)
{
this.root = view;
}
#region LayoutManage
VisibleUIElement visibleElement;
/// <summary>
/// 在可视范围内的元素
/// </summary>
public VisibleUIElement VisibleUIElements { get { return visibleElement; } }
internal readonly HashSet<UIElement> _toMeasure = new HashSet<UIElement>();
internal readonly HashSet<UIElement> _toArrange = new HashSet<UIElement>();
private bool _queued;
private bool _running;
internal readonly List<NativeElement> nativeElements = new List<NativeElement>();
public void InvalidateMeasure(UIElement control)
{
//Contract.Requires<ArgumentNullException>(control != null);
if (control.IsDisposed || control.IsDisposing)
{
return;
}
Dispatcher.MainThread.VerifyAccess();
_toMeasure.Add(control);
_toArrange.Add(control);
QueueLayoutPass();
}
public void InvalidateArrange(UIElement control)
{
if (control.IsDisposed || control.IsDisposing)
{
return;
}
//Contract.Requires<ArgumentNullException>(control != null);
Dispatcher.MainThread.VerifyAccess();
_toArrange.Add(control);
QueueLayoutPass();
}
/// <inheritdoc/>
public void ExecuteLayoutPass()
{
Dispatcher.MainThread.VerifyAccess();
if (!_running)
{
_running = true;
var updateVisible = _toArrange.Count > 0 || _toMeasure.Count > 0;
if (visibleElement == null && !updateVisible)
{
_toArrange.Add(root);
root.viewRenderRect = true;
updateVisible = true;
}
//try
//{
while (true)
{
ExecuteMeasurePass();
ExecuteArrangePass();
if (_toMeasure.Count == 0 && _toArrange.Count == 0)
{
break;
}
}
//}
//finally
//{
_running = false;
//}
if (updateVisible)
{
visibleElements.Clear();
Desdroy();
visibleElement = VisibleUIElement(root, new Rect(new Point(), root.ActualSize));
foreach (var item in nativeElements)
{
if (item.IsDisposed)
{
continue;
}
var rect = GetRect(item.Parent, item.GetContentBounds(), item);
rect.Intersect(new Rect(new Point(), item.ActualSize));
item.NativeImpl.SetBounds(item.RenderBounds, rect, IsVisible(item));
}
}
}
_queued = false;
}
private Rect GetRect(UIElement control, Rect rect, UIElement owner)
{
Rect r;
if (control.Parent != null)
{
r = GetRect(control.Parent, control.GetContentBounds(), owner);
}
else
{
r = control.GetContentBounds();
}
if (control.ClipToBounds || control.IsRoot)
{
r.Intersect(rect);
}
else
{
//r = rect;
}
r.Offset(-rect.Left, -rect.Top);
return r;
}
HashSet<UIElement> visibleElements = new HashSet<UIElement>();
/// <summary>
/// 元素是否在可视化范围内
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
public bool IsVisible(UIElement element)
{
return visibleElements.Contains(element);
}
internal void ExecuteInitialLayoutPass(UIElement root)
{
Measure(root);
Arrange(root);
ExecuteLayoutPass();
}
private void ExecuteMeasurePass()
{
while (_toMeasure.Count > 0)
{
var next = _toMeasure.First();
Measure(next);
}
}
private void ExecuteArrangePass()
{
while (_toArrange.Count > 0 && _toMeasure.Count == 0)
{
var next = _toArrange.First();
Arrange(next);
}
}
private void Measure(UIElement control)
{
_toMeasure.Remove(control);
if (control.Root == null)
{
return;
}
if (control.IsRoot)
{
var window = control as ITopLevel;
if (window != null && control.Parent == null)
{
//var w = window.Width;
//var h = window.Height;
//if (w.Unit == Unit.Percent || w.IsAuto || h.Unit == Unit.Percent || h.IsAuto)
//{
ITopLevel root = window;
Screen scr = root.Screen;
//if (window is Popup popup && popup.PlacementTarget != null && popup.PlacementTarget.Root != null)
//{
// root = popup.PlacementTarget.Root;
// scr = root.Screen;
//}
//else
//{
// scr = window.Screen;
//}
control.Measure(new Size(scr.WorkingArea.Width / root.LayoutScaling, scr.WorkingArea.Height / root.LayoutScaling));
//}
//else
//{
// control.Measure(new Size(w.Value, h.Value));
//}
}
else
{
control.Measure(new Size(control.Width.Value, control.Height.Value));
}
}
//else if (parent != null)
//{
// Measure(parent);
//}
if (!control.IsMeasureValid)
{
if (control._previousMeasure.HasValue)
{
control.Measure(control._previousMeasure.Value);
}
else
{
//var parent = control.Parent;
//while (!parent._previousMeasure.HasValue)
//{
// parent = parent.Parent;
//}
////parent.Measure(parent._previousMeasure.Value);
//_toMeasure.Add(parent);
}
}
}
private void Arrange(UIElement control)
{
_toArrange.Remove(control);
if (control.Root == null || control.Visibility == Visibility.Collapsed)
{
if (control.Root != null)
{
control.IsArrangeValid = true;
}
return;
}
//var parent = control.Parent;
if (control.IsRoot)
{
var window = control as ITopLevel;
if (window != null && control.Parent == null)
{
//var w = window.Width;
//var h = window.Height;
//if (w.Unit == Unit.Percent || w.IsAuto || h.Unit == Unit.Percent || h.IsAuto)
//{
ITopLevel root = window;
Screen scr = root.Screen;
control.Arrange(new Rect(0, 0, scr.WorkingArea.Width / root.LayoutScaling, scr.WorkingArea.Height / root.LayoutScaling));
//}
//else
//{
// control.Arrange(new Rect(0, 0, w.Value, h.Value));
//}
}
else
{
control.Arrange(new Rect(0, 0, control.Width.Value, control.Height.Value));
}
}
//else if (parent != null)
//{
// Arrange(parent);
//}
else if (control._previousArrange.HasValue)
{
control.Arrange(control._previousArrange.Value);
}
//control.Invalidate();
//control.RendRect();
//if (control.offsetChange.HasValue)
//{
// foreach (var item in control.Find<UIElement>())
// {
// var r = item.RenderBounds;
// var offset = control.offsetChange.Value;
// item.RenderBounds = new Rect(r.X + offset.X, r.Y + offset.Y, r.Width, r.Height);
// }
// control.offsetChange = null;
//}
//else
//{
//foreach (var item in control.Find<UIElement>().Where(a => a.Visibility != Visibility.Collapsed))
//{
// item.RendRect();
//}
//}
if (control.Visibility != Visibility.Collapsed)
{
if (control.IsRoot && control.viewRenderRect || !control.IsRoot)
{
RendRect(control);
}
control.viewRenderRect = false;
}
}
void RendRect(UIElement element)
{
element.RendRect();
var length = element.Children.Count;
for (int i = 0; i < length; i++)
{
var item = element.Children[i];
if (item.Visibility != Visibility.Collapsed)
{
RendRect(item);
}
}
//foreach (var item in element.Children)
//{
// if (item.Visibility != Visibility.Collapsed)
// {
// RendRect(item);
// }
//}
}
private VisibleUIElement VisibleUIElement(UIElement control, Rect rect)
{
VisibleUIElement i = null;
if (control.Visibility == Visibility.Collapsed)
{
return i;
}
if (rect.IntersectsWith(control.RenderBounds) || root.InputManager.MouseDevice.Captured == control)
{
visibleElements.Add(control);
//i = new VisibleUIElement { Element = control };
i = Create(control);
}
//foreach (UIElement item in control.Children.OrderByZIndexList())
var list = control.Children.OrderByZIndexList();
for (int j = 0; j < list.Count; j++)
{
var ii = VisibleUIElement(list[j], rect);
if (ii != null)
{
if (i == null)
{
visibleElements.Add(control);
//i = new VisibleUIElement { Element = control };
i = Create(control);
}
i.Children.Add(ii);
}
}
return i;
}
private void QueueLayoutPass()
{
if (!_queued && !_running)
{
_queued = true;
Dispatcher.MainThread.BeginInvoke(ExecuteLayoutPass);
}
}
#endregion
//对象池减少布局时候GC
List<VisibleUIElement> empties = new List<VisibleUIElement>();
List<VisibleUIElement> useds = new List<VisibleUIElement>();
VisibleUIElement Create(UIElement ele)
{
if (empties.Count == 0)
{
var v = new VisibleUIElement();
v.SetElement(ele);
useds.Add(v);
return v;
}
var vu = empties[empties.Count - 1];
empties.RemoveAt(empties.Count - 1);
useds.Add(vu);
vu.SetElement(ele);
return vu;
}
void Desdroy()
{
foreach (var item in useds)
{
item.SetElement(null);
item.Children.Clear();
empties.Add(item);
}
useds.Clear();
}
}
}