You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kev/Drawer/UCDraw/SigmaDrawerStyle/FrmGradientRulerEditor.cs

388 lines
16 KiB
C#

1 month ago
using DevExpress.Utils.Drawing;
using DevExpress.XtraEditors;
using GeoSigmaDrawLib;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows.Forms;
namespace GeoSigma.SigmaDrawerStyle
{
public class FrmGradientRulerEditor : XtraUserControl
{
public class MarkItem
{
public ColorScaleEntry RawData;
public double NormalizedPosition { get; set; }
internal float CurrentY { get; set; }
internal RectangleF Bounds { get; set; }
internal RectangleF TextBounds { get; set; }
internal bool IsHovered { get; set; }
internal bool IsSelected { get; set; }
}
private List<MarkItem> _items = new List<MarkItem>();
private MarkItem _draggingItem = null;
private MarkItem _editingItem = null;
private MarkItem _selectedItem = null;
private TextBox _editTextBox;
private double _globalMin, _globalMax;
private bool _isReversed; // 标记是否为反转状态
private ColorBlend _fixedGradientBlend;
private const int GradientWidth = 40;
private const int PaddingTopBottom = 30;
private Rectangle _gradRect;
public FrmGradientRulerEditor()
{
this.DoubleBuffered = true;
this.Font = new Font("Tahoma", 9F);
_editTextBox = new TextBox { Visible = false, Width = 60 };
_editTextBox.Leave += (s, e) => EndEdit(true);
_editTextBox.KeyDown += (s, e) => {
if (e.KeyCode == Keys.Enter) EndEdit(true);
if (e.KeyCode == Keys.Escape) EndEdit(false);
};
this.Controls.Add(_editTextBox);
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Delete)
{
DeleteSelected();
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
// 接收是否反转的参数
public void SetData(IEnumerable<ColorScaleEntry> bgEntries, IEnumerable<ColorScaleEntry> markEntries, double min, double max, bool isReversed)
{
_globalMin = min;
_globalMax = max;
_isReversed = isReversed;
double range = max - min;
UpdateGradientBlend(bgEntries, min, max);
_items.Clear();
foreach (var entry in markEntries)
{
double pos = (range == 0) ? 0.5 : (entry.z - min) / range;
_items.Add(new MarkItem { RawData = entry, NormalizedPosition = pos });
}
Invalidate();
}
private void UpdateGradientBlend(IEnumerable<ColorScaleEntry> entries, double min, double max)
{
var baseSorted = entries.OrderBy(e => e.z).ToList();
if (baseSorted.Count > 1)
{
float[] posArray = new float[baseSorted.Count];
Color[] colorArray = new Color[baseSorted.Count];
double range = max - min;
for (int i = 0; i < baseSorted.Count; i++)
{
float pos = (range == 0) ? 0 : (float)((baseSorted[i].z - min) / range);
//强制限制在 0.0 ~ 1.0 之间,防越界崩溃
posArray[i] = Math.Max(0.0f, Math.Min(1.0f, pos));
colorArray[i] = Color.FromArgb(baseSorted[i].a, baseSorted[i].r, baseSorted[i].g, baseSorted[i].b);
}
if (!_isReversed)
{
for (int i = 0; i < posArray.Length; i++) posArray[i] = 1.0f - posArray[i];
}
Array.Sort(posArray, colorArray);
// GDI+ 终极保险:强制首尾两端绝对等于 0.0 和 1.0
posArray[0] = 0.0f;
posArray[posArray.Length - 1] = 1.0f;
// 防重叠崩溃GDI+ 要求位置必须严格递增,如果有两个节点在同一位置会直接崩溃
for (int i = 1; i < posArray.Length; i++)
{
if (posArray[i] <= posArray[i - 1])
{
posArray[i] = posArray[i - 1] + 0.00001f; // 微微偏移
if (posArray[i] > 1.0f) posArray[i] = 1.0f;
}
}
// 再次确保最后一位是 1.0f (防偏移挤压)
posArray[posArray.Length - 1] = 1.0f;
_fixedGradientBlend = new ColorBlend { Colors = colorArray, Positions = posArray };
}
}
public void AddPointByValue(double val)
{
val = Math.Max(_globalMin, Math.Min(_globalMax, val));
// 【新增防重叠】:检查是否已经存在非常接近该 Z 值的节点
if (_items.Any(item => Math.Abs(item.RawData.z - val) < 1e-5))
{
// 如果是用户点击按钮手动输入,给个弹窗提示;双击误触则静默忽略
XtraMessageBox.Show("已存在相同数值的标注点!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
double range = _globalMax - _globalMin;
double pos = (range == 0) ? 0.5 : (val - _globalMin) / range;
// 获取采样色
float blendPos = _isReversed ? (float)pos : 1.0f - (float)pos;
Color sampled = GetInterpolatedColor(_fixedGradientBlend, blendPos);
var entry = new ColorScaleEntry { z = val, r = sampled.R, g = sampled.G, b = sampled.B, a = sampled.A, isContinue = 1 };
var newItem = new MarkItem { RawData = entry, NormalizedPosition = pos };
_items.Add(newItem);
SetSelectedItem(newItem);
Invalidate();
}
public void DeleteSelected()
{
if (_selectedItem != null && _items.Count > 1)
{
_items.Remove(_selectedItem);
_selectedItem = null;
Invalidate();
}
}
private void SetSelectedItem(MarkItem item)
{
_items.ForEach(i => i.IsSelected = false);
if (item != null) item.IsSelected = true;
_selectedItem = item;
Invalidate();
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (_editTextBox.Visible) { EndEdit(true); return; }
var hit = _items.FirstOrDefault(p => p.Bounds.Contains(e.Location));
if (hit != null)
{
SetSelectedItem(hit);
_draggingItem = hit;
this.Focus();
Capture = true;
}
else
{
SetSelectedItem(null);
}
}
protected override void OnMouseDoubleClick(MouseEventArgs e)
{
var hitText = _items.FirstOrDefault(p => p.TextBounds.Contains(e.Location));
if (hitText != null) { StartEdit(hitText); return; }
if (_gradRect.Contains(e.Location))
{
float h = Height - (PaddingTopBottom * 2);
double ratio = (e.Y - PaddingTopBottom) / h;
// 正向时,从上往下数值递减;反向时,从上往下数值递增
if (!_isReversed) ratio = 1.0 - ratio;
double val = _globalMin + (_globalMax - _globalMin) * ratio;
AddPointByValue(val);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (_editTextBox.Visible) return;
foreach (var item in _items) item.IsHovered = item.Bounds.Contains(e.Location);
if (_draggingItem != null)
{
float h = Height - (PaddingTopBottom * 2);
double newPos = (e.Y - PaddingTopBottom) / h;
if (!_isReversed) newPos = 1.0 - newPos; // 正反转映射
newPos = Math.Max(0.0, Math.Min(1.0, newPos));
_draggingItem.NormalizedPosition = newPos;
var data = _draggingItem.RawData;
data.z = _globalMin + (_globalMax - _globalMin) * newPos;
// 实时采样颜色
float blendPos = _isReversed ? (float)newPos : 1.0f - (float)newPos;
Color sampled = GetInterpolatedColor(_fixedGradientBlend, blendPos);
data.r = sampled.R;
data.g = sampled.G;
data.b = sampled.B;
_draggingItem.RawData = data;
}
Invalidate();
}
protected override void OnMouseUp(MouseEventArgs e) { _draggingItem = null; Capture = false; }
private void StartEdit(MarkItem item)
{
_editingItem = item;
_editTextBox.Text = item.RawData.z.ToString("F3");
_editTextBox.Location = new Point((int)item.TextBounds.X, (int)item.TextBounds.Y);
_editTextBox.Visible = true;
_editTextBox.Focus(); _editTextBox.SelectAll();
}
private void EndEdit(bool commit)
{
if (_editingItem != null && commit && double.TryParse(_editTextBox.Text, out double newVal))
{
newVal = Math.Max(_globalMin, Math.Min(_globalMax, newVal));
if (_items.Any(item => item != _editingItem && Math.Abs(item.RawData.z - newVal) < 1e-5))
{
XtraMessageBox.Show("已存在相同数值的标注点,修改被取消!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
var data = _editingItem.RawData;
data.z = newVal;
double range = _globalMax - _globalMin;
double pos = (range == 0) ? 0.5 : (newVal - _globalMin) / range;
_editingItem.NormalizedPosition = pos;
// 实时采样颜色
float blendPos = _isReversed ? (float)pos : 1.0f - (float)pos;
Color sampled = GetInterpolatedColor(_fixedGradientBlend, blendPos);
data.r = sampled.R;
data.g = sampled.G;
data.b = sampled.B;
_editingItem.RawData = data;
}
}
_editTextBox.Visible = false;
_editingItem = null;
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle rect = ClientRectangle;
_gradRect = new Rectangle(rect.X + 40, rect.Y + PaddingTopBottom, GradientWidth, rect.Height - (PaddingTopBottom * 2));
if (_fixedGradientBlend != null)
{
using (LinearGradientBrush brush = new LinearGradientBrush(_gradRect, Color.Black, Color.White, 90f))
{
brush.InterpolationColors = _fixedGradientBlend;
g.FillRectangle(brush, _gradRect);
}
g.DrawRectangle(Pens.DimGray, _gradRect);
foreach (var item in _items)
{
// 【关键】:反转时 Min 在顶Max 在底;正向时 Max 在顶Min 在底
if (_isReversed)
item.CurrentY = _gradRect.Top + (float)(item.NormalizedPosition * _gradRect.Height);
else
item.CurrentY = _gradRect.Bottom - (float)(item.NormalizedPosition * _gradRect.Height);
item.Bounds = new RectangleF(_gradRect.Right, item.CurrentY - 8, 150, 16);
item.TextBounds = new RectangleF(_gradRect.Right + 30, item.CurrentY - 8, 120, 16);
// 绘制横线色 (保持实时采样色)
float blendPos = _isReversed ? (float)item.NormalizedPosition : 1.0f - (float)item.NormalizedPosition;
Color sampledColor = GetInterpolatedColor(_fixedGradientBlend, blendPos);
using (Pen p = new Pen(sampledColor, item.IsHovered ? 2.5f : 1.5f))
{
g.DrawLine(p, _gradRect.Right, item.CurrentY, _gradRect.Right + 25, item.CurrentY);
}
if (_editingItem != item)
{
string label = item.RawData.z.ToString("F3");
Brush textBrush = item.IsSelected ? Brushes.Blue : (item.IsHovered ? Brushes.Red : Brushes.Black);
using (Font textFont = new Font(this.Font, item.IsSelected ? FontStyle.Bold : FontStyle.Regular))
{
g.DrawString(label, textFont, textBrush, item.TextBounds.X, item.TextBounds.Y);
}
}
}
}
}
private Color GetInterpolatedColor(ColorBlend blend, float pos)
{
if (pos <= blend.Positions[0]) return blend.Colors[0];
if (pos >= blend.Positions.Last()) return blend.Colors.Last();
for (int i = 1; i < blend.Positions.Length; i++)
{
if (pos < blend.Positions[i])
{
float t = (pos - blend.Positions[i - 1]) / (blend.Positions[i] - blend.Positions[i - 1]);
Color c1 = blend.Colors[i - 1], c2 = blend.Colors[i];
return Color.FromArgb((int)(c1.A + (c2.A - c1.A) * t), (int)(c1.R + (c2.R - c1.R) * t), (int)(c1.G + (c2.G - c1.G) * t), (int)(c1.B + (c2.B - c1.B) * t));
}
}
return blend.Colors.Last();
}
public List<ColorScaleEntry> GetResult() => _items.Select(i => i.RawData).OrderBy(d => d.z).ToList();
// 接收是否反转的参数 isReversed
public static List<ColorScaleEntry> ShowEditor(IWin32Window owner, IEnumerable<ColorScaleEntry> bgEntries, IEnumerable<ColorScaleEntry> markEntries, double min, double max, bool isReversed)
{
using (XtraForm form = new XtraForm())
{
form.Text = "色标编辑器";
form.Size = new Size(315, 600);
form.StartPosition = FormStartPosition.CenterParent;
form.FormBorderStyle = FormBorderStyle.FixedDialog;
form.MaximizeBox = false;
form.MinimizeBox = false;
var editor = new FrmGradientRulerEditor { Dock = DockStyle.Fill };
editor.SetData(bgEntries, markEntries, min, max, isReversed);
PanelControl pnlBottom = new PanelControl { Dock = DockStyle.Bottom, Height = 60 };
SimpleButton btnAdd = new SimpleButton { Text = "增加", Size = new Size(80, 30), Location = new Point(15, 15) };
SimpleButton btnDel = new SimpleButton { Text = "删除", Size = new Size(80, 30), Location = new Point(105, 15) };
SimpleButton btnOk = new SimpleButton { Text = "保存", Size = new Size(100, 30), Location = new Point(195, 15), DialogResult = DialogResult.OK };
btnAdd.Click += (s, e) => {
XtraInputBoxArgs args = new XtraInputBoxArgs();
args.Caption = "增加标注";
args.Prompt = "请输入Z值";
args.DefaultResponse = min.ToString("F3");
args.Buttons = new DialogResult[] { DialogResult.OK, DialogResult.Cancel };
var result = XtraInputBox.Show(args);
if (result != null && double.TryParse(result.ToString(), out double dVal))
{
editor.AddPointByValue(dVal);
}
};
btnDel.Click += (s, e) => editor.DeleteSelected();
pnlBottom.Controls.AddRange(new Control[] { btnAdd, btnDel, btnOk });
form.Controls.Add(editor);
form.Controls.Add(pnlBottom);
return (form.ShowDialog(owner) == DialogResult.OK) ? editor.GetResult() : null;
}
}
}
}